allow into duration to take an integer amount of ns (#10286)

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` 😋

# 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
This commit is contained in:
Antoine Stevan 2023-09-09 20:49:08 +02:00 committed by GitHub
parent 40eca52ed5
commit 17abbdf6e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -19,6 +19,7 @@ impl Command for SubCommand {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("into duration") Signature::build("into duration")
.input_output_types(vec![ .input_output_types(vec![
(Type::Int, Type::Duration),
(Type::String, Type::Duration), (Type::String, Type::Duration),
(Type::Duration, Type::Duration), (Type::Duration, Type::Duration),
(Type::Table(vec![]), Type::Table(vec![])), (Type::Table(vec![]), Type::Table(vec![])),
@ -26,6 +27,12 @@ impl Command for SubCommand {
//(Type::Record(vec![]), Type::Record(vec![])), //(Type::Record(vec![]), Type::Record(vec![])),
]) ])
//.allow_variants_without_examples(true) //.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(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
@ -107,6 +114,16 @@ impl Command for SubCommand {
example: "420sec | into duration", example: "420sec | into duration",
result: Some(Value::duration(7 * 60 * NS_PER_SEC, span)), 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<CellPath> = call.rest(engine_state, stack, 0)?; let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let unit = match call.get_flag::<String>(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( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {
action(&v, span) action(&v, &unit.clone(), span)
} else { } else {
let unitclone = &unit.clone();
let mut ret = v; let mut ret = v;
for path in &column_paths { for path in &column_paths {
let r = let r = ret.update_cell_path(
ret.update_cell_path(&path.members, Box::new(move |old| action(old, span))); &path.members,
Box::new(move |old| action(old, unitclone, span)),
);
if let Err(error) = r { if let Err(error) = r {
return Value::error(error, span); return Value::error(error, span);
} }
@ -202,7 +243,7 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
}) })
} }
fn action(input: &Value, span: Span) -> Value { fn action(input: &Value, unit: &str, span: Span) -> Value {
let value_span = input.span(); let value_span = input.span();
match input { match input {
Value::Duration { .. } => input.clone(), Value::Duration { .. } => input.clone(),
@ -210,6 +251,20 @@ fn action(input: &Value, span: Span) -> Value {
Ok(val) => Value::duration(val, span), Ok(val) => Value::duration(val, span),
Err(error) => Value::error(error, 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. // Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(), Value::Error { .. } => input.clone(),
other => Value::error( 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 #[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) { 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 { match actual {
Value::Duration { Value::Duration {
val: observed_val, .. val: observed_val, ..