From ec7d49b0d21fe77bb64449eaced2e76d6a526924 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Mon, 22 Jul 2019 15:52:57 +1200 Subject: [PATCH] Add and edit functionality --- Cargo.toml | 8 ++++ README.md | 2 + src/commands/to_json.rs | 1 - src/commands/to_toml.rs | 37 +++++------------ src/object/base.rs | 50 +++++++++++++++++++++++ src/plugins/add.rs | 89 +++++++++++++++++++++++++++++++++++++++++ src/plugins/edit.rs | 89 +++++++++++++++++++++++++++++++++++++++++ tests/tests.rs | 26 ++++++++++-- 8 files changed, 270 insertions(+), 32 deletions(-) create mode 100644 src/plugins/add.rs create mode 100644 src/plugins/edit.rs diff --git a/Cargo.toml b/Cargo.toml index 953c89f7e..08559b23c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,14 @@ path = "src/lib.rs" name = "nu_plugin_inc" path = "src/plugins/inc.rs" +[[bin]] +name = "nu_plugin_add" +path = "src/plugins/add.rs" + +[[bin]] +name = "nu_plugin_edit" +path = "src/plugins/edit.rs" + [[bin]] name = "nu_plugin_skip" path = "src/plugins/skip.rs" diff --git a/README.md b/README.md index fd382bfa1..459697a29 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat | sort-by ...columns | Sort by the given columns | | where condition | Filter table to match the condition | | inc (field) | Increment a value or version. Optional use the field of a table | +| add field value | Add a new field to the table | +| edit field value | Edit an existing field to have a new value | | skip amount | Skip a number of rows | | first amount | Show only the first number of rows | | to-array | Collapse rows into a single list | diff --git a/src/commands/to_json.rs b/src/commands/to_json.rs index 25f1c7187..5cd4c913c 100644 --- a/src/commands/to_json.rs +++ b/src/commands/to_json.rs @@ -1,6 +1,5 @@ use crate::object::{Primitive, Value}; use crate::prelude::*; -use log::trace; pub fn value_to_json_value(v: &Value) -> serde_json::Value { match v { diff --git a/src/commands/to_toml.rs b/src/commands/to_toml.rs index 354976a87..1551ba78d 100644 --- a/src/commands/to_toml.rs +++ b/src/commands/to_toml.rs @@ -36,36 +36,17 @@ pub fn to_toml(args: CommandArgs) -> Result { Ok(out .values - .map(move |a| { - match toml::to_string(&value_to_toml_value(&a)) { - Ok(val) => { - return ReturnSuccess::value( - Value::Primitive(Primitive::String(val)).spanned(name_span), - ) - } - - Err(err) => Err(ShellError::type_error( - "serializable to toml", - format!("{:?} - {:?}", a.type_name(), err).spanned(name_span), - )), // toml::Value::String(String) => { - // return ReturnSuccess::value( - // Value::Primitive(Primitive::String(x)).spanned(name_span), - // ) - // } - // toml::Value::Integer(i64) => "Integer", - // toml::Value::Float(f64) => "Decimal", - // toml::Value::Boolean(bool) => "Boolean", - // toml::Value::Datetime(Datetime) => "Date", - // toml::Value::Array(Array) => "Array", - // toml::Value::Table(Table) => "Table", + .map(move |a| match toml::to_string(&value_to_toml_value(&a)) { + Ok(val) => { + return ReturnSuccess::value( + Value::Primitive(Primitive::String(val)).spanned(name_span), + ) } - // return Err(ShellError::type_error("String", ty.spanned(name_span))); - // Err(_) => Err(ShellError::maybe_labeled_error( - // "Can not convert to TOML string", - // "can not convert piped data to TOML string", - // name_span, - // )), + Err(err) => Err(ShellError::type_error( + "Can not convert to a TOML string", + format!("{:?} - {:?}", a.type_name(), err).spanned(name_span), + )), }) .to_output_stream()) } diff --git a/src/object/base.rs b/src/object/base.rs index 9075fc123..168bbdd8e 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -362,6 +362,56 @@ impl Value { }) } + pub fn insert_data_at_path( + &'a self, + span: Span, + path: &str, + new_value: Value, + ) -> Option> { + let mut new_obj = self.clone(); + + let split_path: Vec<_> = path.split(".").collect(); + + if let Value::Object(ref mut o) = new_obj { + let mut current = o; + for idx in 0..split_path.len() - 1 { + match current.entries.get_mut(split_path[idx]) { + Some(next) => { + if idx == (split_path.len() - 2) { + match &mut next.item { + Value::Object(o) => { + o.entries.insert( + split_path[idx + 1].to_string(), + Spanned { + item: new_value, + span, + }, + ); + } + _ => {} + } + + return Some(Spanned { + item: new_obj, + span, + }); + } else { + match next.item { + Value::Object(ref mut o) => { + current = o; + } + _ => return None, + } + } + } + _ => return None, + } + } + } + + None + } + pub fn replace_data_at_path( &'a self, span: Span, diff --git a/src/plugins/add.rs b/src/plugins/add.rs new file mode 100644 index 000000000..7a80a37b5 --- /dev/null +++ b/src/plugins/add.rs @@ -0,0 +1,89 @@ +use indexmap::IndexMap; +use nu::{ + serve_plugin, CallInfo, CommandConfig, Plugin, PositionalType, Primitive, ReturnSuccess, + ReturnValue, ShellError, Spanned, Value, +}; + +struct Add { + field: Option, + value: Option, +} +impl Add { + fn new() -> Add { + Add { + field: None, + value: None, + } + } + + fn add(&self, value: Spanned) -> Result, ShellError> { + match (value.item, self.value.clone()) { + (obj @ Value::Object(_), Some(v)) => match &self.field { + Some(f) => match obj.insert_data_at_path(value.span, &f, v) { + Some(v) => return Ok(v), + None => { + return Err(ShellError::string( + "add could not find place to insert field", + )) + } + }, + None => Err(ShellError::string( + "add needs a field when adding a value to an object", + )), + }, + x => Err(ShellError::string(format!( + "Unrecognized type in stream: {:?}", + x + ))), + } + } +} + +impl Plugin for Add { + fn config(&mut self) -> Result { + Ok(CommandConfig { + name: "add".to_string(), + positional: vec![ + PositionalType::mandatory_any("Field"), + PositionalType::mandatory_any("Value"), + ], + is_filter: true, + is_sink: false, + named: IndexMap::new(), + rest_positional: true, + }) + } + fn begin_filter(&mut self, call_info: CallInfo) -> Result<(), ShellError> { + if let Some(args) = call_info.args.positional { + match &args[0] { + Spanned { + item: Value::Primitive(Primitive::String(s)), + .. + } => { + self.field = Some(s.clone()); + } + _ => { + return Err(ShellError::string(format!( + "Unrecognized type in params: {:?}", + args[0] + ))) + } + } + match &args[1] { + Spanned { item: v, .. } => { + self.value = Some(v.clone()); + } + } + } + + Ok(()) + } + + fn filter(&mut self, input: Spanned) -> Result, ShellError> { + Ok(vec![ReturnSuccess::value(self.add(input)?)]) + } +} + +fn main() { + serve_plugin(&mut Add::new()); +} diff --git a/src/plugins/edit.rs b/src/plugins/edit.rs new file mode 100644 index 000000000..c37fd2ba6 --- /dev/null +++ b/src/plugins/edit.rs @@ -0,0 +1,89 @@ +use indexmap::IndexMap; +use nu::{ + serve_plugin, CallInfo, CommandConfig, Plugin, PositionalType, Primitive, ReturnSuccess, + ReturnValue, ShellError, Spanned, Value, +}; + +struct Edit { + field: Option, + value: Option, +} +impl Edit { + fn new() -> Edit { + Edit { + field: None, + value: None, + } + } + + fn edit(&self, value: Spanned) -> Result, ShellError> { + match (value.item, self.value.clone()) { + (obj @ Value::Object(_), Some(v)) => match &self.field { + Some(f) => match obj.replace_data_at_path(value.span, &f, v) { + Some(v) => return Ok(v), + None => { + return Err(ShellError::string( + "edit could not find place to insert field", + )) + } + }, + None => Err(ShellError::string( + "edit needs a field when adding a value to an object", + )), + }, + x => Err(ShellError::string(format!( + "Unrecognized type in stream: {:?}", + x + ))), + } + } +} + +impl Plugin for Edit { + fn config(&mut self) -> Result { + Ok(CommandConfig { + name: "edit".to_string(), + positional: vec![ + PositionalType::mandatory_any("Field"), + PositionalType::mandatory_any("Value"), + ], + is_filter: true, + is_sink: false, + named: IndexMap::new(), + rest_positional: true, + }) + } + fn begin_filter(&mut self, call_info: CallInfo) -> Result<(), ShellError> { + if let Some(args) = call_info.args.positional { + match &args[0] { + Spanned { + item: Value::Primitive(Primitive::String(s)), + .. + } => { + self.field = Some(s.clone()); + } + _ => { + return Err(ShellError::string(format!( + "Unrecognized type in params: {:?}", + args[0] + ))) + } + } + match &args[1] { + Spanned { item: v, .. } => { + self.value = Some(v.clone()); + } + } + } + + Ok(()) + } + + fn filter(&mut self, input: Spanned) -> Result, ShellError> { + Ok(vec![ReturnSuccess::value(self.edit(input)?)]) + } +} + +fn main() { + serve_plugin(&mut Edit::new()); +} diff --git a/tests/tests.rs b/tests/tests.rs index 8fd540088..227df5e07 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -4,10 +4,12 @@ use helpers::in_directory as cwd; #[test] fn external_num() { - nu!(output, + nu!( + output, cwd("tests/fixtures/formats"), - "open sgml_description.json | get glossary.GlossDiv.GlossList.GlossEntry.Height | echo $it"); - + "open sgml_description.json | get glossary.GlossDiv.GlossList.GlossEntry.Height | echo $it" + ); + assert_eq!(output, "10"); } @@ -19,3 +21,21 @@ fn inc_plugin() { assert_eq!(output, "11"); } + +#[test] +fn add_plugin() { + nu!(output, + cwd("tests/fixtures/formats"), + "open cargo_sample.toml | add dev-dependencies.newdep \"1\" | get dev-dependencies.newdep | echo $it"); + + assert_eq!(output, "1"); +} + +#[test] +fn edit_plugin() { + nu!(output, + cwd("tests/fixtures/formats"), + "open cargo_sample.toml | edit dev-dependencies.pretty_assertions \"7\" | get dev-dependencies.pretty_assertions | echo $it"); + + assert_eq!(output, "7"); +}