mirror of
https://github.com/nushell/nushell
synced 2024-11-10 15:14:14 +00:00
Error make (#948)
* Add `error make` and improve `metadata` * Allow metadata to work on just a pipeline
This commit is contained in:
parent
e45e8109aa
commit
8a93548de2
5 changed files with 241 additions and 39 deletions
125
crates/nu-command/src/core_commands/error_make.rs
Normal file
125
crates/nu-command/src/core_commands/error_make.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -33,6 +33,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||
Do,
|
||||
Du,
|
||||
Echo,
|
||||
ErrorMake,
|
||||
ExportCommand,
|
||||
ExportDef,
|
||||
ExportDefEnv,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue