From 17abbdf6e0e178154cb9091c5cab615298e42792 Mon Sep 17 00:00:00 2001 From: Antoine Stevan <44101798+amtoine@users.noreply.github.com> Date: Sat, 9 Sep 2023 20:49:08 +0200 Subject: [PATCH] allow `into duration` to take an integer amount of ns (#10286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit related to - https://discord.com/channels/601130461678272522/615329862395101194/1149717458786197524 # Description because `1_234 | into datetime` takes an integer number of `ns` and `1_234 | into filesize` takes an integer amount of bytes, i think `1_234 | into duration` should also be valid and see `1_234` as an integer amount of `ns` :yum: # User-Facing Changes ## before either ```nushell 1234 | into string | $in ++ "ns" | into duration ``` ```nushell 1234 | $"($in)ns" | into duration ``` or ```nushell 1234 * 1ns ``` and ```nushell > 1_234 | into duration Error: nu::parser::input_type_mismatch × Command does not support int input. ╭─[entry #2:1:1] 1 │ 1_234 | into duration · ──────┬────── · ╰── command doesn't support int input ╰──── ``` ## after ```nushell > 1_234 | into duration 1µs 234ns ``` # Tests + Formatting new example test ```rust Example { description: "Convert a number of ns to duration", example: "1_234_567 | into duration", result: Some(Value::duration(1_234_567, span)), } ``` # After Submitting --- .../src/conversions/into/duration.rs | 69 +++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/crates/nu-command/src/conversions/into/duration.rs b/crates/nu-command/src/conversions/into/duration.rs index 2b1d1bf52b..336e3fdf19 100644 --- a/crates/nu-command/src/conversions/into/duration.rs +++ b/crates/nu-command/src/conversions/into/duration.rs @@ -19,6 +19,7 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("into duration") .input_output_types(vec![ + (Type::Int, Type::Duration), (Type::String, Type::Duration), (Type::Duration, Type::Duration), (Type::Table(vec![]), Type::Table(vec![])), @@ -26,6 +27,12 @@ impl Command for SubCommand { //(Type::Record(vec![]), Type::Record(vec![])), ]) //.allow_variants_without_examples(true) + .named( + "unit", + SyntaxShape::String, + "Unit to convert number into (will have an effect only with integer input)", + Some('u'), + ) .rest( "rest", SyntaxShape::CellPath, @@ -107,6 +114,16 @@ impl Command for SubCommand { example: "420sec | into duration", result: Some(Value::duration(7 * 60 * NS_PER_SEC, span)), }, + Example { + description: "Convert a number of ns to duration", + example: "1_234_567 | into duration", + result: Some(Value::duration(1_234_567, span)), + }, + Example { + description: "Convert a number of an arbitraty unit to duration", + example: "1_234 | into duration --unit ms", + result: Some(Value::duration(1_234 * 1_000_000, span)), + }, ] } } @@ -123,15 +140,39 @@ fn into_duration( }; let column_paths: Vec = call.rest(engine_state, stack, 0)?; + let unit = match call.get_flag::(engine_state, stack, "unit")? { + Some(sep) => { + if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"] + .iter() + .any(|d| d == &sep) + { + sep + } else { + return Err(ShellError::CantConvertToDuration { + details: sep, + dst_span: span, + src_span: span, + help: Some( + "supported units are ns, us/µs, ms, sec, min, hr, day, and wk".to_string(), + ), + }); + } + } + None => "ns".to_string(), + }; + input.map( move |v| { if column_paths.is_empty() { - action(&v, span) + action(&v, &unit.clone(), span) } else { + let unitclone = &unit.clone(); let mut ret = v; for path in &column_paths { - let r = - ret.update_cell_path(&path.members, Box::new(move |old| action(old, span))); + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| action(old, unitclone, span)), + ); if let Err(error) = r { return Value::error(error, span); } @@ -202,7 +243,7 @@ fn string_to_duration(s: &str, span: Span) -> Result { }) } -fn action(input: &Value, span: Span) -> Value { +fn action(input: &Value, unit: &str, span: Span) -> Value { let value_span = input.span(); match input { Value::Duration { .. } => input.clone(), @@ -210,6 +251,20 @@ fn action(input: &Value, span: Span) -> Value { Ok(val) => Value::duration(val, span), Err(error) => Value::error(error, span), }, + Value::Int { val, .. } => { + let ns = match unit { + "ns" => 1, + "us" | "µs" => 1_000, + "ms" => 1_000_000, + "sec" => NS_PER_SEC, + "min" => NS_PER_SEC * 60, + "hr" => NS_PER_SEC * 60 * 60, + "day" => NS_PER_SEC * 60 * 60 * 24, + "wk" => NS_PER_SEC * 60 * 60 * 24 * 7, + _ => 0, + }; + Value::duration(*val * ns, span) + } // Propagate errors by explicitly matching them before the final case. Value::Error { .. } => input.clone(), other => Value::error( @@ -253,7 +308,11 @@ mod test { #[case("14ns 3hr 17sec", 14 + 3 * 3600 * NS_PER_SEC + 17 * NS_PER_SEC)] // compound string with units in random order fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) { - let actual = action(&Value::test_string(phrase), Span::new(0, phrase.len())); + let actual = action( + &Value::test_string(phrase), + "ns", + Span::new(0, phrase.len()), + ); match actual { Value::Duration { val: observed_val, ..