mirror of
https://github.com/nushell/nushell
synced 2024-12-27 13:33:16 +00:00
Allow handling errors with failure callbacks.
This commit is contained in:
parent
cea8fab307
commit
7614ce4b49
7 changed files with 192 additions and 86 deletions
|
@ -1,8 +1,8 @@
|
|||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::meta::tag_for_tagged_list;
|
||||
use crate::data::Value;
|
||||
use crate::errors::ShellError;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::did_you_mean;
|
||||
use log::trace;
|
||||
|
||||
pub struct Get;
|
||||
|
@ -50,56 +50,51 @@ pub fn get_column_path(
|
|||
path: &ColumnPath,
|
||||
obj: &Tagged<Value>,
|
||||
) -> Result<Tagged<Value>, ShellError> {
|
||||
let mut current = Some(obj);
|
||||
for p in path.iter() {
|
||||
if let Some(obj) = current {
|
||||
current = match obj.get_data_by_key(&p) {
|
||||
Some(v) => Some(v),
|
||||
None =>
|
||||
// Before we give up, see if they gave us a path that matches a field name by itself
|
||||
{
|
||||
let possibilities = obj.data_descriptors();
|
||||
let fields = path.clone();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.iter()
|
||||
.map(|x| (natural::distance::levenshtein_distance(x, &p), x))
|
||||
.collect();
|
||||
|
||||
possible_matches.sort();
|
||||
|
||||
if possible_matches.len() > 0 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", possible_matches[0].1),
|
||||
tag_for_tagged_list(path.iter().map(|p| p.tag())),
|
||||
));
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tag_for_tagged_list(path.iter().map(|p| p.tag())),
|
||||
));
|
||||
}
|
||||
let value = obj.get_data_by_column_path(
|
||||
obj.tag(),
|
||||
path,
|
||||
Box::new(move |(obj_source, column_path_tried)| {
|
||||
match did_you_mean(&obj_source, &column_path_tried) {
|
||||
Some(suggestions) => {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
match current {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => match obj {
|
||||
// If its None check for certain values.
|
||||
Tagged {
|
||||
item: Value::Primitive(Primitive::String(_)),
|
||||
..
|
||||
} => Ok(obj.clone()),
|
||||
Tagged {
|
||||
item: Value::Primitive(Primitive::Path(_)),
|
||||
..
|
||||
} => Ok(obj.clone()),
|
||||
_ => Ok(Value::nothing().tagged(&obj.tag)),
|
||||
let res = match value {
|
||||
Ok(fetched) => match fetched {
|
||||
Some(Tagged { item: v, tag }) => Ok((v.clone()).tagged(&tag)),
|
||||
None => match obj {
|
||||
// If its None check for certain values.
|
||||
Tagged {
|
||||
item: Value::Primitive(Primitive::String(_)),
|
||||
..
|
||||
} => Ok(obj.clone()),
|
||||
Tagged {
|
||||
item: Value::Primitive(Primitive::Path(_)),
|
||||
..
|
||||
} => Ok(obj.clone()),
|
||||
_ => Ok(Value::nothing().tagged(&obj.tag)),
|
||||
},
|
||||
},
|
||||
}
|
||||
Err(reason) => Err(reason),
|
||||
};
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
|
@ -118,26 +113,30 @@ pub fn get(
|
|||
|
||||
let member = vec![member.clone()];
|
||||
|
||||
let fields = vec![&member, &fields]
|
||||
let column_paths = vec![&member, &fields]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<&ColumnPath>>();
|
||||
|
||||
for column_path in &fields {
|
||||
match get_column_path(column_path, &item) {
|
||||
Ok(Tagged {
|
||||
item: Value::Table(l),
|
||||
..
|
||||
}) => {
|
||||
for item in l {
|
||||
result.push_back(ReturnSuccess::value(item.clone()));
|
||||
for path in column_paths {
|
||||
let res = get_column_path(&path, &item);
|
||||
|
||||
match res {
|
||||
Ok(got) => match got {
|
||||
Tagged {
|
||||
item: Value::Table(rows),
|
||||
..
|
||||
} => {
|
||||
for item in rows {
|
||||
result.push_back(ReturnSuccess::value(item.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(x) => result.push_back(ReturnSuccess::value(x.clone())),
|
||||
Err(x) => result.push_back(Err(x)),
|
||||
other => result
|
||||
.push_back(ReturnSuccess::value((*other).clone().tagged(&item.tag))),
|
||||
},
|
||||
Err(reason) => result.push_back(Err(reason)),
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
})
|
||||
.flatten();
|
||||
|
|
|
@ -530,7 +530,8 @@ impl Value {
|
|||
&self,
|
||||
tag: Tag,
|
||||
path: &Vec<Tagged<String>>,
|
||||
) -> Option<Tagged<&Value>> {
|
||||
callback: Box<dyn FnOnce((&Value, &Tagged<String>)) -> ShellError>,
|
||||
) -> Result<Option<Tagged<&Value>>, ShellError> {
|
||||
let mut current = self;
|
||||
for p in path {
|
||||
let value = if p.chars().all(char::is_numeric) {
|
||||
|
@ -543,11 +544,11 @@ impl Value {
|
|||
|
||||
match value {
|
||||
Some(v) => current = v,
|
||||
None => return None,
|
||||
None => return Err(callback((¤t.clone(), &p.clone()))),
|
||||
}
|
||||
}
|
||||
|
||||
Some(current.tagged(tag))
|
||||
Ok(Some(current.tagged(tag)))
|
||||
}
|
||||
|
||||
pub fn insert_data_at_path(
|
||||
|
@ -927,6 +928,7 @@ fn coerce_compare_primitive(
|
|||
mod tests {
|
||||
|
||||
use crate::data::meta::*;
|
||||
use crate::ShellError;
|
||||
use crate::Value;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
|
@ -942,6 +944,10 @@ mod tests {
|
|||
Value::table(list).tagged_unknown()
|
||||
}
|
||||
|
||||
fn error_callback() -> impl FnOnce((&Value, &Tagged<String>)) -> ShellError {
|
||||
move |(_obj_source, _column_path_tried)| ShellError::unimplemented("will never be called.")
|
||||
}
|
||||
|
||||
fn column_path(paths: &Vec<Tagged<Value>>) -> Tagged<Vec<Tagged<String>>> {
|
||||
table(
|
||||
&paths
|
||||
|
@ -984,7 +990,10 @@ mod tests {
|
|||
});
|
||||
|
||||
assert_eq!(
|
||||
**value.get_data_by_column_path(tag, &field_path).unwrap(),
|
||||
**value
|
||||
.get_data_by_column_path(tag, &field_path, Box::new(error_callback()))
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
version
|
||||
)
|
||||
}
|
||||
|
@ -1008,7 +1017,10 @@ mod tests {
|
|||
});
|
||||
|
||||
assert_eq!(
|
||||
**value.get_data_by_column_path(tag, &field_path).unwrap(),
|
||||
**value
|
||||
.get_data_by_column_path(tag, &field_path, Box::new(error_callback()))
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
name
|
||||
)
|
||||
}
|
||||
|
@ -1032,7 +1044,10 @@ mod tests {
|
|||
});
|
||||
|
||||
assert_eq!(
|
||||
**value.get_data_by_column_path(tag, &field_path).unwrap(),
|
||||
**value
|
||||
.get_data_by_column_path(tag, &field_path, Box::new(error_callback()))
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
Value::row(indexmap! {
|
||||
"name".into() => string("Andrés N. Robalino")
|
||||
})
|
||||
|
|
|
@ -30,12 +30,12 @@ pub use crate::env::host::BasicHost;
|
|||
pub use crate::parser::hir::SyntaxShape;
|
||||
pub use crate::parser::parse::token_tree_builder::TokenTreeBuilder;
|
||||
pub use crate::plugin::{serve_plugin, Plugin};
|
||||
pub use crate::utils::{AbsoluteFile, AbsolutePath, RelativePath};
|
||||
pub use crate::utils::{did_you_mean, AbsoluteFile, AbsolutePath, RelativePath};
|
||||
pub use cli::cli;
|
||||
pub use data::base::{Primitive, Value};
|
||||
pub use data::config::{config_path, APP_INFO};
|
||||
pub use data::dict::{Dictionary, TaggedDictBuilder};
|
||||
pub use data::meta::{Span, Spanned, SpannedItem, Tag, Tagged, TaggedItem};
|
||||
pub use data::meta::{tag_for_tagged_list, Span, Spanned, SpannedItem, Tag, Tagged, TaggedItem};
|
||||
pub use errors::{CoerceInto, ShellError};
|
||||
pub use num_traits::cast::ToPrimitive;
|
||||
pub use parser::parse::text::Text;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use nu::{
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
SyntaxShape, Tagged, TaggedItem, Value,
|
||||
did_you_mean, serve_plugin, tag_for_tagged_list, CallInfo, Plugin, Primitive, ReturnSuccess,
|
||||
ReturnValue, ShellError, Signature, SyntaxShape, Tagged, TaggedItem, Value,
|
||||
};
|
||||
|
||||
enum Action {
|
||||
|
@ -93,22 +93,51 @@ impl Inc {
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
Value::Row(_) => match self.field {
|
||||
Some(ref f) => {
|
||||
let replacement = match value.item.get_data_by_column_path(value.tag(), f) {
|
||||
Some(result) => self.inc(result.map(|x| x.clone()))?,
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"inc could not find field to replace",
|
||||
"column name",
|
||||
value.tag(),
|
||||
))
|
||||
}
|
||||
let fields = f.clone();
|
||||
|
||||
let replace_for = value.item.get_data_by_column_path(
|
||||
value.tag(),
|
||||
&f,
|
||||
Box::new(move |(obj_source, column_path_tried)| {
|
||||
match did_you_mean(&obj_source, &column_path_tried) {
|
||||
Some(suggestions) => {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
|
||||
)
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let replacement = match replace_for {
|
||||
Ok(got) => match got {
|
||||
Some(result) => self.inc(result.map(|x| x.clone()))?,
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"inc could not find field to replace",
|
||||
"column name",
|
||||
value.tag(),
|
||||
))
|
||||
}
|
||||
},
|
||||
Err(reason) => return Err(reason),
|
||||
};
|
||||
|
||||
match value.item.replace_data_at_column_path(
|
||||
value.tag(),
|
||||
f,
|
||||
&f,
|
||||
replacement.item.clone(),
|
||||
) {
|
||||
Some(v) => return Ok(v),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use nu::{
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
SyntaxShape, Tagged, TaggedItem, Value,
|
||||
did_you_mean, serve_plugin, tag_for_tagged_list, CallInfo, Plugin, Primitive, ReturnSuccess,
|
||||
ReturnValue, ShellError, Signature, SyntaxShape, Tagged, TaggedItem, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
|
@ -92,13 +92,50 @@ impl Str {
|
|||
Value::Primitive(Primitive::String(ref s)) => Ok(self.apply(&s)?.tagged(value.tag())),
|
||||
Value::Row(_) => match self.field {
|
||||
Some(ref f) => {
|
||||
let replacement = match value.item.get_data_by_column_path(value.tag(), f) {
|
||||
Some(result) => self.strutils(result.map(|x| x.clone()))?,
|
||||
None => return Ok(Value::nothing().tagged(value.tag)),
|
||||
let fields = f.clone();
|
||||
|
||||
let replace_for = value.item.get_data_by_column_path(
|
||||
value.tag(),
|
||||
&f,
|
||||
Box::new(move |(obj_source, column_path_tried)| {
|
||||
//let fields = f.clone();
|
||||
|
||||
match did_you_mean(&obj_source, &column_path_tried) {
|
||||
Some(suggestions) => {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
format!("did you mean '{}'?", suggestions[0].1),
|
||||
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
return ShellError::labeled_error(
|
||||
"Unknown column",
|
||||
"row does not contain this column",
|
||||
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
|
||||
)
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let replacement = match replace_for {
|
||||
Ok(got) => match got {
|
||||
Some(result) => self.strutils(result.map(|x| x.clone()))?,
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"inc could not find field to replace",
|
||||
"column name",
|
||||
value.tag(),
|
||||
))
|
||||
}
|
||||
},
|
||||
Err(reason) => return Err(reason),
|
||||
};
|
||||
|
||||
match value.item.replace_data_at_column_path(
|
||||
value.tag(),
|
||||
f,
|
||||
&f,
|
||||
replacement.item.clone(),
|
||||
) {
|
||||
Some(v) => return Ok(v),
|
||||
|
|
|
@ -66,7 +66,9 @@ pub(crate) use crate::commands::RawCommandArgs;
|
|||
pub(crate) use crate::context::CommandRegistry;
|
||||
pub(crate) use crate::context::{AnchorLocation, Context};
|
||||
pub(crate) use crate::data::base as value;
|
||||
pub(crate) use crate::data::meta::{Span, Spanned, SpannedItem, Tag, Tagged, TaggedItem};
|
||||
pub(crate) use crate::data::meta::{
|
||||
tag_for_tagged_list, Span, Spanned, SpannedItem, Tag, Tagged, TaggedItem,
|
||||
};
|
||||
pub(crate) use crate::data::types::ExtractType;
|
||||
pub(crate) use crate::data::{Primitive, Value};
|
||||
pub(crate) use crate::env::host::handle_unexpected;
|
||||
|
|
24
src/utils.rs
24
src/utils.rs
|
@ -5,6 +5,30 @@ use std::fmt;
|
|||
use std::ops::Div;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
pub fn did_you_mean(
|
||||
obj_source: &Value,
|
||||
field_tried: &Tagged<String>,
|
||||
) -> Option<Vec<(usize, String)>> {
|
||||
let possibilities = obj_source.data_descriptors();
|
||||
|
||||
let mut possible_matches: Vec<_> = possibilities
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let word = x.clone();
|
||||
let distance = natural::distance::levenshtein_distance(&word, &field_tried);
|
||||
|
||||
(distance, word)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if possible_matches.len() > 0 {
|
||||
possible_matches.sort();
|
||||
return Some(possible_matches);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub struct AbsoluteFile {
|
||||
inner: PathBuf,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue