From 8a93548de2811b9874b27d0d2d674bdcba7ba77f Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 5 Feb 2022 09:39:51 -0500 Subject: [PATCH] Error make (#948) * Add `error make` and improve `metadata` * Allow metadata to work on just a pipeline --- .../src/core_commands/error_make.rs | 125 +++++++++++++++ .../nu-command/src/core_commands/metadata.rs | 144 +++++++++++++----- crates/nu-command/src/core_commands/mod.rs | 2 + crates/nu-command/src/default_context.rs | 1 + crates/nu-protocol/src/engine/stack.rs | 8 + 5 files changed, 241 insertions(+), 39 deletions(-) create mode 100644 crates/nu-command/src/core_commands/error_make.rs diff --git a/crates/nu-command/src/core_commands/error_make.rs b/crates/nu-command/src/core_commands/error_make.rs new file mode 100644 index 000000000..764cea9ab --- /dev/null +++ b/crates/nu-command/src/core_commands/error_make.rs @@ -0,0 +1,125 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, + Value, +}; + +#[derive(Clone)] +pub struct ErrorMake; + +impl Command for ErrorMake { + fn name(&self) -> &str { + "error make" + } + + fn signature(&self) -> Signature { + Signature::build("error make") + .optional("error-struct", SyntaxShape::Record, "the error to create") + .category(Category::Core) + } + + fn usage(&self) -> &str { + "Create an error." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let span = call.head; + let ctrlc = engine_state.ctrlc.clone(); + let arg: Option = call.opt(engine_state, stack, 0)?; + + if let Some(arg) = arg { + Ok(make_error(&arg) + .map(|err| Value::Error { error: err }) + .unwrap_or_else(|| Value::Error { + error: ShellError::SpannedLabeledError( + "Creating error value not supported.".into(), + "unsupported error format".into(), + span, + ), + }) + .into_pipeline_data()) + } else { + input.map( + move |value| { + make_error(&value) + .map(|err| Value::Error { error: err }) + .unwrap_or_else(|| Value::Error { + error: ShellError::SpannedLabeledError( + "Creating error value not supported.".into(), + "unsupported error format".into(), + span, + ), + }) + }, + ctrlc, + ) + } + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Creates a labeled error", + example: r#"{msg: "The message" , label: {start: 0, end: 141, text: "Helpful message here"}} | error make"#, + result: None, + }, + Example { + description: "Creates a labeled error, using a call argument", + example: r#"error make {msg: "The message" , label: {start: 0, end: 141, text: "Helpful message here"}}"#, + result: None, + }, + Example { + description: "Create a custom error for a custom command", + example: r#"def foo [x] { + let span = (metadata $x).span; + error make {msg: "this is fishy", label: {text: "fish right here", start: $span.start, end: $span.end } } + }"#, + result: None, + }, + ] + } +} + +fn make_error(value: &Value) -> Option { + if let Value::Record { .. } = &value { + let msg = value.get_data_by_key("msg"); + let label = value.get_data_by_key("label"); + + match (msg, &label) { + (Some(Value::String { val: message, .. }), Some(label)) => { + let label_start = label.get_data_by_key("start"); + let label_end = label.get_data_by_key("end"); + let label_text = label.get_data_by_key("text"); + + match (label_start, label_end, label_text) { + ( + Some(Value::Int { val: start, .. }), + Some(Value::Int { val: end, .. }), + Some(Value::String { + val: label_text, .. + }), + ) => Some(ShellError::SpannedLabeledError( + message, + label_text, + Span { + start: start as usize, + end: end as usize, + }, + )), + _ => None, + } + } + _ => None, + } + } else { + None + } +} diff --git a/crates/nu-command/src/core_commands/metadata.rs b/crates/nu-command/src/core_commands/metadata.rs index 17f931099..d84d1a4e8 100644 --- a/crates/nu-command/src/core_commands/metadata.rs +++ b/crates/nu-command/src/core_commands/metadata.rs @@ -1,7 +1,9 @@ -use nu_protocol::ast::Call; +use nu_engine::CallExt; +use nu_protocol::ast::{Call, Expr, Expression}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, DataSource, Example, PipelineData, PipelineMetadata, Signature, Value, + Category, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata, Signature, + Span, SyntaxShape, Value, }; #[derive(Clone)] @@ -17,47 +19,61 @@ impl Command for Metadata { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("metadata").category(Category::Core) + Signature::build("metadata") + .optional( + "expression", + SyntaxShape::Any, + "the expression you want metadata for", + ) + .category(Category::Core) } fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { + let arg = call.positional.get(0); let head = call.head; - let ctrlc = engine_state.ctrlc.clone(); - let metadata = input.metadata(); - - input.map( - move |x| { - let span = x.span(); + match arg { + Some(Expression { + expr: Expr::FullCellPath(full_cell_path), + span, + .. + }) => { + if full_cell_path.tail.is_empty() { + match &full_cell_path.head { + Expression { + expr: Expr::Var(var_id), + .. + } => { + let origin = stack.get_var_with_origin(*var_id, *span)?; + Ok(build_metadata_record(&origin, &input.metadata(), head) + .into_pipeline_data()) + } + _ => { + let val: Value = call.req(engine_state, stack, 0)?; + Ok(build_metadata_record(&val, &input.metadata(), head) + .into_pipeline_data()) + } + } + } else { + let val: Value = call.req(engine_state, stack, 0)?; + Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data()) + } + } + Some(_) => { + let val: Value = call.req(engine_state, stack, 0)?; + Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data()) + } + None => { let mut cols = vec![]; let mut vals = vec![]; - - cols.push("span".into()); - if let Ok(span) = span { - vals.push(Value::Record { - cols: vec!["start".into(), "end".into()], - vals: vec![ - Value::Int { - val: span.start as i64, - span, - }, - Value::Int { - val: span.end as i64, - span, - }, - ], - span: head, - }); - } - - if let Some(x) = &metadata { + if let Some(x) = &input.metadata() { match x { PipelineMetadata { data_source: DataSource::Ls, @@ -71,22 +87,72 @@ impl Command for Metadata { } } - Value::Record { + Ok(Value::Record { cols, vals, span: head, } - }, - ctrlc, - ) + .into_pipeline_data()) + } + } } fn examples(&self) -> Vec { - vec![Example { - description: "Get the metadata of a value", - example: "3 | metadata", - result: None, - }] + vec![ + Example { + description: "Get the metadata of a variable", + example: "metadata $a", + result: None, + }, + Example { + description: "Get the metadata of the input", + example: "ls | metadata", + result: None, + }, + ] + } +} + +fn build_metadata_record(arg: &Value, metadata: &Option, head: Span) -> Value { + let mut cols = vec![]; + let mut vals = vec![]; + + if let Ok(span) = arg.span() { + cols.push("span".into()); + vals.push(Value::Record { + cols: vec!["start".into(), "end".into()], + vals: vec![ + Value::Int { + val: span.start as i64, + span, + }, + Value::Int { + val: span.end as i64, + span, + }, + ], + span: head, + }); + } + + if let Some(x) = &metadata { + match x { + PipelineMetadata { + data_source: DataSource::Ls, + } => { + cols.push("source".into()); + vals.push(Value::String { + val: "ls".into(), + span: head, + }) + } + } + } + + Value::Record { + cols, + vals, + span: head, } } diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index eaa87a107..b63275a8b 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -5,6 +5,7 @@ mod def_env; mod describe; mod do_; mod echo; +mod error_make; mod export; mod export_def; mod export_def_env; @@ -30,6 +31,7 @@ pub use def_env::DefEnv; pub use describe::Describe; pub use do_::Do; pub use echo::Echo; +pub use error_make::ErrorMake; pub use export::ExportCommand; pub use export_def::ExportDef; pub use export_def_env::ExportDefEnv; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 28b94edb6..05b198719 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -33,6 +33,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Do, Du, Echo, + ErrorMake, ExportCommand, ExportDef, ExportDefEnv, diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 94c96fc8b..f898abffa 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -65,6 +65,14 @@ impl Stack { Err(ShellError::VariableNotFoundAtRuntime(span)) } + pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result { + if let Some(v) = self.vars.get(&var_id) { + return Ok(v.clone()); + } + + Err(ShellError::VariableNotFoundAtRuntime(span)) + } + pub fn add_var(&mut self, var_id: VarId, value: Value) { self.vars.insert(var_id, value); }