From 0b8bbd8637c3aa5338b0371a74dfa4c38a52b9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 06:41:24 -0500 Subject: [PATCH 01/14] Unit Testing WIP. --- src/plugins/str.rs | 198 ++++++++++++++++++++++++++++++++++++++++-- tests/filters_test.rs | 2 +- 2 files changed, 193 insertions(+), 7 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 0bae95618..63a7e9617 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -4,6 +4,8 @@ use nu::{ ReturnSuccess, ReturnValue, ShellError, Tagged, Value, }; +use log::trace; + struct Str { field: Option, error: Option, @@ -22,9 +24,17 @@ impl Str { } fn is_valid(&self) -> bool { + self.at_least_one() || self.none() + } + + fn at_least_one(&self) -> bool { (self.downcase && !self.upcase) || (!self.downcase && self.upcase) } + fn none(&self) -> bool { + (!self.downcase && !self.upcase) + } + fn log_error(&mut self, message: &str) { self.error = Some(message.to_string()); } @@ -62,7 +72,7 @@ impl Str { } fn usage(&self) -> &'static str { - "Usage: str [--downcase, --upcase]" + "Usage: str field [--downcase|--upcase]" } } @@ -95,9 +105,11 @@ impl Str { } } } - None => Err(ShellError::string( + None => Err(ShellError::string(format!( + "{}: {}", "str needs a field when applying it to a value in an object", - )), + self.usage() + ))), }, x => Err(ShellError::string(format!( "Unrecognized type in stream: {:?}", @@ -124,6 +136,8 @@ impl Plugin for Str { } fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { + trace!("{:?}", call_info); + if call_info.args.has("downcase") { self.for_downcase(); } @@ -155,10 +169,8 @@ impl Plugin for Str { Some(reason) => { return Err(ShellError::string(format!("{}: {}", reason, self.usage()))) } - None => {} + None => Ok(vec![]), } - - Ok(vec![]) } fn filter(&mut self, input: Tagged) -> Result, ShellError> { @@ -171,3 +183,177 @@ impl Plugin for Str { fn main() { serve_plugin(&mut Str::new()); } + +#[cfg(test)] +mod tests { + + use super::Str; + use indexmap::IndexMap; + use nu::{ + Args, CallInfo, Plugin, ReturnSuccess, SourceMap, Span, Spanned, SpannedDictBuilder, + SpannedItem, Value, + }; + + struct CallStub { + positionals: Vec>, + flags: IndexMap>, + } + + impl CallStub { + fn new() -> CallStub { + CallStub { + positionals: vec![], + flags: indexmap::IndexMap::new(), + } + } + + fn with_long_flag(&mut self, name: &str) -> &mut Self { + self.flags.insert( + name.to_string(), + Value::boolean(true).spanned(Span::unknown()), + ); + self + } + + fn with_parameter(&mut self, name: &str) -> &mut Self { + self.positionals + .push(Value::string(name.to_string()).spanned(Span::unknown())); + self + } + + fn create(&self) -> CallInfo { + CallInfo { + args: Args::new(Some(self.positionals.clone()), Some(self.flags.clone())), + source_map: SourceMap::new(), + name_span: None, + } + } + } + + fn sample_record(value: &str) -> Spanned { + let mut record = SpannedDictBuilder::new(Span::unknown()); + record.insert_spanned( + "name", + Value::string(value.to_string()).spanned(Span::unknown()), + ); + record.into_spanned_value() + } + + #[test] + fn str_accepts_downcase() { + let mut strutils = Str::new(); + + assert!(strutils + .begin_filter(CallStub::new().with_long_flag("downcase").create()) + .is_ok()); + assert!(strutils.is_valid()); + assert!(strutils.downcase); + } + + #[test] + fn str_accepts_upcase() { + let mut strutils = Str::new(); + + assert!(strutils + .begin_filter(CallStub::new().with_long_flag("upcase").create()) + .is_ok()); + assert!(strutils.is_valid()); + assert!(strutils.upcase); + } + + #[test] + fn str_accepts_only_one_flag() { + let mut strutils = Str::new(); + + assert!(strutils + .begin_filter( + CallStub::new() + .with_long_flag("upcase") + .with_long_flag("downcase") + .create(), + ) + .is_err()); + assert!(!strutils.is_valid()); + assert_eq!(Some("can only apply one".to_string()), strutils.error); + } + + #[test] + fn str_accepts_field() { + let mut strutils = Str::new(); + + assert!(strutils + .begin_filter( + CallStub::new() + .with_parameter("package.description") + .create() + ) + .is_ok()); + + assert_eq!(Some("package.description".to_string()), strutils.field); + } + + #[test] + fn str_reports_error_if_no_field_given_for_object() { + let mut strutils = Str::new(); + let subject = sample_record("jotandrehuda"); + + assert!(strutils.begin_filter(CallStub::new().create()).is_ok()); + assert!(strutils.filter(subject).is_err()); + } + + #[test] + fn str_applies_upcase() { + let mut strutils = Str::new(); + + assert!(strutils + .begin_filter( + CallStub::new() + .with_long_flag("upcase") + .with_parameter("name") + .create() + ) + .is_ok()); + + let subject = sample_record("jotandrehuda"); + let output = strutils.filter(subject).unwrap(); + + match output[0].as_ref().unwrap() { + ReturnSuccess::Value(Spanned { + item: Value::Object(o), + .. + }) => assert_eq!( + *o.get_data(&String::from("name")).borrow(), + Value::string(String::from("JOTANDREHUDA")) + ), + _ => {} + } + } + + #[test] + fn str_applies_downcase() { + let mut strutils = Str::new(); + + assert!(strutils + .begin_filter( + CallStub::new() + .with_long_flag("downcase") + .with_parameter("name") + .create() + ) + .is_ok()); + + let subject = sample_record("JOTANDREHUDA"); + let output = strutils.filter(subject).unwrap(); + + match output[0].as_ref().unwrap() { + ReturnSuccess::Value(Spanned { + item: Value::Object(o), + .. + }) => assert_eq!( + *o.get_data(&String::from("name")).borrow(), + Value::string(String::from("jotandrehuda")) + ), + _ => {} + } + } +} diff --git a/tests/filters_test.rs b/tests/filters_test.rs index 6c542b442..7b970c729 100644 --- a/tests/filters_test.rs +++ b/tests/filters_test.rs @@ -73,7 +73,7 @@ fn str_can_only_apply_one() { "open caco3_plastics.csv | first 1 | str origin --downcase --upcase" ); - assert!(output.contains("Usage: str [--downcase, --upcase]")); + assert!(output.contains("Usage: str field [--downcase|--upcase]")); } #[test] From c3034d32478364b542b183b6290212708aa7c6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 06:47:08 -0500 Subject: [PATCH 02/14] No longer need to trace call_info --- src/plugins/str.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 63a7e9617..430dce76e 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -4,8 +4,6 @@ use nu::{ ReturnSuccess, ReturnValue, ShellError, Tagged, Value, }; -use log::trace; - struct Str { field: Option, error: Option, @@ -136,8 +134,6 @@ impl Plugin for Str { } fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { - trace!("{:?}", call_info); - if call_info.args.has("downcase") { self.for_downcase(); } From b29e7c1e36a3684d8857d5a2cb529348d55b7a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 08:37:57 -0500 Subject: [PATCH 03/14] cover raw strutils to upcase and downcase --- src/plugins/str.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 430dce76e..cfa2c608c 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -297,6 +297,20 @@ mod tests { assert!(strutils.filter(subject).is_err()); } + #[test] + fn str_downcases() { + let mut strutils = Str::new(); + strutils.for_downcase(); + assert_eq!("andres", strutils.apply("ANDRES")); + } + + #[test] + fn str_upcases() { + let mut strutils = Str::new(); + strutils.for_upcase(); + assert_eq!("ANDRES", strutils.apply("andres")); + } + #[test] fn str_applies_upcase() { let mut strutils = Str::new(); From a0890b551a00b3da15fab186c941550ff175f4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 11:24:24 -0500 Subject: [PATCH 04/14] strutils can also convert to an integer now. --- src/plugins/str.rs | 131 ++++++++++++++++++++++++++++++++++-------- tests/filters_test.rs | 13 ++++- 2 files changed, 120 insertions(+), 24 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index cfa2c608c..04bb14dc2 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -1,4 +1,5 @@ use indexmap::IndexMap; +use std::str; use nu::{ serve_plugin, CallInfo, CommandConfig, NamedType, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, ShellError, Tagged, Value, @@ -9,6 +10,7 @@ struct Str { error: Option, downcase: bool, upcase: bool, + int: bool, } impl Str { @@ -18,19 +20,35 @@ impl Str { error: None, downcase: false, upcase: false, + int: false, } } - fn is_valid(&self) -> bool { - self.at_least_one() || self.none() + fn fields(&self) -> u8 { + [ + self.downcase, + self.upcase, + self.int + ].iter() + .fold(0, |acc, &field| { + if field { + acc + 1 + } else { + acc + } + }) } - fn at_least_one(&self) -> bool { - (self.downcase && !self.upcase) || (!self.downcase && self.upcase) + fn is_valid(&self) -> bool { + self.at_most_one() || self.none() + } + + fn at_most_one(&self) -> bool { + self.fields() == 1 } fn none(&self) -> bool { - (!self.downcase && !self.upcase) + self.fields() == 0 } fn log_error(&mut self, message: &str) { @@ -40,6 +58,14 @@ impl Str { fn for_input(&mut self, field: String) { self.field = Some(field); } + + fn for_to_int(&mut self) { + self.int = true; + + if !self.is_valid() { + self.log_error("can only apply one") + } + } fn for_downcase(&mut self) { self.downcase = true; @@ -57,20 +83,27 @@ impl Str { } } - fn apply(&self, input: &str) -> String { + fn apply(&self, input: &str) -> Value { if self.downcase { - return input.to_ascii_lowercase(); + return Value::string(input.to_ascii_lowercase()); } if self.upcase { - return input.to_ascii_uppercase(); + return Value::string(input.to_ascii_uppercase()); } - input.to_string() + if self.int { + match input.trim().parse::() { + Ok(v) => return Value::int(v), + Err(_) => return Value::string(input), + } + } + + Value::string(input.to_string()) } fn usage(&self) -> &'static str { - "Usage: str field [--downcase|--upcase]" + "Usage: str field [--downcase|--upcase|--to-int]" } } @@ -81,10 +114,10 @@ impl Str { field: &Option, ) -> Result, ShellError> { match value.item { - Value::Primitive(Primitive::String(ref s)) => Ok(Tagged::from_item( - Value::string(self.apply(&s)), - value.span(), - )), + Value::Primitive(Primitive::String(s)) => Ok(Spanned { + item: self.apply(&s), + span: value.span, + }), Value::Object(_) => match field { Some(f) => { let replacement = match value.item.get_data_by_path(value.span(), f) { @@ -122,6 +155,7 @@ impl Plugin for Str { let mut named = IndexMap::new(); named.insert("downcase".to_string(), NamedType::Switch); named.insert("upcase".to_string(), NamedType::Switch); + named.insert("to-int".to_string(), NamedType::Switch); Ok(CommandConfig { name: "str".to_string(), @@ -142,6 +176,10 @@ impl Plugin for Str { self.for_upcase(); } + if call_info.args.has("to-int") { + self.for_to_int(); + } + if let Some(args) = call_info.args.positional { for arg in args { match arg { @@ -226,11 +264,11 @@ mod tests { } } - fn sample_record(value: &str) -> Spanned { + fn sample_record(key: &str, value: &str) -> Spanned { let mut record = SpannedDictBuilder::new(Span::unknown()); - record.insert_spanned( - "name", - Value::string(value.to_string()).spanned(Span::unknown()), + record.insert( + key.clone(), + Value::string(value), ); record.into_spanned_value() } @@ -257,6 +295,17 @@ mod tests { assert!(strutils.upcase); } + #[test] + fn str_accepts_to_int() { + let mut strutils = Str::new(); + + assert!(strutils + .begin_filter(CallStub::new().with_long_flag("to-int").create()) + .is_ok()); + assert!(strutils.is_valid()); + assert!(strutils.int); + } + #[test] fn str_accepts_only_one_flag() { let mut strutils = Str::new(); @@ -266,6 +315,7 @@ mod tests { CallStub::new() .with_long_flag("upcase") .with_long_flag("downcase") + .with_long_flag("to-int") .create(), ) .is_err()); @@ -291,7 +341,7 @@ mod tests { #[test] fn str_reports_error_if_no_field_given_for_object() { let mut strutils = Str::new(); - let subject = sample_record("jotandrehuda"); + let subject = sample_record("name", "jotandrehuda"); assert!(strutils.begin_filter(CallStub::new().create()).is_ok()); assert!(strutils.filter(subject).is_err()); @@ -301,14 +351,21 @@ mod tests { fn str_downcases() { let mut strutils = Str::new(); strutils.for_downcase(); - assert_eq!("andres", strutils.apply("ANDRES")); + assert_eq!(Value::string("andres"), strutils.apply("ANDRES")); } #[test] fn str_upcases() { let mut strutils = Str::new(); strutils.for_upcase(); - assert_eq!("ANDRES", strutils.apply("andres")); + assert_eq!(Value::string("ANDRES"), strutils.apply("andres")); + } + + #[test] + fn str_to_int() { + let mut strutils = Str::new(); + strutils.for_to_int(); + assert_eq!(Value::int(9999 as i64), strutils.apply("9999")); } #[test] @@ -324,7 +381,7 @@ mod tests { ) .is_ok()); - let subject = sample_record("jotandrehuda"); + let subject = sample_record("name", "jotandrehuda"); let output = strutils.filter(subject).unwrap(); match output[0].as_ref().unwrap() { @@ -352,7 +409,7 @@ mod tests { ) .is_ok()); - let subject = sample_record("JOTANDREHUDA"); + let subject = sample_record("name", "JOTANDREHUDA"); let output = strutils.filter(subject).unwrap(); match output[0].as_ref().unwrap() { @@ -366,4 +423,32 @@ mod tests { _ => {} } } + + #[test] + fn str_applies_to_int() { + let mut strutils = Str::new(); + + assert!(strutils + .begin_filter( + CallStub::new() + .with_long_flag("to-int") + .with_parameter("Nu_birthday") + .create() + ) + .is_ok()); + + let subject = sample_record("Nu_birthday", "10"); + let output = strutils.filter(subject).unwrap(); + + match output[0].as_ref().unwrap() { + ReturnSuccess::Value(Spanned { + item: Value::Object(o), + .. + }) => assert_eq!( + *o.get_data(&String::from("Nu_birthday")).borrow(), + Value::int(10) + ), + _ => {} + } + } } diff --git a/tests/filters_test.rs b/tests/filters_test.rs index 7b970c729..c77b725cb 100644 --- a/tests/filters_test.rs +++ b/tests/filters_test.rs @@ -73,7 +73,7 @@ fn str_can_only_apply_one() { "open caco3_plastics.csv | first 1 | str origin --downcase --upcase" ); - assert!(output.contains("Usage: str field [--downcase|--upcase]")); + assert!(output.contains("Usage: str field [--downcase|--upcase|--to-int]")); } #[test] @@ -98,6 +98,17 @@ fn str_upcases() { assert_eq!(output, "NUSHELL"); } +#[test] +fn str_converts_to_int() { + nu!( + output, + cwd("tests/fixtures/formats"), + "open caco3_plastics.csv | get 0 | str tariff_item --to-int | where tariff_item == 2509000000 | get tariff_item | echo $it" + ); + + assert_eq!(output, "2509000000"); +} + #[test] fn can_inc_version() { nu!( From 8ac36e0e8326e0d3c07ed655582eaf8f78825467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 11:27:36 -0500 Subject: [PATCH 05/14] str from std not needed. --- src/plugins/str.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 04bb14dc2..8a2cf37af 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -1,5 +1,4 @@ use indexmap::IndexMap; -use std::str; use nu::{ serve_plugin, CallInfo, CommandConfig, NamedType, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue, ShellError, Tagged, Value, From 193b8dbe20d5d407110e3e03651f2d82159510d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 11:29:11 -0500 Subject: [PATCH 06/14] Syntax cleaning bit. --- src/plugins/str.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 8a2cf37af..704901a25 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -24,18 +24,16 @@ impl Str { } fn fields(&self) -> u8 { - [ - self.downcase, - self.upcase, - self.int - ].iter() - .fold(0, |acc, &field| { - if field { + [self.downcase, self.upcase, self.int].iter().fold( + 0, + |acc, &field| { + if field { acc + 1 } else { acc } - }) + }, + ) } fn is_valid(&self) -> bool { @@ -57,7 +55,7 @@ impl Str { fn for_input(&mut self, field: String) { self.field = Some(field); } - + fn for_to_int(&mut self) { self.int = true; @@ -265,10 +263,7 @@ mod tests { fn sample_record(key: &str, value: &str) -> Spanned { let mut record = SpannedDictBuilder::new(Span::unknown()); - record.insert( - key.clone(), - Value::string(value), - ); + record.insert(key.clone(), Value::string(value)); record.into_spanned_value() } From 81d796472a0c99addb2360ea469272e578f44d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 11:53:31 -0500 Subject: [PATCH 07/14] Improved code readability. --- src/plugins/str.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 704901a25..f5dec64ca 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -23,7 +23,7 @@ impl Str { } } - fn fields(&self) -> u8 { + fn actions_desired(&self) -> u8 { [self.downcase, self.upcase, self.int].iter().fold( 0, |acc, &field| { @@ -41,11 +41,11 @@ impl Str { } fn at_most_one(&self) -> bool { - self.fields() == 1 + self.actions_desired() == 1 } fn none(&self) -> bool { - self.fields() == 0 + self.actions_desired() == 0 } fn log_error(&mut self, message: &str) { @@ -301,7 +301,7 @@ mod tests { } #[test] - fn str_accepts_only_one_flag() { + fn str_accepts_only_one_action() { let mut strutils = Str::new(); assert!(strutils @@ -332,15 +332,6 @@ mod tests { assert_eq!(Some("package.description".to_string()), strutils.field); } - #[test] - fn str_reports_error_if_no_field_given_for_object() { - let mut strutils = Str::new(); - let subject = sample_record("name", "jotandrehuda"); - - assert!(strutils.begin_filter(CallStub::new().create()).is_ok()); - assert!(strutils.filter(subject).is_err()); - } - #[test] fn str_downcases() { let mut strutils = Str::new(); @@ -363,7 +354,7 @@ mod tests { } #[test] - fn str_applies_upcase() { + fn str_plugin_applies_upcase() { let mut strutils = Str::new(); assert!(strutils @@ -391,7 +382,7 @@ mod tests { } #[test] - fn str_applies_downcase() { + fn str_plugin_applies_downcase() { let mut strutils = Str::new(); assert!(strutils @@ -419,7 +410,7 @@ mod tests { } #[test] - fn str_applies_to_int() { + fn str_plugin_applies_to_int() { let mut strutils = Str::new(); assert!(strutils From 832c329363dc6c0c7424ad69e8156234dfa07100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 12:17:33 -0500 Subject: [PATCH 08/14] Check plugin str flags are wired properly when configuring. --- src/plugins/str.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index f5dec64ca..86a66fa04 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -23,17 +23,14 @@ impl Str { } } + fn actions(&self) -> Vec { + vec![self.downcase, self.upcase, self.int] + } + fn actions_desired(&self) -> u8 { - [self.downcase, self.upcase, self.int].iter().fold( - 0, - |acc, &field| { - if field { - acc + 1 - } else { - acc - } - }, - ) + self.actions() + .iter() + .fold(0, |acc, &field| if field { acc + 1 } else { acc }) } fn is_valid(&self) -> bool { @@ -267,6 +264,17 @@ mod tests { record.into_spanned_value() } + #[test] + fn str_plugin_configuration_flags_wired() { + let mut strutils = Str::new(); + + let config = strutils.config().unwrap(); + + for action_flag in &["downcase", "upcase", "to-int"] { + assert!(config.named.get(*action_flag).is_some()); + } + } + #[test] fn str_accepts_downcase() { let mut strutils = Str::new(); From e7fb58ef9aa7b148ce5dcc16a151e46563964e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 12:46:49 -0500 Subject: [PATCH 09/14] Tests communicate better (separate) plugin wiring vs str features. --- src/plugins/str.rs | 72 +++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 86a66fa04..b13537089 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -266,53 +266,53 @@ mod tests { #[test] fn str_plugin_configuration_flags_wired() { - let mut strutils = Str::new(); + let mut plugin = Str::new(); - let config = strutils.config().unwrap(); + let configured = plugin.config().unwrap(); for action_flag in &["downcase", "upcase", "to-int"] { - assert!(config.named.get(*action_flag).is_some()); + assert!(configured.named.get(*action_flag).is_some()); } } #[test] - fn str_accepts_downcase() { - let mut strutils = Str::new(); + fn str_plugin_accepts_downcase() { + let mut plugin = Str::new(); - assert!(strutils + assert!(plugin .begin_filter(CallStub::new().with_long_flag("downcase").create()) .is_ok()); - assert!(strutils.is_valid()); - assert!(strutils.downcase); + assert!(plugin.is_valid()); + assert!(plugin.downcase); } #[test] - fn str_accepts_upcase() { - let mut strutils = Str::new(); + fn str_plugin_accepts_upcase() { + let mut plugin = Str::new(); - assert!(strutils + assert!(plugin .begin_filter(CallStub::new().with_long_flag("upcase").create()) .is_ok()); - assert!(strutils.is_valid()); - assert!(strutils.upcase); + assert!(plugin.is_valid()); + assert!(plugin.upcase); } #[test] - fn str_accepts_to_int() { - let mut strutils = Str::new(); + fn str_plugin_accepts_to_int() { + let mut plugin = Str::new(); - assert!(strutils + assert!(plugin .begin_filter(CallStub::new().with_long_flag("to-int").create()) .is_ok()); - assert!(strutils.is_valid()); - assert!(strutils.int); + assert!(plugin.is_valid()); + assert!(plugin.int); } #[test] - fn str_accepts_only_one_action() { - let mut strutils = Str::new(); + fn str_plugin_accepts_only_one_action() { + let mut plugin = Str::new(); - assert!(strutils + assert!(plugin .begin_filter( CallStub::new() .with_long_flag("upcase") @@ -321,15 +321,15 @@ mod tests { .create(), ) .is_err()); - assert!(!strutils.is_valid()); - assert_eq!(Some("can only apply one".to_string()), strutils.error); + assert!(!plugin.is_valid()); + assert_eq!(Some("can only apply one".to_string()), plugin.error); } #[test] - fn str_accepts_field() { - let mut strutils = Str::new(); + fn str_plugin_accepts_field() { + let mut plugin = Str::new(); - assert!(strutils + assert!(plugin .begin_filter( CallStub::new() .with_parameter("package.description") @@ -337,7 +337,7 @@ mod tests { ) .is_ok()); - assert_eq!(Some("package.description".to_string()), strutils.field); + assert_eq!(Some("package.description".to_string()), plugin.field); } #[test] @@ -363,9 +363,9 @@ mod tests { #[test] fn str_plugin_applies_upcase() { - let mut strutils = Str::new(); + let mut plugin = Str::new(); - assert!(strutils + assert!(plugin .begin_filter( CallStub::new() .with_long_flag("upcase") @@ -375,7 +375,7 @@ mod tests { .is_ok()); let subject = sample_record("name", "jotandrehuda"); - let output = strutils.filter(subject).unwrap(); + let output = plugin.filter(subject).unwrap(); match output[0].as_ref().unwrap() { ReturnSuccess::Value(Spanned { @@ -391,9 +391,9 @@ mod tests { #[test] fn str_plugin_applies_downcase() { - let mut strutils = Str::new(); + let mut plugin = Str::new(); - assert!(strutils + assert!(plugin .begin_filter( CallStub::new() .with_long_flag("downcase") @@ -403,7 +403,7 @@ mod tests { .is_ok()); let subject = sample_record("name", "JOTANDREHUDA"); - let output = strutils.filter(subject).unwrap(); + let output = plugin.filter(subject).unwrap(); match output[0].as_ref().unwrap() { ReturnSuccess::Value(Spanned { @@ -419,9 +419,9 @@ mod tests { #[test] fn str_plugin_applies_to_int() { - let mut strutils = Str::new(); + let mut plugin = Str::new(); - assert!(strutils + assert!(plugin .begin_filter( CallStub::new() .with_long_flag("to-int") @@ -431,7 +431,7 @@ mod tests { .is_ok()); let subject = sample_record("Nu_birthday", "10"); - let output = strutils.filter(subject).unwrap(); + let output = plugin.filter(subject).unwrap(); match output[0].as_ref().unwrap() { ReturnSuccess::Value(Spanned { From d105d77928ea4eb55497fac660b01ec075c49ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 30 Jul 2019 12:54:20 -0500 Subject: [PATCH 10/14] Actual (results) on left hand side and expected values on the right. "toint" makes it more clear than "int" under Str(strutils) plugin. --- src/plugins/str.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index b13537089..92b692574 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -9,7 +9,7 @@ struct Str { error: Option, downcase: bool, upcase: bool, - int: bool, + toint: bool, } impl Str { @@ -19,12 +19,12 @@ impl Str { error: None, downcase: false, upcase: false, - int: false, + toint: false, } } fn actions(&self) -> Vec { - vec![self.downcase, self.upcase, self.int] + vec![self.downcase, self.upcase, self.toint] } fn actions_desired(&self) -> u8 { @@ -54,7 +54,7 @@ impl Str { } fn for_to_int(&mut self) { - self.int = true; + self.toint = true; if !self.is_valid() { self.log_error("can only apply one") @@ -86,7 +86,7 @@ impl Str { return Value::string(input.to_ascii_uppercase()); } - if self.int { + if self.toint { match input.trim().parse::() { Ok(v) => return Value::int(v), Err(_) => return Value::string(input), @@ -305,7 +305,7 @@ mod tests { .begin_filter(CallStub::new().with_long_flag("to-int").create()) .is_ok()); assert!(plugin.is_valid()); - assert!(plugin.int); + assert!(plugin.toint); } #[test] @@ -322,7 +322,7 @@ mod tests { ) .is_err()); assert!(!plugin.is_valid()); - assert_eq!(Some("can only apply one".to_string()), plugin.error); + assert_eq!(plugin.error, Some("can only apply one".to_string())); } #[test] @@ -337,28 +337,28 @@ mod tests { ) .is_ok()); - assert_eq!(Some("package.description".to_string()), plugin.field); + assert_eq!(plugin.field, Some("package.description".to_string())); } #[test] fn str_downcases() { let mut strutils = Str::new(); strutils.for_downcase(); - assert_eq!(Value::string("andres"), strutils.apply("ANDRES")); + assert_eq!(strutils.apply("ANDRES"), Value::string("andres")); } #[test] fn str_upcases() { let mut strutils = Str::new(); strutils.for_upcase(); - assert_eq!(Value::string("ANDRES"), strutils.apply("andres")); + assert_eq!(strutils.apply("andres"), Value::string("ANDRES")); } #[test] fn str_to_int() { let mut strutils = Str::new(); strutils.for_to_int(); - assert_eq!(Value::int(9999 as i64), strutils.apply("9999")); + assert_eq!(strutils.apply("9999"), Value::int(9999 as i64)); } #[test] From 174abf68bc8a2ad6be09d33a1dba6b76aa17eab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Wed, 31 Jul 2019 17:26:04 -0500 Subject: [PATCH 11/14] Refactored. --- src/plugins/str.rs | 124 ++++++++++++++------------------------------- 1 file changed, 37 insertions(+), 87 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 92b692574..7e8ba6101 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -4,12 +4,16 @@ use nu::{ ReturnSuccess, ReturnValue, ShellError, Tagged, Value, }; +enum Action { + Downcase, + Upcase, + ToInteger, +} + struct Str { field: Option, error: Option, - downcase: bool, - upcase: bool, - toint: bool, + action: Option, } impl Str { @@ -17,83 +21,49 @@ impl Str { Str { field: None, error: None, - downcase: false, - upcase: false, - toint: false, + action: None, } } - fn actions(&self) -> Vec { - vec![self.downcase, self.upcase, self.toint] - } - - fn actions_desired(&self) -> u8 { - self.actions() - .iter() - .fold(0, |acc, &field| if field { acc + 1 } else { acc }) - } - - fn is_valid(&self) -> bool { - self.at_most_one() || self.none() - } - - fn at_most_one(&self) -> bool { - self.actions_desired() == 1 - } - - fn none(&self) -> bool { - self.actions_desired() == 0 - } - - fn log_error(&mut self, message: &str) { - self.error = Some(message.to_string()); + fn apply(&self, input: &str) -> Value { + match self.action { + Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()), + Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()), + Some(Action::ToInteger) => match input.trim().parse::() { + Ok(v) => Value::int(v), + Err(_) => Value::string(input), + } + None => Value::string(input.to_string()), + } } fn for_input(&mut self, field: String) { self.field = Some(field); } - fn for_to_int(&mut self) { - self.toint = true; - - if !self.is_valid() { - self.log_error("can only apply one") + fn update(&mut self) { + if self.action.is_some() { + self.log_error("can only apply one"); } } + fn log_error(&mut self, message: &str) { + self.error = Some(message.to_string()); + } + + fn for_to_int(&mut self) { + self.update(); + self.action = Some(Action::ToInteger); + } + fn for_downcase(&mut self) { - self.downcase = true; - - if !self.is_valid() { - self.log_error("can only apply one") - } + self.update(); + self.action = Some(Action::Downcase); } fn for_upcase(&mut self) { - self.upcase = true; - - if !self.is_valid() { - self.log_error("can only apply one") - } - } - - fn apply(&self, input: &str) -> Value { - if self.downcase { - return Value::string(input.to_ascii_lowercase()); - } - - if self.upcase { - return Value::string(input.to_ascii_uppercase()); - } - - if self.toint { - match input.trim().parse::() { - Ok(v) => return Value::int(v), - Err(_) => return Value::string(input), - } - } - - Value::string(input.to_string()) + self.update(); + self.action = Some(Action::Upcase); } fn usage(&self) -> &'static str { @@ -282,8 +252,7 @@ mod tests { assert!(plugin .begin_filter(CallStub::new().with_long_flag("downcase").create()) .is_ok()); - assert!(plugin.is_valid()); - assert!(plugin.downcase); + assert!(plugin.action.is_some()); } #[test] @@ -293,8 +262,7 @@ mod tests { assert!(plugin .begin_filter(CallStub::new().with_long_flag("upcase").create()) .is_ok()); - assert!(plugin.is_valid()); - assert!(plugin.upcase); + assert!(plugin.action.is_some()); } #[test] @@ -304,25 +272,7 @@ mod tests { assert!(plugin .begin_filter(CallStub::new().with_long_flag("to-int").create()) .is_ok()); - assert!(plugin.is_valid()); - assert!(plugin.toint); - } - - #[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") - .create(), - ) - .is_err()); - assert!(!plugin.is_valid()); - assert_eq!(plugin.error, Some("can only apply one".to_string())); + assert!(plugin.action.is_some()); } #[test] From c195c1d21db2931bfc1886776cd08ae09d575568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Wed, 31 Jul 2019 17:29:40 -0500 Subject: [PATCH 12/14] Revert back test deleted by accident. --- src/plugins/str.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 7e8ba6101..7053bd5e1 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -32,7 +32,7 @@ impl Str { Some(Action::ToInteger) => match input.trim().parse::() { Ok(v) => Value::int(v), Err(_) => Value::string(input), - } + }, None => Value::string(input.to_string()), } } @@ -290,6 +290,22 @@ mod tests { assert_eq!(plugin.field, Some("package.description".to_string())); } + #[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") + .create(), + ) + .is_err()); + assert_eq!(plugin.error, Some("can only apply one".to_string())); + } + #[test] fn str_downcases() { let mut strutils = Str::new(); From 0231e64e3713eff7e395081daab053e9e24ee12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Thu, 1 Aug 2019 00:32:36 -0500 Subject: [PATCH 13/14] Spanned as Tagged. --- src/plugins/str.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 7053bd5e1..b411b5bdb 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -78,10 +78,9 @@ impl Str { field: &Option, ) -> Result, ShellError> { match value.item { - Value::Primitive(Primitive::String(s)) => Ok(Spanned { - item: self.apply(&s), - span: value.span, - }), + Value::Primitive(Primitive::String(ref s)) => { + Ok(Tagged::from_item(self.apply(&s), value.span())) + } Value::Object(_) => match field { Some(f) => { let replacement = match value.item.get_data_by_path(value.span(), f) { @@ -188,13 +187,13 @@ mod tests { use super::Str; use indexmap::IndexMap; use nu::{ - Args, CallInfo, Plugin, ReturnSuccess, SourceMap, Span, Spanned, SpannedDictBuilder, - SpannedItem, Value, + Args, CallInfo, Plugin, ReturnSuccess, SourceMap, Span, Tagged, TaggedDictBuilder, + TaggedItem, Value, }; struct CallStub { - positionals: Vec>, - flags: IndexMap>, + positionals: Vec>, + flags: IndexMap>, } impl CallStub { @@ -208,14 +207,14 @@ mod tests { fn with_long_flag(&mut self, name: &str) -> &mut Self { self.flags.insert( name.to_string(), - Value::boolean(true).spanned(Span::unknown()), + Value::boolean(true).tagged(Span::unknown()), ); self } fn with_parameter(&mut self, name: &str) -> &mut Self { self.positionals - .push(Value::string(name.to_string()).spanned(Span::unknown())); + .push(Value::string(name.to_string()).tagged(Span::unknown())); self } @@ -228,10 +227,10 @@ mod tests { } } - fn sample_record(key: &str, value: &str) -> Spanned { - let mut record = SpannedDictBuilder::new(Span::unknown()); + fn sample_record(key: &str, value: &str) -> Tagged { + let mut record = TaggedDictBuilder::new(Span::unknown()); record.insert(key.clone(), Value::string(value)); - record.into_spanned_value() + record.into_tagged_value() } #[test] @@ -344,7 +343,7 @@ mod tests { let output = plugin.filter(subject).unwrap(); match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Spanned { + ReturnSuccess::Value(Tagged { item: Value::Object(o), .. }) => assert_eq!( @@ -372,7 +371,7 @@ mod tests { let output = plugin.filter(subject).unwrap(); match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Spanned { + ReturnSuccess::Value(Tagged { item: Value::Object(o), .. }) => assert_eq!( @@ -400,7 +399,7 @@ mod tests { let output = plugin.filter(subject).unwrap(); match output[0].as_ref().unwrap() { - ReturnSuccess::Value(Spanned { + ReturnSuccess::Value(Tagged { item: Value::Object(o), .. }) => assert_eq!( From c5568b426cc5d05351e59b2d8fe6ef11c81b13be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Thu, 1 Aug 2019 19:19:31 -0500 Subject: [PATCH 14/14] Communicate better. update -> permit. Thanks @jonathandturner --- src/plugins/str.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/plugins/str.rs b/src/plugins/str.rs index b411b5bdb..216b70195 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -41,10 +41,8 @@ impl Str { self.field = Some(field); } - fn update(&mut self) { - if self.action.is_some() { - self.log_error("can only apply one"); - } + fn permit(&mut self) -> bool { + self.action.is_none() } fn log_error(&mut self, message: &str) { @@ -52,18 +50,27 @@ impl Str { } fn for_to_int(&mut self) { - self.update(); - self.action = Some(Action::ToInteger); + if self.permit() { + self.action = Some(Action::ToInteger); + } else { + self.log_error("can only apply one"); + } } fn for_downcase(&mut self) { - self.update(); - self.action = Some(Action::Downcase); + if self.permit() { + self.action = Some(Action::Downcase); + } else { + self.log_error("can only apply one"); + } } fn for_upcase(&mut self) { - self.update(); - self.action = Some(Action::Upcase); + if self.permit() { + self.action = Some(Action::Upcase); + } else { + self.log_error("can only apply one"); + } } fn usage(&self) -> &'static str {