diff --git a/crates/nu-command/src/commands/str_/capitalize.rs b/crates/nu-command/src/commands/str_/capitalize.rs index 113baaa63e..b6024286a1 100644 --- a/crates/nu-command/src/commands/str_/capitalize.rs +++ b/crates/nu-command/src/commands/str_/capitalize.rs @@ -8,9 +8,8 @@ use nu_protocol::{ use nu_source::Tag; use nu_value_ext::ValueExt; -#[derive(Deserialize)] struct Arguments { - rest: Vec, + column_paths: Vec, } pub struct SubCommand; @@ -45,18 +44,20 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { rest }, input) = args.process()?; - - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arguments { + column_paths: params.rest_args()?, + }) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag())?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag())), diff --git a/crates/nu-command/src/commands/str_/case/mod.rs b/crates/nu-command/src/commands/str_/case/mod.rs index a5003862e2..8d572deb33 100644 --- a/crates/nu-command/src/commands/str_/case/mod.rs +++ b/crates/nu-command/src/commands/str_/case/mod.rs @@ -16,26 +16,28 @@ pub use pascal_case::SubCommand as PascalCase; pub use screaming_snake_case::SubCommand as ScreamingSnakeCase; pub use snake_case::SubCommand as SnakeCase; -#[derive(Deserialize)] struct Arguments { - rest: Vec, + column_paths: Vec, } pub fn operate(args: CommandArgs, case_operation: &'static F) -> Result where F: Fn(&str) -> String + Send + Sync + 'static, { - let (Arguments { rest }, input) = args.process()?; + let (options, input) = args.extract(|params| { + Ok(Arguments { + column_paths: params.rest_args()?, + }) + })?; - let column_paths: Vec<_> = rest; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag(), &case_operation)?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag(), &case_operation)), diff --git a/crates/nu-command/src/commands/str_/collect.rs b/crates/nu-command/src/commands/str_/collect.rs index f11f71d080..47dc014595 100644 --- a/crates/nu-command/src/commands/str_/collect.rs +++ b/crates/nu-command/src/commands/str_/collect.rs @@ -6,6 +6,16 @@ use nu_source::Tagged; pub struct SubCommand; +struct Arguments { + separator: Option>, +} + +impl Arguments { + pub fn separator(&self) -> &str { + self.separator.as_ref().map_or("", |sep| &sep.item) + } +} + impl WholeStreamCommand for SubCommand { fn name(&self) -> &str { "str collect" @@ -38,15 +48,18 @@ impl WholeStreamCommand for SubCommand { pub fn collect(args: CommandArgs) -> Result { let tag = args.call_info.name_tag.clone(); - //let (SubCommandArgs { separator }, input) = args.process()?; - let args = args.evaluate_once()?; - let separator: Option, ShellError>> = args.opt(0); - let input = args.input; - let separator = if let Some(separator) = separator { - separator?.item - } else { - "".into() - }; + + let (options, input) = args.extract(|params| { + Ok(Arguments { + separator: if let Some(arg) = params.opt(0) { + Some(arg?) + } else { + None + }, + }) + })?; + + let separator = options.separator(); let strings: Vec> = input.map(|value| value.as_string()).collect(); let strings: Result, _> = strings.into_iter().collect::>(); diff --git a/crates/nu-command/src/commands/str_/contains.rs b/crates/nu-command/src/commands/str_/contains.rs index 9116a03da5..674b8293dd 100644 --- a/crates/nu-command/src/commands/str_/contains.rs +++ b/crates/nu-command/src/commands/str_/contains.rs @@ -8,11 +8,10 @@ use nu_protocol::{ use nu_source::{Tag, Tagged}; use nu_value_ext::ValueExt; -#[derive(Deserialize)] struct Arguments { pattern: Tagged, - rest: Vec, insensitive: bool, + column_paths: Vec, } pub struct SubCommand; @@ -57,28 +56,27 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let ( - Arguments { - pattern, - rest, - insensitive, - }, - input, - ) = args.process()?; - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arc::new(Arguments { + pattern: params.req(0)?, + insensitive: params.has_flag("insensitive"), + column_paths: params.rest(1)?, + })) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { - ReturnSuccess::value(action(&v, &pattern, insensitive, v.tag())?) + if options.column_paths.is_empty() { + ReturnSuccess::value(action(&v, &options, v.tag())?) } else { let mut ret = v; - for path in &column_paths { - let pattern = pattern.clone(); + for path in &options.column_paths { + let options = options.clone(); + ret = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, &pattern, insensitive, old.tag())), + Box::new(move |old| action(old, &options, old.tag())), )?; } @@ -90,16 +88,19 @@ fn operate(args: CommandArgs) -> Result { fn action( input: &Value, - pattern: &str, - insensitive: bool, + Arguments { + ref pattern, + insensitive, + .. + }: &Arguments, tag: impl Into, ) -> Result { match &input.value { UntaggedValue::Primitive(Primitive::String(s)) => { - let contains = if insensitive { + let contains = if *insensitive { s.to_lowercase().contains(&pattern.to_lowercase()) } else { - s.contains(pattern) + s.contains(&pattern.item) }; Ok(UntaggedValue::boolean(contains).into_value(tag)) @@ -118,9 +119,9 @@ fn action( #[cfg(test)] mod tests { use super::ShellError; - use super::{action, SubCommand}; + use super::{action, Arguments, SubCommand}; use nu_protocol::UntaggedValue; - use nu_source::Tag; + use nu_source::{Tag, TaggedItem}; use nu_test_support::value::string; #[test] @@ -133,44 +134,64 @@ mod tests { #[test] fn string_contains_other_string_case_sensitive() { let word = string("Cargo.tomL"); - let pattern = ".tomL"; - let insensitive = false; - let expected = UntaggedValue::boolean(true).into_untagged_value(); - let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap(); + let options = Arguments { + pattern: String::from(".tomL").tagged_unknown(), + insensitive: false, + column_paths: vec![], + }; + + let expected = UntaggedValue::boolean(true).into_untagged_value(); + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); } #[test] fn string_does_not_contain_other_string_case_sensitive() { let word = string("Cargo.tomL"); - let pattern = "Lomt."; - let insensitive = false; - let expected = UntaggedValue::boolean(false).into_untagged_value(); - let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap(); + let options = Arguments { + pattern: String::from("Lomt.").tagged_unknown(), + insensitive: false, + column_paths: vec![], + }; + + let expected = UntaggedValue::boolean(false).into_untagged_value(); + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); } #[test] fn string_contains_other_string_case_insensitive() { let word = string("Cargo.ToMl"); - let pattern = ".TOML"; - let insensitive = true; - let expected = UntaggedValue::boolean(true).into_untagged_value(); - let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap(); + let options = Arguments { + pattern: String::from(".TOML").tagged_unknown(), + insensitive: true, + column_paths: vec![], + }; + + let expected = UntaggedValue::boolean(true).into_untagged_value(); + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); } #[test] fn string_does_not_contain_other_string_case_insensitive() { let word = string("Cargo.tOml"); - let pattern = "lomt."; - let insensitive = true; - let expected = UntaggedValue::boolean(false).into_untagged_value(); - let actual = action(&word, &pattern, insensitive, Tag::unknown()).unwrap(); + let options = Arguments { + pattern: String::from("lomt.").tagged_unknown(), + insensitive: true, + column_paths: vec![], + }; + + let expected = UntaggedValue::boolean(false).into_untagged_value(); + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); } } diff --git a/crates/nu-command/src/commands/str_/downcase.rs b/crates/nu-command/src/commands/str_/downcase.rs index f350a486ee..82df78c616 100644 --- a/crates/nu-command/src/commands/str_/downcase.rs +++ b/crates/nu-command/src/commands/str_/downcase.rs @@ -8,9 +8,8 @@ use nu_protocol::{ use nu_source::Tag; use nu_value_ext::ValueExt; -#[derive(Deserialize)] struct Arguments { - rest: Vec, + column_paths: Vec, } pub struct SubCommand; @@ -45,18 +44,20 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { rest }, input) = args.process()?; - - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arguments { + column_paths: params.rest_args()?, + }) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag())?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag())), @@ -102,9 +103,8 @@ mod tests { #[test] fn downcases() { let word = string("ANDRES"); - let expected = string("andres"); let actual = action(&word, Tag::unknown()).unwrap(); - assert_eq!(actual, expected); + assert_eq!(actual, string("andres")); } } diff --git a/crates/nu-command/src/commands/str_/ends_with.rs b/crates/nu-command/src/commands/str_/ends_with.rs index 45d4cf95a1..da7d334d34 100644 --- a/crates/nu-command/src/commands/str_/ends_with.rs +++ b/crates/nu-command/src/commands/str_/ends_with.rs @@ -8,12 +8,12 @@ use nu_protocol::{ use nu_source::{Tag, Tagged}; use nu_value_ext::ValueExt; -#[derive(Deserialize)] +pub struct SubCommand; + struct Arguments { pattern: Tagged, - rest: Vec, + column_paths: Vec, } -pub struct SubCommand; impl WholeStreamCommand for SubCommand { fn name(&self) -> &str { @@ -47,22 +47,26 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { pattern, rest }, input) = args.process()?; - - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arc::new(Arguments { + pattern: params.req(0)?, + column_paths: params.rest(1)?, + })) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { - ReturnSuccess::value(action(&v, &pattern, v.tag())?) + if options.column_paths.is_empty() { + ReturnSuccess::value(action(&v, &options.pattern, v.tag())?) } else { let mut ret = v; - for path in &column_paths { - let pattern = pattern.clone(); + for path in &options.column_paths { + let options = options.clone(); + ret = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, &pattern, old.tag())), + Box::new(move |old| action(old, &options.pattern, old.tag())), )?; } diff --git a/crates/nu-command/src/commands/str_/find_replace.rs b/crates/nu-command/src/commands/str_/find_replace.rs index 2fd9266b35..e9f4e5485d 100644 --- a/crates/nu-command/src/commands/str_/find_replace.rs +++ b/crates/nu-command/src/commands/str_/find_replace.rs @@ -9,12 +9,11 @@ use nu_source::{Tag, Tagged}; use nu_value_ext::ValueExt; use regex::Regex; -#[derive(Deserialize)] struct Arguments { + all: bool, find: Tagged, replace: Tagged, - rest: Vec, - all: bool, + column_paths: Vec, } pub struct SubCommand; @@ -59,35 +58,31 @@ impl WholeStreamCommand for SubCommand { } } -#[derive(Clone)] -struct FindReplace(String, String); +struct FindReplace<'a>(&'a str, &'a str); fn operate(args: CommandArgs) -> Result { - let ( - Arguments { - find, - replace, - rest, - all, - }, - input, - ) = args.process()?; - let options = FindReplace(find.item, replace.item); - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arc::new(Arguments { + all: params.has_flag("all"), + find: params.req(0)?, + replace: params.req(1)?, + column_paths: params.rest(2)?, + })) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { - ReturnSuccess::value(action(&v, &options, v.tag(), all)?) + if options.column_paths.is_empty() { + ReturnSuccess::value(action(&v, &options, v.tag())?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { let options = options.clone(); ret = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, &options, old.tag(), all)), + Box::new(move |old| action(old, &options, old.tag())), )?; } @@ -99,29 +94,27 @@ fn operate(args: CommandArgs) -> Result { fn action( input: &Value, - options: &FindReplace, + Arguments { + find, replace, all, .. + }: &Arguments, tag: impl Into, - all: bool, ) -> Result { match &input.value { UntaggedValue::Primitive(Primitive::String(s)) => { - let find = &options.0; - let replacement = &options.1; + let FindReplace(find, replacement) = FindReplace(&find, &replace); + let regex = Regex::new(find); - let regex = Regex::new(find.as_str()); - - let out = match regex { + Ok(match regex { Ok(re) => { - if all { - UntaggedValue::string(re.replace_all(s, replacement.as_str()).to_owned()) + if *all { + UntaggedValue::string(re.replace_all(s, replacement).to_owned()) } else { - UntaggedValue::string(re.replace(s, replacement.as_str()).to_owned()) + UntaggedValue::string(re.replace(s, replacement).to_owned()) } } Err(_) => UntaggedValue::string(s), - }; - - Ok(out.into_value(tag)) + } + .into_value(tag)) } other => { let got = format!("got {}", other.type_name()); @@ -137,8 +130,8 @@ fn action( #[cfg(test)] mod tests { use super::ShellError; - use super::{action, FindReplace, SubCommand}; - use nu_source::Tag; + use super::{action, Arguments, SubCommand}; + use nu_source::{Tag, TaggedItem}; use nu_test_support::value::string; #[test] @@ -151,11 +144,15 @@ mod tests { #[test] fn can_have_capture_groups() { let word = string("Cargo.toml"); - let expected = string("Carga.toml"); - let all = false; - let find_replace_options = FindReplace("Cargo.(.+)".to_string(), "Carga.$1".to_string()); - let actual = action(&word, &find_replace_options, Tag::unknown(), all).unwrap(); - assert_eq!(actual, expected); + let options = Arguments { + find: String::from("Cargo.(.+)").tagged_unknown(), + replace: String::from("Carga.$1").tagged_unknown(), + column_paths: vec![], + all: false, + }; + + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, string("Carga.toml")); } } diff --git a/crates/nu-command/src/commands/str_/from.rs b/crates/nu-command/src/commands/str_/from.rs index 34b167821e..6f8172345b 100644 --- a/crates/nu-command/src/commands/str_/from.rs +++ b/crates/nu-command/src/commands/str_/from.rs @@ -14,12 +14,10 @@ use std::iter; pub struct SubCommand; -#[derive(Deserialize)] struct Arguments { - rest: Vec, decimals: Option>, - #[serde(rename(deserialize = "group-digits"))] group_digits: bool, + column_paths: Vec, } impl WholeStreamCommand for SubCommand { @@ -39,15 +37,6 @@ impl WholeStreamCommand for SubCommand { "decimal digits to which to round", Some('d'), ) - /* - FIXME: this isn't currently supported because of num_format being out of date. Once it's updated, re-enable this - .switch( - "group-digits", - // TODO according to system localization - "group digits, currently by thousand with commas", - Some('g'), - ) - */ } fn usage(&self) -> &str { @@ -80,23 +69,28 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let ( - Arguments { - decimals, - group_digits, - rest: column_paths, - }, - input, - ) = args.process()?; - let digits = decimals.map(|tagged| tagged.item); + let (options, input) = args.extract(|params| { + Ok(Arguments { + decimals: if let Some(arg) = params.get_flag("decimals") { + Some(arg?) + } else { + None + }, + group_digits: false, + column_paths: params.rest_args()?, + }) + })?; + + let digits = options.decimals.as_ref().map(|tagged| tagged.item); + let group_digits = options.group_digits; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag(), digits, group_digits)?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag(), digits, group_digits)), diff --git a/crates/nu-command/src/commands/str_/index_of.rs b/crates/nu-command/src/commands/str_/index_of.rs index f98b90628e..1d959e4d1c 100644 --- a/crates/nu-command/src/commands/str_/index_of.rs +++ b/crates/nu-command/src/commands/str_/index_of.rs @@ -8,12 +8,11 @@ use nu_protocol::{ use nu_source::{Tag, Tagged}; use nu_value_ext::{as_string, ValueExt}; -#[derive(Deserialize)] struct Arguments { - pattern: Tagged, - rest: Vec, - range: Option, end: bool, + pattern: Tagged, + range: Option, + column_paths: Vec, } pub struct SubCommand; @@ -91,33 +90,32 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let ( - Arguments { - pattern, - rest, - range, - end, - }, - input, - ) = args.process()?; - let range = range.unwrap_or_else(|| { - UntaggedValue::Primitive(Primitive::String("".to_string())).into_untagged_value() - }); - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arc::new(Arguments { + pattern: params.req(0)?, + range: if let Some(arg) = params.get_flag("range") { + Some(arg?) + } else { + None + }, + end: params.has_flag("end"), + column_paths: params.rest(1)?, + })) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { - ReturnSuccess::value(action(&v, &pattern, &range, end, v.tag())?) + if options.column_paths.is_empty() { + ReturnSuccess::value(action(&v, &options, v.tag())?) } else { let mut ret = v; - for path in &column_paths { - let range = range.clone(); - let pattern = pattern.clone(); + for path in &options.column_paths { + let options = options.clone(); + ret = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, &pattern, &range, end, old.tag())), + Box::new(move |old| action(old, &options, old.tag())), )?; } @@ -129,29 +127,38 @@ fn operate(args: CommandArgs) -> Result { fn action( input: &Value, - pattern: &str, - range: &Value, - end: bool, + Arguments { + ref pattern, + range, + end, + .. + }: &Arguments, tag: impl Into, ) -> Result { + let tag = tag.into(); + + let range = match range { + Some(range) => range.clone(), + None => UntaggedValue::string("").into_value(&tag), + }; + let r = process_range(&input, &range)?; + match &input.value { UntaggedValue::Primitive(Primitive::String(s)) => { let start_index = r.0 as usize; let end_index = r.1 as usize; - if end { - if let Some(result) = s[start_index..end_index].rfind(pattern) { + if *end { + if let Some(result) = s[start_index..end_index].rfind(&**pattern) { Ok(UntaggedValue::int(result + start_index).into_value(tag)) } else { - let not_found = -1; - Ok(UntaggedValue::int(not_found).into_value(tag)) + Ok(UntaggedValue::int(-1).into_value(tag)) } - } else if let Some(result) = s[start_index..end_index].find(pattern) { + } else if let Some(result) = s[start_index..end_index].find(&**pattern) { Ok(UntaggedValue::int(result + start_index).into_value(tag)) } else { - let not_found = -1; - Ok(UntaggedValue::int(not_found).into_value(tag)) + Ok(UntaggedValue::int(-1).into_value(tag)) } } other => { @@ -159,7 +166,7 @@ fn action( Err(ShellError::labeled_error( "value is not string", got, - tag.into().span, + tag.span, )) } } @@ -234,10 +241,9 @@ fn process_range(input: &Value, range: &Value) -> Result Result<(), ShellError> { @@ -249,76 +255,90 @@ mod tests { #[test] fn returns_index_of_substring() { let word = string("Cargo.tomL"); - let pattern = ".tomL"; - let end = false; - let index_of_bounds = - UntaggedValue::Primitive(Primitive::String("".to_string())).into_untagged_value(); - let expected = UntaggedValue::Primitive(Primitive::Int(5.into())).into_untagged_value(); - let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap(); - assert_eq!(actual, expected); + let options = Arguments { + pattern: String::from(".tomL").tagged_unknown(), + range: Some(string("")), + column_paths: vec![], + end: false, + }; + + let actual = action(&word, &options, Tag::unknown()).unwrap(); + + assert_eq!(actual, int(5)); } #[test] fn index_of_does_not_exist_in_string() { let word = string("Cargo.tomL"); - let pattern = "Lm"; - let end = false; - let index_of_bounds = - UntaggedValue::Primitive(Primitive::String("".to_string())).into_untagged_value(); - let expected = UntaggedValue::Primitive(Primitive::Int((-1).into())).into_untagged_value(); - let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap(); - assert_eq!(actual, expected); + let options = Arguments { + pattern: String::from("Lm").tagged_unknown(), + range: Some(string("")), + column_paths: vec![], + end: false, + }; + + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, int(-1)); } #[test] fn returns_index_of_next_substring() { let word = string("Cargo.Cargo"); - let pattern = "Cargo"; - let end = false; - let index_of_bounds = - UntaggedValue::Primitive(Primitive::String("1,".to_string())).into_untagged_value(); - let expected = UntaggedValue::Primitive(Primitive::Int(6.into())).into_untagged_value(); - let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap(); - assert_eq!(actual, expected); + let options = Arguments { + pattern: String::from("Cargo").tagged_unknown(), + range: Some(string("1,")), + column_paths: vec![], + end: false, + }; + + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, int(6)); } + #[test] fn index_does_not_exist_due_to_end_index() { let word = string("Cargo.Banana"); - let pattern = "Banana"; - let end = false; - let index_of_bounds = - UntaggedValue::Primitive(Primitive::String(",5".to_string())).into_untagged_value(); - let expected = UntaggedValue::Primitive(Primitive::Int((-1).into())).into_untagged_value(); - let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap(); - assert_eq!(actual, expected); + let options = Arguments { + pattern: String::from("Banana").tagged_unknown(), + range: Some(string(",5")), + column_paths: vec![], + end: false, + }; + + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, int(-1)); } #[test] fn returns_index_of_nums_in_middle_due_to_index_limit_from_both_ends() { let word = string("123123123"); - let pattern = "123"; - let end = false; - let index_of_bounds = - UntaggedValue::Primitive(Primitive::String("2,6".to_string())).into_untagged_value(); - let expected = UntaggedValue::Primitive(Primitive::Int(3.into())).into_untagged_value(); - let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap(); - assert_eq!(actual, expected); + let options = Arguments { + pattern: String::from("123").tagged_unknown(), + range: Some(string("2,6")), + column_paths: vec![], + end: false, + }; + + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, int(3)); } #[test] fn index_does_not_exists_due_to_strict_bounds() { let word = string("123456"); - let pattern = "1"; - let end = false; - let index_of_bounds = - UntaggedValue::Primitive(Primitive::String("2,4".to_string())).into_untagged_value(); - let expected = UntaggedValue::Primitive(Primitive::Int((-1).into())).into_untagged_value(); - let actual = action(&word, &pattern, &index_of_bounds, end, Tag::unknown()).unwrap(); - assert_eq!(actual, expected); + let options = Arguments { + pattern: String::from("1").tagged_unknown(), + range: Some(string("2,4")), + column_paths: vec![], + end: false, + }; + + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, int(-1)); } } diff --git a/crates/nu-command/src/commands/str_/length.rs b/crates/nu-command/src/commands/str_/length.rs index fdb05b8912..d9a07009e5 100644 --- a/crates/nu-command/src/commands/str_/length.rs +++ b/crates/nu-command/src/commands/str_/length.rs @@ -8,9 +8,8 @@ use nu_protocol::{ pub struct SubCommand; -#[derive(Deserialize)] struct Arguments { - rest: Vec, + column_paths: Vec, } impl WholeStreamCommand for SubCommand { @@ -53,17 +52,20 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { rest }, input) = args.process()?; - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arguments { + column_paths: params.rest_args()?, + }) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag())?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag())), diff --git a/crates/nu-command/src/commands/str_/lpad.rs b/crates/nu-command/src/commands/str_/lpad.rs index 54610d692b..d3342eb684 100644 --- a/crates/nu-command/src/commands/str_/lpad.rs +++ b/crates/nu-command/src/commands/str_/lpad.rs @@ -8,11 +8,10 @@ use nu_protocol::{ use nu_source::{Tag, Tagged}; use nu_value_ext::ValueExt; -#[derive(Deserialize)] struct Arguments { length: Tagged, character: Tagged, - rest: Vec, + column_paths: Vec, } pub struct SubCommand; @@ -78,30 +77,27 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let ( - Arguments { - length, - character, - rest, - }, - input, - ) = args.process()?; - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arc::new(Arguments { + length: params.req_named("length")?, + character: params.req_named("character")?, + column_paths: params.rest_args()?, + })) + })?; Ok(input .map(move |v| { - let len = length.item; - let character = character.item.clone(); - if column_paths.is_empty() { - ReturnSuccess::value(action(&v, len, character, v.tag())?) + if options.column_paths.is_empty() { + ReturnSuccess::value(action(&v, &options, v.tag())?) } else { let mut ret = v; - for path in &column_paths { - let str_clone = character.clone(); + for path in &options.column_paths { + let options = options.clone(); + ret = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, len, str_clone, old.tag())), + Box::new(move |old| action(old, &options, old.tag())), )?; } @@ -113,19 +109,20 @@ fn operate(args: CommandArgs) -> Result { fn action( input: &Value, - length: usize, - character: String, + Arguments { + character, length, .. + }: &Arguments, tag: impl Into, ) -> Result { match &input.value { UntaggedValue::Primitive(Primitive::String(s)) => { - if length < s.len() { + if **length < s.len() { Ok( - UntaggedValue::string(s.chars().take(length).collect::()) + UntaggedValue::string(s.chars().take(**length).collect::()) .into_value(tag), ) } else { - let mut res = character.repeat(length - s.chars().count()); + let mut res = character.repeat(**length - s.chars().count()); res += s.as_ref(); Ok(UntaggedValue::string(res).into_value(tag)) } @@ -143,10 +140,10 @@ fn action( #[cfg(test)] mod tests { - use super::{action, SubCommand}; + use super::{action, Arguments, SubCommand}; use nu_errors::ShellError; use nu_protocol::UntaggedValue; - use nu_source::Tag; + use nu_source::{Tag, TaggedItem}; use nu_test_support::value::string; #[test] @@ -159,22 +156,32 @@ mod tests { #[test] fn left_pad_with_zeros() { let word = string("123"); - let pad_char = '0'.to_string(); - let pad_len = 10; - let expected = UntaggedValue::string("0000000123").into_untagged_value(); - let actual = action(&word, pad_len, pad_char, Tag::unknown()).unwrap(); + let options = Arguments { + character: String::from("0").tagged_unknown(), + length: 10_usize.tagged_unknown(), + column_paths: vec![], + }; + + let expected = UntaggedValue::string("0000000123").into_untagged_value(); + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); } #[test] fn left_pad_but_truncate() { let word = string("123456789"); - let pad_char = '0'.to_string(); - let pad_len = 3; - let expected = UntaggedValue::string("123").into_untagged_value(); - let actual = action(&word, pad_len, pad_char, Tag::unknown()).unwrap(); + let options = Arguments { + character: String::from("0").tagged_unknown(), + length: 3_usize.tagged_unknown(), + column_paths: vec![], + }; + + let expected = UntaggedValue::string("123").into_untagged_value(); + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); } } diff --git a/crates/nu-command/src/commands/str_/reverse.rs b/crates/nu-command/src/commands/str_/reverse.rs index 9870388557..8642ff2cb8 100644 --- a/crates/nu-command/src/commands/str_/reverse.rs +++ b/crates/nu-command/src/commands/str_/reverse.rs @@ -8,9 +8,8 @@ use nu_protocol::{ pub struct SubCommand; -#[derive(Deserialize)] struct Arguments { - rest: Vec, + column_paths: Vec, } impl WholeStreamCommand for SubCommand { @@ -43,17 +42,20 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { rest }, input) = args.process()?; - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arguments { + column_paths: params.rest_args()?, + }) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag())?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag())), diff --git a/crates/nu-command/src/commands/str_/rpad.rs b/crates/nu-command/src/commands/str_/rpad.rs index c304490413..f0848a1d8a 100644 --- a/crates/nu-command/src/commands/str_/rpad.rs +++ b/crates/nu-command/src/commands/str_/rpad.rs @@ -8,11 +8,10 @@ use nu_protocol::{ use nu_source::{Tag, Tagged}; use nu_value_ext::ValueExt; -#[derive(Deserialize)] struct Arguments { length: Tagged, character: Tagged, - rest: Vec, + column_paths: Vec, } pub struct SubCommand; @@ -78,30 +77,27 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let ( - Arguments { - length, - character, - rest, - }, - input, - ) = args.process()?; - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arc::new(Arguments { + length: params.req_named("length")?, + character: params.req_named("character")?, + column_paths: params.rest_args()?, + })) + })?; Ok(input .map(move |v| { - let len = length.item; - let character = character.item.clone(); - if column_paths.is_empty() { - ReturnSuccess::value(action(&v, len, character, v.tag())?) + if options.column_paths.is_empty() { + ReturnSuccess::value(action(&v, &options, v.tag())?) } else { let mut ret = v; - for path in &column_paths { - let str_clone = character.clone(); + for path in &options.column_paths { + let options = options.clone(); + ret = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, len, str_clone, old.tag())), + Box::new(move |old| action(old, &options, old.tag())), )?; } @@ -113,20 +109,21 @@ fn operate(args: CommandArgs) -> Result { fn action( input: &Value, - length: usize, - character: String, + Arguments { + length, character, .. + }: &Arguments, tag: impl Into, ) -> Result { match &input.value { UntaggedValue::Primitive(Primitive::String(s)) => { - if length < s.len() { + if **length < s.len() { Ok( - UntaggedValue::string(s.chars().take(length).collect::()) + UntaggedValue::string(s.chars().take(**length).collect::()) .into_value(tag), ) } else { let mut res = s.to_string(); - res += character.repeat(length - s.chars().count()).as_str(); + res += character.repeat(**length - s.chars().count()).as_str(); Ok(UntaggedValue::string(res).into_value(tag)) } } @@ -143,10 +140,10 @@ fn action( #[cfg(test)] mod tests { - use super::{action, SubCommand}; + use super::{action, Arguments, SubCommand}; use nu_errors::ShellError; use nu_protocol::UntaggedValue; - use nu_source::Tag; + use nu_source::{Tag, TaggedItem}; use nu_test_support::value::string; #[test] @@ -159,22 +156,32 @@ mod tests { #[test] fn right_pad_with_zeros() { let word = string("123"); - let pad_char = '0'.to_string(); - let pad_len = 10; - let expected = UntaggedValue::string("1230000000").into_untagged_value(); - let actual = action(&word, pad_len, pad_char, Tag::unknown()).unwrap(); + let options = Arguments { + character: String::from("0").tagged_unknown(), + length: 10_usize.tagged_unknown(), + column_paths: vec![], + }; + + let expected = UntaggedValue::string("1230000000").into_untagged_value(); + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); } #[test] fn right_pad_but_truncate() { let word = string("123456789"); - let pad_char = '0'.to_string(); - let pad_len = 3; - let expected = UntaggedValue::string("123").into_untagged_value(); - let actual = action(&word, pad_len, pad_char, Tag::unknown()).unwrap(); + let options = Arguments { + character: String::from("0").tagged_unknown(), + length: 3_usize.tagged_unknown(), + column_paths: vec![], + }; + + let expected = UntaggedValue::string("123").into_untagged_value(); + let actual = action(&word, &options, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); } } diff --git a/crates/nu-command/src/commands/str_/starts_with.rs b/crates/nu-command/src/commands/str_/starts_with.rs index 1440924096..dfd038647b 100644 --- a/crates/nu-command/src/commands/str_/starts_with.rs +++ b/crates/nu-command/src/commands/str_/starts_with.rs @@ -8,12 +8,12 @@ use nu_protocol::{ use nu_source::{Tag, Tagged}; use nu_value_ext::ValueExt; -#[derive(Deserialize)] +pub struct SubCommand; + struct Arguments { pattern: Tagged, - rest: Vec, + column_paths: Vec, } -pub struct SubCommand; impl WholeStreamCommand for SubCommand { fn name(&self) -> &str { @@ -47,22 +47,26 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { pattern, rest }, input) = args.process()?; - - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arc::new(Arguments { + pattern: params.req(0)?, + column_paths: params.rest(1)?, + })) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { - ReturnSuccess::value(action(&v, &pattern, v.tag())?) + if options.column_paths.is_empty() { + ReturnSuccess::value(action(&v, &options.pattern, v.tag())?) } else { let mut ret = v; - for path in &column_paths { - let pattern = pattern.clone(); + for path in &options.column_paths { + let options = options.clone(); + ret = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, &pattern, old.tag())), + Box::new(move |old| action(old, &options.pattern, old.tag())), )?; } diff --git a/crates/nu-command/src/commands/str_/substring.rs b/crates/nu-command/src/commands/str_/substring.rs index 3cae367277..247d7ca1de 100644 --- a/crates/nu-command/src/commands/str_/substring.rs +++ b/crates/nu-command/src/commands/str_/substring.rs @@ -11,10 +11,9 @@ use nu_value_ext::{as_string, ValueExt}; use std::cmp::Ordering; use std::convert::TryInto; -#[derive(Deserialize)] struct Arguments { range: Value, - rest: Vec, + column_paths: Vec, } pub struct SubCommand; @@ -90,24 +89,28 @@ struct SubstringText(String, String); fn operate(args: CommandArgs) -> Result { let name = args.call_info.name_tag.clone(); - let (Arguments { range, rest }, input) = args.process()?; + let (options, input) = args.extract(|params| { + Ok(Arguments { + range: params.req(0)?, + column_paths: params.rest(1)?, + }) + })?; - let column_paths: Vec<_> = rest; - let options = process_arguments(range, name)?.into(); + let indexes = Arc::new(process_arguments(&options, name)?.into()); Ok(input .map(move |v| { - if column_paths.is_empty() { - ReturnSuccess::value(action(&v, &options, v.tag())?) + if options.column_paths.is_empty() { + ReturnSuccess::value(action(&v, &indexes, v.tag())?) } else { let mut ret = v; - for path in &column_paths { - let options = options.clone(); + for path in &options.column_paths { + let indexes = indexes.clone(); ret = ret.swap_data_by_column_path( path, - Box::new(move |old| action(old, &options, old.tag())), + Box::new(move |old| action(old, &indexes, old.tag())), )?; } @@ -174,10 +177,13 @@ fn action(input: &Value, options: &Substring, tag: impl Into) -> Result) -> Result<(isize, isize), ShellError> { +fn process_arguments( + options: &Arguments, + name: impl Into, +) -> Result<(isize, isize), ShellError> { let name = name.into(); - let search = match &range.value { + let search = match &options.range.value { UntaggedValue::Table(indexes) => { if indexes.len() > 2 { Err(ShellError::labeled_error( diff --git a/crates/nu-command/src/commands/str_/to_datetime.rs b/crates/nu-command/src/commands/str_/to_datetime.rs index 355e88ee6d..d27e95ab0d 100644 --- a/crates/nu-command/src/commands/str_/to_datetime.rs +++ b/crates/nu-command/src/commands/str_/to_datetime.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use crate::utils::arguments::arguments; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; use nu_protocol::{ @@ -10,12 +11,11 @@ use nu_value_ext::ValueExt; use chrono::{DateTime, FixedOffset, Local, LocalResult, Offset, TimeZone, Utc}; -#[derive(Deserialize)] struct Arguments { timezone: Option>, offset: Option>, format: Option>, - rest: Vec, + column_paths: Vec, } // In case it may be confused with chrono::TimeZone @@ -78,7 +78,7 @@ impl WholeStreamCommand for SubCommand { Some('f'), ) .rest( - SyntaxShape::ColumnPath, + SyntaxShape::Any, "optionally convert text into datetime by column paths", ) } @@ -127,55 +127,62 @@ impl WholeStreamCommand for SubCommand { struct DatetimeFormat(String); fn operate(args: CommandArgs) -> Result { - let ( - Arguments { - timezone, - offset, - format, - rest, - }, - input, - ) = args.process()?; + let (options, input) = args.extract(|params| { + let (column_paths, _) = arguments(&mut params.rest_args()?)?; - let column_paths: Vec<_> = rest; + Ok(Arguments { + timezone: if let Some(arg) = params.get_flag("timezone") { + Some(arg?) + } else { + None + }, + offset: if let Some(arg) = params.get_flag("offset") { + Some(arg?) + } else { + None + }, + format: if let Some(arg) = params.get_flag("format") { + Some(arg?) + } else { + None + }, + column_paths, + }) + })?; // if zone-offset is specified, then zone will be neglected let zone_options = if let Some(Tagged { item: zone_offset, - tag: _tag, - }) = offset + tag, + }) = &options.offset { Some(Tagged { - item: Zone::new(zone_offset), - tag: _tag, + item: Zone::new(*zone_offset), + tag: tag.into(), }) - } else if let Some(Tagged { - item: zone, - tag: _tag, - }) = timezone - { + } else if let Some(Tagged { item: zone, tag }) = &options.timezone { Some(Tagged { - item: Zone::from_string(zone), - tag: _tag, + item: Zone::from_string(zone.clone()), + tag: tag.into(), }) } else { None }; - let format_options = if let Some(Tagged { item: fmt, .. }) = format { - Some(DatetimeFormat(fmt)) + let format_options = if let Some(Tagged { item: fmt, .. }) = &options.format { + Some(DatetimeFormat(fmt.to_string())) } else { None }; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, &zone_options, &format_options, v.tag())?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { let zone_options = zone_options.clone(); let format_options = format_options.clone(); diff --git a/crates/nu-command/src/commands/str_/to_decimal.rs b/crates/nu-command/src/commands/str_/to_decimal.rs index 10363b858e..defc41c615 100644 --- a/crates/nu-command/src/commands/str_/to_decimal.rs +++ b/crates/nu-command/src/commands/str_/to_decimal.rs @@ -11,9 +11,8 @@ use nu_value_ext::ValueExt; use bigdecimal::BigDecimal; use std::str::FromStr; -#[derive(Deserialize)] struct Arguments { - rest: Vec, + column_paths: Vec, } pub struct SubCommand; @@ -48,18 +47,20 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { rest }, input) = args.process()?; - - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arguments { + column_paths: params.rest_args()?, + }) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag())?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag())), diff --git a/crates/nu-command/src/commands/str_/to_integer.rs b/crates/nu-command/src/commands/str_/to_integer.rs index 538dd8fcbe..10f7dc5a37 100644 --- a/crates/nu-command/src/commands/str_/to_integer.rs +++ b/crates/nu-command/src/commands/str_/to_integer.rs @@ -3,17 +3,18 @@ use crate::utils::arguments::arguments; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; use nu_protocol::ShellTypeName; -use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; +use nu_protocol::{ + ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, +}; use nu_source::{Tag, Tagged}; use nu_value_ext::ValueExt; use num_bigint::BigInt; use num_traits::Num; -#[derive(Deserialize)] struct Arguments { - rest: Vec, radix: Option>, + column_paths: Vec, } pub struct SubCommand; @@ -67,19 +68,29 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { mut rest, radix }, input) = args.process()?; - let (column_paths, _) = arguments(&mut rest)?; + let (options, input) = args.extract(|params| { + let (column_paths, _) = arguments(&mut params.rest_args()?)?; - let radix = radix.map(|r| r.item).unwrap_or(10); + Ok(Arguments { + radix: if let Some(arg) = params.get_flag("radix") { + Some(arg?) + } else { + None + }, + column_paths, + }) + })?; + + let radix = options.radix.as_ref().map(|r| r.item).unwrap_or(10); Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag(), radix)?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag(), radix)), diff --git a/crates/nu-command/src/commands/str_/trim/mod.rs b/crates/nu-command/src/commands/str_/trim/mod.rs index 3d8e6c0580..cd42ae3d2c 100644 --- a/crates/nu-command/src/commands/str_/trim/mod.rs +++ b/crates/nu-command/src/commands/str_/trim/mod.rs @@ -14,25 +14,31 @@ pub use trim_both_ends::SubCommand as Trim; pub use trim_left::SubCommand as TrimLeft; pub use trim_right::SubCommand as TrimRight; -#[derive(Deserialize)] struct Arguments { - rest: Vec, - #[serde(rename(deserialize = "char"))] - char_: Option>, + character: Option>, + column_paths: Vec, } pub fn operate(args: CommandArgs, trim_operation: &'static F) -> Result where F: Fn(&str, Option) -> String + Send + Sync + 'static, { - let (Arguments { rest, char_ }, input) = args.process()?; + let (options, input) = args.extract(|params| { + Ok(Arc::new(Arguments { + character: if let Some(arg) = params.get_flag("char") { + Some(arg?) + } else { + None + }, + column_paths: params.rest_args()?, + })) + })?; - let column_paths: Vec<_> = rest; - let to_trim = char_.map(|tagged| tagged.item); + let to_trim = options.character.as_ref().map(|tagged| tagged.item); Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action( &v, v.tag(), @@ -43,7 +49,7 @@ where } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| { diff --git a/crates/nu-command/src/commands/str_/upcase.rs b/crates/nu-command/src/commands/str_/upcase.rs index 8d31a748c2..e41c8f5f64 100644 --- a/crates/nu-command/src/commands/str_/upcase.rs +++ b/crates/nu-command/src/commands/str_/upcase.rs @@ -8,9 +8,8 @@ use nu_protocol::{ use nu_source::Tag; use nu_value_ext::ValueExt; -#[derive(Deserialize)] struct Arguments { - rest: Vec, + column_paths: Vec, } pub struct SubCommand; @@ -45,18 +44,20 @@ impl WholeStreamCommand for SubCommand { } fn operate(args: CommandArgs) -> Result { - let (Arguments { rest }, input) = args.process()?; - - let column_paths: Vec<_> = rest; + let (options, input) = args.extract(|params| { + Ok(Arguments { + column_paths: params.rest_args()?, + }) + })?; Ok(input .map(move |v| { - if column_paths.is_empty() { + if options.column_paths.is_empty() { ReturnSuccess::value(action(&v, v.tag())?) } else { let mut ret = v; - for path in &column_paths { + for path in &options.column_paths { ret = ret.swap_data_by_column_path( path, Box::new(move |old| action(old, old.tag())), @@ -102,9 +103,8 @@ mod tests { #[test] fn upcases() { let word = string("andres"); - let expected = string("ANDRES"); let actual = action(&word, Tag::unknown()).unwrap(); - assert_eq!(actual, expected); + assert_eq!(actual, string("ANDRES")); } } diff --git a/crates/nu-engine/src/command_args.rs b/crates/nu-engine/src/command_args.rs index 88b546482a..cd79eb54b5 100644 --- a/crates/nu-engine/src/command_args.rs +++ b/crates/nu-engine/src/command_args.rs @@ -78,23 +78,27 @@ impl std::fmt::Debug for CommandArgs { impl CommandArgs { pub fn evaluate_once(self) -> Result { - let ctx = EvaluationContext::from_args(&self); - let host = self.host.clone(); - let ctrl_c = self.ctrl_c.clone(); - let configs = self.configs.clone(); - let shell_manager = self.shell_manager.clone(); + let ctx = EvaluationContext::new( + self.scope, + self.host, + self.current_errors, + self.ctrl_c, + self.configs, + self.shell_manager, + Arc::new(Mutex::new(std::collections::HashMap::new())), + ); + let input = self.input; let call_info = self.call_info.evaluate(&ctx)?; - let scope = self.scope.clone(); Ok(EvaluatedWholeStreamCommandArgs::new( - host, - ctrl_c, - configs, - shell_manager, + ctx.host, + ctx.ctrl_c, + ctx.configs, + ctx.shell_manager, call_info, input, - scope, + ctx.scope, )) } @@ -112,6 +116,15 @@ impl CommandArgs { (self.input, new_context) } + pub fn extract( + self, + f: impl FnOnce(&EvaluatedCommandArgs) -> Result, + ) -> Result<(T, InputStream), ShellError> { + let evaluated_args = self.evaluate_once()?; + + Ok((f(&evaluated_args.args)?, evaluated_args.input)) + } + pub fn process<'de, T: Deserialize<'de>>(self) -> Result<(T, InputStream), ShellError> { let args = self.evaluate_once()?; let call_info = args.call_info.clone(); @@ -205,6 +218,13 @@ impl EvaluatedCommandArgs { .map(|x| FromValue::from_value(x)) } + pub fn req_named(&self, name: &str) -> Result { + self.call_info + .args + .expect_get(name) + .and_then(|x| FromValue::from_value(x)) + } + pub fn has_flag(&self, name: &str) -> bool { self.call_info.args.has(name) } @@ -229,6 +249,10 @@ impl EvaluatedCommandArgs { } } + pub fn rest_args(&self) -> Result, ShellError> { + self.rest(0) + } + pub fn rest(&self, starting_pos: usize) -> Result, ShellError> { let mut output = vec![]; diff --git a/crates/nu-engine/src/env/host.rs b/crates/nu-engine/src/env/host.rs index 9e6fe84e1c..d914453830 100644 --- a/crates/nu-engine/src/env/host.rs +++ b/crates/nu-engine/src/env/host.rs @@ -22,6 +22,12 @@ pub trait Host: Debug + Send { fn is_external_cmd(&self, cmd_name: &str) -> bool; } +impl Default for Box { + fn default() -> Self { + Box::new(BasicHost) + } +} + impl Host for Box { fn stdout(&mut self, out: &str) { (**self).stdout(out) diff --git a/crates/nu-engine/src/evaluation_context.rs b/crates/nu-engine/src/evaluation_context.rs index 7a72ccfcb0..e4c524aacf 100644 --- a/crates/nu-engine/src/evaluation_context.rs +++ b/crates/nu-engine/src/evaluation_context.rs @@ -14,7 +14,7 @@ use parking_lot::Mutex; use std::sync::atomic::AtomicBool; use std::{path::Path, sync::Arc}; -#[derive(Clone)] +#[derive(Clone, Default)] pub struct EvaluationContext { pub scope: Scope, pub host: Arc>>, @@ -28,6 +28,26 @@ pub struct EvaluationContext { } impl EvaluationContext { + pub fn new( + scope: Scope, + host: Arc>>, + current_errors: Arc>>, + ctrl_c: Arc, + configs: Arc>, + shell_manager: ShellManager, + windows_drives_previous_cwd: Arc>>, + ) -> Self { + Self { + scope, + host, + current_errors, + ctrl_c, + configs, + shell_manager, + windows_drives_previous_cwd, + } + } + pub fn from_args(args: &CommandArgs) -> EvaluationContext { EvaluationContext { scope: args.scope.clone(), diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs index a0de716dd3..f3a0a7f531 100644 --- a/crates/nu-engine/src/from_value.rs +++ b/crates/nu-engine/src/from_value.rs @@ -40,6 +40,12 @@ impl FromValue for num_bigint::BigInt { } } } +impl FromValue for Tagged { + fn from_value(v: &Value) -> Result { + let tag = v.tag.clone(); + v.as_u64().map(|s| s.tagged(tag)) + } +} impl FromValue for u64 { fn from_value(v: &Value) -> Result { @@ -47,6 +53,34 @@ impl FromValue for u64 { } } +impl FromValue for Tagged { + fn from_value(v: &Value) -> Result { + let tag = v.tag.clone(); + v.as_u32().map(|s| s.tagged(tag)) + } +} + +impl FromValue for Tagged { + fn from_value(v: &Value) -> Result { + let tag = v.tag.clone(); + v.as_i16().map(|s| s.tagged(tag)) + } +} + +impl FromValue for Tagged { + fn from_value(v: &Value) -> Result { + let tag = v.tag.clone(); + v.as_usize().map(|s| s.tagged(tag)) + } +} + +impl FromValue for Tagged { + fn from_value(v: &Value) -> Result { + let tag = v.tag.clone(); + v.as_char().map(|c| c.tagged(tag)) + } +} + impl FromValue for usize { fn from_value(v: &Value) -> Result { v.as_usize() diff --git a/crates/nu-engine/src/shell/shell_manager.rs b/crates/nu-engine/src/shell/shell_manager.rs index a408a377df..e6f68dc955 100644 --- a/crates/nu-engine/src/shell/shell_manager.rs +++ b/crates/nu-engine/src/shell/shell_manager.rs @@ -12,7 +12,7 @@ use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; -#[derive(Clone, Debug)] +#[derive(Clone, Default, Debug)] pub struct ShellManager { pub current_shell: Arc, pub shells: Arc>>>, diff --git a/crates/nu-protocol/src/call_info.rs b/crates/nu-protocol/src/call_info.rs index e2bbbd2515..81e2831a66 100644 --- a/crates/nu-protocol/src/call_info.rs +++ b/crates/nu-protocol/src/call_info.rs @@ -80,6 +80,16 @@ impl EvaluatedArgs { } } + /// Gets the corresponding Value for the named argument given, error if not possible + pub fn expect_get(&self, name: &str) -> Result<&Value, ShellError> { + match &self.named { + None => Err(ShellError::unimplemented("Better error: expect_get")), + Some(named) => named + .get(name) + .ok_or_else(|| ShellError::unimplemented("Better error: expect_get")), + } + } + /// Iterates over the positional arguments pub fn positional_iter(&self) -> PositionalIter<'_> { match &self.positional { diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 9d8f7da7f7..8e6d494aaa 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -429,6 +429,14 @@ impl Value { matches!(&self.value, UntaggedValue::Primitive(_)) } + /// View the Value as char, if possible + pub fn as_char(&self) -> Result { + match &self.value { + UntaggedValue::Primitive(primitive) => primitive.as_char(self.tag.span), + _ => Err(ShellError::type_error("char", self.spanned_type_name())), + } + } + /// View the Value as unsigned size, if possible pub fn as_usize(&self) -> Result { match &self.value { @@ -453,7 +461,15 @@ impl Value { } } - /// View the Value as signed 64-bit, if possible + /// View the Value as unsigned 32-bit, if possible + pub fn as_u32(&self) -> Result { + match &self.value { + UntaggedValue::Primitive(primitive) => primitive.as_u32(self.tag.span), + _ => Err(ShellError::type_error("integer", self.spanned_type_name())), + } + } + + /// View the Value as signed 32-bit, if possible pub fn as_i32(&self) -> Result { match &self.value { UntaggedValue::Primitive(primitive) => primitive.as_i32(self.tag.span), @@ -461,6 +477,14 @@ impl Value { } } + /// View the Value as signed 16-bit, if possible + pub fn as_i16(&self) -> Result { + match &self.value { + UntaggedValue::Primitive(primitive) => primitive.as_i16(self.tag.span), + _ => Err(ShellError::type_error("integer", self.spanned_type_name())), + } + } + /// View the Value as boolean, if possible pub fn as_bool(&self) -> Result { match &self.value { diff --git a/crates/nu-protocol/src/value/primitive.rs b/crates/nu-protocol/src/value/primitive.rs index c89107c39f..12832d16c4 100644 --- a/crates/nu-protocol/src/value/primitive.rs +++ b/crates/nu-protocol/src/value/primitive.rs @@ -60,6 +60,27 @@ pub enum Primitive { } impl Primitive { + /// Converts a primitive value to a char, if possible. Uses a span to build an error if the conversion isn't possible. + pub fn as_char(&self, span: Span) -> Result { + match self { + Primitive::String(s) => { + if s.len() > 1 { + return Err(ShellError::type_error( + "char", + self.type_name().spanned(span), + )); + } + s.chars() + .next() + .ok_or_else(|| ShellError::type_error("char", self.type_name().spanned(span))) + } + other => Err(ShellError::type_error( + "char", + other.type_name().spanned(span), + )), + } + } + /// Converts a primitive value to a u64, if possible. Uses a span to build an error if the conversion isn't possible. pub fn as_usize(&self, span: Span) -> Result { match self { @@ -108,6 +129,7 @@ impl Primitive { } } + /// Converts a primitive value to a i64, if possible. Uses a span to build an error if the conversion isn't possible. pub fn as_i64(&self, span: Span) -> Result { match self { Primitive::Int(int) => int.to_i64().ok_or_else(|| { @@ -131,6 +153,30 @@ impl Primitive { } } + /// Converts a primitive value to a u32, if possible. Uses a span to build an error if the conversion isn't possible. + pub fn as_u32(&self, span: Span) -> Result { + match self { + Primitive::Int(int) => int.to_u32().ok_or_else(|| { + ShellError::range_error( + ExpectedRange::U32, + &format!("{}", int).spanned(span), + "converting an integer into a unsigned 32-bit integer", + ) + }), + Primitive::Decimal(decimal) => decimal.to_u32().ok_or_else(|| { + ShellError::range_error( + ExpectedRange::U32, + &format!("{}", decimal).spanned(span), + "converting a decimal into a unsigned 32-bit integer", + ) + }), + other => Err(ShellError::type_error( + "number", + other.type_name().spanned(span), + )), + } + } + pub fn as_i32(&self, span: Span) -> Result { match self { Primitive::Int(int) => int.to_i32().ok_or_else(|| { @@ -154,6 +200,29 @@ impl Primitive { } } + pub fn as_i16(&self, span: Span) -> Result { + match self { + Primitive::Int(int) => int.to_i16().ok_or_else(|| { + ShellError::range_error( + ExpectedRange::I16, + &format!("{}", int).spanned(span), + "converting an integer into a signed 16-bit integer", + ) + }), + Primitive::Decimal(decimal) => decimal.to_i16().ok_or_else(|| { + ShellError::range_error( + ExpectedRange::I16, + &format!("{}", decimal).spanned(span), + "converting a decimal into a signed 16-bit integer", + ) + }), + other => Err(ShellError::type_error( + "number", + other.type_name().spanned(span), + )), + } + } + // FIXME: This is a bad name, but no other way to differentiate with our own Duration. pub fn into_chrono_duration(self, span: Span) -> Result { match self {