diff --git a/src/object/base.rs b/src/object/base.rs index 56ec87ad4..9075fc123 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -347,6 +347,62 @@ impl Value { } } + pub fn get_data_by_path(&'a self, span: Span, path: &str) -> Option> { + let mut current = self; + for p in path.split(".") { + match current.get_data_by_key(p) { + Some(v) => current = v, + None => return None, + } + } + + Some(Spanned { + item: current, + span, + }) + } + + pub fn replace_data_at_path( + &'a self, + span: Span, + path: &str, + replaced_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() { + match current.entries.get_mut(split_path[idx]) { + Some(next) => { + if idx == (split_path.len() - 1) { + *next = Spanned { + item: replaced_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 get_data(&'a self, desc: &String) -> MaybeOwned<'a, Value> { match self { p @ Value::Primitive(_) => MaybeOwned::Borrowed(p), diff --git a/src/parser/parse/span.rs b/src/parser/parse/span.rs index 30d4efeb7..d82f43468 100644 --- a/src/parser/parse/span.rs +++ b/src/parser/parse/span.rs @@ -50,7 +50,7 @@ impl Spanned { } } - crate fn map(self, input: impl FnOnce(T) -> U) -> Spanned { + pub fn map(self, input: impl FnOnce(T) -> U) -> Spanned { let Spanned { span, item } = self; let mapped = input(item); diff --git a/src/plugins/inc.rs b/src/plugins/inc.rs index faf5ccc2b..8d69f91d5 100644 --- a/src/plugins/inc.rs +++ b/src/plugins/inc.rs @@ -1,57 +1,80 @@ use indexmap::IndexMap; use nu::{ - serve_plugin, Args, CommandConfig, Plugin, PositionalType, Primitive, ReturnSuccess, + serve_plugin, Args, CommandConfig, NamedType, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, ShellError, Spanned, SpannedItem, Value, }; struct Inc { - inc_by: i64, + field: Option, + major: bool, + minor: bool, + patch: bool, } impl Inc { fn new() -> Inc { - Inc { inc_by: 1 } + Inc { + field: None, + major: false, + minor: false, + patch: false, + } } -} -impl Plugin for Inc { - fn config(&mut self) -> Result { - Ok(CommandConfig { - name: "inc".to_string(), - positional: vec![PositionalType::optional_any("Increment")], - is_filter: true, - is_sink: false, - named: IndexMap::new(), - rest_positional: true, - }) - } - fn begin_filter(&mut self, args: Args) -> Result<(), ShellError> { - if let Some(args) = args.positional { - for arg in args { - match arg { - Spanned { - item: Value::Primitive(Primitive::Int(i)), - .. - } => { - self.inc_by = i; + fn inc( + &self, + value: Spanned, + field: &Option, + ) -> Result, ShellError> { + match value.item { + Value::Primitive(Primitive::Int(i)) => Ok(Value::int(i + 1).spanned(value.span)), + Value::Primitive(Primitive::Bytes(b)) => { + Ok(Value::bytes(b + 1 as u64).spanned(value.span)) + } + Value::Primitive(Primitive::String(s)) => { + if let Ok(i) = s.parse::() { + Ok(Spanned { + item: Value::string(format!("{}", i + 1)), + span: value.span, + }) + } else if let Ok(mut ver) = semver::Version::parse(&s) { + if self.major { + ver.increment_major(); + } else if self.minor { + ver.increment_minor(); + } else { + self.patch; + ver.increment_patch(); } - _ => return Err(ShellError::string("Unrecognized type in params")), + Ok(Spanned { + item: Value::string(ver.to_string()), + span: value.span, + }) + } else { + Err(ShellError::string("string could not be incremented")) } } - } - - Ok(()) - } - - fn filter(&mut self, input: Spanned) -> Result, ShellError> { - let span = input.span; - - match input.item { - Value::Primitive(Primitive::Int(i)) => Ok(vec![ReturnSuccess::value( - Value::int(i + self.inc_by).spanned(span), - )]), - Value::Primitive(Primitive::Bytes(b)) => Ok(vec![ReturnSuccess::value( - Value::bytes(b + self.inc_by as u64).spanned(span), - )]), + Value::Object(_) => match field { + Some(f) => { + let replacement = match value.item.get_data_by_path(value.span, f) { + Some(result) => self.inc(result.map(|x| x.clone()), &None)?, + None => { + return Err(ShellError::string("inc could not find field to replace")) + } + }; + match value + .item + .replace_data_at_path(value.span, f, replacement.item.clone()) + { + Some(v) => return Ok(v), + None => { + return Err(ShellError::string("inc could not find field to replace")) + } + } + } + None => Err(ShellError::string( + "inc needs a field when incrementing a value in an object", + )), + }, x => Err(ShellError::string(format!( "Unrecognized type in stream: {:?}", x @@ -60,6 +83,60 @@ impl Plugin for Inc { } } +impl Plugin for Inc { + fn config(&mut self) -> Result { + let mut named = IndexMap::new(); + named.insert("major".to_string(), NamedType::Switch); + named.insert("minor".to_string(), NamedType::Switch); + named.insert("patch".to_string(), NamedType::Switch); + + Ok(CommandConfig { + name: "inc".to_string(), + positional: vec![PositionalType::optional_any("Field")], + is_filter: true, + is_sink: false, + named, + rest_positional: true, + }) + } + fn begin_filter(&mut self, args: Args) -> Result<(), ShellError> { + if args.has("major") { + self.major = true; + } + if args.has("minor") { + self.minor = true; + } + if args.has("patch") { + self.patch = true; + } + + if let Some(args) = args.positional { + for arg in args { + match arg { + Spanned { + item: Value::Primitive(Primitive::String(s)), + .. + } => { + self.field = Some(s); + } + _ => { + return Err(ShellError::string(format!( + "Unrecognized type in params: {:?}", + arg + ))) + } + } + } + } + + Ok(()) + } + + fn filter(&mut self, input: Spanned) -> Result, ShellError> { + Ok(vec![ReturnSuccess::value(self.inc(input, &self.field)?)]) + } +} + fn main() { serve_plugin(&mut Inc::new()); } diff --git a/tests/filters_test.rs b/tests/filters_test.rs index 98ec17298..c9eb4baff 100644 --- a/tests/filters_test.rs +++ b/tests/filters_test.rs @@ -51,6 +51,28 @@ fn can_split_by_column() { assert_eq!(output, "name"); } +#[test] +fn can_inc_version() { + nu!( + output, + cwd("tests/fixtures/formats"), + "open cargo_sample.toml | inc package.version --minor | get package.version | echo $it" + ); + + assert_eq!(output, "0.2.0"); +} + +#[test] +fn can_inc_field() { + nu!( + output, + cwd("tests/fixtures/formats"), + "open cargo_sample.toml | inc package.edition | get package.edition | echo $it" + ); + + assert_eq!(output, "2019"); +} + #[test] fn can_filter_by_unit_size_comparison() { nu!(