diff --git a/Cargo.lock b/Cargo.lock index 8c6e2d2fb..4940540be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1955,6 +1955,7 @@ dependencies = [ "nu-errors", "nu-macros", "nu-parser", + "nu-plugin", "nu-protocol", "nu-source", "nu-test-support", @@ -2082,6 +2083,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "nu-plugin" +version = "0.7.0" +dependencies = [ + "indexmap", + "nu-build", + "nu-errors", + "nu-protocol", + "nu-source", + "nu-value-ext", + "num-bigint", + "serde 1.0.103", + "serde_json", +] + [[package]] name = "nu-protocol" version = "0.7.0" @@ -2159,6 +2175,7 @@ version = "0.7.0" dependencies = [ "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", ] @@ -2173,6 +2190,7 @@ dependencies = [ "neso", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "pretty-hex", @@ -2186,6 +2204,7 @@ dependencies = [ "futures-preview", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "surf", @@ -2199,6 +2218,7 @@ dependencies = [ "indexmap", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "nu-value-ext", @@ -2212,6 +2232,7 @@ dependencies = [ "futures-preview", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "regex", @@ -2225,6 +2246,7 @@ dependencies = [ "futures-preview", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "num-traits 0.2.10", @@ -2243,6 +2265,7 @@ dependencies = [ "heim", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "pin-utils", @@ -2255,6 +2278,7 @@ dependencies = [ "indexmap", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "nu-value-ext", @@ -2268,6 +2292,7 @@ version = "0.7.0" dependencies = [ "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", ] @@ -2282,6 +2307,7 @@ dependencies = [ "heim", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", ] @@ -2294,6 +2320,7 @@ dependencies = [ "crossterm", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "onig_sys", @@ -2308,6 +2335,7 @@ dependencies = [ "derive-new", "nu-build", "nu-errors", + "nu-plugin", "nu-protocol", "nu-source", "ptree", diff --git a/Cargo.toml b/Cargo.toml index 7d5a3c136..1f2d685e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "crates/nu_plugin_textview", "crates/nu_plugin_tree", "crates/nu-protocol", + "crates/nu-plugin", "crates/nu-parser", "crates/nu-value-ext", "crates/nu-build" @@ -39,6 +40,7 @@ members = [ [dependencies] nu-source = { version = "0.7.0", path = "./crates/nu-source" } +nu-plugin = { version = "0.7.0", path = "./crates/nu-plugin" } nu-protocol = { version = "0.7.0", path = "./crates/nu-protocol" } nu-errors = { version = "0.7.0", path = "./crates/nu-errors" } nu-parser = { version = "0.7.0", path = "./crates/nu-parser" } @@ -143,7 +145,7 @@ sys = ["heim", "battery"] ps = ["heim", "futures-timer"] textview = ["crossterm", "syntect", "onig_sys", "url"] inc = ["semver"] -str = [] +str = ["nu_plugin_str"] # Stable average = ["nu_plugin_average"] diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml new file mode 100644 index 000000000..96084192c --- /dev/null +++ b/crates/nu-plugin/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "nu-plugin" +version = "0.7.0" +authors = ["Yehuda Katz ", "Jonathan Turner ", "Andrés N. Robalino "] +edition = "2018" +description = "Nushell Plugin" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +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" } + +indexmap = { version = "1.3.0", features = ["serde-1"] } +serde = { version = "1.0.103", features = ["derive"] } +num-bigint = { version = "0.2.3", features = ["serde"] } +serde_json = "1.0.44" + +[build-dependencies] +nu-build = { version = "0.7.0", path = "../nu-build" } diff --git a/crates/nu-plugin/build.rs b/crates/nu-plugin/build.rs new file mode 100644 index 000000000..b7511cfc6 --- /dev/null +++ b/crates/nu-plugin/build.rs @@ -0,0 +1,3 @@ +fn main() -> Result<(), Box> { + nu_build::build() +} diff --git a/crates/nu-plugin/src/lib.rs b/crates/nu-plugin/src/lib.rs new file mode 100644 index 000000000..edf145d3a --- /dev/null +++ b/crates/nu-plugin/src/lib.rs @@ -0,0 +1,4 @@ +mod plugin; +pub mod test_helpers; + +pub use crate::plugin::{serve_plugin, Plugin}; diff --git a/crates/nu-protocol/src/plugin.rs b/crates/nu-plugin/src/plugin.rs similarity index 97% rename from crates/nu-protocol/src/plugin.rs rename to crates/nu-plugin/src/plugin.rs index 4fc56e0af..fff540855 100644 --- a/crates/nu-protocol/src/plugin.rs +++ b/crates/nu-plugin/src/plugin.rs @@ -1,8 +1,5 @@ -use crate::call_info::CallInfo; -use crate::return_value::ReturnValue; -use crate::signature::Signature; -use crate::value::Value; use nu_errors::ShellError; +use nu_protocol::{outln, CallInfo, ReturnValue, Signature, Value}; use serde::{Deserialize, Serialize}; use std::io; diff --git a/crates/nu-plugin/src/test_helpers.rs b/crates/nu-plugin/src/test_helpers.rs new file mode 100644 index 000000000..585f5f44b --- /dev/null +++ b/crates/nu-plugin/src/test_helpers.rs @@ -0,0 +1,159 @@ +use crate::Plugin; +use indexmap::IndexMap; +use nu_errors::ShellError; +use nu_protocol::{CallInfo, EvaluatedArgs, Primitive, ReturnValue, UntaggedValue, Value}; +use nu_source::Tag; +use nu_value_ext::ValueExt; + +pub struct PluginTest<'a, T: Plugin> { + plugin: &'a mut T, + call_info: CallInfo, + input: Value, +} + +impl<'a, T: Plugin> PluginTest<'a, T> { + pub fn for_plugin(plugin: &'a mut T) -> Self { + PluginTest { + plugin: plugin, + call_info: CallStub::new().create(), + input: UntaggedValue::nothing().into_value(Tag::unknown()), + } + } + + pub fn args(&mut self, call_info: CallInfo) -> &mut PluginTest<'a, T> { + self.call_info = call_info; + self + } + + pub fn configure(&mut self, callback: impl FnOnce(Vec)) -> &mut PluginTest<'a, T> { + let signature = self + .plugin + .config() + .expect("There was a problem configuring the plugin."); + callback(signature.named.keys().map(String::from).collect()); + self + } + + pub fn input(&mut self, value: Value) -> &mut PluginTest<'a, T> { + self.input = value; + self + } + + pub fn test(&mut self) -> Result, ShellError> { + let return_values = self.plugin.filter(self.input.clone()); + + let mut return_values = match return_values { + Ok(filtered) => filtered, + Err(reason) => return Err(reason), + }; + + let end = self.plugin.end_filter(); + + match end { + Ok(filter_ended) => return_values.extend(filter_ended), + Err(reason) => return Err(reason), + } + + self.plugin.quit(); + Ok(return_values) + } + + pub fn setup( + &mut self, + callback: impl FnOnce(&mut T, Result, ShellError>), + ) -> &mut PluginTest<'a, T> { + let call_stub = self.call_info.clone(); + + self.configure(|flags_configured| { + let flags_registered = &call_stub.args.named; + + let flag_passed = match flags_registered { + Some(names) => Some(names.keys().map(String::from).collect::>()), + None => None, + }; + + if let Some(flags) = flag_passed { + for flag in flags { + assert!( + flags_configured.iter().any(|f| *f == flag), + format!( + "The flag you passed ({}) is not configured in the plugin.", + flag + ) + ); + } + } + }); + + let began = self.plugin.begin_filter(call_stub); + + let return_values = match began { + Ok(values) => Ok(values), + Err(reason) => Err(reason), + }; + + callback(self.plugin, return_values); + self + } +} + +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, +} + +impl CallStub { + pub fn new() -> CallStub { + CallStub { + positionals: vec![], + flags: indexmap::IndexMap::new(), + } + } + + pub fn with_named_parameter(&mut self, name: &str, value: Value) -> &mut Self { + self.flags.insert(name.to_string(), value); + self + } + + pub fn with_long_flag(&mut self, name: &str) -> &mut Self { + self.flags.insert( + name.to_string(), + UntaggedValue::boolean(true).into_value(Tag::unknown()), + ); + self + } + + pub fn with_parameter(&mut self, name: &str) -> &mut Self { + let fields: Vec = name + .split(".") + .map(|s| UntaggedValue::string(s.to_string()).into_value(Tag::unknown())) + .collect(); + + self.positionals.push(column_path(&fields)); + self + } + + pub fn create(&self) -> CallInfo { + CallInfo { + args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())), + name_tag: Tag::unknown(), + } + } +} diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index f77dc6f85..5d55ee498 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -3,7 +3,6 @@ mod macros; mod call_info; mod maybe_owned; -mod plugin; mod return_value; mod signature; mod syntax_shape; @@ -13,7 +12,6 @@ mod value; pub use crate::call_info::{CallInfo, EvaluatedArgs}; pub use crate::maybe_owned::MaybeOwned; -pub use crate::plugin::{serve_plugin, Plugin}; pub use crate::return_value::{CommandAction, ReturnSuccess, ReturnValue}; pub use crate::signature::{NamedType, PositionalType, Signature}; pub use crate::syntax_shape::SyntaxShape; diff --git a/crates/nu-protocol/src/return_value.rs b/crates/nu-protocol/src/return_value.rs index ddca03093..b35a4c269 100644 --- a/crates/nu-protocol/src/return_value.rs +++ b/crates/nu-protocol/src/return_value.rs @@ -62,6 +62,14 @@ impl Into for Value { } impl ReturnSuccess { + pub fn raw_value(&self) -> Option { + match self { + ReturnSuccess::Value(raw) => Some(raw.clone()), + ReturnSuccess::DebugValue(raw) => Some(raw.clone()), + ReturnSuccess::Action(_) => None, + } + } + pub fn change_cwd(path: String) -> ReturnValue { Ok(ReturnSuccess::Action(CommandAction::ChangePath(path))) } diff --git a/crates/nu_plugin_average/Cargo.toml b/crates/nu_plugin_average/Cargo.toml index 6b1dcd6cd..92bf0b187 100644 --- a/crates/nu_plugin_average/Cargo.toml +++ b/crates/nu_plugin_average/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_average/src/main.rs b/crates/nu_plugin_average/src/main.rs index 80685236e..48b1ad68a 100644 --- a/crates/nu_plugin_average/src/main.rs +++ b/crates/nu_plugin_average/src/main.rs @@ -1,7 +1,7 @@ use nu_errors::{CoerceInto, ShellError}; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, Signature, - UntaggedValue, Value, + CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value, }; use nu_source::TaggedItem; diff --git a/crates/nu_plugin_binaryview/Cargo.toml b/crates/nu_plugin_binaryview/Cargo.toml index 8bfdbd8b3..bb0f77387 100644 --- a/crates/nu_plugin_binaryview/Cargo.toml +++ b/crates/nu_plugin_binaryview/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT" [dependencies] ansi_term = "0.12.1" crossterm = { version = "0.10.2" } +nu-plugin = { path = "../nu-plugin", version="0.7.0" } 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" } diff --git a/crates/nu_plugin_binaryview/src/main.rs b/crates/nu_plugin_binaryview/src/main.rs index 21eb496c1..ed5ac07ea 100644 --- a/crates/nu_plugin_binaryview/src/main.rs +++ b/crates/nu_plugin_binaryview/src/main.rs @@ -1,8 +1,7 @@ use crossterm::{cursor, terminal, Attribute, RawScreen}; use nu_errors::ShellError; -use nu_protocol::{ - outln, serve_plugin, CallInfo, Plugin, Primitive, Signature, UntaggedValue, Value, -}; +use nu_plugin::{serve_plugin, Plugin}; +use nu_protocol::{outln, CallInfo, Primitive, Signature, UntaggedValue, Value}; use nu_source::AnchorLocation; use pretty_hex::*; diff --git a/crates/nu_plugin_fetch/Cargo.toml b/crates/nu_plugin_fetch/Cargo.toml index ea87aad9a..ff467fd6d 100644 --- a/crates/nu_plugin_fetch/Cargo.toml +++ b/crates/nu_plugin_fetch/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_fetch/src/main.rs b/crates/nu_plugin_fetch/src/main.rs index 6192b18bd..d0568e780 100644 --- a/crates/nu_plugin_fetch/src/main.rs +++ b/crates/nu_plugin_fetch/src/main.rs @@ -1,9 +1,10 @@ use futures::executor::block_on; use mime::Mime; use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, CommandAction, Plugin, ReturnSuccess, ReturnValue, Signature, - SyntaxShape, UntaggedValue, Value, + CallInfo, CommandAction, ReturnSuccess, ReturnValue, Signature, SyntaxShape, UntaggedValue, + Value, }; use nu_source::{AnchorLocation, Span, Tag}; use std::path::PathBuf; diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index 56525de6a..630568b78 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_inc/src/main.rs b/crates/nu_plugin_inc/src/main.rs index 3fd6ca89e..3decf0f44 100644 --- a/crates/nu_plugin_inc/src/main.rs +++ b/crates/nu_plugin_inc/src/main.rs @@ -1,7 +1,8 @@ use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - did_you_mean, serve_plugin, CallInfo, ColumnPath, Plugin, Primitive, ReturnSuccess, - ReturnValue, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value, + 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; @@ -218,11 +219,11 @@ mod tests { use super::{Inc, SemVerAction}; use indexmap::IndexMap; + use nu_plugin::Plugin; use nu_protocol::{ - CallInfo, EvaluatedArgs, PathMember, ReturnSuccess, UnspannedPathMember, UntaggedValue, - Value, + CallInfo, EvaluatedArgs, PathMember, ReturnSuccess, TaggedDictBuilder, UnspannedPathMember, + UntaggedValue, Value, }; - use nu_protocol::{Plugin, TaggedDictBuilder}; use nu_source::{Span, Tag}; struct CallStub { diff --git a/crates/nu_plugin_match/Cargo.toml b/crates/nu_plugin_match/Cargo.toml index d18f27615..d74590a33 100644 --- a/crates/nu_plugin_match/Cargo.toml +++ b/crates/nu_plugin_match/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_match/src/main.rs b/crates/nu_plugin_match/src/main.rs index 2a9783aea..dc787781d 100644 --- a/crates/nu_plugin_match/src/main.rs +++ b/crates/nu_plugin_match/src/main.rs @@ -1,7 +1,7 @@ use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, - UntaggedValue, Value, + CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, UntaggedValue, Value, }; use regex::Regex; diff --git a/crates/nu_plugin_post/Cargo.toml b/crates/nu_plugin_post/Cargo.toml index f9437ea65..44148c656 100644 --- a/crates/nu_plugin_post/Cargo.toml +++ b/crates/nu_plugin_post/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_post/src/main.rs b/crates/nu_plugin_post/src/main.rs index 8a5bd606a..4b0b12595 100644 --- a/crates/nu_plugin_post/src/main.rs +++ b/crates/nu_plugin_post/src/main.rs @@ -2,9 +2,10 @@ use base64::encode; use futures::executor::block_on; use mime::Mime; use nu_errors::{CoerceInto, ShellError}; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, CommandAction, Plugin, Primitive, ReturnSuccess, ReturnValue, - Signature, SyntaxShape, UnspannedPathMember, UntaggedValue, Value, + CallInfo, CommandAction, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, + UnspannedPathMember, UntaggedValue, Value, }; use nu_source::{AnchorLocation, Tag, TaggedItem}; use num_traits::cast::ToPrimitive; diff --git a/crates/nu_plugin_ps/Cargo.toml b/crates/nu_plugin_ps/Cargo.toml index 4c9286320..f3b20e38f 100644 --- a/crates/nu_plugin_ps/Cargo.toml +++ b/crates/nu_plugin_ps/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_ps/src/main.rs b/crates/nu_plugin_ps/src/main.rs index 517d4e2b9..ce265ca7f 100644 --- a/crates/nu_plugin_ps/src/main.rs +++ b/crates/nu_plugin_ps/src/main.rs @@ -7,9 +7,9 @@ use heim::units::{information, ratio, Ratio}; use std::usize; use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, Plugin, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, - UntaggedValue, Value, + CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value, }; use nu_source::Tag; diff --git a/crates/nu_plugin_str/Cargo.toml b/crates/nu_plugin_str/Cargo.toml index 53742d77d..d8570673b 100644 --- a/crates/nu_plugin_str/Cargo.toml +++ b/crates/nu_plugin_str/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_str/src/lib.rs b/crates/nu_plugin_str/src/lib.rs new file mode 100644 index 000000000..6283fce54 --- /dev/null +++ b/crates/nu_plugin_str/src/lib.rs @@ -0,0 +1,88 @@ +mod nu_plugin_str; +mod strutils; + +pub use strutils::Str; + +#[cfg(test)] +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_value_ext::ValueExt; + use num_bigint::BigInt; + + impl Str { + 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)), + } + } + } + + 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/main.rs b/crates/nu_plugin_str/src/main.rs index b7aaf8ea0..b1d8d8c8a 100644 --- a/crates/nu_plugin_str/src/main.rs +++ b/crates/nu_plugin_str/src/main.rs @@ -1,980 +1,6 @@ -use nu_errors::ShellError; -use nu_protocol::{ - did_you_mean, serve_plugin, CallInfo, ColumnPath, Plugin, Primitive, ReturnSuccess, - ReturnValue, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value, -}; -use nu_source::{span_for_spanned_list, Tagged}; -use nu_value_ext::ValueExt; - -use regex::Regex; -use std::cmp; - -#[derive(Debug, Eq, PartialEq)] -enum Action { - Downcase, - Upcase, - ToInteger, - Substring(usize, usize), - Replace(ReplaceAction), -} - -#[derive(Debug, Eq, PartialEq)] -enum ReplaceAction { - Direct(String), - FindAndReplace(String, String), -} - -struct Str { - field: Option>, - error: Option, - action: Option, -} - -impl Str { - fn new() -> Str { - Str { - field: None, - error: None, - action: None, - } - } - - fn apply(&self, input: &str) -> Result { - let applied = match self.action.as_ref() { - Some(Action::Downcase) => UntaggedValue::string(input.to_ascii_lowercase()), - Some(Action::Upcase) => UntaggedValue::string(input.to_ascii_uppercase()), - Some(Action::Substring(s, e)) => { - let end: usize = cmp::min(*e, input.len()); - let start: usize = *s; - if start > input.len() - 1 { - UntaggedValue::string("") - } else { - UntaggedValue::string( - &input - .chars() - .skip(start) - .take(end - start) - .collect::(), - ) - } - } - Some(Action::Replace(mode)) => match mode { - ReplaceAction::Direct(replacement) => UntaggedValue::string(replacement.as_str()), - ReplaceAction::FindAndReplace(find, replacement) => { - let regex = Regex::new(find.as_str()); - - match regex { - Ok(re) => UntaggedValue::string( - re.replace(input, replacement.as_str()).to_owned(), - ), - Err(_) => UntaggedValue::string(input), - } - } - }, - Some(Action::ToInteger) => match input.trim() { - other => match other.parse::() { - Ok(v) => UntaggedValue::int(v), - Err(_) => UntaggedValue::string(input), - }, - }, - None => UntaggedValue::string(input), - }; - - Ok(applied) - } - - fn for_field(&mut self, column_path: Tagged) { - self.field = Some(column_path); - } - - fn permit(&mut self) -> bool { - self.action.is_none() - } - - fn log_error(&mut self, message: &str) { - self.error = Some(message.to_string()); - } - - fn for_to_int(&mut self) { - if self.permit() { - self.action = Some(Action::ToInteger); - } else { - self.log_error("can only apply one"); - } - } - - fn for_downcase(&mut self) { - if self.permit() { - self.action = Some(Action::Downcase); - } else { - self.log_error("can only apply one"); - } - } - - fn for_upcase(&mut self) { - if self.permit() { - self.action = Some(Action::Upcase); - } else { - self.log_error("can only apply one"); - } - } - - fn for_substring(&mut self, s: String) { - let v: Vec<&str> = s.split(',').collect(); - let start: usize = match v[0] { - "" => 0, - _ => v[0].trim().parse().unwrap(), - }; - let end: usize = match v[1] { - "" => usize::max_value(), - _ => v[1].trim().parse().unwrap(), - }; - if start > end { - self.log_error("End must be greater than or equal to Start"); - } else if self.permit() { - self.action = Some(Action::Substring(start, end)); - } else { - self.log_error("can only apply one"); - } - } - - fn for_replace(&mut self, mode: ReplaceAction) { - if self.permit() { - self.action = Some(Action::Replace(mode)); - } else { - self.log_error("can only apply one"); - } - } - - pub fn usage() -> &'static str { - "Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"|--replace|--find-replace [pattern replacement]]]" - } -} - -impl Str { - fn strutils(&self, value: Value) -> Result { - match &value.value { - UntaggedValue::Primitive(Primitive::String(ref s)) => { - Ok(self.apply(&s)?.into_value(value.tag())) - } - UntaggedValue::Primitive(Primitive::Line(ref s)) => { - Ok(self.apply(&s)?.into_value(value.tag())) - } - 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, error)| { - 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 => error, - } - }), - ); - - let got = replace_for?; - let replacement = self.strutils(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( - "str could not find field to replace", - "column name", - value.tag(), - )), - } - } - None => Err(ShellError::untagged_runtime_error(format!( - "{}: {}", - "str needs a column when applied to a value in a row", - Str::usage() - ))), - }, - _ => Err(ShellError::labeled_error( - "Unrecognized type in stream", - value.type_name(), - value.tag, - )), - } - } -} - -impl Plugin for Str { - fn config(&mut self) -> Result { - Ok(Signature::build("str") - .desc("Apply string function. Optional use the column of a table") - .switch("downcase", "convert string to lowercase") - .switch("upcase", "convert string to uppercase") - .switch("to-int", "convert string to integer") - .named("replace", SyntaxShape::String, "replaces the string") - .named( - "find-replace", - SyntaxShape::Any, - "finds and replaces [pattern replacement]", - ) - .named( - "substring", - SyntaxShape::String, - "convert string to portion of original, requires \"start,end\"", - ) - .rest(SyntaxShape::ColumnPath, "the column(s) to convert") - .filter()) - } - - fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { - let args = call_info.args; - - if args.has("downcase") { - self.for_downcase(); - } - if args.has("upcase") { - self.for_upcase(); - } - if args.has("to-int") { - self.for_to_int(); - } - if args.has("substring") { - if let Some(start_end) = args.get("substring") { - match start_end { - Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - } => { - self.for_substring(s.to_string()); - } - _ => { - return Err(ShellError::labeled_error( - "Unrecognized type in params", - start_end.type_name(), - &start_end.tag, - )) - } - } - } - } - if args.has("replace") { - if let Some(Value { - value: UntaggedValue::Primitive(Primitive::String(replacement)), - .. - }) = args.get("replace") - { - self.for_replace(ReplaceAction::Direct(replacement.clone())); - } - } - - if args.has("find-replace") { - if let Some(Value { - value: UntaggedValue::Table(arguments), - .. - }) = args.get("find-replace") - { - self.for_replace(ReplaceAction::FindAndReplace( - arguments.get(0).unwrap().as_string()?.to_string(), - arguments.get(1).unwrap().as_string()?.to_string(), - )); - } - } - - if let Some(possible_field) = args.nth(0) { - let possible_field = possible_field.as_column_path()?; - self.for_field(possible_field); - } - - match &self.error { - Some(reason) => Err(ShellError::untagged_runtime_error(format!( - "{}: {}", - reason, - Str::usage() - ))), - None => Ok(vec![]), - } - } - - fn filter(&mut self, input: Value) -> Result, ShellError> { - Ok(vec![ReturnSuccess::value(self.strutils(input)?)]) - } -} +use nu_plugin::serve_plugin; +use nu_plugin_str::Str; fn main() { - serve_plugin(&mut Str::new()); -} - -#[cfg(test)] -mod tests { - use super::{Action, ReplaceAction, Str}; - use indexmap::IndexMap; - use nu_protocol::{ - CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, TaggedDictBuilder, - UntaggedValue, Value, - }; - use nu_source::Tag; - use nu_value_ext::ValueExt; - use num_bigint::BigInt; - - fn string(input: impl Into) -> Value { - UntaggedValue::string(input.into()).into_untagged_value() - } - - fn table(list: &Vec) -> Value { - UntaggedValue::table(list).into_untagged_value() - } - - fn column_path(paths: &Vec) -> Value { - UntaggedValue::Primitive(Primitive::ColumnPath( - table(&paths.iter().cloned().collect()) - .as_column_path() - .unwrap() - .item, - )) - .into_untagged_value() - } - struct CallStub { - positionals: Vec, - flags: IndexMap, - } - - impl CallStub { - fn new() -> CallStub { - CallStub { - positionals: vec![], - flags: indexmap::IndexMap::new(), - } - } - - fn with_named_parameter(&mut self, name: &str, value: Value) -> &mut Self { - self.flags.insert(name.to_string(), value); - self - } - - 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| UntaggedValue::string(s.to_string()).into_value(Tag::unknown())) - .collect(); - - self.positionals.push(column_path(&fields)); - self - } - - fn create(&self) -> CallInfo { - CallInfo { - args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())), - name_tag: Tag::unknown(), - } - } - } - - 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() - } - - fn unstructured_sample_record(value: &str) -> Value { - UntaggedValue::string(value).into_value(Tag::unknown()) - } - - #[test] - fn str_plugin_configuration_flags_wired() { - let mut plugin = Str::new(); - - let configured = plugin.config().unwrap(); - - for action_flag in &[ - "downcase", - "upcase", - "to-int", - "substring", - "replace", - "find-replace", - ] { - assert!(configured.named.get(*action_flag).is_some()); - } - } - - #[test] - fn str_plugin_accepts_downcase() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("downcase").create()) - .is_ok()); - assert_eq!(plugin.action.unwrap(), Action::Downcase); - } - - #[test] - fn str_plugin_accepts_upcase() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("upcase").create()) - .is_ok()); - assert_eq!(plugin.action.unwrap(), Action::Upcase); - } - - #[test] - fn str_plugin_accepts_to_int() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("to-int").create()) - .is_ok()); - assert_eq!(plugin.action.unwrap(), Action::ToInteger); - } - - #[test] - fn str_plugin_accepts_replace() { - let mut plugin = Str::new(); - - let argument = String::from("replace_text"); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("replace", string(&argument)) - .create() - ) - .is_ok()); - - match plugin.action { - Some(Action::Replace(ReplaceAction::Direct(replace_with))) => { - assert_eq!(replace_with, argument) - } - Some(_) | None => panic!("Din't accept."), - } - } - - #[test] - fn str_plugin_accepts_find_replace() { - let mut plugin = Str::new(); - - let search_argument = String::from("kittens"); - let replace_argument = String::from("jotandrehuda"); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter( - "find-replace", - table(&vec![string(&search_argument), string(&replace_argument)]) - ) - .create() - ) - .is_ok()); - - match plugin.action { - Some(Action::Replace(ReplaceAction::FindAndReplace(find_with, replace_with))) => { - assert_eq!(find_with, search_argument); - assert_eq!(replace_with, replace_argument); - } - Some(_) | None => panic!("Din't accept."), - } - } - #[test] - fn str_plugin_accepts_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_parameter("package.description") - .create() - ) - .is_ok()); - - let actual = &*plugin.field.unwrap(); - let actual = UntaggedValue::Primitive(Primitive::ColumnPath(actual.clone())); - let actual = actual.into_value(Tag::unknown()); - - assert_eq!( - actual, - column_path(&vec![string("package"), string("description")]) - ) - } - - #[test] - fn str_plugin_accepts_only_one_action() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("upcase") - .with_long_flag("downcase") - .with_long_flag("to-int") - .with_long_flag("substring") - .create(), - ) - .is_err()); - assert_eq!(plugin.error, Some("can only apply one".to_string())); - } - - #[test] - fn str_downcases() { - let mut strutils = Str::new(); - strutils.for_downcase(); - assert_eq!( - strutils.apply("ANDRES").unwrap(), - UntaggedValue::string("andres") - ); - } - - #[test] - fn str_upcases() { - let mut strutils = Str::new(); - strutils.for_upcase(); - assert_eq!( - strutils.apply("andres").unwrap(), - UntaggedValue::string("ANDRES") - ); - } - - #[test] - fn str_to_int() { - let mut strutils = Str::new(); - strutils.for_to_int(); - assert_eq!( - strutils.apply("9999").unwrap(), - UntaggedValue::int(9999 as i64) - ); - } - - #[test] - fn str_replace() { - let mut strutils = Str::new(); - strutils.for_replace(ReplaceAction::Direct("robalino".to_string())); - - assert_eq!( - strutils.apply("andres").unwrap(), - UntaggedValue::string("robalino") - ); - } - - #[test] - fn str_find_replace() { - let mut strutils = Str::new(); - strutils.for_replace(ReplaceAction::FindAndReplace( - "kittens".to_string(), - "jotandrehuda".to_string(), - )); - assert_eq!( - strutils.apply("wykittens").unwrap(), - UntaggedValue::string("wyjotandrehuda") - ); - } - - #[test] - fn str_plugin_applies_upcase_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("upcase") - .with_parameter("name") - .create() - ) - .is_ok()); - - let subject = structured_sample_record("name", "jotandrehuda"); - 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("name")).borrow(), - UntaggedValue::string(String::from("JOTANDREHUDA")).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_upcase_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("upcase").create()) - .is_ok()); - - let subject = unstructured_sample_record("jotandrehuda"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("JOTANDREHUDA")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_downcase_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("downcase") - .with_parameter("name") - .create() - ) - .is_ok()); - - let subject = structured_sample_record("name", "JOTANDREHUDA"); - 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("name")).borrow(), - UntaggedValue::string(String::from("jotandrehuda")).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_downcase_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("downcase").create()) - .is_ok()); - - let subject = unstructured_sample_record("JOTANDREHUDA"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("jotandrehuda")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_to_int_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("to-int") - .with_parameter("Nu_birthday") - .create() - ) - .is_ok()); - - let subject = structured_sample_record("Nu_birthday", "10"); - 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("Nu_birthday")).borrow(), - UntaggedValue::int(10).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_to_int_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("to-int").create()) - .is_ok()); - - let subject = unstructured_sample_record("10"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::Int(i)), - .. - }) => assert_eq!(*i, BigInt::from(10)), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("0,1")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("0")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_exceeding_string_length() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("0,11")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("0123456789")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_returns_blank_if_start_exceeds_length() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("20,30")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_treats_blank_start_as_zero() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string(",5")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("01234")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_treats_blank_end_as_length() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("2,")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("23456789")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_returns_error_if_start_exceeds_end() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("3,1")) - .create() - ) - .is_err()); - assert_eq!( - plugin.error, - Some("End must be greater than or equal to Start".to_string()) - ); - } - - #[test] - fn str_plugin_applies_replace_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_parameter("rustconf") - .with_named_parameter("replace", string("22nd August 2019")) - .create() - ) - .is_ok()); - - let subject = structured_sample_record("rustconf", "1st January 1970"); - 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("rustconf")).borrow(), - Value { - value: UntaggedValue::string(String::from("22nd August 2019")), - tag: Tag::unknown() - } - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_replace_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("replace", string("22nd August 2019")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("1st January 1970"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("22nd August 2019")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_find_replace_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_parameter("staff") - .with_named_parameter( - "find-replace", - table(&vec![string("kittens"), string("jotandrehuda")]) - ) - .create() - ) - .is_ok()); - - let subject = structured_sample_record("staff", "wykittens"); - 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("staff")).borrow(), - Value { - value: UntaggedValue::string(String::from("wyjotandrehuda")), - tag: Tag::unknown() - } - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_find_replace_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter( - "find-replace", - table(&vec![string("kittens"), string("jotandrehuda")]) - ) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("wykittens"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("wyjotandrehuda")), - _ => {} - } - } + serve_plugin(&mut Str::new()) } diff --git a/crates/nu_plugin_str/src/nu_plugin_str/mod.rs b/crates/nu_plugin_str/src/nu_plugin_str/mod.rs new file mode 100644 index 000000000..22ad8c025 --- /dev/null +++ b/crates/nu_plugin_str/src/nu_plugin_str/mod.rs @@ -0,0 +1,108 @@ +#[cfg(test)] +mod tests; + +use crate::strutils::ReplaceAction; +use crate::Str; +use nu_errors::ShellError; +use nu_plugin::Plugin; +use nu_protocol::{ + CallInfo, Primitive, ReturnSuccess, ReturnValue, ShellTypeName, Signature, SyntaxShape, + UntaggedValue, Value, +}; +use nu_value_ext::ValueExt; + +impl Plugin for Str { + fn config(&mut self) -> Result { + Ok(Signature::build("str") + .desc("Apply string function. Optional use the column of a table") + .switch("downcase", "convert string to lowercase") + .switch("upcase", "convert string to uppercase") + .switch("to-int", "convert string to integer") + .named("replace", SyntaxShape::String, "replaces the string") + .named( + "find-replace", + SyntaxShape::Any, + "finds and replaces [pattern replacement]", + ) + .named( + "substring", + SyntaxShape::String, + "convert string to portion of original, requires \"start,end\"", + ) + .rest(SyntaxShape::ColumnPath, "the column(s) to convert") + .filter()) + } + + fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { + let args = call_info.args; + + if args.has("downcase") { + self.for_downcase(); + } + if args.has("upcase") { + self.for_upcase(); + } + if args.has("to-int") { + self.for_to_int(); + } + if args.has("substring") { + if let Some(start_end) = args.get("substring") { + match start_end { + Value { + value: UntaggedValue::Primitive(Primitive::String(s)), + .. + } => { + self.for_substring(s.to_string()); + } + _ => { + return Err(ShellError::labeled_error( + "Unrecognized type in params", + start_end.type_name(), + &start_end.tag, + )) + } + } + } + } + if args.has("replace") { + if let Some(Value { + value: UntaggedValue::Primitive(Primitive::String(replacement)), + .. + }) = args.get("replace") + { + self.for_replace(ReplaceAction::Direct(replacement.clone())); + } + } + + if args.has("find-replace") { + if let Some(Value { + value: UntaggedValue::Table(arguments), + .. + }) = args.get("find-replace") + { + self.for_replace(ReplaceAction::FindAndReplace( + arguments.get(0).unwrap().as_string()?.to_string(), + arguments.get(1).unwrap().as_string()?.to_string(), + )); + } + } + + if let Some(possible_field) = args.nth(0) { + let possible_field = possible_field.as_column_path()?; + self.for_field(possible_field); + } + + match &self.error { + Some(reason) => Err(ShellError::untagged_runtime_error(format!( + "{}: {}", + reason, + Str::usage() + ))), + None => Ok(vec![]), + } + } + + fn filter(&mut self, input: Value) -> Result, ShellError> { + Ok(vec![ReturnSuccess::value(self.strutils(input)?)]) + } +} diff --git a/crates/nu_plugin_str/src/nu_plugin_str/tests.rs b/crates/nu_plugin_str/src/nu_plugin_str/tests.rs new file mode 100644 index 000000000..94cbeb48d --- /dev/null +++ b/crates/nu_plugin_str/src/nu_plugin_str/tests.rs @@ -0,0 +1,367 @@ +mod integration { + use crate::strutils::{Action, ReplaceAction}; + use crate::tests::{ + expect_return_value_at, get_data, int, string, structured_sample_record, + unstructured_sample_record, + }; + use crate::Str; + use nu_plugin::test_helpers::{column_path, plugin, table, CallStub}; + use nu_protocol::UntaggedValue; + + #[test] + fn picks_up_one_action_flag_only() { + plugin(&mut Str::new()) + .args( + CallStub::new() + .with_long_flag("upcase") + .with_long_flag("downcase") + .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_downcase_flag() { + plugin(&mut Str::new()) + .args(CallStub::new().with_long_flag("downcase").create()) + .setup(|plugin, _| plugin.expect_action(Action::Downcase)); + } + + #[test] + fn picks_up_upcase_flag() { + plugin(&mut Str::new()) + .args(CallStub::new().with_long_flag("upcase").create()) + .setup(|plugin, _| plugin.expect_action(Action::Upcase)); + } + + #[test] + fn picks_up_to_int_flag() { + plugin(&mut Str::new()) + .args(CallStub::new().with_long_flag("to-int").create()) + .setup(|plugin, _| plugin.expect_action(Action::ToInteger)); + } + + #[test] + fn picks_up_arguments_for_replace_flag() { + let argument = String::from("replace_text"); + + plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter("replace", string(&argument)) + .create(), + ) + .setup(|plugin, _| { + let strategy = ReplaceAction::Direct(argument); + plugin.expect_action(Action::Replace(strategy)); + }); + } + + #[test] + fn picks_up_arguments_for_find_replace() { + let search_argument = String::from("kittens"); + let replace_argument = String::from("jotandrehuda"); + + plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter( + "find-replace", + table(&vec![string(&search_argument), string(&replace_argument)]), + ) + .create(), + ) + .setup(|plugin, _| { + let strategy = ReplaceAction::FindAndReplace(search_argument, replace_argument); + plugin.expect_action(Action::Replace(strategy)) + }); + } + + #[test] + fn picks_up_argument_for_field() { + plugin(&mut Str::new()) + .args( + CallStub::new() + .with_parameter("package.description") + .create(), + ) + .setup(|plugin, _| { + plugin.expect_field(column_path(&vec![string("package"), string("description")])) + }); + } + + #[test] + fn substring_errors_if_start_index_is_greater_than_end_index() { + plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter("substring", string("3,1")) + .create(), + ) + .setup(|plugin, returned_values| { + let actual = format!("{}", returned_values.unwrap_err()); + + assert!(actual.contains("End must be greater than or equal to Start")); + assert_eq!( + plugin.error, + Some("End must be greater than or equal to Start".to_string()) + ); + }); + } + + #[test] + fn upcases_the_input_using_the_field_passed_as_parameter() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_long_flag("upcase") + .with_parameter("name") + .create(), + ) + .input(structured_sample_record("name", "jotandrehuda")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(get_data(actual, "name"), string("JOTANDREHUDA")); + } + + #[test] + fn downcases_the_input_using_the_field_passed_as_parameter() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_long_flag("downcase") + .with_parameter("name") + .create(), + ) + .input(structured_sample_record("name", "JOTANDREHUDA")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(get_data(actual, "name"), string("jotandrehuda")); + } + + #[test] + fn converts_the_input_to_integer_using_the_field_passed_as_parameter() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_long_flag("to-int") + .with_parameter("Nu_birthday") + .create(), + ) + .input(structured_sample_record("Nu_birthday", "10")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(get_data(actual, "Nu_birthday"), int(10)); + } + + #[test] + fn replaces_the_input_using_the_field_passed_as_parameter() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_parameter("rustconf") + .with_named_parameter("replace", string("22nd August 2019")) + .create(), + ) + .input(structured_sample_record("rustconf", "1st January 1970")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(get_data(actual, "rustconf"), string("22nd August 2019")); + } + + #[test] + fn find_and_replaces_the_input_using_the_field_passed_as_parameter() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_parameter("staff") + .with_named_parameter( + "find-replace", + table(&vec![string("kittens"), string("jotandrehuda")]), + ) + .create(), + ) + .input(structured_sample_record("staff", "wykittens")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(get_data(actual, "staff"), string("wyjotandrehuda")); + } + + #[test] + fn upcases_the_input() { + let run = plugin(&mut Str::new()) + .args(CallStub::new().with_long_flag("upcase").create()) + .input(unstructured_sample_record("joandrehuda")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + assert_eq!(actual, string("JOANDREHUDA")); + } + + #[test] + fn downcases_the_input() { + let run = plugin(&mut Str::new()) + .args(CallStub::new().with_long_flag("downcase").create()) + .input(unstructured_sample_record("JOANDREHUDA")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + assert_eq!(actual, string("joandrehuda")); + } + + #[test] + fn converts_the_input_to_integer() { + let run = plugin(&mut Str::new()) + .args(CallStub::new().with_long_flag("to-int").create()) + .input(unstructured_sample_record("10")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(actual, UntaggedValue::int(10).into_untagged_value()); + } + + #[test] + fn substrings_the_input() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter("substring", string("0,1")) + .create(), + ) + .input(unstructured_sample_record("0123456789")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(actual, string("0")); + } + + #[test] + fn substrings_the_input_and_returns_the_string_if_end_index_exceeds_length() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter("substring", string("0,11")) + .create(), + ) + .input(unstructured_sample_record("0123456789")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(actual, string("0123456789")); + } + + #[test] + fn substrings_the_input_and_returns_blank_if_start_index_exceeds_length() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter("substring", string("20,30")) + .create(), + ) + .input(unstructured_sample_record("0123456789")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(actual, string("")); + } + + #[test] + fn substrings_the_input_and_treats_start_index_as_zero_if_blank_start_index_given() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter("substring", string(",5")) + .create(), + ) + .input(unstructured_sample_record("0123456789")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(actual, string("01234")); + } + + #[test] + fn substrings_the_input_and_treats_end_index_as_length_if_blank_end_index_given() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter("substring", string("2,")) + .create(), + ) + .input(unstructured_sample_record("0123456789")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(actual, string("23456789")); + } + + #[test] + fn replaces_the_input() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter("replace", string("22nd August 2019")) + .create(), + ) + .input(unstructured_sample_record("1st January 1970")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(actual, string("22nd August 2019")); + } + + #[test] + fn find_and_replaces_the_input() { + let run = plugin(&mut Str::new()) + .args( + CallStub::new() + .with_named_parameter( + "find-replace", + table(&vec![string("kittens"), string("jotandrehuda")]), + ) + .create(), + ) + .input(unstructured_sample_record("wykittens")) + .setup(|_, _| {}) + .test(); + + let actual = expect_return_value_at(run, 0); + + assert_eq!(actual, string("wyjotandrehuda")); + } +} diff --git a/crates/nu_plugin_str/src/strutils.rs b/crates/nu_plugin_str/src/strutils.rs new file mode 100644 index 000000000..7768cfd97 --- /dev/null +++ b/crates/nu_plugin_str/src/strutils.rs @@ -0,0 +1,262 @@ +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; + +#[derive(Debug, Eq, PartialEq)] +pub enum Action { + Downcase, + Upcase, + ToInteger, + Substring(usize, usize), + Replace(ReplaceAction), +} + +#[derive(Debug, Eq, PartialEq)] +pub enum ReplaceAction { + Direct(String), + FindAndReplace(String, String), +} + +pub struct Str { + pub field: Option>, + pub error: Option, + pub action: Option, +} + +impl Str { + pub fn new() -> Str { + Str { + field: None, + error: None, + action: None, + } + } + + fn apply(&self, input: &str) -> Result { + let applied = match self.action.as_ref() { + Some(Action::Downcase) => UntaggedValue::string(input.to_ascii_lowercase()), + Some(Action::Upcase) => UntaggedValue::string(input.to_ascii_uppercase()), + Some(Action::Substring(s, e)) => { + let end: usize = cmp::min(*e, input.len()); + let start: usize = *s; + if start > input.len() - 1 { + UntaggedValue::string("") + } else { + UntaggedValue::string( + &input + .chars() + .skip(start) + .take(end - start) + .collect::(), + ) + } + } + Some(Action::Replace(mode)) => match mode { + ReplaceAction::Direct(replacement) => UntaggedValue::string(replacement.as_str()), + ReplaceAction::FindAndReplace(find, replacement) => { + let regex = Regex::new(find.as_str()); + + match regex { + Ok(re) => UntaggedValue::string( + re.replace(input, replacement.as_str()).to_owned(), + ), + Err(_) => UntaggedValue::string(input), + } + } + }, + Some(Action::ToInteger) => match input.trim() { + other => match other.parse::() { + Ok(v) => UntaggedValue::int(v), + Err(_) => UntaggedValue::string(input), + }, + }, + None => UntaggedValue::string(input), + }; + + Ok(applied) + } + + pub fn for_field(&mut self, column_path: Tagged) { + self.field = Some(column_path); + } + + fn permit(&mut self) -> bool { + self.action.is_none() + } + + fn log_error(&mut self, message: &str) { + self.error = Some(message.to_string()); + } + + pub fn for_to_int(&mut self) { + if self.permit() { + self.action = Some(Action::ToInteger); + } else { + self.log_error("can only apply one"); + } + } + + pub fn for_downcase(&mut self) { + if self.permit() { + self.action = Some(Action::Downcase); + } else { + self.log_error("can only apply one"); + } + } + + pub fn for_upcase(&mut self) { + if self.permit() { + self.action = Some(Action::Upcase); + } else { + self.log_error("can only apply one"); + } + } + + pub fn for_substring(&mut self, s: String) { + let v: Vec<&str> = s.split(',').collect(); + let start: usize = match v[0] { + "" => 0, + _ => v[0].trim().parse().unwrap(), + }; + let end: usize = match v[1] { + "" => usize::max_value(), + _ => v[1].trim().parse().unwrap(), + }; + if start > end { + self.log_error("End must be greater than or equal to Start"); + } else if self.permit() { + self.action = Some(Action::Substring(start, end)); + } else { + self.log_error("can only apply one"); + } + } + + pub fn for_replace(&mut self, mode: ReplaceAction) { + if self.permit() { + self.action = Some(Action::Replace(mode)); + } else { + self.log_error("can only apply one"); + } + } + + pub fn usage() -> &'static str { + "Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"|--replace|--find-replace [pattern replacement]]]" + } + + pub fn strutils(&self, value: Value) -> Result { + match &value.value { + UntaggedValue::Primitive(Primitive::String(ref s)) => { + Ok(self.apply(&s)?.into_value(value.tag())) + } + UntaggedValue::Primitive(Primitive::Line(ref s)) => { + Ok(self.apply(&s)?.into_value(value.tag())) + } + 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, error)| { + 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 => error, + } + }), + ); + + let got = replace_for?; + let replacement = self.strutils(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( + "str could not find field to replace", + "column name", + value.tag(), + )), + } + } + None => Err(ShellError::untagged_runtime_error(format!( + "{}: {}", + "str needs a column when applied to a value in a row", + Str::usage() + ))), + }, + _ => Err(ShellError::labeled_error( + "Unrecognized type in stream", + value.type_name(), + value.tag, + )), + } + } +} + +#[cfg(test)] +pub mod tests { + use super::{ReplaceAction, Str}; + use crate::tests::{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); + } + + #[test] + fn find_and_replaces() { + let mut strutils = Str::new(); + + strutils.for_replace(ReplaceAction::FindAndReplace( + "kittens".to_string(), + "jotandrehuda".to_string(), + )); + + assert_eq!( + strutils.apply("wykittens").unwrap(), + string("wyjotandrehuda").value + ); + } +} diff --git a/crates/nu_plugin_sum/Cargo.toml b/crates/nu_plugin_sum/Cargo.toml index f48152bc0..2e9fe549b 100644 --- a/crates/nu_plugin_sum/Cargo.toml +++ b/crates/nu_plugin_sum/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_sum/src/main.rs b/crates/nu_plugin_sum/src/main.rs index 58c697cef..6ead7c702 100644 --- a/crates/nu_plugin_sum/src/main.rs +++ b/crates/nu_plugin_sum/src/main.rs @@ -1,7 +1,7 @@ use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, Signature, - UntaggedValue, Value, + CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value, }; struct Sum { diff --git a/crates/nu_plugin_sys/Cargo.toml b/crates/nu_plugin_sys/Cargo.toml index e9287b5c2..590a66079 100644 --- a/crates/nu_plugin_sys/Cargo.toml +++ b/crates/nu_plugin_sys/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_sys/src/main.rs b/crates/nu_plugin_sys/src/main.rs index c44d98e45..9d482b750 100644 --- a/crates/nu_plugin_sys/src/main.rs +++ b/crates/nu_plugin_sys/src/main.rs @@ -6,9 +6,9 @@ use futures_util::StreamExt; use heim::units::{frequency, information, thermodynamic_temperature, time}; use heim::{disk, host, memory, net, sensors}; use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, Plugin, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, - UntaggedValue, Value, + CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value, }; use nu_source::Tag; diff --git a/crates/nu_plugin_textview/Cargo.toml b/crates/nu_plugin_textview/Cargo.toml index f509e96ff..76fe542b9 100644 --- a/crates/nu_plugin_textview/Cargo.toml +++ b/crates/nu_plugin_textview/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_textview/src/main.rs b/crates/nu_plugin_textview/src/main.rs index 8f4dc48d5..d7f6d750d 100644 --- a/crates/nu_plugin_textview/src/main.rs +++ b/crates/nu_plugin_textview/src/main.rs @@ -1,9 +1,8 @@ use crossterm::{cursor, terminal, RawScreen}; use crossterm::{InputEvent, KeyEvent}; use nu_errors::ShellError; -use nu_protocol::{ - outln, serve_plugin, CallInfo, Plugin, Primitive, Signature, UntaggedValue, Value, -}; +use nu_plugin::{serve_plugin, Plugin}; +use nu_protocol::{outln, CallInfo, Primitive, Signature, UntaggedValue, Value}; use nu_source::AnchorLocation; use syntect::easy::HighlightLines; diff --git a/crates/nu_plugin_tree/Cargo.toml b/crates/nu_plugin_tree/Cargo.toml index c701d8551..0d8bc9445 100644 --- a/crates/nu_plugin_tree/Cargo.toml +++ b/crates/nu_plugin_tree/Cargo.toml @@ -9,6 +9,7 @@ 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" } nu-source = { path = "../nu-source", version = "0.7.0" } nu-errors = { path = "../nu-errors", version = "0.7.0" } diff --git a/crates/nu_plugin_tree/src/main.rs b/crates/nu_plugin_tree/src/main.rs index e82a34510..4d9781c8f 100644 --- a/crates/nu_plugin_tree/src/main.rs +++ b/crates/nu_plugin_tree/src/main.rs @@ -1,8 +1,7 @@ use derive_new::new; use nu_errors::ShellError; -use nu_protocol::{ - format_primitive, serve_plugin, CallInfo, Plugin, Signature, UntaggedValue, Value, -}; +use nu_plugin::{serve_plugin, Plugin}; +use nu_protocol::{format_primitive, CallInfo, Signature, UntaggedValue, Value}; use ptree::item::StringItem; use ptree::output::print_tree_with; use ptree::print_config::PrintConfig; diff --git a/src/lib.rs b/src/lib.rs index f1506015c..30de3cbe8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,4 +31,5 @@ pub use nu_value_ext::ValueExt; pub use num_traits::cast::ToPrimitive; // TODO: Temporary redirect -pub use nu_protocol::{did_you_mean, serve_plugin, Plugin, TaggedDictBuilder}; +pub use nu_protocol::{did_you_mean, TaggedDictBuilder}; +//pub use nu_plugin::{serve_plugin, Plugin}; diff --git a/src/plugins/nu_plugin_core_inc.rs b/src/plugins/nu_plugin_core_inc.rs index 3fd6ca89e..3decf0f44 100644 --- a/src/plugins/nu_plugin_core_inc.rs +++ b/src/plugins/nu_plugin_core_inc.rs @@ -1,7 +1,8 @@ use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - did_you_mean, serve_plugin, CallInfo, ColumnPath, Plugin, Primitive, ReturnSuccess, - ReturnValue, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value, + 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; @@ -218,11 +219,11 @@ mod tests { use super::{Inc, SemVerAction}; use indexmap::IndexMap; + use nu_plugin::Plugin; use nu_protocol::{ - CallInfo, EvaluatedArgs, PathMember, ReturnSuccess, UnspannedPathMember, UntaggedValue, - Value, + CallInfo, EvaluatedArgs, PathMember, ReturnSuccess, TaggedDictBuilder, UnspannedPathMember, + UntaggedValue, Value, }; - use nu_protocol::{Plugin, TaggedDictBuilder}; use nu_source::{Span, Tag}; struct CallStub { diff --git a/src/plugins/nu_plugin_core_ps.rs b/src/plugins/nu_plugin_core_ps.rs index 517d4e2b9..ce265ca7f 100644 --- a/src/plugins/nu_plugin_core_ps.rs +++ b/src/plugins/nu_plugin_core_ps.rs @@ -7,9 +7,9 @@ use heim::units::{information, ratio, Ratio}; use std::usize; use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, Plugin, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, - UntaggedValue, Value, + CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value, }; use nu_source::Tag; diff --git a/src/plugins/nu_plugin_core_str.rs b/src/plugins/nu_plugin_core_str.rs index b7aaf8ea0..279650ca4 100644 --- a/src/plugins/nu_plugin_core_str.rs +++ b/src/plugins/nu_plugin_core_str.rs @@ -1,980 +1,5 @@ -use nu_errors::ShellError; -use nu_protocol::{ - did_you_mean, serve_plugin, CallInfo, ColumnPath, Plugin, Primitive, ReturnSuccess, - ReturnValue, ShellTypeName, Signature, SyntaxShape, UntaggedValue, Value, -}; -use nu_source::{span_for_spanned_list, Tagged}; -use nu_value_ext::ValueExt; - -use regex::Regex; -use std::cmp; - -#[derive(Debug, Eq, PartialEq)] -enum Action { - Downcase, - Upcase, - ToInteger, - Substring(usize, usize), - Replace(ReplaceAction), -} - -#[derive(Debug, Eq, PartialEq)] -enum ReplaceAction { - Direct(String), - FindAndReplace(String, String), -} - -struct Str { - field: Option>, - error: Option, - action: Option, -} - -impl Str { - fn new() -> Str { - Str { - field: None, - error: None, - action: None, - } - } - - fn apply(&self, input: &str) -> Result { - let applied = match self.action.as_ref() { - Some(Action::Downcase) => UntaggedValue::string(input.to_ascii_lowercase()), - Some(Action::Upcase) => UntaggedValue::string(input.to_ascii_uppercase()), - Some(Action::Substring(s, e)) => { - let end: usize = cmp::min(*e, input.len()); - let start: usize = *s; - if start > input.len() - 1 { - UntaggedValue::string("") - } else { - UntaggedValue::string( - &input - .chars() - .skip(start) - .take(end - start) - .collect::(), - ) - } - } - Some(Action::Replace(mode)) => match mode { - ReplaceAction::Direct(replacement) => UntaggedValue::string(replacement.as_str()), - ReplaceAction::FindAndReplace(find, replacement) => { - let regex = Regex::new(find.as_str()); - - match regex { - Ok(re) => UntaggedValue::string( - re.replace(input, replacement.as_str()).to_owned(), - ), - Err(_) => UntaggedValue::string(input), - } - } - }, - Some(Action::ToInteger) => match input.trim() { - other => match other.parse::() { - Ok(v) => UntaggedValue::int(v), - Err(_) => UntaggedValue::string(input), - }, - }, - None => UntaggedValue::string(input), - }; - - Ok(applied) - } - - fn for_field(&mut self, column_path: Tagged) { - self.field = Some(column_path); - } - - fn permit(&mut self) -> bool { - self.action.is_none() - } - - fn log_error(&mut self, message: &str) { - self.error = Some(message.to_string()); - } - - fn for_to_int(&mut self) { - if self.permit() { - self.action = Some(Action::ToInteger); - } else { - self.log_error("can only apply one"); - } - } - - fn for_downcase(&mut self) { - if self.permit() { - self.action = Some(Action::Downcase); - } else { - self.log_error("can only apply one"); - } - } - - fn for_upcase(&mut self) { - if self.permit() { - self.action = Some(Action::Upcase); - } else { - self.log_error("can only apply one"); - } - } - - fn for_substring(&mut self, s: String) { - let v: Vec<&str> = s.split(',').collect(); - let start: usize = match v[0] { - "" => 0, - _ => v[0].trim().parse().unwrap(), - }; - let end: usize = match v[1] { - "" => usize::max_value(), - _ => v[1].trim().parse().unwrap(), - }; - if start > end { - self.log_error("End must be greater than or equal to Start"); - } else if self.permit() { - self.action = Some(Action::Substring(start, end)); - } else { - self.log_error("can only apply one"); - } - } - - fn for_replace(&mut self, mode: ReplaceAction) { - if self.permit() { - self.action = Some(Action::Replace(mode)); - } else { - self.log_error("can only apply one"); - } - } - - pub fn usage() -> &'static str { - "Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"|--replace|--find-replace [pattern replacement]]]" - } -} - -impl Str { - fn strutils(&self, value: Value) -> Result { - match &value.value { - UntaggedValue::Primitive(Primitive::String(ref s)) => { - Ok(self.apply(&s)?.into_value(value.tag())) - } - UntaggedValue::Primitive(Primitive::Line(ref s)) => { - Ok(self.apply(&s)?.into_value(value.tag())) - } - 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, error)| { - 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 => error, - } - }), - ); - - let got = replace_for?; - let replacement = self.strutils(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( - "str could not find field to replace", - "column name", - value.tag(), - )), - } - } - None => Err(ShellError::untagged_runtime_error(format!( - "{}: {}", - "str needs a column when applied to a value in a row", - Str::usage() - ))), - }, - _ => Err(ShellError::labeled_error( - "Unrecognized type in stream", - value.type_name(), - value.tag, - )), - } - } -} - -impl Plugin for Str { - fn config(&mut self) -> Result { - Ok(Signature::build("str") - .desc("Apply string function. Optional use the column of a table") - .switch("downcase", "convert string to lowercase") - .switch("upcase", "convert string to uppercase") - .switch("to-int", "convert string to integer") - .named("replace", SyntaxShape::String, "replaces the string") - .named( - "find-replace", - SyntaxShape::Any, - "finds and replaces [pattern replacement]", - ) - .named( - "substring", - SyntaxShape::String, - "convert string to portion of original, requires \"start,end\"", - ) - .rest(SyntaxShape::ColumnPath, "the column(s) to convert") - .filter()) - } - - fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { - let args = call_info.args; - - if args.has("downcase") { - self.for_downcase(); - } - if args.has("upcase") { - self.for_upcase(); - } - if args.has("to-int") { - self.for_to_int(); - } - if args.has("substring") { - if let Some(start_end) = args.get("substring") { - match start_end { - Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - } => { - self.for_substring(s.to_string()); - } - _ => { - return Err(ShellError::labeled_error( - "Unrecognized type in params", - start_end.type_name(), - &start_end.tag, - )) - } - } - } - } - if args.has("replace") { - if let Some(Value { - value: UntaggedValue::Primitive(Primitive::String(replacement)), - .. - }) = args.get("replace") - { - self.for_replace(ReplaceAction::Direct(replacement.clone())); - } - } - - if args.has("find-replace") { - if let Some(Value { - value: UntaggedValue::Table(arguments), - .. - }) = args.get("find-replace") - { - self.for_replace(ReplaceAction::FindAndReplace( - arguments.get(0).unwrap().as_string()?.to_string(), - arguments.get(1).unwrap().as_string()?.to_string(), - )); - } - } - - if let Some(possible_field) = args.nth(0) { - let possible_field = possible_field.as_column_path()?; - self.for_field(possible_field); - } - - match &self.error { - Some(reason) => Err(ShellError::untagged_runtime_error(format!( - "{}: {}", - reason, - Str::usage() - ))), - None => Ok(vec![]), - } - } - - fn filter(&mut self, input: Value) -> Result, ShellError> { - Ok(vec![ReturnSuccess::value(self.strutils(input)?)]) - } -} - +use nu_plugin::serve_plugin; +use nu_plugin_str::Str; fn main() { serve_plugin(&mut Str::new()); } - -#[cfg(test)] -mod tests { - use super::{Action, ReplaceAction, Str}; - use indexmap::IndexMap; - use nu_protocol::{ - CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, TaggedDictBuilder, - UntaggedValue, Value, - }; - use nu_source::Tag; - use nu_value_ext::ValueExt; - use num_bigint::BigInt; - - fn string(input: impl Into) -> Value { - UntaggedValue::string(input.into()).into_untagged_value() - } - - fn table(list: &Vec) -> Value { - UntaggedValue::table(list).into_untagged_value() - } - - fn column_path(paths: &Vec) -> Value { - UntaggedValue::Primitive(Primitive::ColumnPath( - table(&paths.iter().cloned().collect()) - .as_column_path() - .unwrap() - .item, - )) - .into_untagged_value() - } - struct CallStub { - positionals: Vec, - flags: IndexMap, - } - - impl CallStub { - fn new() -> CallStub { - CallStub { - positionals: vec![], - flags: indexmap::IndexMap::new(), - } - } - - fn with_named_parameter(&mut self, name: &str, value: Value) -> &mut Self { - self.flags.insert(name.to_string(), value); - self - } - - 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| UntaggedValue::string(s.to_string()).into_value(Tag::unknown())) - .collect(); - - self.positionals.push(column_path(&fields)); - self - } - - fn create(&self) -> CallInfo { - CallInfo { - args: EvaluatedArgs::new(Some(self.positionals.clone()), Some(self.flags.clone())), - name_tag: Tag::unknown(), - } - } - } - - 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() - } - - fn unstructured_sample_record(value: &str) -> Value { - UntaggedValue::string(value).into_value(Tag::unknown()) - } - - #[test] - fn str_plugin_configuration_flags_wired() { - let mut plugin = Str::new(); - - let configured = plugin.config().unwrap(); - - for action_flag in &[ - "downcase", - "upcase", - "to-int", - "substring", - "replace", - "find-replace", - ] { - assert!(configured.named.get(*action_flag).is_some()); - } - } - - #[test] - fn str_plugin_accepts_downcase() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("downcase").create()) - .is_ok()); - assert_eq!(plugin.action.unwrap(), Action::Downcase); - } - - #[test] - fn str_plugin_accepts_upcase() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("upcase").create()) - .is_ok()); - assert_eq!(plugin.action.unwrap(), Action::Upcase); - } - - #[test] - fn str_plugin_accepts_to_int() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("to-int").create()) - .is_ok()); - assert_eq!(plugin.action.unwrap(), Action::ToInteger); - } - - #[test] - fn str_plugin_accepts_replace() { - let mut plugin = Str::new(); - - let argument = String::from("replace_text"); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("replace", string(&argument)) - .create() - ) - .is_ok()); - - match plugin.action { - Some(Action::Replace(ReplaceAction::Direct(replace_with))) => { - assert_eq!(replace_with, argument) - } - Some(_) | None => panic!("Din't accept."), - } - } - - #[test] - fn str_plugin_accepts_find_replace() { - let mut plugin = Str::new(); - - let search_argument = String::from("kittens"); - let replace_argument = String::from("jotandrehuda"); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter( - "find-replace", - table(&vec![string(&search_argument), string(&replace_argument)]) - ) - .create() - ) - .is_ok()); - - match plugin.action { - Some(Action::Replace(ReplaceAction::FindAndReplace(find_with, replace_with))) => { - assert_eq!(find_with, search_argument); - assert_eq!(replace_with, replace_argument); - } - Some(_) | None => panic!("Din't accept."), - } - } - #[test] - fn str_plugin_accepts_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_parameter("package.description") - .create() - ) - .is_ok()); - - let actual = &*plugin.field.unwrap(); - let actual = UntaggedValue::Primitive(Primitive::ColumnPath(actual.clone())); - let actual = actual.into_value(Tag::unknown()); - - assert_eq!( - actual, - column_path(&vec![string("package"), string("description")]) - ) - } - - #[test] - fn str_plugin_accepts_only_one_action() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("upcase") - .with_long_flag("downcase") - .with_long_flag("to-int") - .with_long_flag("substring") - .create(), - ) - .is_err()); - assert_eq!(plugin.error, Some("can only apply one".to_string())); - } - - #[test] - fn str_downcases() { - let mut strutils = Str::new(); - strutils.for_downcase(); - assert_eq!( - strutils.apply("ANDRES").unwrap(), - UntaggedValue::string("andres") - ); - } - - #[test] - fn str_upcases() { - let mut strutils = Str::new(); - strutils.for_upcase(); - assert_eq!( - strutils.apply("andres").unwrap(), - UntaggedValue::string("ANDRES") - ); - } - - #[test] - fn str_to_int() { - let mut strutils = Str::new(); - strutils.for_to_int(); - assert_eq!( - strutils.apply("9999").unwrap(), - UntaggedValue::int(9999 as i64) - ); - } - - #[test] - fn str_replace() { - let mut strutils = Str::new(); - strutils.for_replace(ReplaceAction::Direct("robalino".to_string())); - - assert_eq!( - strutils.apply("andres").unwrap(), - UntaggedValue::string("robalino") - ); - } - - #[test] - fn str_find_replace() { - let mut strutils = Str::new(); - strutils.for_replace(ReplaceAction::FindAndReplace( - "kittens".to_string(), - "jotandrehuda".to_string(), - )); - assert_eq!( - strutils.apply("wykittens").unwrap(), - UntaggedValue::string("wyjotandrehuda") - ); - } - - #[test] - fn str_plugin_applies_upcase_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("upcase") - .with_parameter("name") - .create() - ) - .is_ok()); - - let subject = structured_sample_record("name", "jotandrehuda"); - 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("name")).borrow(), - UntaggedValue::string(String::from("JOTANDREHUDA")).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_upcase_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("upcase").create()) - .is_ok()); - - let subject = unstructured_sample_record("jotandrehuda"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("JOTANDREHUDA")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_downcase_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("downcase") - .with_parameter("name") - .create() - ) - .is_ok()); - - let subject = structured_sample_record("name", "JOTANDREHUDA"); - 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("name")).borrow(), - UntaggedValue::string(String::from("jotandrehuda")).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_downcase_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("downcase").create()) - .is_ok()); - - let subject = unstructured_sample_record("JOTANDREHUDA"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("jotandrehuda")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_to_int_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_long_flag("to-int") - .with_parameter("Nu_birthday") - .create() - ) - .is_ok()); - - let subject = structured_sample_record("Nu_birthday", "10"); - 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("Nu_birthday")).borrow(), - UntaggedValue::int(10).into_untagged_value() - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_to_int_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter(CallStub::new().with_long_flag("to-int").create()) - .is_ok()); - - let subject = unstructured_sample_record("10"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::Int(i)), - .. - }) => assert_eq!(*i, BigInt::from(10)), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("0,1")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("0")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_exceeding_string_length() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("0,11")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("0123456789")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_returns_blank_if_start_exceeds_length() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("20,30")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_treats_blank_start_as_zero() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string(",5")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("01234")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_treats_blank_end_as_length() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("2,")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("0123456789"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("23456789")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_substring_returns_error_if_start_exceeds_end() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("substring", string("3,1")) - .create() - ) - .is_err()); - assert_eq!( - plugin.error, - Some("End must be greater than or equal to Start".to_string()) - ); - } - - #[test] - fn str_plugin_applies_replace_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_parameter("rustconf") - .with_named_parameter("replace", string("22nd August 2019")) - .create() - ) - .is_ok()); - - let subject = structured_sample_record("rustconf", "1st January 1970"); - 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("rustconf")).borrow(), - Value { - value: UntaggedValue::string(String::from("22nd August 2019")), - tag: Tag::unknown() - } - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_replace_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter("replace", string("22nd August 2019")) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("1st January 1970"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("22nd August 2019")), - _ => {} - } - } - - #[test] - fn str_plugin_applies_find_replace_with_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_parameter("staff") - .with_named_parameter( - "find-replace", - table(&vec![string("kittens"), string("jotandrehuda")]) - ) - .create() - ) - .is_ok()); - - let subject = structured_sample_record("staff", "wykittens"); - 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("staff")).borrow(), - Value { - value: UntaggedValue::string(String::from("wyjotandrehuda")), - tag: Tag::unknown() - } - ), - _ => {} - } - } - - #[test] - fn str_plugin_applies_find_replace_without_field() { - let mut plugin = Str::new(); - - assert!(plugin - .begin_filter( - CallStub::new() - .with_named_parameter( - "find-replace", - table(&vec![string("kittens"), string("jotandrehuda")]) - ) - .create() - ) - .is_ok()); - - let subject = unstructured_sample_record("wykittens"); - let output = plugin.filter(subject).unwrap(); - - match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - .. - }) => assert_eq!(*s, String::from("wyjotandrehuda")), - _ => {} - } - } -} diff --git a/src/plugins/nu_plugin_core_sys.rs b/src/plugins/nu_plugin_core_sys.rs index c44d98e45..9d482b750 100644 --- a/src/plugins/nu_plugin_core_sys.rs +++ b/src/plugins/nu_plugin_core_sys.rs @@ -6,9 +6,9 @@ use futures_util::StreamExt; use heim::units::{frequency, information, thermodynamic_temperature, time}; use heim::{disk, host, memory, net, sensors}; use nu_errors::ShellError; +use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{ - serve_plugin, CallInfo, Plugin, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, - UntaggedValue, Value, + CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value, }; use nu_source::Tag; diff --git a/src/plugins/nu_plugin_core_textview.rs b/src/plugins/nu_plugin_core_textview.rs index 71b840569..3d06015de 100644 --- a/src/plugins/nu_plugin_core_textview.rs +++ b/src/plugins/nu_plugin_core_textview.rs @@ -1,9 +1,8 @@ use crossterm::{cursor, terminal, RawScreen}; use crossterm::{InputEvent, KeyEvent}; use nu_errors::ShellError; -use nu_protocol::{ - outln, serve_plugin, CallInfo, Plugin, Primitive, Signature, UntaggedValue, Value, -}; +use nu_plugin::{serve_plugin, Plugin}; +use nu_protocol::{outln, CallInfo, Primitive, Signature, UntaggedValue, Value}; use nu_source::AnchorLocation; use syntect::easy::HighlightLines;