Allow handling errors with failure callbacks.

This commit is contained in:
Andrés N. Robalino 2019-10-30 17:46:40 -05:00
parent cea8fab307
commit 7614ce4b49
7 changed files with 192 additions and 86 deletions

View File

@ -1,8 +1,8 @@
use crate::commands::WholeStreamCommand; use crate::commands::WholeStreamCommand;
use crate::data::meta::tag_for_tagged_list;
use crate::data::Value; use crate::data::Value;
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::did_you_mean;
use log::trace; use log::trace;
pub struct Get; pub struct Get;
@ -50,56 +50,51 @@ pub fn get_column_path(
path: &ColumnPath, path: &ColumnPath,
obj: &Tagged<Value>, obj: &Tagged<Value>,
) -> Result<Tagged<Value>, ShellError> { ) -> Result<Tagged<Value>, ShellError> {
let mut current = Some(obj); let fields = path.clone();
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 mut possible_matches: Vec<_> = possibilities let value = obj.get_data_by_column_path(
.iter() obj.tag(),
.map(|x| (natural::distance::levenshtein_distance(x, &p), x)) path,
.collect(); Box::new(move |(obj_source, column_path_tried)| {
match did_you_mean(&obj_source, &column_path_tried) {
possible_matches.sort(); Some(suggestions) => {
return ShellError::labeled_error(
if possible_matches.len() > 0 { "Unknown column",
return Err(ShellError::labeled_error( format!("did you mean '{}'?", suggestions[0].1),
"Unknown column", tag_for_tagged_list(fields.iter().map(|p| p.tag())),
format!("did you mean '{}'?", possible_matches[0].1), )
tag_for_tagged_list(path.iter().map(|p| p.tag())), }
)); None => {
} else { return ShellError::labeled_error(
return Err(ShellError::labeled_error( "Unknown column",
"Unknown column", "row does not contain this column",
"row does not contain this column", tag_for_tagged_list(fields.iter().map(|p| p.tag())),
tag_for_tagged_list(path.iter().map(|p| p.tag())), )
));
}
} }
} }
} }),
} );
match current { let res = match value {
Some(v) => Ok(v.clone()), Ok(fetched) => match fetched {
None => match obj { Some(Tagged { item: v, tag }) => Ok((v.clone()).tagged(&tag)),
// If its None check for certain values. None => match obj {
Tagged { // If its None check for certain values.
item: Value::Primitive(Primitive::String(_)), Tagged {
.. item: Value::Primitive(Primitive::String(_)),
} => Ok(obj.clone()), ..
Tagged { } => Ok(obj.clone()),
item: Value::Primitive(Primitive::Path(_)), Tagged {
.. item: Value::Primitive(Primitive::Path(_)),
} => Ok(obj.clone()), ..
_ => Ok(Value::nothing().tagged(&obj.tag)), } => Ok(obj.clone()),
_ => Ok(Value::nothing().tagged(&obj.tag)),
},
}, },
} Err(reason) => Err(reason),
};
res
} }
pub fn get( pub fn get(
@ -118,26 +113,30 @@ pub fn get(
let member = vec![member.clone()]; let member = vec![member.clone()];
let fields = vec![&member, &fields] let column_paths = vec![&member, &fields]
.into_iter() .into_iter()
.flatten() .flatten()
.collect::<Vec<&ColumnPath>>(); .collect::<Vec<&ColumnPath>>();
for column_path in &fields { for path in column_paths {
match get_column_path(column_path, &item) { let res = get_column_path(&path, &item);
Ok(Tagged {
item: Value::Table(l), match res {
.. Ok(got) => match got {
}) => { Tagged {
for item in l { item: Value::Table(rows),
result.push_back(ReturnSuccess::value(item.clone())); ..
} => {
for item in rows {
result.push_back(ReturnSuccess::value(item.clone()));
}
} }
} other => result
Ok(x) => result.push_back(ReturnSuccess::value(x.clone())), .push_back(ReturnSuccess::value((*other).clone().tagged(&item.tag))),
Err(x) => result.push_back(Err(x)), },
Err(reason) => result.push_back(Err(reason)),
} }
} }
result result
}) })
.flatten(); .flatten();

View File

@ -530,7 +530,8 @@ impl Value {
&self, &self,
tag: Tag, tag: Tag,
path: &Vec<Tagged<String>>, path: &Vec<Tagged<String>>,
) -> Option<Tagged<&Value>> { callback: Box<dyn FnOnce((&Value, &Tagged<String>)) -> ShellError>,
) -> Result<Option<Tagged<&Value>>, ShellError> {
let mut current = self; let mut current = self;
for p in path { for p in path {
let value = if p.chars().all(char::is_numeric) { let value = if p.chars().all(char::is_numeric) {
@ -543,11 +544,11 @@ impl Value {
match value { match value {
Some(v) => current = v, Some(v) => current = v,
None => return None, None => return Err(callback((&current.clone(), &p.clone()))),
} }
} }
Some(current.tagged(tag)) Ok(Some(current.tagged(tag)))
} }
pub fn insert_data_at_path( pub fn insert_data_at_path(
@ -927,6 +928,7 @@ fn coerce_compare_primitive(
mod tests { mod tests {
use crate::data::meta::*; use crate::data::meta::*;
use crate::ShellError;
use crate::Value; use crate::Value;
use indexmap::IndexMap; use indexmap::IndexMap;
@ -942,6 +944,10 @@ mod tests {
Value::table(list).tagged_unknown() 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>>> { fn column_path(paths: &Vec<Tagged<Value>>) -> Tagged<Vec<Tagged<String>>> {
table( table(
&paths &paths
@ -984,7 +990,10 @@ mod tests {
}); });
assert_eq!( 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 version
) )
} }
@ -1008,7 +1017,10 @@ mod tests {
}); });
assert_eq!( 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 name
) )
} }
@ -1032,7 +1044,10 @@ mod tests {
}); });
assert_eq!( 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! { Value::row(indexmap! {
"name".into() => string("Andrés N. Robalino") "name".into() => string("Andrés N. Robalino")
}) })

View File

@ -30,12 +30,12 @@ pub use crate::env::host::BasicHost;
pub use crate::parser::hir::SyntaxShape; pub use crate::parser::hir::SyntaxShape;
pub use crate::parser::parse::token_tree_builder::TokenTreeBuilder; pub use crate::parser::parse::token_tree_builder::TokenTreeBuilder;
pub use crate::plugin::{serve_plugin, Plugin}; 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 cli::cli;
pub use data::base::{Primitive, Value}; pub use data::base::{Primitive, Value};
pub use data::config::{config_path, APP_INFO}; pub use data::config::{config_path, APP_INFO};
pub use data::dict::{Dictionary, TaggedDictBuilder}; 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 errors::{CoerceInto, ShellError};
pub use num_traits::cast::ToPrimitive; pub use num_traits::cast::ToPrimitive;
pub use parser::parse::text::Text; pub use parser::parse::text::Text;

View File

@ -1,6 +1,6 @@
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, did_you_mean, serve_plugin, tag_for_tagged_list, CallInfo, Plugin, Primitive, ReturnSuccess,
SyntaxShape, Tagged, TaggedItem, Value, ReturnValue, ShellError, Signature, SyntaxShape, Tagged, TaggedItem, Value,
}; };
enum Action { enum Action {
@ -93,22 +93,51 @@ impl Inc {
)); ));
} }
} }
Value::Row(_) => match self.field { Value::Row(_) => match self.field {
Some(ref f) => { Some(ref f) => {
let replacement = match value.item.get_data_by_column_path(value.tag(), f) { let fields = f.clone();
Some(result) => self.inc(result.map(|x| x.clone()))?,
None => { let replace_for = value.item.get_data_by_column_path(
return Err(ShellError::labeled_error( value.tag(),
"inc could not find field to replace", &f,
"column name", Box::new(move |(obj_source, column_path_tried)| {
value.tag(), 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( match value.item.replace_data_at_column_path(
value.tag(), value.tag(),
f, &f,
replacement.item.clone(), replacement.item.clone(),
) { ) {
Some(v) => return Ok(v), Some(v) => return Ok(v),

View File

@ -1,6 +1,6 @@
use nu::{ use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, did_you_mean, serve_plugin, tag_for_tagged_list, CallInfo, Plugin, Primitive, ReturnSuccess,
SyntaxShape, Tagged, TaggedItem, Value, ReturnValue, ShellError, Signature, SyntaxShape, Tagged, TaggedItem, Value,
}; };
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
@ -92,13 +92,50 @@ impl Str {
Value::Primitive(Primitive::String(ref s)) => Ok(self.apply(&s)?.tagged(value.tag())), Value::Primitive(Primitive::String(ref s)) => Ok(self.apply(&s)?.tagged(value.tag())),
Value::Row(_) => match self.field { Value::Row(_) => match self.field {
Some(ref f) => { Some(ref f) => {
let replacement = match value.item.get_data_by_column_path(value.tag(), f) { let fields = f.clone();
Some(result) => self.strutils(result.map(|x| x.clone()))?,
None => return Ok(Value::nothing().tagged(value.tag)), 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( match value.item.replace_data_at_column_path(
value.tag(), value.tag(),
f, &f,
replacement.item.clone(), replacement.item.clone(),
) { ) {
Some(v) => return Ok(v), Some(v) => return Ok(v),

View File

@ -66,7 +66,9 @@ pub(crate) use crate::commands::RawCommandArgs;
pub(crate) use crate::context::CommandRegistry; pub(crate) use crate::context::CommandRegistry;
pub(crate) use crate::context::{AnchorLocation, Context}; pub(crate) use crate::context::{AnchorLocation, Context};
pub(crate) use crate::data::base as value; 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::types::ExtractType;
pub(crate) use crate::data::{Primitive, Value}; pub(crate) use crate::data::{Primitive, Value};
pub(crate) use crate::env::host::handle_unexpected; pub(crate) use crate::env::host::handle_unexpected;

View File

@ -5,6 +5,30 @@ use std::fmt;
use std::ops::Div; use std::ops::Div;
use std::path::{Component, Path, PathBuf}; 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 { pub struct AbsoluteFile {
inner: PathBuf, inner: PathBuf,
} }