diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 38b05434f6..7e8093717c 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -2,8 +2,8 @@ use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, engine::{Command, EngineState, Stack}, - Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, - SyntaxShape, Value, + into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, + Span, SyntaxShape, Value, }; // TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml) @@ -247,6 +247,15 @@ pub fn action( val: input.into_string(", ", config), span, }, + Value::Error { error } => Value::String { + val: { + match into_code(error) { + Some(code) => code, + None => "".to_string(), + } + }, + span, + }, Value::Nothing { .. } => Value::String { val: "".to_string(), span, diff --git a/crates/nu-command/src/core_commands/do_.rs b/crates/nu-command/src/core_commands/do_.rs index 41c4741c80..13ecb8dbfe 100644 --- a/crates/nu-command/src/core_commands/do_.rs +++ b/crates/nu-command/src/core_commands/do_.rs @@ -1,7 +1,9 @@ use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; -use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value, +}; #[derive(Clone)] pub struct Do; @@ -23,6 +25,11 @@ impl Command for Do { "ignore errors as the block runs", Some('i'), ) + .switch( + "capture-errors", + "capture errors as the block runs and return it", + Some('c'), + ) .rest("rest", SyntaxShape::Any, "the parameter(s) for the block") .category(Category::Core) } @@ -37,6 +44,7 @@ impl Command for Do { let block: CaptureBlock = call.req(engine_state, stack, 0)?; let rest: Vec = call.rest(engine_state, stack, 1)?; let ignore_errors = call.has_flag("ignore-errors"); + let capture_errors = call.has_flag("capture-errors"); let mut stack = stack.captures_to_stack(&block.captures); let block = engine_state.get_block(block.block_id); @@ -85,7 +93,7 @@ impl Command for Do { block, input, call.redirect_stdout, - ignore_errors, + ignore_errors || capture_errors, ); if ignore_errors { @@ -93,6 +101,11 @@ impl Command for Do { Ok(x) => Ok(x), Err(_) => Ok(PipelineData::new(call.head)), } + } else if capture_errors { + match result { + Ok(x) => Ok(x), + Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()), + } } else { result } diff --git a/crates/nu-command/tests/commands/do_.rs b/crates/nu-command/tests/commands/do_.rs new file mode 100644 index 0000000000..8d64b0375b --- /dev/null +++ b/crates/nu-command/tests/commands/do_.rs @@ -0,0 +1,13 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn capture_errors_works() { + let actual = nu!( + cwd: ".", pipeline( + r#" + do -c {$env.use} | describe + "# + )); + + assert_eq!(actual.out, "error"); +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 73bf0c9920..0c56bacb29 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -9,6 +9,7 @@ mod cp; mod date; mod def; mod default; +mod do_; mod drop; mod each; mod echo; diff --git a/crates/nu-command/tests/commands/str_/into_string.rs b/crates/nu-command/tests/commands/str_/into_string.rs index b2e0a2cdd4..5b04210077 100644 --- a/crates/nu-command/tests/commands/str_/into_string.rs +++ b/crates/nu-command/tests/commands/str_/into_string.rs @@ -171,3 +171,15 @@ fn from_nothing() { assert_eq!(actual.out, ""); } + +#[test] +fn from_error() { + let actual = nu!( + cwd: ".", pipeline( + r#" + do -c {$env.use} | into string + "# + )); + + assert_eq!(actual.out, "nu::shell::name_not_found"); +} diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 88c9bf934d..dbc1cf27a9 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2670,6 +2670,7 @@ pub fn parse_shape_name( b"record" => SyntaxShape::Record, b"list" => SyntaxShape::List(Box::new(SyntaxShape::Any)), b"table" => SyntaxShape::Table, + b"error" => SyntaxShape::Error, _ => { if bytes.contains(&b'@') { let str = String::from_utf8_lossy(bytes); diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 5d01efc2fd..91ef77f655 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -724,6 +724,10 @@ impl From> for ShellError { } } +pub fn into_code(err: &ShellError) -> Option { + err.code().map(|code| code.to_string()) +} + pub fn did_you_mean(possibilities: &[String], tried: &str) -> Option { let mut possible_matches: Vec<_> = possibilities .iter() diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 8626157b3b..74aaa071e4 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -92,6 +92,9 @@ pub enum SyntaxShape { /// A record value Record, + /// An error value + Error, + /// A custom shape with custom completion logic Custom(Box, DeclId), } @@ -112,6 +115,7 @@ impl SyntaxShape { SyntaxShape::Filesize => Type::Filesize, SyntaxShape::FullCellPath => Type::Any, SyntaxShape::GlobPattern => Type::String, + SyntaxShape::Error => Type::Error, SyntaxShape::ImportPattern => Type::Any, SyntaxShape::Int => Type::Int, SyntaxShape::List(x) => { @@ -168,6 +172,7 @@ impl Display for SyntaxShape { SyntaxShape::Signature => write!(f, "signature"), SyntaxShape::Expression => write!(f, "expression"), SyntaxShape::Boolean => write!(f, "bool"), + SyntaxShape::Error => write!(f, "error"), SyntaxShape::Custom(x, _) => write!(f, "custom<{}>", x), } }