diff --git a/Cargo.lock b/Cargo.lock index 4940540be3..193adb0c86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1986,7 +1986,6 @@ dependencies = [ "roxmltree", "rusqlite", "rustyline", - "semver", "serde 1.0.103", "serde-hjson 0.9.1", "serde_bytes", @@ -2215,7 +2214,6 @@ dependencies = [ name = "nu_plugin_inc" version = "0.7.0" dependencies = [ - "indexmap", "nu-build", "nu-errors", "nu-plugin", @@ -2275,7 +2273,6 @@ dependencies = [ name = "nu_plugin_str" version = "0.7.0" dependencies = [ - "indexmap", "nu-build", "nu-errors", "nu-plugin", diff --git a/Cargo.toml b/Cargo.toml index 1f2d685e64..e7ed9c5e44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,7 +134,6 @@ onig_sys = {version = "=69.1.0", optional = true } crossterm = {version = "0.10.2", optional = true} futures-timer = {version = "1.0.2", optional = true} url = {version = "2.1.0", optional = true} -semver = {version = "0.9.0", optional = true} [features] default = ["sys", "ps", "textview", "inc", "str"] @@ -144,7 +143,7 @@ stable = ["sys", "ps", "textview", "inc", "str", "starship-prompt", "binaryview" sys = ["heim", "battery"] ps = ["heim", "futures-timer"] textview = ["crossterm", "syntect", "onig_sys", "url"] -inc = ["semver"] +inc = ["nu_plugin_inc"] str = ["nu_plugin_str"] # Stable @@ -173,6 +172,7 @@ nu-build = { version = "0.7.0", path = "./crates/nu-build" } [lib] name = "nu" +doctest = false path = "src/lib.rs" # Core plugins that ship with `cargo install nu` by default diff --git a/crates/nu-build/Cargo.toml b/crates/nu-build/Cargo.toml index f9ccdac9a6..5e1d4d4f14 100644 --- a/crates/nu-build/Cargo.toml +++ b/crates/nu-build/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "Core build system for nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] serde = { version = "1.0.103", features = ["derive"] } diff --git a/crates/nu-errors/Cargo.toml b/crates/nu-errors/Cargo.toml index e16a292177..0f204c272f 100644 --- a/crates/nu-errors/Cargo.toml +++ b/crates/nu-errors/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "Core error subsystem for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] nu-source = { path = "../nu-source", version = "0.7.0" } diff --git a/crates/nu-macros/Cargo.toml b/crates/nu-macros/Cargo.toml index 45a1205fe8..53012edbe1 100644 --- a/crates/nu-macros/Cargo.toml +++ b/crates/nu-macros/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "nu-macros" version = "0.7.0" -authors = ["Yehuda Katz "] +authors = ["Yehuda Katz ", "Jonathan Turner ", "Andrés N. Robalino "] edition = "2018" description = "Core macros for building Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.7.0" } \ No newline at end of file +nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index 9d44b4d1f7..edba6794f6 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "Core parser used in Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index 96084192cc..d76cb9e500 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "Nushell Plugin" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu-plugin/src/test_helpers.rs b/crates/nu-plugin/src/test_helpers.rs index 585f5f44b8..c482150623 100644 --- a/crates/nu-plugin/src/test_helpers.rs +++ b/crates/nu-plugin/src/test_helpers.rs @@ -1,9 +1,8 @@ use crate::Plugin; use indexmap::IndexMap; use nu_errors::ShellError; -use nu_protocol::{CallInfo, EvaluatedArgs, Primitive, ReturnValue, UntaggedValue, Value}; +use nu_protocol::{CallInfo, EvaluatedArgs, ReturnSuccess, ReturnValue, UntaggedValue, Value}; use nu_source::Tag; -use nu_value_ext::ValueExt; pub struct PluginTest<'a, T: Plugin> { plugin: &'a mut T, @@ -100,20 +99,6 @@ impl<'a, T: Plugin> PluginTest<'a, T> { pub fn plugin(plugin: &mut T) -> PluginTest { PluginTest::for_plugin(plugin) } - -pub fn table(list: &Vec) -> Value { - UntaggedValue::table(list).into_untagged_value() -} - -pub fn column_path(paths: &Vec) -> Value { - UntaggedValue::Primitive(Primitive::ColumnPath( - table(&paths.iter().cloned().collect()) - .as_column_path() - .unwrap() - .item, - )) - .into_untagged_value() -} pub struct CallStub { positionals: Vec, flags: IndexMap, @@ -146,7 +131,7 @@ impl CallStub { .map(|s| UntaggedValue::string(s.to_string()).into_value(Tag::unknown())) .collect(); - self.positionals.push(column_path(&fields)); + self.positionals.push(value::column_path(&fields)); self } @@ -157,3 +142,71 @@ impl CallStub { } } } + +pub fn expect_return_value_at( + for_results: Result>, ShellError>, + at: usize, +) -> Value { + let return_values = for_results + .expect("Failed! This seems to be an error getting back the results from the plugin."); + + for (idx, item) in return_values.iter().enumerate() { + let item = match item { + Ok(return_value) => return_value, + Err(reason) => panic!(format!("{}", reason)), + }; + + if idx == at { + return item.raw_value().unwrap(); + } + } + + panic!(format!( + "Couldn't get return value from stream at {}. (There are {} items)", + at, + return_values.len() - 1 + )) +} + +pub mod value { + use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value}; + use nu_source::Tag; + use nu_value_ext::ValueExt; + use num_bigint::BigInt; + + pub fn get_data(for_value: Value, key: &str) -> Value { + for_value.get_data(&key.to_string()).borrow().clone() + } + + pub fn int(i: impl Into) -> Value { + UntaggedValue::Primitive(Primitive::Int(i.into())).into_untagged_value() + } + + pub fn string(input: impl Into) -> Value { + UntaggedValue::string(input.into()).into_untagged_value() + } + + pub fn structured_sample_record(key: &str, value: &str) -> Value { + let mut record = TaggedDictBuilder::new(Tag::unknown()); + record.insert_untagged(key.clone(), UntaggedValue::string(value)); + record.into_value() + } + + pub fn unstructured_sample_record(value: &str) -> Value { + UntaggedValue::string(value).into_value(Tag::unknown()) + } + + pub fn table(list: &Vec) -> Value { + UntaggedValue::table(list).into_untagged_value() + } + + pub fn column_path(paths: &Vec) -> Value { + UntaggedValue::Primitive(Primitive::ColumnPath( + table(&paths.iter().cloned().collect()) + .as_column_path() + .unwrap() + .item, + )) + .into_untagged_value() + } +} diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index d4339f659b..a00353d414 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "Core values and protocols for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] nu-source = { path = "../nu-source", version = "0.7.0" } diff --git a/crates/nu-source/Cargo.toml b/crates/nu-source/Cargo.toml index 79c909c17c..d85ad469d6 100644 --- a/crates/nu-source/Cargo.toml +++ b/crates/nu-source/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "A source string characterizer for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] serde = { version = "1.0.103", features = ["derive"] } diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml index a574f7d20a..1612c293a2 100644 --- a/crates/nu-test-support/Cargo.toml +++ b/crates/nu-test-support/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "A source string characterizer for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] app_dirs = "1.2.1" diff --git a/crates/nu-value-ext/Cargo.toml b/crates/nu-value-ext/Cargo.toml index 6f07393760..e6b2802faa 100644 --- a/crates/nu-value-ext/Cargo.toml +++ b/crates/nu-value-ext/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "Extension traits for values in Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] nu-source = { path = "../nu-source", version = "0.7.0" } diff --git a/crates/nu_plugin_average/Cargo.toml b/crates/nu_plugin_average/Cargo.toml index 92bf0b1871..4ac7717688 100644 --- a/crates/nu_plugin_average/Cargo.toml +++ b/crates/nu_plugin_average/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "An average value plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu_plugin_binaryview/Cargo.toml b/crates/nu_plugin_binaryview/Cargo.toml index bb0f77387b..affee895d9 100644 --- a/crates/nu_plugin_binaryview/Cargo.toml +++ b/crates/nu_plugin_binaryview/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "A binary viewer plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] ansi_term = "0.12.1" crossterm = { version = "0.10.2" } diff --git a/crates/nu_plugin_fetch/Cargo.toml b/crates/nu_plugin_fetch/Cargo.toml index ff467fd6d1..49ebac7f68 100644 --- a/crates/nu_plugin_fetch/Cargo.toml +++ b/crates/nu_plugin_fetch/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "A URL fetch plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index 630568b789..43c041e443 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "A version incrementer plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } @@ -15,7 +16,6 @@ nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-value-ext = { path = "../nu-value-ext", version = "0.7.0" } semver = "0.9.0" -indexmap = "1.3.0" [build-dependencies] nu-build = { version = "0.7.0", path = "../nu-build" } diff --git a/crates/nu_plugin_inc/src/inc.rs b/crates/nu_plugin_inc/src/inc.rs new file mode 100644 index 0000000000..da290e4455 --- /dev/null +++ b/crates/nu_plugin_inc/src/inc.rs @@ -0,0 +1,179 @@ +use nu_errors::ShellError; +use nu_protocol::{did_you_mean, ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value}; +use nu_source::{span_for_spanned_list, HasSpan, SpannedItem, Tagged}; +use nu_value_ext::ValueExt; + +#[derive(Debug, Eq, PartialEq)] +pub enum Action { + SemVerAction(SemVerAction), + Default, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum SemVerAction { + Major, + Minor, + Patch, +} + +pub struct Inc { + pub field: Option>, + pub error: Option, + pub action: Option, +} + +impl Inc { + pub fn new() -> Inc { + Inc { + field: None, + error: None, + action: None, + } + } + + fn apply(&self, input: &str) -> Result { + let applied = match &self.action { + Some(Action::SemVerAction(act_on)) => { + let mut ver = match semver::Version::parse(&input) { + Ok(parsed_ver) => parsed_ver, + Err(_) => return Ok(UntaggedValue::string(input.to_string())), + }; + + match act_on { + SemVerAction::Major => ver.increment_major(), + SemVerAction::Minor => ver.increment_minor(), + SemVerAction::Patch => ver.increment_patch(), + } + + UntaggedValue::string(ver.to_string()) + } + Some(Action::Default) | None => match input.parse::() { + Ok(v) => UntaggedValue::string(format!("{}", v + 1)), + Err(_) => UntaggedValue::string(input), + }, + }; + + Ok(applied) + } + + pub fn for_semver(&mut self, part: SemVerAction) { + if self.permit() { + self.action = Some(Action::SemVerAction(part)); + } else { + self.log_error("can only apply one"); + } + } + + fn permit(&mut self) -> bool { + self.action.is_none() + } + + fn log_error(&mut self, message: &str) { + self.error = Some(message.to_string()); + } + + pub fn usage() -> &'static str { + "Usage: inc field [--major|--minor|--patch]" + } + + pub fn inc(&self, value: Value) -> Result { + match &value.value { + UntaggedValue::Primitive(Primitive::Int(i)) => { + Ok(UntaggedValue::int(i + 1).into_value(value.tag())) + } + UntaggedValue::Primitive(Primitive::Bytes(b)) => { + Ok(UntaggedValue::bytes(b + 1 as u64).into_value(value.tag())) + } + UntaggedValue::Primitive(Primitive::String(ref s)) => { + Ok(self.apply(&s)?.into_value(value.tag())) + } + UntaggedValue::Table(values) => { + if values.len() == 1 { + Ok(UntaggedValue::Table(vec![self.inc(values[0].clone())?]) + .into_value(value.tag())) + } else { + Err(ShellError::type_error( + "incrementable value", + value.type_name().spanned(value.span()), + )) + } + } + + UntaggedValue::Row(_) => match self.field { + Some(ref f) => { + let fields = f.clone(); + + let replace_for = value.get_data_by_column_path( + &f, + Box::new(move |(obj_source, column_path_tried, _)| { + match did_you_mean(&obj_source, &column_path_tried) { + Some(suggestions) => ShellError::labeled_error( + "Unknown column", + format!("did you mean '{}'?", suggestions[0].1), + span_for_spanned_list(fields.iter().map(|p| p.span)), + ), + None => ShellError::labeled_error( + "Unknown column", + "row does not contain this column", + span_for_spanned_list(fields.iter().map(|p| p.span)), + ), + } + }), + ); + + let got = replace_for?; + let replacement = self.inc(got.clone())?; + + match value.replace_data_at_column_path( + &f, + replacement.value.clone().into_untagged_value(), + ) { + Some(v) => Ok(v), + None => Err(ShellError::labeled_error( + "inc could not find field to replace", + "column name", + value.tag(), + )), + } + } + None => Err(ShellError::untagged_runtime_error( + "inc needs a field when incrementing a column in a table", + )), + }, + _ => Err(ShellError::type_error( + "incrementable value", + value.type_name().spanned(value.span()), + )), + } + } +} + +#[cfg(test)] +mod tests { + mod semver { + use crate::inc::SemVerAction; + use crate::Inc; + use nu_plugin::test_helpers::value::string; + + #[test] + fn major() { + let mut inc = Inc::new(); + inc.for_semver(SemVerAction::Major); + assert_eq!(inc.apply("0.1.3").unwrap(), string("1.0.0").value); + } + + #[test] + fn minor() { + let mut inc = Inc::new(); + inc.for_semver(SemVerAction::Minor); + assert_eq!(inc.apply("0.1.3").unwrap(), string("0.2.0").value); + } + + #[test] + fn patch() { + let mut inc = Inc::new(); + inc.for_semver(SemVerAction::Patch); + assert_eq!(inc.apply("0.1.3").unwrap(), string("0.1.4").value); + } + } +} diff --git a/crates/nu_plugin_inc/src/lib.rs b/crates/nu_plugin_inc/src/lib.rs new file mode 100644 index 0000000000..7a8998e8e5 --- /dev/null +++ b/crates/nu_plugin_inc/src/lib.rs @@ -0,0 +1,38 @@ +mod inc; +mod nu_plugin_inc; + +pub use inc::Inc; + +#[cfg(test)] +mod tests { + use super::Inc; + use crate::inc::Action; + use nu_protocol::Value; + use nu_value_ext::ValueExt; + + impl Inc { + pub fn expect_action(&self, action: Action) { + match &self.action { + Some(set) if set == &action => {} + Some(other) => panic!(format!("\nExpected {:#?}\n\ngot {:#?}", action, other)), + None => panic!(format!("\nAction {:#?} not found.", action)), + } + } + + pub fn expect_field(&self, field: Value) { + let field = match field.as_column_path() { + Ok(column_path) => column_path, + Err(reason) => panic!(format!( + "\nExpected {:#?} to be a ColumnPath, \n\ngot {:#?}", + field, reason + )), + }; + + match &self.field { + Some(column_path) if column_path == &field => {} + Some(other) => panic!(format!("\nExpected {:#?} \n\ngot {:#?}", field, other)), + None => panic!(format!("\nField {:#?} not found.", field)), + } + } + } +} diff --git a/crates/nu_plugin_inc/src/main.rs b/crates/nu_plugin_inc/src/main.rs index 3decf0f44d..7245f1fbca 100644 --- a/crates/nu_plugin_inc/src/main.rs +++ b/crates/nu_plugin_inc/src/main.rs @@ -1,457 +1,6 @@ -use nu_errors::ShellError; -use nu_plugin::{serve_plugin, Plugin}; -use nu_protocol::{ - did_you_mean, CallInfo, ColumnPath, Primitive, ReturnSuccess, ReturnValue, ShellTypeName, - Signature, SyntaxShape, UntaggedValue, Value, -}; -use nu_source::{span_for_spanned_list, HasSpan, SpannedItem, Tagged}; -use nu_value_ext::ValueExt; - -enum Action { - SemVerAction(SemVerAction), - Default, -} - -pub enum SemVerAction { - Major, - Minor, - Patch, -} - -struct Inc { - field: Option>, - error: Option, - action: Option, -} - -impl Inc { - fn new() -> Inc { - Inc { - field: None, - error: None, - action: None, - } - } - - fn apply(&self, input: &str) -> Result { - let applied = match &self.action { - Some(Action::SemVerAction(act_on)) => { - let mut ver = match semver::Version::parse(&input) { - Ok(parsed_ver) => parsed_ver, - Err(_) => return Ok(UntaggedValue::string(input.to_string())), - }; - - match act_on { - SemVerAction::Major => ver.increment_major(), - SemVerAction::Minor => ver.increment_minor(), - SemVerAction::Patch => ver.increment_patch(), - } - - UntaggedValue::string(ver.to_string()) - } - Some(Action::Default) | None => match input.parse::() { - Ok(v) => UntaggedValue::string(format!("{}", v + 1)), - Err(_) => UntaggedValue::string(input), - }, - }; - - Ok(applied) - } - - fn for_semver(&mut self, part: SemVerAction) { - if self.permit() { - self.action = Some(Action::SemVerAction(part)); - } else { - self.log_error("can only apply one"); - } - } - - fn permit(&mut self) -> bool { - self.action.is_none() - } - - fn log_error(&mut self, message: &str) { - self.error = Some(message.to_string()); - } - - pub fn usage() -> &'static str { - "Usage: inc field [--major|--minor|--patch]" - } - - fn inc(&self, value: Value) -> Result { - match &value.value { - UntaggedValue::Primitive(Primitive::Int(i)) => { - Ok(UntaggedValue::int(i + 1).into_value(value.tag())) - } - UntaggedValue::Primitive(Primitive::Bytes(b)) => { - Ok(UntaggedValue::bytes(b + 1 as u64).into_value(value.tag())) - } - UntaggedValue::Primitive(Primitive::String(ref s)) => { - Ok(self.apply(&s)?.into_value(value.tag())) - } - UntaggedValue::Table(values) => { - if values.len() == 1 { - Ok(UntaggedValue::Table(vec![self.inc(values[0].clone())?]) - .into_value(value.tag())) - } else { - Err(ShellError::type_error( - "incrementable value", - value.type_name().spanned(value.span()), - )) - } - } - - UntaggedValue::Row(_) => match self.field { - Some(ref f) => { - let fields = f.clone(); - - let replace_for = value.get_data_by_column_path( - &f, - Box::new(move |(obj_source, column_path_tried, _)| { - match did_you_mean(&obj_source, &column_path_tried) { - Some(suggestions) => ShellError::labeled_error( - "Unknown column", - format!("did you mean '{}'?", suggestions[0].1), - span_for_spanned_list(fields.iter().map(|p| p.span)), - ), - None => ShellError::labeled_error( - "Unknown column", - "row does not contain this column", - span_for_spanned_list(fields.iter().map(|p| p.span)), - ), - } - }), - ); - - let got = replace_for?; - let replacement = self.inc(got.clone())?; - - match value.replace_data_at_column_path( - &f, - replacement.value.clone().into_untagged_value(), - ) { - Some(v) => Ok(v), - None => Err(ShellError::labeled_error( - "inc could not find field to replace", - "column name", - value.tag(), - )), - } - } - None => Err(ShellError::untagged_runtime_error( - "inc needs a field when incrementing a column in a table", - )), - }, - _ => Err(ShellError::type_error( - "incrementable value", - value.type_name().spanned(value.span()), - )), - } - } -} - -impl Plugin for Inc { - fn config(&mut self) -> Result { - Ok(Signature::build("inc") - .desc("Increment a value or version. Optionally use the column of a table.") - .switch("major", "increment the major version (eg 1.2.1 -> 2.0.0)") - .switch("minor", "increment the minor version (eg 1.2.1 -> 1.3.0)") - .switch("patch", "increment the patch version (eg 1.2.1 -> 1.2.2)") - .rest(SyntaxShape::ColumnPath, "the column(s) to update") - .filter()) - } - - fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { - if call_info.args.has("major") { - self.for_semver(SemVerAction::Major); - } - if call_info.args.has("minor") { - self.for_semver(SemVerAction::Minor); - } - if call_info.args.has("patch") { - self.for_semver(SemVerAction::Patch); - } - - if let Some(args) = call_info.args.positional { - for arg in args { - match arg { - table @ Value { - value: UntaggedValue::Primitive(Primitive::ColumnPath(_)), - .. - } => { - self.field = Some(table.as_column_path()?); - } - value => { - return Err(ShellError::type_error( - "table", - value.type_name().spanned(value.span()), - )) - } - } - } - } - - if self.action.is_none() { - self.action = Some(Action::Default); - } - - match &self.error { - Some(reason) => Err(ShellError::untagged_runtime_error(format!( - "{}: {}", - reason, - Inc::usage() - ))), - None => Ok(vec![]), - } - } - - fn filter(&mut self, input: Value) -> Result, ShellError> { - Ok(vec![ReturnSuccess::value(self.inc(input)?)]) - } -} +use nu_plugin::serve_plugin; +use nu_plugin_inc::Inc; fn main() { - serve_plugin(&mut Inc::new()); -} - -#[cfg(test)] -mod tests { - - use super::{Inc, SemVerAction}; - use indexmap::IndexMap; - use nu_plugin::Plugin; - use nu_protocol::{ - CallInfo, EvaluatedArgs, PathMember, ReturnSuccess, TaggedDictBuilder, UnspannedPathMember, - UntaggedValue, Value, - }; - use nu_source::{Span, Tag}; - - struct CallStub { - positionals: Vec, - flags: IndexMap, - } - - impl CallStub { - fn new() -> CallStub { - CallStub { - positionals: vec![], - flags: indexmap::IndexMap::new(), - } - } - - fn with_long_flag(&mut self, name: &str) -> &mut Self { - self.flags.insert( - name.to_string(), - UntaggedValue::boolean(true).into_value(Tag::unknown()), - ); - self - } - - fn with_parameter(&mut self, name: &str) -> &mut Self { - let fields: Vec = name - .split(".") - .map(|s| { - UnspannedPathMember::String(s.to_string()).into_path_member(Span::unknown()) - }) - .collect(); - - self.positionals - .push(UntaggedValue::column_path(fields).into_untagged_value()); - self - } - - fn create(&self) -> CallInfo { - CallInfo { - args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())), - name_tag: Tag::unknown(), - } - } - } - - fn cargo_sample_record(with_version: &str) -> Value { - let mut package = TaggedDictBuilder::new(Tag::unknown()); - package.insert_untagged("version", UntaggedValue::string(with_version)); - package.into_value() - } - - #[test] - fn inc_plugin_configuration_flags_wired() { - let mut plugin = Inc::new(); - - let configured = plugin.config().expect("Can not configure plugin"); - - for action_flag in &["major", "minor", "patch"] { - assert!(configured.named.get(*action_flag).is_some()); - } - } - - #[test] - fn inc_plugin_accepts_major() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("major").create()) - .is_ok()); - assert!(plugin.action.is_some()); - } - - #[test] - fn inc_plugin_accepts_minor() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("minor").create()) - .is_ok()); - assert!(plugin.action.is_some()); - } - - #[test] - fn inc_plugin_accepts_patch() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("patch").create()) - .is_ok()); - assert!(plugin.action.is_some()); - } - - #[test] - fn inc_plugin_accepts_only_one_action() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("major") - .with_long_flag("minor") - .create(), - ) - .is_err()); - assert_eq!(plugin.error, Some("can only apply one".to_string())); - } - - #[test] - fn inc_plugin_accepts_field() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_parameter("package.version").create()) - .is_ok()); - - assert_eq!( - plugin - .field - .map(|f| f.iter().map(|f| f.unspanned.clone()).collect()), - Some(vec![ - UnspannedPathMember::String("package".to_string()), - UnspannedPathMember::String("version".to_string()) - ]) - ); - } - - #[test] - fn incs_major() { - let mut inc = Inc::new(); - inc.for_semver(SemVerAction::Major); - assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("1.0.0")); - } - - #[test] - fn incs_minor() { - let mut inc = Inc::new(); - inc.for_semver(SemVerAction::Minor); - assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("0.2.0")); - } - - #[test] - fn incs_patch() { - let mut inc = Inc::new(); - inc.for_semver(SemVerAction::Patch); - assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("0.1.4")); - } - - #[test] - fn inc_plugin_applies_major() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("major") - .with_parameter("version") - .create() - ) - .is_ok()); - - let subject = cargo_sample_record("0.1.3"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Row(o), - .. - }) => assert_eq!( - *o.get_data(&String::from("version")).borrow(), - UntaggedValue::string(String::from("1.0.0")).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn inc_plugin_applies_minor() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("minor") - .with_parameter("version") - .create() - ) - .is_ok()); - - let subject = cargo_sample_record("0.1.3"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Row(o), - .. - }) => assert_eq!( - *o.get_data(&String::from("version")).borrow(), - UntaggedValue::string(String::from("0.2.0")).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn inc_plugin_applies_patch() { - let field = String::from("version"); - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("patch") - .with_parameter(&field) - .create() - ) - .is_ok()); - - let subject = cargo_sample_record("0.1.3"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Row(o), - .. - }) => assert_eq!( - *o.get_data(&field).borrow(), - UntaggedValue::string(String::from("0.1.4")).into_untagged_value() - ), - _ => {} - } - } + serve_plugin(&mut Inc::new()) } diff --git a/crates/nu_plugin_inc/src/nu_plugin_inc/mod.rs b/crates/nu_plugin_inc/src/nu_plugin_inc/mod.rs new file mode 100644 index 0000000000..b1c3811c7d --- /dev/null +++ b/crates/nu_plugin_inc/src/nu_plugin_inc/mod.rs @@ -0,0 +1,73 @@ +#[cfg(test)] +mod tests; + +use crate::inc::{Action, SemVerAction}; +use crate::Inc; +use nu_errors::ShellError; +use nu_plugin::Plugin; +use nu_protocol::{ + CallInfo, Primitive, ReturnSuccess, ReturnValue, ShellTypeName, Signature, SyntaxShape, + UntaggedValue, Value, +}; +use nu_source::{HasSpan, SpannedItem}; +use nu_value_ext::ValueExt; + +impl Plugin for Inc { + fn config(&mut self) -> Result { + Ok(Signature::build("inc") + .desc("Increment a value or version. Optionally use the column of a table.") + .switch("major", "increment the major version (eg 1.2.1 -> 2.0.0)") + .switch("minor", "increment the minor version (eg 1.2.1 -> 1.3.0)") + .switch("patch", "increment the patch version (eg 1.2.1 -> 1.2.2)") + .rest(SyntaxShape::ColumnPath, "the column(s) to update") + .filter()) + } + + fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { + if call_info.args.has("major") { + self.for_semver(SemVerAction::Major); + } + if call_info.args.has("minor") { + self.for_semver(SemVerAction::Minor); + } + if call_info.args.has("patch") { + self.for_semver(SemVerAction::Patch); + } + + if let Some(args) = call_info.args.positional { + for arg in args { + match arg { + table @ Value { + value: UntaggedValue::Primitive(Primitive::ColumnPath(_)), + .. + } => { + self.field = Some(table.as_column_path()?); + } + value => { + return Err(ShellError::type_error( + "table", + value.type_name().spanned(value.span()), + )) + } + } + } + } + + if self.action.is_none() { + self.action = Some(Action::Default); + } + + match &self.error { + Some(reason) => Err(ShellError::untagged_runtime_error(format!( + "{}: {}", + reason, + Inc::usage() + ))), + None => Ok(vec![]), + } + } + + fn filter(&mut self, input: Value) -> Result, ShellError> { + Ok(vec![ReturnSuccess::value(self.inc(input)?)]) + } +} diff --git a/crates/nu_plugin_inc/src/nu_plugin_inc/tests.rs b/crates/nu_plugin_inc/src/nu_plugin_inc/tests.rs new file mode 100644 index 0000000000..34dcef9bd1 --- /dev/null +++ b/crates/nu_plugin_inc/src/nu_plugin_inc/tests.rs @@ -0,0 +1,126 @@ +mod integration { + use crate::inc::{Action, SemVerAction}; + use crate::Inc; + use nu_plugin::test_helpers::value::{column_path, string}; + use nu_plugin::test_helpers::{plugin, CallStub}; + + #[test] + fn picks_up_one_action_flag_only() { + plugin(&mut Inc::new()) + .args( + CallStub::new() + .with_long_flag("major") + .with_long_flag("minor") + .create(), + ) + .setup(|plugin, returned_values| { + let actual = format!("{}", returned_values.unwrap_err()); + + assert!(actual.contains("can only apply one")); + assert_eq!(plugin.error, Some("can only apply one".to_string())); + }); + } + + #[test] + fn picks_up_major_flag() { + plugin(&mut Inc::new()) + .args(CallStub::new().with_long_flag("major").create()) + .setup(|plugin, _| { + let sem_version_part = SemVerAction::Major; + plugin.expect_action(Action::SemVerAction(sem_version_part)) + }); + } + + #[test] + fn picks_up_minor_flag() { + plugin(&mut Inc::new()) + .args(CallStub::new().with_long_flag("minor").create()) + .setup(|plugin, _| { + let sem_version_part = SemVerAction::Minor; + plugin.expect_action(Action::SemVerAction(sem_version_part)) + }); + } + + #[test] + fn picks_up_patch_flag() { + plugin(&mut Inc::new()) + .args(CallStub::new().with_long_flag("patch").create()) + .setup(|plugin, _| { + let sem_version_part = SemVerAction::Patch; + plugin.expect_action(Action::SemVerAction(sem_version_part)) + }); + } + + #[test] + fn picks_up_argument_for_field() { + plugin(&mut Inc::new()) + .args(CallStub::new().with_parameter("package.version").create()) + .setup(|plugin, _| { + plugin.expect_field(column_path(&vec![string("package"), string("version")])) + }); + } + + mod sem_ver { + use crate::Inc; + use nu_plugin::test_helpers::value::{get_data, string, structured_sample_record}; + use nu_plugin::test_helpers::{expect_return_value_at, plugin, CallStub}; + + fn cargo_sample_record(with_version: &str) -> nu_protocol::Value { + structured_sample_record("version", with_version) + } + + #[test] + fn major_input_using_the_field_passed_as_parameter() { + let run = plugin(&mut Inc::new()) + .args( + CallStub::new() + .with_long_flag("major") + .with_parameter("version") + .create(), + ) + .input(cargo_sample_record("0.1.3")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(get_data(actual, "version"), string("1.0.0")); + } + + #[test] + fn minor_input_using_the_field_passed_as_parameter() { + let run = plugin(&mut Inc::new()) + .args( + CallStub::new() + .with_long_flag("minor") + .with_parameter("version") + .create(), + ) + .input(cargo_sample_record("0.1.3")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(get_data(actual, "version"), string("0.2.0")); + } + + #[test] + fn patch_input_using_the_field_passed_as_parameter() { + let run = plugin(&mut Inc::new()) + .args( + CallStub::new() + .with_long_flag("patch") + .with_parameter("version") + .create(), + ) + .input(cargo_sample_record("0.1.3")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(get_data(actual, "version"), string("0.1.4")); + } + } +} diff --git a/crates/nu_plugin_match/Cargo.toml b/crates/nu_plugin_match/Cargo.toml index d74590a333..47262ce0f2 100644 --- a/crates/nu_plugin_match/Cargo.toml +++ b/crates/nu_plugin_match/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "A regex match plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu_plugin_post/Cargo.toml b/crates/nu_plugin_post/Cargo.toml index 44148c6569..b3bd1a1c60 100644 --- a/crates/nu_plugin_post/Cargo.toml +++ b/crates/nu_plugin_post/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "An HTTP post plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu_plugin_ps/Cargo.toml b/crates/nu_plugin_ps/Cargo.toml index f3b20e38f2..7770d7f372 100644 --- a/crates/nu_plugin_ps/Cargo.toml +++ b/crates/nu_plugin_ps/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "A process list plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu_plugin_str/Cargo.toml b/crates/nu_plugin_str/Cargo.toml index d8570673b0..f32879f0fa 100644 --- a/crates/nu_plugin_str/Cargo.toml +++ b/crates/nu_plugin_str/Cargo.toml @@ -6,7 +6,8 @@ edition = "2018" description = "A string manipulation plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +doctest = false [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } @@ -14,9 +15,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.7.0" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } nu-value-ext = { path = "../nu-value-ext", version = "0.7.0" } - regex = "1" -indexmap = "1.3.0" num-bigint = "0.2.3" [build-dependencies] diff --git a/crates/nu_plugin_str/src/lib.rs b/crates/nu_plugin_str/src/lib.rs index 6283fce546..f55180fbb2 100644 --- a/crates/nu_plugin_str/src/lib.rs +++ b/crates/nu_plugin_str/src/lib.rs @@ -7,11 +7,8 @@ pub use strutils::Str; mod tests { use super::Str; use crate::strutils::Action; - use nu_errors::ShellError; - use nu_protocol::{Primitive, ReturnSuccess, TaggedDictBuilder, UntaggedValue, Value}; - use nu_source::Tag; + use nu_protocol::Value; use nu_value_ext::ValueExt; - use num_bigint::BigInt; impl Str { pub fn expect_action(&self, action: Action) { @@ -38,51 +35,4 @@ mod tests { } } } - - pub fn get_data(for_value: Value, key: &str) -> Value { - for_value.get_data(&key.to_string()).borrow().clone() - } - - pub fn expect_return_value_at( - for_results: Result>, ShellError>, - at: usize, - ) -> Value { - let return_values = for_results - .expect("Failed! This seems to be an error getting back the results from the plugin."); - - for (idx, item) in return_values.iter().enumerate() { - let item = match item { - Ok(return_value) => return_value, - Err(reason) => panic!(format!("{}", reason)), - }; - - if idx == at { - return item.raw_value().unwrap(); - } - } - - panic!(format!( - "Couldn't get return value from stream at {}. (There are {} items)", - at, - return_values.len() - 1 - )) - } - - pub fn int(i: impl Into) -> Value { - UntaggedValue::Primitive(Primitive::Int(i.into())).into_untagged_value() - } - - pub fn string(input: impl Into) -> Value { - UntaggedValue::string(input.into()).into_untagged_value() - } - - pub fn structured_sample_record(key: &str, value: &str) -> Value { - let mut record = TaggedDictBuilder::new(Tag::unknown()); - record.insert_untagged(key.clone(), UntaggedValue::string(value)); - record.into_value() - } - - pub fn unstructured_sample_record(value: &str) -> Value { - UntaggedValue::string(value).into_value(Tag::unknown()) - } } diff --git a/crates/nu_plugin_str/src/nu_plugin_str/tests.rs b/crates/nu_plugin_str/src/nu_plugin_str/tests.rs index 94cbeb48d7..d198686619 100644 --- a/crates/nu_plugin_str/src/nu_plugin_str/tests.rs +++ b/crates/nu_plugin_str/src/nu_plugin_str/tests.rs @@ -1,11 +1,11 @@ mod integration { use crate::strutils::{Action, ReplaceAction}; - use crate::tests::{ - expect_return_value_at, get_data, int, string, structured_sample_record, + use crate::Str; + use nu_plugin::test_helpers::value::{ + column_path, get_data, int, string, structured_sample_record, table, unstructured_sample_record, }; - use crate::Str; - use nu_plugin::test_helpers::{column_path, plugin, table, CallStub}; + use nu_plugin::test_helpers::{expect_return_value_at, plugin, CallStub}; use nu_protocol::UntaggedValue; #[test] @@ -24,6 +24,7 @@ mod integration { assert_eq!(plugin.error, Some("can only apply one".to_string())); }); } + #[test] fn picks_up_downcase_flag() { plugin(&mut Str::new()) diff --git a/crates/nu_plugin_str/src/strutils.rs b/crates/nu_plugin_str/src/strutils.rs index 7768cfd97e..59d907d99e 100644 --- a/crates/nu_plugin_str/src/strutils.rs +++ b/crates/nu_plugin_str/src/strutils.rs @@ -2,7 +2,6 @@ use nu_errors::ShellError; use nu_protocol::{did_you_mean, ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value}; use nu_source::{span_for_spanned_list, Tagged}; use nu_value_ext::ValueExt; - use regex::Regex; use std::cmp; @@ -206,42 +205,35 @@ impl Str { #[cfg(test)] pub mod tests { - use super::{ReplaceAction, Str}; - use crate::tests::{int, string}; + use super::ReplaceAction; + use super::Str; + use nu_plugin::test_helpers::value::{int, string}; #[test] fn downcases() { let mut strutils = Str::new(); - strutils.for_downcase(); - assert_eq!(strutils.apply("ANDRES").unwrap(), string("andres").value); } #[test] fn upcases() { let mut strutils = Str::new(); - strutils.for_upcase(); - assert_eq!(strutils.apply("andres").unwrap(), string("ANDRES").value); } #[test] fn converts_to_int() { let mut strutils = Str::new(); - strutils.for_to_int(); - assert_eq!(strutils.apply("9999").unwrap(), int(9999 as i64).value); } #[test] fn replaces() { let mut strutils = Str::new(); - strutils.for_replace(ReplaceAction::Direct("robalino".to_string())); - assert_eq!(strutils.apply("andres").unwrap(), string("robalino").value); } diff --git a/crates/nu_plugin_sum/Cargo.toml b/crates/nu_plugin_sum/Cargo.toml index 2e9fe549bd..6ece509803 100644 --- a/crates/nu_plugin_sum/Cargo.toml +++ b/crates/nu_plugin_sum/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "A simple summation plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu_plugin_sys/Cargo.toml b/crates/nu_plugin_sys/Cargo.toml index 590a660793..bb92531f3e 100644 --- a/crates/nu_plugin_sys/Cargo.toml +++ b/crates/nu_plugin_sys/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "A system info plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu_plugin_textview/Cargo.toml b/crates/nu_plugin_textview/Cargo.toml index 76fe542b9c..c2972ebb91 100644 --- a/crates/nu_plugin_textview/Cargo.toml +++ b/crates/nu_plugin_textview/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "Text viewer plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/crates/nu_plugin_tree/Cargo.toml b/crates/nu_plugin_tree/Cargo.toml index 0d8bc94458..bbef11b63c 100644 --- a/crates/nu_plugin_tree/Cargo.toml +++ b/crates/nu_plugin_tree/Cargo.toml @@ -6,8 +6,6 @@ edition = "2018" description = "Tree viewer plugin for Nushell" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] nu-plugin = { path = "../nu-plugin", version="0.7.0" } nu-protocol = { path = "../nu-protocol", version = "0.7.0" } diff --git a/src/plugins/nu_plugin_core_inc.rs b/src/plugins/nu_plugin_core_inc.rs index 3decf0f44d..407dcd8521 100644 --- a/src/plugins/nu_plugin_core_inc.rs +++ b/src/plugins/nu_plugin_core_inc.rs @@ -1,457 +1,6 @@ -use nu_errors::ShellError; -use nu_plugin::{serve_plugin, Plugin}; -use nu_protocol::{ - did_you_mean, CallInfo, ColumnPath, Primitive, ReturnSuccess, ReturnValue, ShellTypeName, - Signature, SyntaxShape, UntaggedValue, Value, -}; -use nu_source::{span_for_spanned_list, HasSpan, SpannedItem, Tagged}; -use nu_value_ext::ValueExt; - -enum Action { - SemVerAction(SemVerAction), - Default, -} - -pub enum SemVerAction { - Major, - Minor, - Patch, -} - -struct Inc { - field: Option>, - error: Option, - action: Option, -} - -impl Inc { - fn new() -> Inc { - Inc { - field: None, - error: None, - action: None, - } - } - - fn apply(&self, input: &str) -> Result { - let applied = match &self.action { - Some(Action::SemVerAction(act_on)) => { - let mut ver = match semver::Version::parse(&input) { - Ok(parsed_ver) => parsed_ver, - Err(_) => return Ok(UntaggedValue::string(input.to_string())), - }; - - match act_on { - SemVerAction::Major => ver.increment_major(), - SemVerAction::Minor => ver.increment_minor(), - SemVerAction::Patch => ver.increment_patch(), - } - - UntaggedValue::string(ver.to_string()) - } - Some(Action::Default) | None => match input.parse::() { - Ok(v) => UntaggedValue::string(format!("{}", v + 1)), - Err(_) => UntaggedValue::string(input), - }, - }; - - Ok(applied) - } - - fn for_semver(&mut self, part: SemVerAction) { - if self.permit() { - self.action = Some(Action::SemVerAction(part)); - } else { - self.log_error("can only apply one"); - } - } - - fn permit(&mut self) -> bool { - self.action.is_none() - } - - fn log_error(&mut self, message: &str) { - self.error = Some(message.to_string()); - } - - pub fn usage() -> &'static str { - "Usage: inc field [--major|--minor|--patch]" - } - - fn inc(&self, value: Value) -> Result { - match &value.value { - UntaggedValue::Primitive(Primitive::Int(i)) => { - Ok(UntaggedValue::int(i + 1).into_value(value.tag())) - } - UntaggedValue::Primitive(Primitive::Bytes(b)) => { - Ok(UntaggedValue::bytes(b + 1 as u64).into_value(value.tag())) - } - UntaggedValue::Primitive(Primitive::String(ref s)) => { - Ok(self.apply(&s)?.into_value(value.tag())) - } - UntaggedValue::Table(values) => { - if values.len() == 1 { - Ok(UntaggedValue::Table(vec![self.inc(values[0].clone())?]) - .into_value(value.tag())) - } else { - Err(ShellError::type_error( - "incrementable value", - value.type_name().spanned(value.span()), - )) - } - } - - UntaggedValue::Row(_) => match self.field { - Some(ref f) => { - let fields = f.clone(); - - let replace_for = value.get_data_by_column_path( - &f, - Box::new(move |(obj_source, column_path_tried, _)| { - match did_you_mean(&obj_source, &column_path_tried) { - Some(suggestions) => ShellError::labeled_error( - "Unknown column", - format!("did you mean '{}'?", suggestions[0].1), - span_for_spanned_list(fields.iter().map(|p| p.span)), - ), - None => ShellError::labeled_error( - "Unknown column", - "row does not contain this column", - span_for_spanned_list(fields.iter().map(|p| p.span)), - ), - } - }), - ); - - let got = replace_for?; - let replacement = self.inc(got.clone())?; - - match value.replace_data_at_column_path( - &f, - replacement.value.clone().into_untagged_value(), - ) { - Some(v) => Ok(v), - None => Err(ShellError::labeled_error( - "inc could not find field to replace", - "column name", - value.tag(), - )), - } - } - None => Err(ShellError::untagged_runtime_error( - "inc needs a field when incrementing a column in a table", - )), - }, - _ => Err(ShellError::type_error( - "incrementable value", - value.type_name().spanned(value.span()), - )), - } - } -} - -impl Plugin for Inc { - fn config(&mut self) -> Result { - Ok(Signature::build("inc") - .desc("Increment a value or version. Optionally use the column of a table.") - .switch("major", "increment the major version (eg 1.2.1 -> 2.0.0)") - .switch("minor", "increment the minor version (eg 1.2.1 -> 1.3.0)") - .switch("patch", "increment the patch version (eg 1.2.1 -> 1.2.2)") - .rest(SyntaxShape::ColumnPath, "the column(s) to update") - .filter()) - } - - fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { - if call_info.args.has("major") { - self.for_semver(SemVerAction::Major); - } - if call_info.args.has("minor") { - self.for_semver(SemVerAction::Minor); - } - if call_info.args.has("patch") { - self.for_semver(SemVerAction::Patch); - } - - if let Some(args) = call_info.args.positional { - for arg in args { - match arg { - table @ Value { - value: UntaggedValue::Primitive(Primitive::ColumnPath(_)), - .. - } => { - self.field = Some(table.as_column_path()?); - } - value => { - return Err(ShellError::type_error( - "table", - value.type_name().spanned(value.span()), - )) - } - } - } - } - - if self.action.is_none() { - self.action = Some(Action::Default); - } - - match &self.error { - Some(reason) => Err(ShellError::untagged_runtime_error(format!( - "{}: {}", - reason, - Inc::usage() - ))), - None => Ok(vec![]), - } - } - - fn filter(&mut self, input: Value) -> Result, ShellError> { - Ok(vec![ReturnSuccess::value(self.inc(input)?)]) - } -} +use nu_plugin::serve_plugin; +use nu_plugin_inc::Inc; fn main() { serve_plugin(&mut Inc::new()); } - -#[cfg(test)] -mod tests { - - use super::{Inc, SemVerAction}; - use indexmap::IndexMap; - use nu_plugin::Plugin; - use nu_protocol::{ - CallInfo, EvaluatedArgs, PathMember, ReturnSuccess, TaggedDictBuilder, UnspannedPathMember, - UntaggedValue, Value, - }; - use nu_source::{Span, Tag}; - - struct CallStub { - positionals: Vec, - flags: IndexMap, - } - - impl CallStub { - fn new() -> CallStub { - CallStub { - positionals: vec![], - flags: indexmap::IndexMap::new(), - } - } - - fn with_long_flag(&mut self, name: &str) -> &mut Self { - self.flags.insert( - name.to_string(), - UntaggedValue::boolean(true).into_value(Tag::unknown()), - ); - self - } - - fn with_parameter(&mut self, name: &str) -> &mut Self { - let fields: Vec = name - .split(".") - .map(|s| { - UnspannedPathMember::String(s.to_string()).into_path_member(Span::unknown()) - }) - .collect(); - - self.positionals - .push(UntaggedValue::column_path(fields).into_untagged_value()); - self - } - - fn create(&self) -> CallInfo { - CallInfo { - args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())), - name_tag: Tag::unknown(), - } - } - } - - fn cargo_sample_record(with_version: &str) -> Value { - let mut package = TaggedDictBuilder::new(Tag::unknown()); - package.insert_untagged("version", UntaggedValue::string(with_version)); - package.into_value() - } - - #[test] - fn inc_plugin_configuration_flags_wired() { - let mut plugin = Inc::new(); - - let configured = plugin.config().expect("Can not configure plugin"); - - for action_flag in &["major", "minor", "patch"] { - assert!(configured.named.get(*action_flag).is_some()); - } - } - - #[test] - fn inc_plugin_accepts_major() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("major").create()) - .is_ok()); - assert!(plugin.action.is_some()); - } - - #[test] - fn inc_plugin_accepts_minor() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("minor").create()) - .is_ok()); - assert!(plugin.action.is_some()); - } - - #[test] - fn inc_plugin_accepts_patch() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("patch").create()) - .is_ok()); - assert!(plugin.action.is_some()); - } - - #[test] - fn inc_plugin_accepts_only_one_action() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("major") - .with_long_flag("minor") - .create(), - ) - .is_err()); - assert_eq!(plugin.error, Some("can only apply one".to_string())); - } - - #[test] - fn inc_plugin_accepts_field() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_parameter("package.version").create()) - .is_ok()); - - assert_eq!( - plugin - .field - .map(|f| f.iter().map(|f| f.unspanned.clone()).collect()), - Some(vec![ - UnspannedPathMember::String("package".to_string()), - UnspannedPathMember::String("version".to_string()) - ]) - ); - } - - #[test] - fn incs_major() { - let mut inc = Inc::new(); - inc.for_semver(SemVerAction::Major); - assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("1.0.0")); - } - - #[test] - fn incs_minor() { - let mut inc = Inc::new(); - inc.for_semver(SemVerAction::Minor); - assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("0.2.0")); - } - - #[test] - fn incs_patch() { - let mut inc = Inc::new(); - inc.for_semver(SemVerAction::Patch); - assert_eq!(inc.apply("0.1.3").unwrap(), UntaggedValue::string("0.1.4")); - } - - #[test] - fn inc_plugin_applies_major() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("major") - .with_parameter("version") - .create() - ) - .is_ok()); - - let subject = cargo_sample_record("0.1.3"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Row(o), - .. - }) => assert_eq!( - *o.get_data(&String::from("version")).borrow(), - UntaggedValue::string(String::from("1.0.0")).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn inc_plugin_applies_minor() { - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("minor") - .with_parameter("version") - .create() - ) - .is_ok()); - - let subject = cargo_sample_record("0.1.3"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Row(o), - .. - }) => assert_eq!( - *o.get_data(&String::from("version")).borrow(), - UntaggedValue::string(String::from("0.2.0")).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn inc_plugin_applies_patch() { - let field = String::from("version"); - let mut plugin = Inc::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("patch") - .with_parameter(&field) - .create() - ) - .is_ok()); - - let subject = cargo_sample_record("0.1.3"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Row(o), - .. - }) => assert_eq!( - *o.get_data(&field).borrow(), - UntaggedValue::string(String::from("0.1.4")).into_untagged_value() - ), - _ => {} - } - } -} diff --git a/src/plugins/nu_plugin_core_str.rs b/src/plugins/nu_plugin_core_str.rs index 279650ca43..5ff87af4c8 100644 --- a/src/plugins/nu_plugin_core_str.rs +++ b/src/plugins/nu_plugin_core_str.rs @@ -1,5 +1,6 @@ use nu_plugin::serve_plugin; use nu_plugin_str::Str; + fn main() { serve_plugin(&mut Str::new()); }