refactor(get,select,reject)!: deprecate --ignore-errors in favor of --optional (#16007)

# Description
As decided on the team meeting on 2025-06-19, rename `--ignore-errors
(-i)` to `--optional (-o)` with a (currently) indefinite grace period.

After `--ignore-errors (-i)` is removed, the short flag `-i` can be used
for `--ignore-case` (not implemented as of this PR)

# User-Facing Changes
`get`/`select`/`reject`: rename `--ignore-errors (-i)` to `--optional
(-o)` to better reflect its behavior.

# Tests + Formatting
- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting
Update docs and inform third parties that integrate with nushell.

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
This commit is contained in:
Bahex
2025-07-15 07:26:41 +03:00
committed by GitHub
parent a506d3f9b5
commit beb3ec6a49
15 changed files with 77 additions and 27 deletions

View File

@ -82,7 +82,7 @@ impl Command for Default {
}, },
Example { Example {
description: "Get the env value of `MY_ENV` with a default value 'abc' if not present", description: "Get the env value of `MY_ENV` with a default value 'abc' if not present",
example: "$env | get --ignore-errors MY_ENV | default 'abc'", example: "$env | get --optional MY_ENV | default 'abc'",
result: Some(Value::test_string("abc")), result: Some(Value::test_string("abc")),
}, },
Example { Example {

View File

@ -40,9 +40,14 @@ If multiple cell paths are given, this will produce a list of values."#
"The cell path to the data.", "The cell path to the data.",
) )
.rest("rest", SyntaxShape::CellPath, "Additional cell paths.") .rest("rest", SyntaxShape::CellPath, "Additional cell paths.")
.switch(
"optional",
"make all cell path members optional (returns `null` for missing values)",
Some('o'),
)
.switch( .switch(
"ignore-errors", "ignore-errors",
"ignore missing data (make all cell path members optional)", "ignore missing data (make all cell path members optional) (deprecated)",
Some('i'), Some('i'),
) )
.switch( .switch(
@ -131,13 +136,14 @@ If multiple cell paths are given, this will produce a list of values."#
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let cell_path: CellPath = call.req(engine_state, stack, 0)?; let cell_path: CellPath = call.req(engine_state, stack, 0)?;
let rest: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let rest: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; let optional = call.has_flag(engine_state, stack, "optional")?
|| call.has_flag(engine_state, stack, "ignore-errors")?;
let metadata = input.metadata(); let metadata = input.metadata();
action( action(
input, input,
cell_path, cell_path,
rest, rest,
ignore_errors, optional,
engine_state.signals().clone(), engine_state.signals().clone(),
call.head, call.head,
) )
@ -152,6 +158,13 @@ If multiple cell paths are given, this will produce a list of values."#
since: Some("0.105.0".into()), since: Some("0.105.0".into()),
expected_removal: None, expected_removal: None,
help: Some("Cell-paths are now case-sensitive by default.\nTo access fields case-insensitively, add `!` after the relevant path member.".into()) help: Some("Cell-paths are now case-sensitive by default.\nTo access fields case-insensitively, add `!` after the relevant path member.".into())
},
DeprecationEntry {
ty: DeprecationType::Flag("ignore-errors".into()),
report_mode: ReportMode::FirstUse,
since: Some("0.106.0".into()),
expected_removal: None,
help: Some("This flag has been renamed to `--optional (-o)` to better reflect its behavior.".into())
} }
] ]
} }
@ -161,11 +174,11 @@ fn action(
input: PipelineData, input: PipelineData,
mut cell_path: CellPath, mut cell_path: CellPath,
mut rest: Vec<CellPath>, mut rest: Vec<CellPath>,
ignore_errors: bool, optional: bool,
signals: Signals, signals: Signals,
span: Span, span: Span,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
if ignore_errors { if optional {
cell_path.make_optional(); cell_path.make_optional();
for path in &mut rest { for path in &mut rest {
path.make_optional(); path.make_optional();

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::{ast::PathMember, casing::Casing}; use nu_protocol::{DeprecationEntry, DeprecationType, ReportMode, ast::PathMember, casing::Casing};
use std::{cmp::Reverse, collections::HashSet}; use std::{cmp::Reverse, collections::HashSet};
#[derive(Clone)] #[derive(Clone)]
@ -17,9 +17,10 @@ impl Command for Reject {
(Type::table(), Type::table()), (Type::table(), Type::table()),
(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( .switch(
"ignore-errors", "ignore-errors",
"ignore missing data (make all cell path members optional)", "ignore missing data (make all cell path members optional) (deprecated)",
Some('i'), Some('i'),
) )
.rest( .rest(
@ -90,8 +91,9 @@ impl Command for Reject {
} }
let span = call.head; let span = call.head;
let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; let optional = call.has_flag(engine_state, stack, "optional")?
if ignore_errors { || call.has_flag(engine_state, stack, "ignore-errors")?;
if optional {
for cell_path in &mut new_columns { for cell_path in &mut new_columns {
cell_path.make_optional(); cell_path.make_optional();
} }
@ -100,6 +102,19 @@ impl Command for Reject {
reject(engine_state, span, input, new_columns) reject(engine_state, span, input, new_columns)
} }
fn deprecation_info(&self) -> Vec<DeprecationEntry> {
vec![DeprecationEntry {
ty: DeprecationType::Flag("ignore-errors".into()),
report_mode: ReportMode::FirstUse,
since: Some("0.106.0".into()),
expected_removal: None,
help: Some(
"This flag has been renamed to `--optional (-o)` to better reflect its behavior."
.into(),
),
}]
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,5 +1,8 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::{PipelineIterator, ast::PathMember, casing::Casing}; use nu_protocol::{
DeprecationEntry, DeprecationType, PipelineIterator, ReportMode, ast::PathMember,
casing::Casing,
};
use std::collections::BTreeSet; use std::collections::BTreeSet;
#[derive(Clone)] #[derive(Clone)]
@ -18,9 +21,14 @@ impl Command for Select {
(Type::table(), Type::table()), (Type::table(), Type::table()),
(Type::List(Box::new(Type::Any)), Type::Any), (Type::List(Box::new(Type::Any)), Type::Any),
]) ])
.switch(
"optional",
"make all cell path members optional (returns `null` for missing values)",
Some('o'),
)
.switch( .switch(
"ignore-errors", "ignore-errors",
"ignore missing data (make all cell path members optional)", "ignore missing data (make all cell path members optional) (deprecated)",
Some('i'), Some('i'),
) )
.rest( .rest(
@ -100,10 +108,11 @@ produce a table, a list will produce a list, and a record will produce a record.
} }
} }
} }
let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; let optional = call.has_flag(engine_state, stack, "optional")?
|| call.has_flag(engine_state, stack, "ignore-errors")?;
let span = call.head; let span = call.head;
if ignore_errors { if optional {
for cell_path in &mut new_columns { for cell_path in &mut new_columns {
cell_path.make_optional(); cell_path.make_optional();
} }
@ -112,6 +121,19 @@ produce a table, a list will produce a list, and a record will produce a record.
select(engine_state, span, new_columns, input) select(engine_state, span, new_columns, input)
} }
fn deprecation_info(&self) -> Vec<DeprecationEntry> {
vec![DeprecationEntry {
ty: DeprecationType::Flag("ignore-errors".into()),
report_mode: ReportMode::FirstUse,
since: Some("0.106.0".into()),
expected_removal: None,
help: Some(
"This flag has been renamed to `--optional (-o)` to better reflect its behavior."
.into(),
),
}]
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -31,7 +31,7 @@ fn adds_row_data_if_column_missing() {
#[test] #[test]
fn default_after_empty_filter() { fn default_after_empty_filter() {
let actual = nu!("[a b] | where $it == 'c' | get -i 0 | default 'd'"); let actual = nu!("[a b] | where $it == 'c' | get -o 0 | default 'd'");
assert_eq!(actual.out, "d"); assert_eq!(actual.out, "d");
} }

View File

@ -196,14 +196,14 @@ fn get_does_not_delve_too_deep_in_nested_lists() {
#[test] #[test]
fn ignore_errors_works() { fn ignore_errors_works() {
let actual = nu!(r#" let path = "foo"; {} | get -i $path | to nuon "#); let actual = nu!(r#" let path = "foo"; {} | get -o $path | to nuon "#);
assert_eq!(actual.out, "null"); assert_eq!(actual.out, "null");
} }
#[test] #[test]
fn ignore_multiple() { fn ignore_multiple() {
let actual = nu!(r#"[[a];[b]] | get -i c d | to nuon"#); let actual = nu!(r#"[[a];[b]] | get -o c d | to nuon"#);
assert_eq!(actual.out, "[[null], [null]]"); assert_eq!(actual.out, "[[null], [null]]");
} }

View File

@ -174,14 +174,14 @@ fn reject_multiple_rows_descending() {
#[test] #[test]
fn test_ignore_errors_flag() { fn test_ignore_errors_flag() {
let actual = nu!("[[a, b]; [1, 2], [3, 4], [5, 6]] | reject 5 -i | to nuon"); let actual = nu!("[[a, b]; [1, 2], [3, 4], [5, 6]] | reject 5 -o | to nuon");
assert_eq!(actual.out, "[[a, b]; [1, 2], [3, 4], [5, 6]]"); assert_eq!(actual.out, "[[a, b]; [1, 2], [3, 4], [5, 6]]");
} }
#[test] #[test]
fn test_ignore_errors_flag_var() { fn test_ignore_errors_flag_var() {
let actual = let actual =
nu!("let arg = [5 c]; [[a, b]; [1, 2], [3, 4], [5, 6]] | reject ...$arg -i | to nuon"); nu!("let arg = [5 c]; [[a, b]; [1, 2], [3, 4], [5, 6]] | reject ...$arg -o | to nuon");
assert_eq!(actual.out, "[[a, b]; [1, 2], [3, 4], [5, 6]]"); assert_eq!(actual.out, "[[a, b]; [1, 2], [3, 4], [5, 6]]");
} }

View File

@ -236,7 +236,7 @@ fn select_repeated_column() {
fn ignore_errors_works() { fn ignore_errors_works() {
let actual = nu!(r#" let actual = nu!(r#"
let path = "foo"; let path = "foo";
[{}] | select -i $path | to nuon [{}] | select -o $path | to nuon
"#); "#);
assert_eq!(actual.out, "[[foo]; [null]]"); assert_eq!(actual.out, "[[foo]; [null]]");

View File

@ -326,7 +326,7 @@ fn from_csv_text_with_missing_columns_to_table() {
r#" r#"
open los_tres_caballeros.txt open los_tres_caballeros.txt
| from csv --flexible | from csv --flexible
| get -i rusty_luck | get -o rusty_luck
| compact | compact
| length | length
"# "#

View File

@ -245,7 +245,7 @@ fn from_tsv_text_with_missing_columns_to_table() {
r#" r#"
open los_tres_caballeros.txt open los_tres_caballeros.txt
| from tsv --flexible | from tsv --flexible
| get -i rusty_luck | get -o rusty_luck
| compact | compact
| length | length
"# "#

View File

@ -42,7 +42,7 @@ def get-annotated [
| from nuon | from nuon
| each {|e| | each {|e|
# filter commands with test attributes, and map attributes to annotation name # filter commands with test attributes, and map attributes to annotation name
let test_attributes = $e.attributes.name | each {|x| $valid_annotations | get -i $x } let test_attributes = $e.attributes.name | each {|x| $valid_annotations | get -o $x }
if ($test_attributes | is-not-empty) { if ($test_attributes | is-not-empty) {
$e | update attributes $test_attributes.0 $e | update attributes $test_attributes.0
} }

View File

@ -11,7 +11,7 @@ def run [
} else { } else {
^$nu.current-exe --no-config-file --commands $'use std; use std/log; NU_LOG_LEVEL=($system_level) log ($message_level) "test message"' ^$nu.current-exe --no-config-file --commands $'use std; use std/log; NU_LOG_LEVEL=($system_level) log ($message_level) "test message"'
} }
| complete | get --ignore-errors stderr | complete | get --optional stderr
} }
def "assert no message" [ def "assert no message" [

View File

@ -19,7 +19,7 @@ def run-command [
} else { } else {
^$nu.current-exe --no-config-file --commands $'use std/log; NU_LOG_LEVEL=($system_level) log custom "($message)" "($format)" ($log_level) --level-prefix "($level_prefix)" --ansi "($ansi)"' ^$nu.current-exe --no-config-file --commands $'use std/log; NU_LOG_LEVEL=($system_level) log custom "($message)" "($format)" ($log_level) --level-prefix "($level_prefix)" --ansi "($ansi)"'
} }
| complete | get --ignore-errors stderr | complete | get --optional stderr
} }
@test @test

View File

@ -16,7 +16,7 @@ def run-command [
} else { } else {
^$nu.current-exe --no-config-file --commands $'use std; use std/log; NU_LOG_LEVEL=($system_level) log ($message_level) --format "($format)" "($message)"' ^$nu.current-exe --no-config-file --commands $'use std; use std/log; NU_LOG_LEVEL=($system_level) log ($message_level) --format "($format)" "($message)"'
} }
| complete | get --ignore-errors stderr | complete | get --optional stderr
} }

View File

@ -10,7 +10,7 @@ export def "update" [
let input = $in let input = $in
match ($input | describe | str replace --regex '<.*' '') { match ($input | describe | str replace --regex '<.*' '') {
record => { record => {
if ($input | get -i $field) != null { if ($input | get -o $field) != null {
$input | orig update $field $value $input | orig update $field $value
} else { $input } } else { $input }
} }