diff --git a/src/data/base.rs b/src/data/base.rs index f7b875ef53..c95ee26e86 100644 --- a/src/data/base.rs +++ b/src/data/base.rs @@ -8,6 +8,7 @@ use crate::Text; use chrono::{DateTime, Utc}; use chrono_humanize::Humanize; use derive_new::new; +use indexmap::IndexMap; use log::trace; use serde::{Deserialize, Serialize}; use std::fmt; @@ -452,7 +453,7 @@ impl Value { match self { Value::Primitive(p) => p.type_name(), Value::Row(_) => format!("row"), - Value::Table(_) => format!("list"), + Value::Table(_) => format!("table"), Value::Block(_) => format!("block"), Value::Error(_) => format!("error"), } @@ -684,6 +685,15 @@ impl Value { Value::Row(ref mut o) => { current = o; } + Value::Table(ref mut l) => match l.get_mut(0) { + Some(Tagged { + item: Value::Row(ref mut dict), + .. + }) => { + current = dict; + } + _ => return None, + }, _ => return None, } } @@ -769,6 +779,21 @@ impl Value { } } + #[allow(unused)] + pub fn row(entries: IndexMap>) -> Value { + Value::Row(entries.into()) + } + + pub fn table(list: &Vec>) -> Value { + let mut out = vec![]; + + for v in list { + out.push(v.clone()); + } + + Value::Table(out) + } + pub fn string(s: impl Into) -> Value { Value::Primitive(Primitive::String(s.into())) } @@ -927,3 +952,106 @@ fn coerce_compare_primitive( _ => return Err((left.type_name(), right.type_name())), }) } +#[cfg(test)] +mod tests { + + use crate::data::meta::*; + use crate::Value; + use indexmap::IndexMap; + + fn string(input: impl Into) -> Tagged { + Value::string(input.into()).tagged_unknown() + } + + fn row(entries: IndexMap>) -> Tagged { + Value::row(entries).tagged_unknown() + } + + fn table(list: &Vec>) -> Tagged { + Value::table(list).tagged_unknown() + } + + fn column_path(paths: &Vec>) -> Tagged>> { + let paths = paths + .iter() + .map(|p| string(p.as_string().unwrap())) + .collect(); + let table = table(&paths); + table.as_column_path().unwrap() + } + #[test] + fn gets_the_matching_field_from_a_row() { + let field = "amigos"; + + let row = Value::row(indexmap! { + field.into() => table(&vec![ + string("andres"), + string("jonathan"), + string("yehuda"), + ]), + }); + + assert_eq!( + table(&vec![ + string("andres"), + string("jonathan"), + string("yehuda") + ]), + *row.get_data_by_key(field).unwrap() + ); + } + + #[test] + fn gets_the_first_row_with_matching_field_from_rows_inside_a_table() { + let field = "name"; + + let table = Value::table(&vec![ + row(indexmap! {field.into() => string("andres")}), + row(indexmap! {field.into() => string("jonathan")}), + row(indexmap! {field.into() => string("yehuda")}), + ]); + + assert_eq!(string("andres"), *table.get_data_by_key(field).unwrap()); + } + + #[test] + fn gets_the_matching_field_from_nested_rows_inside_a_row() { + let _field = "package.version"; + let field = vec![string("package"), string("version")]; + let field = column_path(&field); + + let (version, tag) = string("0.4.0").into_parts(); + + let row = Value::row(indexmap! { + "package".into() => row(indexmap!{ + "name".into() => string("nu"), + "version".into() => string("0.4.0"), + }) + }); + + assert_eq!(version, **row.get_data_by_column_path(tag, &field).unwrap()) + } + + #[test] + fn gets_the_first_row_with_matching_field_from_nested_rows_inside_a_table() { + let _field = "package.authors.name"; + let field = vec![string("package"), string("authors"), string("name")]; + let field = column_path(&field); + + let (name, tag) = string("Andrés N. Robalino").into_parts(); + + let row = Value::row(indexmap! { + "package".into() => row(indexmap!{ + "authors".into() => table(&vec![ + row(indexmap!{"name".into()=> string("Andrés N. Robalino")}), + row(indexmap!{"name".into()=> string("Jonathan Turner")}), + row(indexmap!{"name".into() => string("Yehuda Katz")}) + ]), + "name".into() => string("nu"), + "version".into() => string("0.4.0"), + }) + }); + + assert_eq!(name, **row.get_data_by_column_path(tag, &field).unwrap()) + } +} diff --git a/src/lib.rs b/src/lib.rs index bfcaa4510f..520e08a136 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ #![recursion_limit = "1024"] +#[cfg(test)] +#[macro_use] +extern crate indexmap; + #[macro_use] mod prelude; diff --git a/src/plugins/embed.rs b/src/plugins/embed.rs index 97dd6a2713..e659bfeb3b 100644 --- a/src/plugins/embed.rs +++ b/src/plugins/embed.rs @@ -1,6 +1,9 @@ +#[macro_use] +extern crate indexmap; + use nu::{ serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature, - SyntaxShape, Tag, Tagged, TaggedDictBuilder, Value, + SyntaxShape, Tag, Tagged, TaggedItem, Value, }; struct Embed { @@ -16,22 +19,8 @@ impl Embed { } fn embed(&mut self, value: Tagged) -> Result<(), ShellError> { - match value { - Tagged { item, tag } => match &self.field { - Some(_) => { - self.values.push(Tagged { - item: item, - tag: tag, - }); - Ok(()) - } - None => Err(ShellError::labeled_error( - "embed needs a field when embedding a value", - "original value", - &tag, - )), - }, - } + self.values.push(value); + Ok(()) } } @@ -39,8 +28,7 @@ impl Plugin for Embed { fn config(&mut self) -> Result { Ok(Signature::build("embed") .desc("Embeds a new field to the table.") - .required("Field", SyntaxShape::String) - .rest(SyntaxShape::String) + .optional("field", SyntaxShape::String) .filter()) } @@ -67,15 +55,15 @@ impl Plugin for Embed { } fn end_filter(&mut self) -> Result, ShellError> { - let mut root = TaggedDictBuilder::new(Tag::unknown()); - root.insert_tagged( - self.field.as_ref().unwrap(), - Tagged { - item: Value::Table(self.values.clone()), - tag: Tag::unknown(), - }, - ); - Ok(vec![ReturnSuccess::value(root.into_tagged_value())]) + let row = Value::row(indexmap! { + match &self.field { + Some(key) => key.clone(), + None => "root".into(), + } => Value::table(&self.values).tagged(Tag::unknown()), + }) + .tagged(Tag::unknown()); + + Ok(vec![ReturnSuccess::value(row)]) } } diff --git a/src/plugins/inc.rs b/src/plugins/inc.rs index 38788014ad..1cb6cb2b97 100644 --- a/src/plugins/inc.rs +++ b/src/plugins/inc.rs @@ -14,7 +14,7 @@ pub enum SemVerAction { Patch, } -pub type ColumnPath = Tagged>>; +pub type ColumnPath = Vec>; struct Inc { field: Option, @@ -83,6 +83,16 @@ impl Inc { Ok(Value::bytes(b + 1 as u64).tagged(value.tag())) } Value::Primitive(Primitive::String(ref s)) => Ok(self.apply(&s)?.tagged(value.tag())), + Value::Table(values) => { + if values.len() == 1 { + return Ok(Value::Table(vec![self.inc(values[0].clone())?]).tagged(value.tag())); + } else { + return Err(ShellError::type_error( + "incrementable value", + value.tagged_type_name(), + )); + } + } Value::Row(_) => match self.field { Some(ref f) => { let replacement = match value.item.get_data_by_column_path(value.tag(), f) { @@ -91,10 +101,11 @@ impl Inc { return Err(ShellError::labeled_error( "inc could not find field to replace", "column name", - &f.tag, + value.tag(), )) } }; + match value.item.replace_data_at_column_path( value.tag(), f, @@ -105,7 +116,7 @@ impl Inc { return Err(ShellError::labeled_error( "inc could not find field to replace", "column name", - &f.tag, + value.tag(), )) } } @@ -151,7 +162,7 @@ impl Plugin for Inc { item: Value::Table(_), .. } => { - self.field = Some(table.as_column_path()?); + self.field = Some(table.as_column_path()?.item().to_vec()); } value => return Err(ShellError::type_error("table", value.tagged_type_name())), }