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.
This commit is contained in:
Bahex
2025-08-13 23:45:33 +03:00
committed by GitHub
parent 3fe9c7c00c
commit ee7334a772
4 changed files with 115 additions and 3 deletions

View File

@ -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)", "make all cell path members optional (returns `null` for missing values)",
Some('o'), Some('o'),
) )
.switch(
"ignore-case",
"make all cell path members case insensitive",
None,
)
.switch( .switch(
"ignore-errors", "ignore-errors",
"ignore missing data (make all cell path members optional) (deprecated)", "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(), 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 { Example {
description: "Get a cell from a table", description: "Get a cell from a table",
example: "[{A: A0}] | get 0.A", 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, result: None,
}, },
Example { Example {
description: "Getting Path/PATH in a case insensitive way", description: "Getting environment variables in a case insensitive way, using case insensitive cell-path syntax",
example: "$env | get paTH!", 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, result: None,
}, },
Example { Example {
@ -116,12 +150,14 @@ If multiple cell paths are given, this will produce a list of values."#
let rest: Vec<CellPath> = call.rest_const(working_set, 1)?; let rest: Vec<CellPath> = call.rest_const(working_set, 1)?;
let optional = call.has_flag_const(working_set, "optional")? let optional = call.has_flag_const(working_set, "optional")?
|| call.has_flag_const(working_set, "ignore-errors")?; || call.has_flag_const(working_set, "ignore-errors")?;
let ignore_case = call.has_flag_const(working_set, "ignore-case")?;
let metadata = input.metadata(); let metadata = input.metadata();
action( action(
input, input,
cell_path, cell_path,
rest, rest,
optional, optional,
ignore_case,
working_set.permanent().signals().clone(), working_set.permanent().signals().clone(),
call.head, call.head,
) )
@ -139,12 +175,14 @@ If multiple cell paths are given, this will produce a list of values."#
let rest: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let rest: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let optional = call.has_flag(engine_state, stack, "optional")? let optional = call.has_flag(engine_state, stack, "optional")?
|| call.has_flag(engine_state, stack, "ignore-errors")?; || call.has_flag(engine_state, stack, "ignore-errors")?;
let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
let metadata = input.metadata(); let metadata = input.metadata();
action( action(
input, input,
cell_path, cell_path,
rest, rest,
optional, optional,
ignore_case,
engine_state.signals().clone(), engine_state.signals().clone(),
call.head, call.head,
) )
@ -176,6 +214,7 @@ fn action(
mut cell_path: CellPath, mut cell_path: CellPath,
mut rest: Vec<CellPath>, mut rest: Vec<CellPath>,
optional: bool, optional: bool,
ignore_case: bool,
signals: Signals, signals: Signals,
span: Span, span: Span,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
@ -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 { if let PipelineData::Empty = input {
return Err(ShellError::PipelineEmpty { dst_span: span }); return Err(ShellError::PipelineEmpty { dst_span: span });
} }

View File

@ -18,6 +18,11 @@ impl Command for Reject {
(Type::list(Type::Any), Type::list(Type::Any)), (Type::list(Type::Any), Type::list(Type::Any)),
]) ])
.switch("optional", "make all cell path members optional", Some('o')) .switch("optional", "make all cell path members optional", Some('o'))
.switch(
"ignore-case",
"make all cell path members case insensitive",
None,
)
.switch( .switch(
"ignore-errors", "ignore-errors",
"ignore missing data (make all cell path members optional) (deprecated)", "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")? let optional = call.has_flag(engine_state, stack, "optional")?
|| call.has_flag(engine_state, stack, "ignore-errors")?; || call.has_flag(engine_state, stack, "ignore-errors")?;
let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
if optional { if optional {
for cell_path in &mut new_columns { for cell_path in &mut new_columns {
cell_path.make_optional(); 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) reject(engine_state, span, input, new_columns)
} }

View File

@ -26,6 +26,11 @@ impl Command for Select {
"make all cell path members optional (returns `null` for missing values)", "make all cell path members optional (returns `null` for missing values)",
Some('o'), Some('o'),
) )
.switch(
"ignore-case",
"make all cell path members case insensitive",
None,
)
.switch( .switch(
"ignore-errors", "ignore-errors",
"ignore missing data (make all cell path members optional) (deprecated)", "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")? let optional = call.has_flag(engine_state, stack, "optional")?
|| call.has_flag(engine_state, stack, "ignore-errors")?; || call.has_flag(engine_state, stack, "ignore-errors")?;
let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
let span = call.head; let span = call.head;
if optional { 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) 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") "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 { Example {
description: "Select a field in a record", description: "Select a field in a record",
example: "{a: a b: b} | select a", example: "{a: a b: b} | select a",

View File

@ -305,9 +305,38 @@ fn nullify_holes() -> TestResult {
} }
#[test] #[test]
fn get_insensitive() -> TestResult { fn get_with_insensitive_cellpath() -> TestResult {
run_test( run_test(
r#"[[name, age]; [a, 1] [b, 2]] | get NAmE! | select 0 | get 0"#, r#"[[name, age]; [a, 1] [b, 2]] | get NAmE! | select 0 | get 0"#,
"a", "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}}",
)
}