diff --git a/crates/nu-command/src/bytes/length.rs b/crates/nu-command/src/bytes/length.rs index 4f78902a5..13f3f70af 100644 --- a/crates/nu-command/src/bytes/length.rs +++ b/crates/nu-command/src/bytes/length.rs @@ -1,4 +1,4 @@ -use crate::input_handler::{operate, CmdArgument}; +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::ast::CellPath; @@ -9,16 +9,6 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap #[derive(Clone)] pub struct BytesLen; -struct Arguments { - cell_paths: Option>, -} - -impl CmdArgument for Arguments { - fn take_cell_paths(&mut self) -> Option> { - self.cell_paths.take() - } -} - impl Command for BytesLen { fn name(&self) -> &str { "bytes length" @@ -50,8 +40,7 @@ impl Command for BytesLen { input: PipelineData, ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 1)?; - let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); - let arg = Arguments { cell_paths }; + let arg = CellPathOnlyArgs::from(cell_paths); operate(length, arg, input, call.head, engine_state.ctrlc.clone()) } @@ -74,7 +63,7 @@ impl Command for BytesLen { } } -fn length(val: &Value, _args: &Arguments, span: Span) -> Value { +fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { match val { Value::Binary { val, diff --git a/crates/nu-command/src/bytes/reverse.rs b/crates/nu-command/src/bytes/reverse.rs index 8cbb73916..1ddd583f3 100644 --- a/crates/nu-command/src/bytes/reverse.rs +++ b/crates/nu-command/src/bytes/reverse.rs @@ -1,4 +1,4 @@ -use crate::input_handler::{operate, CmdArgument}; +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::ast::CellPath; @@ -6,16 +6,6 @@ use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::Category; use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; -struct Arguments { - cell_paths: Option>, -} - -impl CmdArgument for Arguments { - fn take_cell_paths(&mut self) -> Option> { - self.cell_paths.take() - } -} - #[derive(Clone)] pub struct BytesReverse; @@ -51,8 +41,7 @@ impl Command for BytesReverse { input: PipelineData, ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; - let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); - let arg = Arguments { cell_paths }; + let arg = CellPathOnlyArgs::from(cell_paths); operate(reverse, arg, input, call.head, engine_state.ctrlc.clone()) } @@ -78,7 +67,7 @@ impl Command for BytesReverse { } } -fn reverse(val: &Value, _args: &Arguments, span: Span) -> Value { +fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { match val { Value::Binary { val, diff --git a/crates/nu-command/src/conversions/fmt.rs b/crates/nu-command/src/conversions/fmt.rs index e6a8c988b..f209b0a1d 100644 --- a/crates/nu-command/src/conversions/fmt.rs +++ b/crates/nu-command/src/conversions/fmt.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, @@ -96,31 +97,12 @@ fn fmt( call: &Call, input: PipelineData, ) -> Result { - let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; - - input.map( - move |v| { - if column_paths.is_empty() { - action(&v, head) - } else { - let mut ret = v; - for path in &column_paths { - let r = - ret.update_cell_path(&path.members, Box::new(move |old| action(old, head))); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret - } - }, - engine_state.ctrlc.clone(), - ) + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let args = CellPathOnlyArgs::from(cell_paths); + operate(action, args, input, call.head, engine_state.ctrlc.clone()) } -pub fn action(input: &Value, span: Span) -> Value { +fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { match input { Value::Int { val, .. } => fmt_it(*val, span), Value::Filesize { val, .. } => fmt_it(*val, span), diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index d2fa20817..06401c112 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, @@ -100,7 +101,7 @@ fn into_binary( input: PipelineData, ) -> Result { let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; match input { PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary { @@ -120,27 +121,10 @@ fn into_binary( } .into_pipeline_data()) } - _ => input.map( - move |v| { - if column_paths.is_empty() { - action(&v, head) - } else { - let mut ret = v; - for path in &column_paths { - let r = ret.update_cell_path( - &path.members, - Box::new(move |old| action(old, head)), - ); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret - } - }, - engine_state.ctrlc.clone(), - ), + _ => { + let arg = CellPathOnlyArgs::from(cell_paths); + operate(action, arg, input, call.head, engine_state.ctrlc.clone()) + } } } @@ -160,7 +144,7 @@ fn float_to_endian(n: f64) -> Vec { } } -pub fn action(input: &Value, span: Span) -> Value { +pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { match input { Value::Binary { .. } => input.clone(), Value::Int { val, .. } => Value::Binary { diff --git a/crates/nu-command/src/conversions/into/bool.rs b/crates/nu-command/src/conversions/into/bool.rs index 00c2d867f..97a37d2f8 100644 --- a/crates/nu-command/src/conversions/into/bool.rs +++ b/crates/nu-command/src/conversions/into/bool.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, @@ -108,28 +109,9 @@ fn into_bool( call: &Call, input: PipelineData, ) -> Result { - let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; - - input.map( - move |v| { - if column_paths.is_empty() { - action(&v, head) - } else { - let mut ret = v; - for path in &column_paths { - let r = - ret.update_cell_path(&path.members, Box::new(move |old| action(old, head))); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret - } - }, - engine_state.ctrlc.clone(), - ) + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let args = CellPathOnlyArgs::from(cell_paths); + operate(action, args, input, call.head, engine_state.ctrlc.clone()) } fn string_to_boolean(s: &str, span: Span) -> Result { @@ -154,7 +136,7 @@ fn string_to_boolean(s: &str, span: Span) -> Result { } } -fn action(input: &Value, span: Span) -> Value { +fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { match input { Value::Bool { .. } => input.clone(), Value::Int { val, .. } => Value::Bool { diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index 581282187..892755859 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate, CmdArgument}; use crate::{generate_strftime_list, parse_date_from_string}; use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc}; use nu_engine::CallExt; @@ -5,14 +6,20 @@ use nu_protocol::ast::Call; use nu_protocol::ast::CellPath; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Value, }; struct Arguments { - timezone: Option>, - offset: Option>, - format: Option, - column_paths: Vec, + zone_options: Option>, + format_options: Option, + cell_paths: Option>, +} + +impl CmdArgument for Arguments { + fn take_cell_paths(&mut self) -> Option> { + self.cell_paths.take() + } } // In case it may be confused with chrono::TimeZone @@ -95,7 +102,36 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + if call.has_flag("list") { + Ok(generate_strftime_list(call.head, true).into_pipeline_data()) + } else { + let cell_paths = call.rest(engine_state, stack, 0)?; + let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); + + // if zone-offset is specified, then zone will be neglected + let timezone = call.get_flag::>(engine_state, stack, "timezone")?; + let zone_options = + match &call.get_flag::>(engine_state, stack, "offset")? { + Some(zone_offset) => Some(Spanned { + item: Zone::new(zone_offset.item), + span: zone_offset.span, + }), + None => timezone.as_ref().map(|zone| Spanned { + item: Zone::from_string(zone.item.clone()), + span: zone.span, + }), + }; + let format_options = call + .get_flag::(engine_state, stack, "format")? + .as_ref() + .map(|fmt| DatetimeFormat(fmt.to_string())); + let args = Arguments { + format_options, + zone_options, + cell_paths, + }; + operate(action, args, input, call.head, engine_state.ctrlc.clone()) + } } fn usage(&self) -> &str { @@ -162,72 +198,9 @@ impl Command for SubCommand { #[derive(Clone)] struct DatetimeFormat(String); -fn operate( - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, -) -> Result { - let head = call.head; - - let options = Arguments { - timezone: call.get_flag(engine_state, stack, "timezone")?, - offset: call.get_flag(engine_state, stack, "offset")?, - format: call.get_flag(engine_state, stack, "format")?, - column_paths: call.rest(engine_state, stack, 0)?, - }; - - // if zone-offset is specified, then zone will be neglected - let zone_options = match &options.offset { - Some(zone_offset) => Some(Spanned { - item: Zone::new(zone_offset.item), - span: zone_offset.span, - }), - None => options.timezone.as_ref().map(|zone| Spanned { - item: Zone::from_string(zone.item.clone()), - span: zone.span, - }), - }; - - let list_flag = call.has_flag("list"); - - let format_options = options - .format - .as_ref() - .map(|fmt| DatetimeFormat(fmt.to_string())); - - input.map( - move |v| { - if options.column_paths.is_empty() && !list_flag { - action(&v, &zone_options, &format_options, head) - } else if list_flag { - generate_strftime_list(head, true) - } else { - let mut ret = v; - for path in &options.column_paths { - let zone_options = zone_options.clone(); - let format_options = format_options.clone(); - let r = ret.update_cell_path( - &path.members, - Box::new(move |old| action(old, &zone_options, &format_options, head)), - ); - if let Err(error) = r { - return Value::Error { error }; - } - } - ret - } - }, - engine_state.ctrlc.clone(), - ) -} - -fn action( - input: &Value, - timezone: &Option>, - dateformat: &Option, - head: Span, -) -> Value { +fn action(input: &Value, args: &Arguments, head: Span) -> Value { + let timezone = &args.zone_options; + let dateformat = &args.format_options; // Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?) let timestamp = match input { Value::Int { val, .. } => Ok(*val), @@ -359,7 +332,12 @@ mod tests { fn takes_a_date_format() { let date_str = Value::test_string("16.11.1984 8:00 am +0000"); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string())); - let actual = action(&date_str, &None, &fmt_options, Span::test_data()); + let args = Arguments { + zone_options: None, + format_options: fmt_options, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); let expected = Value::Date { val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z") .unwrap(), @@ -371,7 +349,12 @@ mod tests { #[test] fn takes_iso8601_date_format() { let date_str = Value::test_string("2020-08-04T16:39:18+00:00"); - let actual = action(&date_str, &None, &None, Span::test_data()); + let args = Arguments { + zone_options: None, + format_options: None, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); let expected = Value::Date { val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z") .unwrap(), @@ -387,7 +370,12 @@ mod tests { item: Zone::East(8), span: Span::test_data(), }); - let actual = action(&date_str, &timezone_option, &None, Span::test_data()); + let args = Arguments { + zone_options: timezone_option, + format_options: None, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); let expected = Value::Date { val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z") .unwrap(), @@ -404,7 +392,12 @@ mod tests { item: Zone::East(8), span: Span::test_data(), }); - let actual = action(&date_int, &timezone_option, &None, Span::test_data()); + let args = Arguments { + zone_options: timezone_option, + format_options: None, + cell_paths: None, + }; + let actual = action(&date_int, &args, Span::test_data()); let expected = Value::Date { val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z") .unwrap(), @@ -421,7 +414,12 @@ mod tests { item: Zone::Local, span: Span::test_data(), }); - let actual = action(&date_str, &timezone_option, &None, Span::test_data()); + let args = Arguments { + zone_options: timezone_option, + format_options: None, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); let expected = Value::Date { val: Local.timestamp(1614434140, 0).into(), span: Span::test_data(), @@ -433,8 +431,12 @@ mod tests { #[test] fn takes_timestamp_without_timezone() { let date_str = Value::test_string("1614434140"); - let timezone_option = None; - let actual = action(&date_str, &timezone_option, &None, Span::test_data()); + let args = Arguments { + zone_options: None, + format_options: None, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); let expected = Value::Date { val: Utc.timestamp(1614434140, 0).into(), @@ -451,7 +453,12 @@ mod tests { item: Zone::Utc, span: Span::test_data(), }); - let actual = action(&date_str, &timezone_option, &None, Span::test_data()); + let args = Arguments { + zone_options: timezone_option, + format_options: None, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); assert_eq!(actual.get_type(), Error); } @@ -460,7 +467,12 @@ mod tests { fn communicates_parsing_error_given_an_invalid_datetimelike_string() { let date_str = Value::test_string("16.11.1984 8:00 am Oops0000"); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string())); - let actual = action(&date_str, &None, &fmt_options, Span::test_data()); + let args = Arguments { + zone_options: None, + format_options: fmt_options, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); assert_eq!(actual.get_type(), Error); } diff --git a/crates/nu-command/src/conversions/into/decimal.rs b/crates/nu-command/src/conversions/into/decimal.rs index c62821d13..a22179ad5 100644 --- a/crates/nu-command/src/conversions/into/decimal.rs +++ b/crates/nu-command/src/conversions/into/decimal.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, @@ -36,7 +37,9 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let args = CellPathOnlyArgs::from(cell_paths); + operate(action, args, input, call.head, engine_state.ctrlc.clone()) } fn examples(&self) -> Vec { @@ -72,37 +75,7 @@ impl Command for SubCommand { } } -fn operate( - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, -) -> Result { - let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; - - input.map( - move |v| { - if column_paths.is_empty() { - action(&v, head) - } else { - let mut ret = v; - for path in &column_paths { - let r = - ret.update_cell_path(&path.members, Box::new(move |old| action(old, head))); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret - } - }, - engine_state.ctrlc.clone(), - ) -} - -fn action(input: &Value, head: Span) -> Value { +fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value { match input { Value::String { val: s, span } => { let other = s.trim(); @@ -163,7 +136,7 @@ mod tests { let word = Value::test_string("3.1415"); let expected = Value::test_float(3.1415); - let actual = action(&word, Span::test_data()); + let actual = action(&word, &CellPathOnlyArgs::from(vec![]), Span::test_data()); assert_eq!(actual, expected); } @@ -171,7 +144,11 @@ mod tests { fn communicates_parsing_error_given_an_invalid_decimallike_string() { let decimal_str = Value::test_string("11.6anra"); - let actual = action(&decimal_str, Span::test_data()); + let actual = action( + &decimal_str, + &CellPathOnlyArgs::from(vec![]), + Span::test_data(), + ); assert_eq!(actual.get_type(), Error); } @@ -180,7 +157,11 @@ mod tests { fn int_to_decimal() { let decimal_str = Value::test_int(10); let expected = Value::test_float(10.0); - let actual = action(&decimal_str, Span::test_data()); + let actual = action( + &decimal_str, + &CellPathOnlyArgs::from(vec![]), + Span::test_data(), + ); assert_eq!(actual, expected); } diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs index b000519da..120e2eb1d 100644 --- a/crates/nu-command/src/conversions/into/filesize.rs +++ b/crates/nu-command/src/conversions/into/filesize.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, @@ -38,7 +39,9 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - into_filesize(engine_state, stack, call, input) + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let args = CellPathOnlyArgs::from(cell_paths); + operate(action, args, input, call.head, engine_state.ctrlc.clone()) } fn examples(&self) -> Vec { @@ -84,37 +87,7 @@ impl Command for SubCommand { } } -fn into_filesize( - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, -) -> Result { - let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; - - input.map( - move |v| { - if column_paths.is_empty() { - action(&v, head) - } else { - let mut ret = v; - for path in &column_paths { - let r = - ret.update_cell_path(&path.members, Box::new(move |old| action(old, head))); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret - } - }, - engine_state.ctrlc.clone(), - ) -} - -pub fn action(input: &Value, span: Span) -> Value { +pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { if let Ok(value_span) = input.span() { match input { Value::Filesize { .. } => input.clone(), diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs index f0e1ed570..77b3f7a0e 100644 --- a/crates/nu-command/src/conversions/into/int.rs +++ b/crates/nu-command/src/conversions/into/int.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate, CmdArgument}; use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, @@ -6,11 +7,17 @@ use nu_protocol::{ }; struct Arguments { - radix: Option, - column_paths: Vec, + radix: u32, + cell_paths: Option>, little_endian: bool, } +impl CmdArgument for Arguments { + fn take_cell_paths(&mut self) -> Option> { + self.cell_paths.take() + } +} + #[derive(Clone)] pub struct SubCommand; @@ -46,7 +53,29 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - into_int(engine_state, stack, call, input) + let cell_paths = call.rest(engine_state, stack, 0)?; + let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); + + let radix = call.get_flag::(engine_state, stack, "radix")?; + let radix: u32 = match radix { + Some(Value::Int { val, span }) => { + if !(2..=36).contains(&val) { + return Err(ShellError::UnsupportedInput( + "Radix must lie in the range [2, 36]".to_string(), + span, + )); + } + val as u32 + } + Some(_) => 10, + None => 10, + }; + let args = Arguments { + radix, + little_endian: call.has_flag("little-endian"), + cell_paths, + }; + operate(action, args, input, call.head, engine_state.ctrlc.clone()) } fn examples(&self) -> Vec { @@ -121,59 +150,9 @@ impl Command for SubCommand { } } -fn into_int( - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, -) -> Result { - let head = call.head; - - let options = Arguments { - radix: call.get_flag(engine_state, stack, "radix")?, - little_endian: call.has_flag("little-endian"), - column_paths: call.rest(engine_state, stack, 0)?, - }; - - let radix: u32 = match options.radix { - Some(Value::Int { val, .. }) => val as u32, - Some(_) => 10, - None => 10, - }; - - if let Some(val) = &options.radix { - if !(2..=36).contains(&radix) { - return Err(ShellError::UnsupportedInput( - "Radix must lie in the range [2, 36]".to_string(), - val.span()?, - )); - } - } - - input.map( - move |v| { - if options.column_paths.is_empty() { - action(&v, head, radix, options.little_endian) - } else { - let mut ret = v; - for path in &options.column_paths { - let r = ret.update_cell_path( - &path.members, - Box::new(move |old| action(old, head, radix, options.little_endian)), - ); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret - } - }, - engine_state.ctrlc.clone(), - ) -} - -pub fn action(input: &Value, span: Span, radix: u32, little_endian: bool) -> Value { +fn action(input: &Value, args: &Arguments, span: Span) -> Value { + let radix = args.radix; + let little_endian = args.little_endian; match input { Value::Int { val: _, .. } => { if radix == 10 { @@ -401,21 +380,45 @@ mod test { let word = Value::test_string("10"); let expected = Value::test_int(10); - let actual = action(&word, Span::test_data(), 10, false); + let actual = action( + &word, + &Arguments { + radix: 10, + cell_paths: None, + little_endian: false, + }, + Span::test_data(), + ); assert_eq!(actual, expected); } #[test] fn turns_binary_to_integer() { let s = Value::test_string("0b101"); - let actual = action(&s, Span::test_data(), 10, false); + let actual = action( + &s, + &Arguments { + radix: 10, + cell_paths: None, + little_endian: false, + }, + Span::test_data(), + ); assert_eq!(actual, Value::test_int(5)); } #[test] fn turns_hex_to_integer() { let s = Value::test_string("0xFF"); - let actual = action(&s, Span::test_data(), 16, false); + let actual = action( + &s, + &Arguments { + radix: 16, + cell_paths: None, + little_endian: false, + }, + Span::test_data(), + ); assert_eq!(actual, Value::test_int(255)); } @@ -423,7 +426,15 @@ mod test { fn communicates_parsing_error_given_an_invalid_integerlike_string() { let integer_str = Value::test_string("36anra"); - let actual = action(&integer_str, Span::test_data(), 10, false); + let actual = action( + &integer_str, + &Arguments { + radix: 10, + cell_paths: None, + little_endian: false, + }, + Span::test_data(), + ); assert_eq!(actual.get_type(), Error) } diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 625510295..7892205cc 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate, CmdArgument}; use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, @@ -8,6 +9,19 @@ use nu_protocol::{ use nu_utils::get_system_locale; use num_format::ToFormattedString; +struct Arguments { + decimals_value: Option, + decimals: bool, + cell_paths: Option>, + config: Config, +} + +impl CmdArgument for Arguments { + fn take_cell_paths(&mut self) -> Option> { + self.cell_paths.take() + } +} + #[derive(Clone)] pub struct SubCommand; @@ -149,9 +163,6 @@ fn string_helper( let decimals = call.has_flag("decimals"); let head = call.head; let decimals_value: Option = call.get_flag(engine_state, stack, "decimals")?; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; - let config = engine_state.get_config().clone(); - if let Some(decimal_val) = decimals_value { if decimals && decimal_val.is_negative() { return Err(ShellError::UnsupportedInput( @@ -160,6 +171,15 @@ fn string_helper( )); } } + let cell_paths = call.rest(engine_state, stack, 0)?; + let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); + let config = engine_state.get_config().clone(); + let args = Arguments { + decimals_value, + decimals, + cell_paths, + config, + }; match input { PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String { @@ -179,45 +199,18 @@ fn string_helper( } .into_pipeline_data()) } - _ => input.map( - move |v| { - if column_paths.is_empty() { - action(&v, head, decimals, decimals_value, false, &config) - } else { - let mut ret = v; - for path in &column_paths { - let config = config.clone(); - let r = ret.update_cell_path( - &path.members, - Box::new(move |old| { - action(old, head, decimals, decimals_value, false, &config) - }), - ); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret - } - }, - engine_state.ctrlc.clone(), - ), + _ => operate(action, args, input, head, engine_state.ctrlc.clone()), } } -pub fn action( - input: &Value, - span: Span, - decimals: bool, - digits: Option, - group_digits: bool, - config: &Config, -) -> Value { +fn action(input: &Value, args: &Arguments, span: Span) -> Value { + let decimals = args.decimals; + let digits = args.decimals_value; + let config = &args.config; match input { Value::Int { val, .. } => { let decimal_value = digits.unwrap_or(0) as usize; - let res = format_int(*val, group_digits, decimal_value); + let res = format_int(*val, false, decimal_value); Value::String { val: res, span } } Value::Float { val, .. } => { diff --git a/crates/nu-command/src/hash/generic_digest.rs b/crates/nu-command/src/hash/generic_digest.rs index e1a6e9e92..299e648d2 100644 --- a/crates/nu-command/src/hash/generic_digest.rs +++ b/crates/nu-command/src/hash/generic_digest.rs @@ -1,6 +1,8 @@ +use crate::input_handler::{operate, CmdArgument}; use nu_engine::CallExt; use nu_protocol::ast::{Call, CellPath}; use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::Span; use nu_protocol::{Example, PipelineData, ShellError, Signature, SyntaxShape, Value}; use std::marker::PhantomData; @@ -26,6 +28,17 @@ impl Default for GenericDigest { } } +pub(super) struct Arguments { + pub(super) cell_paths: Option>, + pub(super) binary: bool, +} + +impl CmdArgument for Arguments { + fn take_cell_paths(&mut self) -> Option> { + self.cell_paths.take() + } +} + impl Command for GenericDigest where D: HashDigest + Send + Sync + 'static, @@ -66,31 +79,19 @@ where ) -> Result { let binary = call.has_flag("binary"); let cell_paths: Vec = call.rest(engine_state, stack, 0)?; - - input.map( - move |v| { - if cell_paths.is_empty() { - action::(binary, &v) - } else { - let mut v = v; - for path in &cell_paths { - let ret = v.update_cell_path( - &path.members, - Box::new(move |old| action::(binary, old)), - ); - if let Err(error) = ret { - return Value::Error { error }; - } - } - v - } - }, + let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); + let args = Arguments { binary, cell_paths }; + operate( + action::, + args, + input, + call.head, engine_state.ctrlc.clone(), ) } } -pub fn action(binary: bool, input: &Value) -> Value +pub(super) fn action(input: &Value, args: &Arguments, _span: Span) -> Value where D: HashDigest, digest::Output: core::fmt::LowerHex, @@ -119,7 +120,7 @@ where let digest = D::digest(bytes); - if binary { + if args.binary { Value::Binary { val: digest.to_vec(), span, diff --git a/crates/nu-command/src/hash/md5.rs b/crates/nu-command/src/hash/md5.rs index c2adbbf36..a27083596 100644 --- a/crates/nu-command/src/hash/md5.rs +++ b/crates/nu-command/src/hash/md5.rs @@ -42,7 +42,7 @@ impl HashDigest for Md5 { #[cfg(test)] mod tests { use super::*; - use crate::hash::generic_digest; + use crate::hash::generic_digest::{self, Arguments}; #[test] fn test_examples() { @@ -59,7 +59,14 @@ mod tests { val: "c3fcd3d76192e4007dfb496cca67e13b".to_owned(), span: Span::test_data(), }; - let actual = generic_digest::action::(false, &binary); + let actual = generic_digest::action::( + &binary, + &Arguments { + cell_paths: None, + binary: false, + }, + Span::test_data(), + ); assert_eq!(actual, expected); } @@ -73,7 +80,14 @@ mod tests { val: "5f80e231382769b0102b1164cf722d83".to_owned(), span: Span::test_data(), }; - let actual = generic_digest::action::(false, &binary); + let actual = generic_digest::action::( + &binary, + &Arguments { + cell_paths: None, + binary: false, + }, + Span::test_data(), + ); assert_eq!(actual, expected); } } diff --git a/crates/nu-command/src/hash/sha256.rs b/crates/nu-command/src/hash/sha256.rs index 810b7e13a..2852ef601 100644 --- a/crates/nu-command/src/hash/sha256.rs +++ b/crates/nu-command/src/hash/sha256.rs @@ -44,7 +44,7 @@ impl HashDigest for Sha256 { #[cfg(test)] mod tests { use super::*; - use crate::hash::generic_digest; + use crate::hash::generic_digest::{self, Arguments}; #[test] fn test_examples() { @@ -61,7 +61,14 @@ mod tests { val: "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73".to_owned(), span: Span::test_data(), }; - let actual = generic_digest::action::(false, &binary); + let actual = generic_digest::action::( + &binary, + &Arguments { + cell_paths: None, + binary: false, + }, + Span::test_data(), + ); assert_eq!(actual, expected); } @@ -75,7 +82,14 @@ mod tests { val: "c47a10dc272b1221f0380a2ae0f7d7fa830b3e378f2f5309bbf13f61ad211913".to_owned(), span: Span::test_data(), }; - let actual = generic_digest::action::(false, &binary); + let actual = generic_digest::action::( + &binary, + &Arguments { + cell_paths: None, + binary: false, + }, + Span::test_data(), + ); assert_eq!(actual, expected); } } diff --git a/crates/nu-command/src/input_handler.rs b/crates/nu-command/src/input_handler.rs index ec3f0ca19..13376cb31 100644 --- a/crates/nu-command/src/input_handler.rs +++ b/crates/nu-command/src/input_handler.rs @@ -7,6 +7,28 @@ pub trait CmdArgument { fn take_cell_paths(&mut self) -> Option>; } +/// Arguments with only cell_path. +/// +/// If commands is going to use `operate` function, and it only required optional cell_paths +/// Using this to simplify code. +pub struct CellPathOnlyArgs { + cell_paths: Option>, +} + +impl CmdArgument for CellPathOnlyArgs { + fn take_cell_paths(&mut self) -> Option> { + self.cell_paths.take() + } +} + +impl From> for CellPathOnlyArgs { + fn from(cell_paths: Vec) -> Self { + Self { + cell_paths: (!cell_paths.is_empty()).then(|| cell_paths), + } + } +} + /// A simple wrapper for `PipelineData::map` method. /// /// In detail, for each elements, invoking relative `cmd` with `arg`. diff --git a/crates/nu-command/src/strings/encode_decode/base64.rs b/crates/nu-command/src/strings/encode_decode/base64.rs index 85c6724b3..c3f233ff2 100644 --- a/crates/nu-command/src/strings/encode_decode/base64.rs +++ b/crates/nu-command/src/strings/encode_decode/base64.rs @@ -1,3 +1,4 @@ +use crate::input_handler::{operate as general_operate, CmdArgument}; use base64::{decode_config, encode_config}; use nu_engine::CallExt; use nu_protocol::ast::{Call, CellPath}; @@ -20,6 +21,18 @@ pub enum ActionType { Decode, } +struct Arguments { + cell_paths: Option>, + binary: bool, + encoding_config: Base64Config, +} + +impl CmdArgument for Arguments { + fn take_cell_paths(&mut self) -> Option> { + self.cell_paths.take() + } +} + pub fn operate( action_type: ActionType, engine_state: &EngineState, @@ -31,7 +44,8 @@ pub fn operate( let character_set: Option> = call.get_flag(engine_state, stack, "character-set")?; let binary = call.has_flag("binary"); - let column_paths: Vec = call.rest(engine_state, stack, 0)?; + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); // Default the character set to standard if the argument is not specified. let character_set = match character_set { @@ -42,50 +56,27 @@ pub fn operate( }, }; - let encoding_config = Base64Config { - character_set, - action_type, + let args = Arguments { + encoding_config: Base64Config { + character_set, + action_type, + }, + binary, + cell_paths, }; - input.map( - move |v| { - if column_paths.is_empty() { - match action(&v, binary, &encoding_config, &head) { - Ok(v) => v, - Err(e) => Value::Error { error: e }, - } - } else { - let mut ret = v; - - for path in &column_paths { - let config = encoding_config.clone(); - - let r = ret.update_cell_path( - &path.members, - Box::new(move |old| match action(old, binary, &config, &head) { - Ok(v) => v, - Err(e) => Value::Error { error: e }, - }), - ); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret - } - }, - engine_state.ctrlc.clone(), - ) + general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) } fn action( input: &Value, // only used for `decode` action - output_binary: bool, - base64_config: &Base64Config, - command_span: &Span, -) -> Result { + args: &Arguments, + command_span: Span, +) -> Value { + let base64_config = &args.encoding_config; + let output_binary = args.binary; + let config_character_set = &base64_config.character_set; let base64_config_enum: base64::Config = match config_character_set.item.as_str() { "standard" => base64::STANDARD, @@ -95,7 +86,7 @@ fn action( "binhex" => base64::BINHEX, "bcrypt" => base64::BCRYPT, "crypt" => base64::CRYPT, - not_valid => return Err(ShellError::GenericError( + not_valid => return Value::Error { error:ShellError::GenericError( "value is not an accepted character set".to_string(), format!( "{} is not a valid character-set.\nPlease use `help hash base64` to see a list of valid character sets.", @@ -104,28 +95,28 @@ fn action( Some(config_character_set.span), None, Vec::new(), - )) + )} }; match input { Value::Binary { val, .. } => match base64_config.action_type { - ActionType::Encode => Ok(Value::string( - encode_config(&val, base64_config_enum), - *command_span, - )), - ActionType::Decode => Err(ShellError::UnsupportedInput( - "Binary data can only support encoding".to_string(), - *command_span, - )), + ActionType::Encode => { + Value::string(encode_config(&val, base64_config_enum), command_span) + } + ActionType::Decode => Value::Error { + error: ShellError::UnsupportedInput( + "Binary data can only support encoding".to_string(), + command_span, + ), + }, }, Value::String { val, span: value_span, } => { match base64_config.action_type { - ActionType::Encode => Ok(Value::string( - encode_config(&val, base64_config_enum), - *command_span, - )), + ActionType::Encode => { + Value::string(encode_config(&val, base64_config_enum), command_span) + } ActionType::Decode => { // for decode, input val may contains invalid new line character, which is ok to omitted them by default. @@ -135,46 +126,51 @@ fn action( match decode_config(&val, base64_config_enum) { Ok(decoded_value) => { if output_binary { - Ok(Value::binary(decoded_value, *command_span)) + Value::binary(decoded_value, command_span) } else { match String::from_utf8(decoded_value) { - Ok(string_value) => { - Ok(Value::string(string_value, *command_span)) - } - Err(e) => Err(ShellError::GenericError( - "base64 payload isn't a valid utf-8 sequence".to_owned(), - e.to_string(), - Some(*value_span), - Some("consider using the `--binary` flag".to_owned()), - Vec::new(), - )), + Ok(string_value) => Value::string(string_value, command_span), + Err(e) => Value::Error { + error: ShellError::GenericError( + "base64 payload isn't a valid utf-8 sequence" + .to_owned(), + e.to_string(), + Some(*value_span), + Some("consider using the `--binary` flag".to_owned()), + Vec::new(), + ), + }, } } } - Err(_) => Err(ShellError::GenericError( - "value could not be base64 decoded".to_string(), - format!( - "invalid base64 input for character set {}", - &config_character_set.item + Err(_) => Value::Error { + error: ShellError::GenericError( + "value could not be base64 decoded".to_string(), + format!( + "invalid base64 input for character set {}", + &config_character_set.item + ), + Some(command_span), + None, + Vec::new(), ), - Some(*command_span), - None, - Vec::new(), - )), + }, } } } } - other => Err(ShellError::TypeMismatch( - format!("value is {}, not string", other.get_type()), - other.span()?, - )), + other => Value::Error { + error: ShellError::TypeMismatch( + format!("value is {}, not string", other.get_type()), + other.span().unwrap_or(command_span), + ), + }, } } #[cfg(test)] mod tests { - use super::{action, ActionType, Base64Config}; + use super::{action, ActionType, Arguments, Base64Config}; use nu_protocol::{Span, Spanned, Value}; #[test] @@ -184,17 +180,19 @@ mod tests { let actual = action( &word, - true, - &Base64Config { - character_set: Spanned { - item: "standard".to_string(), - span: Span::test_data(), + &Arguments { + encoding_config: Base64Config { + character_set: Spanned { + item: "standard".to_string(), + span: Span::test_data(), + }, + action_type: ActionType::Encode, }, - action_type: ActionType::Encode, + binary: true, + cell_paths: None, }, - &Span::test_data(), - ) - .unwrap(); + Span::test_data(), + ); assert_eq!(actual, expected); } @@ -205,17 +203,19 @@ mod tests { let actual = action( &word, - true, - &Base64Config { - character_set: Spanned { - item: "standard-no-padding".to_string(), - span: Span::test_data(), + &Arguments { + encoding_config: Base64Config { + character_set: Spanned { + item: "standard-no-padding".to_string(), + span: Span::test_data(), + }, + action_type: ActionType::Encode, }, - action_type: ActionType::Encode, + binary: true, + cell_paths: None, }, - &Span::test_data(), - ) - .unwrap(); + Span::test_data(), + ); assert_eq!(actual, expected); } @@ -226,17 +226,19 @@ mod tests { let actual = action( &word, - true, - &Base64Config { - character_set: Spanned { - item: "url-safe".to_string(), - span: Span::test_data(), + &Arguments { + encoding_config: Base64Config { + character_set: Spanned { + item: "url-safe".to_string(), + span: Span::test_data(), + }, + action_type: ActionType::Encode, }, - action_type: ActionType::Encode, + binary: true, + cell_paths: None, }, - &Span::test_data(), - ) - .unwrap(); + Span::test_data(), + ); assert_eq!(actual, expected); } @@ -247,17 +249,19 @@ mod tests { let actual = action( &word, - true, - &Base64Config { - character_set: Spanned { - item: "binhex".to_string(), - span: Span::test_data(), + &Arguments { + encoding_config: Base64Config { + character_set: Spanned { + item: "binhex".to_string(), + span: Span::test_data(), + }, + action_type: ActionType::Decode, }, - action_type: ActionType::Decode, + binary: true, + cell_paths: None, }, - &Span::test_data(), - ) - .unwrap(); + Span::test_data(), + ); assert_eq!(actual, expected); } @@ -268,17 +272,19 @@ mod tests { let actual = action( &word, - true, - &Base64Config { - character_set: Spanned { - item: "binhex".to_string(), - span: Span::test_data(), + &Arguments { + encoding_config: Base64Config { + character_set: Spanned { + item: "binhex".to_string(), + span: Span::test_data(), + }, + action_type: ActionType::Decode, }, - action_type: ActionType::Decode, + binary: true, + cell_paths: None, }, - &Span::test_data(), - ) - .unwrap(); + Span::test_data(), + ); assert_eq!(actual, expected); } @@ -292,17 +298,19 @@ mod tests { let actual = action( &word, - true, - &Base64Config { - character_set: Spanned { - item: "standard".to_string(), - span: Span::test_data(), + &Arguments { + encoding_config: Base64Config { + character_set: Spanned { + item: "standard".to_string(), + span: Span::test_data(), + }, + action_type: ActionType::Encode, }, - action_type: ActionType::Encode, + binary: true, + cell_paths: None, }, - &Span::test_data(), - ) - .unwrap(); + Span::test_data(), + ); assert_eq!(actual, expected); } @@ -315,16 +323,23 @@ mod tests { let actual = action( &word, - true, - &Base64Config { - character_set: Spanned { - item: "standard".to_string(), - span: Span::test_data(), + &Arguments { + encoding_config: Base64Config { + character_set: Spanned { + item: "standard".to_string(), + span: Span::test_data(), + }, + action_type: ActionType::Decode, }, - action_type: ActionType::Decode, + binary: true, + cell_paths: None, }, - &Span::test_data(), + Span::test_data(), ); - assert!(actual.is_err()) + + match actual { + Value::Error { .. } => {} + _ => panic!("the result should be Value::Error"), + } } } diff --git a/crates/nu-command/src/strings/str_/length.rs b/crates/nu-command/src/strings/str_/length.rs index 2e99fed27..77c4afb30 100644 --- a/crates/nu-command/src/strings/str_/length.rs +++ b/crates/nu-command/src/strings/str_/length.rs @@ -1,4 +1,4 @@ -use crate::input_handler::{operate, CmdArgument}; +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::ast::CellPath; @@ -8,16 +8,6 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap #[derive(Clone)] pub struct SubCommand; -struct Arguments { - cell_paths: Option>, -} - -impl CmdArgument for Arguments { - fn take_cell_paths(&mut self) -> Option> { - self.cell_paths.take() - } -} - impl Command for SubCommand { fn name(&self) -> &str { "str length" @@ -49,8 +39,7 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; - let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); - let args = Arguments { cell_paths }; + let args = CellPathOnlyArgs::from(cell_paths); operate(action, args, input, call.head, engine_state.ctrlc.clone()) } @@ -73,7 +62,7 @@ impl Command for SubCommand { } } -fn action(input: &Value, _arg: &Arguments, head: Span) -> Value { +fn action(input: &Value, _arg: &CellPathOnlyArgs, head: Span) -> Value { match input { Value::String { val, .. } => Value::Int { val: val.len() as i64, diff --git a/crates/nu-command/src/strings/str_/reverse.rs b/crates/nu-command/src/strings/str_/reverse.rs index abc21894e..6515f1285 100644 --- a/crates/nu-command/src/strings/str_/reverse.rs +++ b/crates/nu-command/src/strings/str_/reverse.rs @@ -1,4 +1,4 @@ -use crate::input_handler::{operate, CmdArgument}; +use crate::input_handler::{operate, CellPathOnlyArgs}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::ast::CellPath; @@ -9,16 +9,6 @@ use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShap #[derive(Clone)] pub struct SubCommand; -struct Arguments { - cell_paths: Option>, -} - -impl CmdArgument for Arguments { - fn take_cell_paths(&mut self) -> Option> { - self.cell_paths.take() - } -} - impl Command for SubCommand { fn name(&self) -> &str { "str reverse" @@ -50,8 +40,7 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; - let cell_paths = (!cell_paths.is_empty()).then(|| cell_paths); - let args = Arguments { cell_paths }; + let args = CellPathOnlyArgs::from(cell_paths); operate(action, args, input, call.head, engine_state.ctrlc.clone()) } @@ -90,7 +79,7 @@ impl Command for SubCommand { } } -fn action(input: &Value, _arg: &Arguments, head: Span) -> Value { +fn action(input: &Value, _arg: &CellPathOnlyArgs, head: Span) -> Value { match input { Value::String { val, .. } => Value::String { val: val.chars().rev().collect::(),