Error make (#948)

* Add `error make` and improve `metadata`

* Allow metadata to work on just a pipeline
This commit is contained in:
JT 2022-02-05 09:39:51 -05:00 committed by GitHub
parent e45e8109aa
commit 8a93548de2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 241 additions and 39 deletions

View File

@ -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<nu_protocol::PipelineData, nu_protocol::ShellError> {
let span = call.head;
let ctrlc = engine_state.ctrlc.clone();
let arg: Option<Value> = 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<Example> {
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<ShellError> {
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
}
}

View File

@ -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::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, DataSource, Example, PipelineData, PipelineMetadata, Signature, Value, Category, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata, Signature,
Span, SyntaxShape, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -17,47 +19,61 @@ impl Command for Metadata {
} }
fn signature(&self) -> nu_protocol::Signature { 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( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let arg = call.positional.get(0);
let head = call.head; let head = call.head;
let ctrlc = engine_state.ctrlc.clone();
let metadata = input.metadata(); match arg {
Some(Expression {
input.map( expr: Expr::FullCellPath(full_cell_path),
move |x| { span,
let span = x.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 cols = vec![];
let mut vals = vec![]; let mut vals = vec![];
if let Some(x) = &input.metadata() {
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 {
match x { match x {
PipelineMetadata { PipelineMetadata {
data_source: DataSource::Ls, data_source: DataSource::Ls,
@ -71,22 +87,72 @@ impl Command for Metadata {
} }
} }
Value::Record { Ok(Value::Record {
cols, cols,
vals, vals,
span: head, span: head,
} }
}, .into_pipeline_data())
ctrlc, }
) }
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "Get the metadata of a value", Example {
example: "3 | metadata", description: "Get the metadata of a variable",
result: None, 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<PipelineMetadata>, 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,
} }
} }

View File

@ -5,6 +5,7 @@ mod def_env;
mod describe; mod describe;
mod do_; mod do_;
mod echo; mod echo;
mod error_make;
mod export; mod export;
mod export_def; mod export_def;
mod export_def_env; mod export_def_env;
@ -30,6 +31,7 @@ pub use def_env::DefEnv;
pub use describe::Describe; pub use describe::Describe;
pub use do_::Do; pub use do_::Do;
pub use echo::Echo; pub use echo::Echo;
pub use error_make::ErrorMake;
pub use export::ExportCommand; pub use export::ExportCommand;
pub use export_def::ExportDef; pub use export_def::ExportDef;
pub use export_def_env::ExportDefEnv; pub use export_def_env::ExportDefEnv;

View File

@ -33,6 +33,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
Do, Do,
Du, Du,
Echo, Echo,
ErrorMake,
ExportCommand, ExportCommand,
ExportDef, ExportDef,
ExportDefEnv, ExportDefEnv,

View File

@ -65,6 +65,14 @@ impl Stack {
Err(ShellError::VariableNotFoundAtRuntime(span)) Err(ShellError::VariableNotFoundAtRuntime(span))
} }
pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
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) { pub fn add_var(&mut self, var_id: VarId, value: Value) {
self.vars.insert(var_id, value); self.vars.insert(var_id, value);
} }