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::{
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<nu_protocol::PipelineData, nu_protocol::ShellError> {
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<Example> {
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<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 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;

View file

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

View file

@ -65,6 +65,14 @@ impl Stack {
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) {
self.vars.insert(var_id, value);
}