From 3fe9c7c00c9fca71e33219c59e459269fc4cbdf9 Mon Sep 17 00:00:00 2001 From: Tyarel8 <98483313+Tyarel8@users.noreply.github.com> Date: Wed, 13 Aug 2025 22:22:24 +0200 Subject: [PATCH] feat(each): noop on single null input, `map-null` equivalent (#16396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Basically, now `null | each { "something" }` will be `null` instead of `"something"`. Thanks to this, `each` can be used to map values similar to `map-null` custom commands, for example: - Before ```nu let args = if $delay != null { ["--delay" ($delay | format duration sec | parse '{secs} {_}' | get 0.secs)] } else { [] } ``` - After ```nu let args = ( $delay | each { ["--delay" ($in | format duration sec | parse '{secs} {_}' | get 0.secs)] } | default [] ) ``` Please let me know if this change messes something up I'm not seeing. # User-Facing Changes - Before ```nu → null | each { "something" } something ``` - After ```nu → null | each { "something" } null ``` # Tests + Formatting Added a test to check for this. # After Submitting --------- Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com> --- crates/nu-command/src/filters/each.rs | 6 ++++++ crates/nu-command/tests/commands/each.rs | 7 +++++++ tests/repl/test_engine.rs | 5 +++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 7f77917b85..412477a36f 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -90,6 +90,11 @@ with 'transpose' first."# Value::nothing(Span::test_data()), ])), }, + Example { + example: r#"$env.name? | each { $"hello ($in)" } | default "bye""#, + description: "Update value if not null, otherwise do nothing", + result: None, + }, ] } @@ -107,6 +112,7 @@ with 'transpose' first."# let metadata = input.metadata(); match input { PipelineData::Empty => Ok(PipelineData::empty()), + PipelineData::Value(Value::Nothing { .. }, ..) => Ok(input), PipelineData::Value(Value::Range { .. }, ..) | PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream(..) => { diff --git a/crates/nu-command/tests/commands/each.rs b/crates/nu-command/tests/commands/each.rs index b3c04af0a4..b03ffe590c 100644 --- a/crates/nu-command/tests/commands/each.rs +++ b/crates/nu-command/tests/commands/each.rs @@ -63,3 +63,10 @@ fn errors_in_nested_each_show() { let actual = nu!("[[1,2]] | each {|x| $x | each {|y| error make {msg: \"oh noes\"} } }"); assert!(actual.err.contains("oh noes")) } + +#[test] +fn each_noop_on_single_null() { + let actual = nu!("null | each { \"test\" } | describe"); + + assert_eq!(actual.out, "nothing"); +} diff --git a/tests/repl/test_engine.rs b/tests/repl/test_engine.rs index 22d81f08cb..d29325eb80 100644 --- a/tests/repl/test_engine.rs +++ b/tests/repl/test_engine.rs @@ -1,4 +1,4 @@ -use crate::repl::tests::{TestResult, fail_test, run_test}; +use crate::repl::tests::{TestResult, fail_test, run_test, run_test_contains}; use rstest::rstest; #[test] @@ -87,7 +87,8 @@ fn in_used_in_range_to() -> TestResult { #[test] fn help_works_with_missing_requirements() -> TestResult { - run_test(r#"each --help | lines | length"#, "72") + fail_test(r#"each"#, "missing_positional")?; + run_test_contains(r#"each --help"#, "Usage") } #[rstest]