From ee7334a772b01dbc506fc37968f6edc60e6b7539 Mon Sep 17 00:00:00 2001 From: Bahex <17417311+Bahex@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:45:33 +0300 Subject: [PATCH] feat(get,select,reject): add `--ignore-case` which interprets cell-paths case insensitively, analogous to `--optional` (#16401) # Description Follow up to #16007 Also added some examples for existing flags which were previously missing. After the deprecation period of `--ignore-errors (-i)`, `-i` will be used as the short form of this flag. # User-Facing Changes `get`, `select`, `reject` commands now have a `--ignore-case` flag, which makes the commands interpret all cell-path arguments as completely case insensitive. # Tests + Formatting +1 # After Submitting Set a reminder for the `--ignore-errors` deprecation and using `-i` as the short flag. Maybe we can make PRs in advance for future versions and mark them with GitHub's milestones. --- crates/nu-command/src/filters/get.rs | 50 ++++++++++++++++++++++++- crates/nu-command/src/filters/reject.rs | 13 +++++++ crates/nu-command/src/filters/select.rs | 24 ++++++++++++ tests/repl/test_table_operations.rs | 31 ++++++++++++++- 4 files changed, 115 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index a44b2952f8..708879fc69 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -45,6 +45,11 @@ If multiple cell paths are given, this will produce a list of values."# "make all cell path members optional (returns `null` for missing values)", Some('o'), ) + .switch( + "ignore-case", + "make all cell path members case insensitive", + None, + ) .switch( "ignore-errors", "ignore missing data (make all cell path members optional) (deprecated)", @@ -74,6 +79,30 @@ If multiple cell paths are given, this will produce a list of values."# Span::test_data(), )), }, + Example { + description: "Get a column from a table where some rows don't have that column, using optional cell-path syntax", + example: "[{A: A0, B: B0}, {B: B1}, {A: A2, B: B2}] | get A?", + result: Some(Value::list( + vec![ + Value::test_string("A0"), + Value::test_nothing(), + Value::test_string("A2"), + ], + Span::test_data(), + )), + }, + Example { + description: "Get a column from a table where some rows don't have that column, using the optional flag", + example: "[{A: A0, B: B0}, {B: B1}, {A: A2, B: B2}] | get -o A", + result: Some(Value::list( + vec![ + Value::test_string("A0"), + Value::test_nothing(), + Value::test_string("A2"), + ], + Span::test_data(), + )), + }, Example { description: "Get a cell from a table", example: "[{A: A0}] | get 0.A", @@ -90,8 +119,13 @@ If multiple cell paths are given, this will produce a list of values."# result: None, }, Example { - description: "Getting Path/PATH in a case insensitive way", - example: "$env | get paTH!", + description: "Getting environment variables in a case insensitive way, using case insensitive cell-path syntax", + example: "$env | get home! path!", + result: None, + }, + Example { + description: "Getting environment variables in a case insensitive way, using the '--ignore-case' flag", + example: "$env | get --ignore-case home path", result: None, }, Example { @@ -116,12 +150,14 @@ If multiple cell paths are given, this will produce a list of values."# let rest: Vec = call.rest_const(working_set, 1)?; let optional = call.has_flag_const(working_set, "optional")? || call.has_flag_const(working_set, "ignore-errors")?; + let ignore_case = call.has_flag_const(working_set, "ignore-case")?; let metadata = input.metadata(); action( input, cell_path, rest, optional, + ignore_case, working_set.permanent().signals().clone(), call.head, ) @@ -139,12 +175,14 @@ If multiple cell paths are given, this will produce a list of values."# let rest: Vec = call.rest(engine_state, stack, 1)?; let optional = call.has_flag(engine_state, stack, "optional")? || call.has_flag(engine_state, stack, "ignore-errors")?; + let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?; let metadata = input.metadata(); action( input, cell_path, rest, optional, + ignore_case, engine_state.signals().clone(), call.head, ) @@ -176,6 +214,7 @@ fn action( mut cell_path: CellPath, mut rest: Vec, optional: bool, + ignore_case: bool, signals: Signals, span: Span, ) -> Result { @@ -186,6 +225,13 @@ fn action( } } + if ignore_case { + cell_path.make_insensitive(); + for path in &mut rest { + path.make_insensitive(); + } + } + if let PipelineData::Empty = input { return Err(ShellError::PipelineEmpty { dst_span: span }); } diff --git a/crates/nu-command/src/filters/reject.rs b/crates/nu-command/src/filters/reject.rs index b51dd1b73a..cea0b8ce84 100644 --- a/crates/nu-command/src/filters/reject.rs +++ b/crates/nu-command/src/filters/reject.rs @@ -18,6 +18,11 @@ impl Command for Reject { (Type::list(Type::Any), Type::list(Type::Any)), ]) .switch("optional", "make all cell path members optional", Some('o')) + .switch( + "ignore-case", + "make all cell path members case insensitive", + None, + ) .switch( "ignore-errors", "ignore missing data (make all cell path members optional) (deprecated)", @@ -93,12 +98,20 @@ impl Command for Reject { let optional = call.has_flag(engine_state, stack, "optional")? || call.has_flag(engine_state, stack, "ignore-errors")?; + let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?; + if optional { for cell_path in &mut new_columns { cell_path.make_optional(); } } + if ignore_case { + for cell_path in &mut new_columns { + cell_path.make_insensitive(); + } + } + reject(engine_state, span, input, new_columns) } diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 06ce40f890..b1ca6d4a12 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -26,6 +26,11 @@ impl Command for Select { "make all cell path members optional (returns `null` for missing values)", Some('o'), ) + .switch( + "ignore-case", + "make all cell path members case insensitive", + None, + ) .switch( "ignore-errors", "ignore missing data (make all cell path members optional) (deprecated)", @@ -110,6 +115,7 @@ produce a table, a list will produce a list, and a record will produce a record. } let optional = call.has_flag(engine_state, stack, "optional")? || call.has_flag(engine_state, stack, "ignore-errors")?; + let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?; let span = call.head; if optional { @@ -118,6 +124,12 @@ produce a table, a list will produce a list, and a record will produce a record. } } + if ignore_case { + for cell_path in &mut new_columns { + cell_path.make_insensitive(); + } + } + select(engine_state, span, new_columns, input) } @@ -143,6 +155,18 @@ produce a table, a list will produce a list, and a record will produce a record. "a" => Value::test_string("a") })])), }, + Example { + description: "Select a column even if some rows are missing that column", + example: "[{a: a0 b: b0} {b: b1}] | select -o a", + result: Some(Value::test_list(vec![ + Value::test_record(record! { + "a" => Value::test_string("a0") + }), + Value::test_record(record! { + "a" => Value::test_nothing() + }), + ])), + }, Example { description: "Select a field in a record", example: "{a: a b: b} | select a", diff --git a/tests/repl/test_table_operations.rs b/tests/repl/test_table_operations.rs index 737f07dc6d..65ccbd4db0 100644 --- a/tests/repl/test_table_operations.rs +++ b/tests/repl/test_table_operations.rs @@ -305,9 +305,38 @@ fn nullify_holes() -> TestResult { } #[test] -fn get_insensitive() -> TestResult { +fn get_with_insensitive_cellpath() -> TestResult { run_test( r#"[[name, age]; [a, 1] [b, 2]] | get NAmE! | select 0 | get 0"#, "a", ) } + +#[test] +fn ignore_case_flag() -> TestResult { + run_test( + r#" + [ + [Origin, Crate, Versions]; + [World, {Name: "nu-cli"}, ['0.21', '0.22']] + ] + | get --ignore-case crate.name.0 + "#, + "nu-cli", + )?; + run_test( + r#" + [ + [Origin, Crate, Versions]; + [World, {Name: "nu-cli"}, ['0.21', '0.22']] + ] + | select --ignore-case origin + | to nuon --raw + "#, + r#"[[origin];[World]]"#, + )?; + run_test( + r#"{A: {B: 3, C: 5}} | reject --ignore-case a.b | to nuon --raw"#, + "{A:{C:5}}", + ) +}