From 7fcebf37ececb98b96e478304138ef920a64507a Mon Sep 17 00:00:00 2001 From: Julian Amarilla Date: Thu, 17 Apr 2025 11:57:25 -0300 Subject: [PATCH] Fix #15440 default --empty fails at empty streams (#15562) Fixes #15440 # Description Wraps ListStream stream type from `impl Iterator` to `Peekable`, this allows checking for empty streams and treating them as empty values Example: ``` # previously $ glob ? | default -e void > # empty list $ echo '' | default -e void > void #################### # now $ glob ? | default -e void > void $ echo '' | default -e void > void ``` # User-Facing Changes empty list streams will behave as `nothing` values when testing for emptiness # Tests + Formatting - Add 2 tests - clippy OK - fmt OK # After Submitting --- crates/nu-command/src/filters/default.rs | 15 ++++++++ crates/nu-command/tests/commands/default.rs | 40 ++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/filters/default.rs b/crates/nu-command/src/filters/default.rs index c9a1506d4b..f0dc641d5f 100644 --- a/crates/nu-command/src/filters/default.rs +++ b/crates/nu-command/src/filters/default.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::{ListStream, Signals}; #[derive(Clone)] pub struct Default; @@ -146,6 +147,20 @@ fn default( && matches!(input, PipelineData::Value(ref value, _) if value.is_empty())) { Ok(value.into_pipeline_data()) + } else if default_when_empty && matches!(input, PipelineData::ListStream(..)) { + let PipelineData::ListStream(ls, metadata) = input else { + unreachable!() + }; + let span = ls.span(); + let mut stream = ls.into_inner().peekable(); + if stream.peek().is_none() { + return Ok(value.into_pipeline_data()); + } + + // stream's internal state already preserves the original signals config, so if this + // Signals::empty list stream gets interrupted it will be caught by the underlying iterator + let ls = ListStream::new(stream, span, Signals::empty()); + Ok(PipelineData::ListStream(ls, metadata)) } else { Ok(input) } diff --git a/crates/nu-command/tests/commands/default.rs b/crates/nu-command/tests/commands/default.rs index 1669aea4d1..b17e334b12 100644 --- a/crates/nu-command/tests/commands/default.rs +++ b/crates/nu-command/tests/commands/default.rs @@ -1,4 +1,4 @@ -use nu_test_support::{nu, pipeline}; +use nu_test_support::{fs::Stub::EmptyFile, nu, pipeline, playground::Playground}; #[test] fn adds_row_data_if_column_missing() { @@ -112,3 +112,41 @@ fn do_not_replace_empty_record() { let actual = nu!(r#"{} | default {a:5} | columns | length"#); assert_eq!(actual.out, "0"); } + +#[test] +fn replace_empty_list_stream() { + // This is specific for testing ListStreams when empty behave like other empty values + Playground::setup("glob_empty_list", |dirs, sandbox| { + sandbox.with_files(&[ + EmptyFile("yehuda.txt"), + EmptyFile("jttxt"), + EmptyFile("andres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), + "glob ? | default -e void", + ); + + assert_eq!(actual.out, "void"); + }) +} + +#[test] +fn do_not_replace_non_empty_list_stream() { + // This is specific for testing ListStreams when empty behave like other empty values + Playground::setup("glob_non_empty_list", |dirs, sandbox| { + sandbox.with_files(&[ + EmptyFile("yehuda.txt"), + EmptyFile("jt.rs"), + EmptyFile("andres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), + "glob '*.txt' | default -e void | length", + ); + + assert_eq!(actual.out, "2"); + }) +}