diff --git a/crates/nu-command/src/conversions/into/duration.rs b/crates/nu-command/src/conversions/into/duration.rs index ff2419c1e6..1c1e9d17c1 100644 --- a/crates/nu-command/src/conversions/into/duration.rs +++ b/crates/nu-command/src/conversions/into/duration.rs @@ -3,7 +3,8 @@ use nu_parser::parse_duration_bytes; use nu_protocol::{ ast::{Call, CellPath, Expr}, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Unit, Value, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Unit, + Value, }; #[derive(Clone)] @@ -16,6 +17,12 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("into duration") + .named( + "convert", + SyntaxShape::String, + "convert duration into another duration", + Some('c'), + ) .rest( "rest", SyntaxShape::CellPath, @@ -106,6 +113,15 @@ impl Command for SubCommand { span, }), }, + Example { + description: "Convert string to a named duration", + example: "'7min' | into duration --convert sec", + result: Some(Value::String { + val: "420 sec".to_string(), + span, + }), + }, + ] } } @@ -117,17 +133,21 @@ fn into_duration( input: PipelineData, ) -> Result { let head = call.head; + let convert_to_unit: Option> = call.get_flag(engine_state, stack, "convert")?; let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { if column_paths.is_empty() { - action(&v, head) + action(&v, &convert_to_unit, 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))); + let d = convert_to_unit.clone(); + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| action(old, &d, head)), + ); if let Err(error) = r { return Value::Error { error }; } @@ -140,6 +160,148 @@ fn into_duration( ) } +fn convert_str_from_unit_to_unit( + val: i64, + from_unit: &str, + to_unit: &str, + span: Span, + value_span: Span, +) -> Result { + match (from_unit, to_unit) { + ("ns", "ns") => Ok(val), + ("ns", "us") => Ok(val / 1000), + ("ns", "ms") => Ok(val / 1000 / 1000), + ("ns", "sec") => Ok(val / 1000 / 1000 / 1000), + ("ns", "min") => Ok(val / 1000 / 1000 / 1000 / 60), + ("ns", "hr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60), + ("ns", "day") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24), + ("ns", "wk") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 7), + ("ns", "month") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 30), + ("ns", "yr") => Ok(val / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365), + ("ns", "dec") => Ok(val / 10 / 1000 / 1000 / 1000 / 60 / 60 / 24 / 365), + + ("us", "ns") => Ok(val * 1000), + ("us", "us") => Ok(val), + ("us", "ms") => Ok(val / 1000), + ("us", "sec") => Ok(val / 1000 / 1000), + ("us", "min") => Ok(val / 1000 / 1000 / 60), + ("us", "hr") => Ok(val / 1000 / 1000 / 60 / 60), + ("us", "day") => Ok(val / 1000 / 1000 / 60 / 60 / 24), + ("us", "wk") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 7), + ("us", "month") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 30), + ("us", "yr") => Ok(val / 1000 / 1000 / 60 / 60 / 24 / 365), + ("us", "dec") => Ok(val / 10 / 1000 / 1000 / 60 / 60 / 24 / 365), + + ("ms", "ns") => Ok(val * 1000 * 1000), + ("ms", "us") => Ok(val * 1000), + ("ms", "ms") => Ok(val), + ("ms", "sec") => Ok(val / 1000), + ("ms", "min") => Ok(val / 1000 / 60), + ("ms", "hr") => Ok(val / 1000 / 60 / 60), + ("ms", "day") => Ok(val / 1000 / 60 / 60 / 24), + ("ms", "wk") => Ok(val / 1000 / 60 / 60 / 24 / 7), + ("ms", "month") => Ok(val / 1000 / 60 / 60 / 24 / 30), + ("ms", "yr") => Ok(val / 1000 / 60 / 60 / 24 / 365), + ("ms", "dec") => Ok(val / 10 / 1000 / 60 / 60 / 24 / 365), + + ("sec", "ns") => Ok(val * 1000 * 1000 * 1000), + ("sec", "us") => Ok(val * 1000 * 1000), + ("sec", "ms") => Ok(val * 1000), + ("sec", "sec") => Ok(val), + ("sec", "min") => Ok(val / 60), + ("sec", "hr") => Ok(val / 60 / 60), + ("sec", "day") => Ok(val / 60 / 60 / 24), + ("sec", "wk") => Ok(val / 60 / 60 / 24 / 7), + ("sec", "month") => Ok(val / 60 / 60 / 24 / 30), + ("sec", "yr") => Ok(val / 60 / 60 / 24 / 365), + ("sec", "dec") => Ok(val / 10 / 60 / 60 / 24 / 365), + + ("min", "ns") => Ok(val * 1000 * 1000 * 1000 * 60), + ("min", "us") => Ok(val * 1000 * 1000 * 60), + ("min", "ms") => Ok(val * 1000 * 60), + ("min", "sec") => Ok(val * 60), + ("min", "min") => Ok(val), + ("min", "hr") => Ok(val / 60), + ("min", "day") => Ok(val / 60 / 24), + ("min", "wk") => Ok(val / 60 / 24 / 7), + ("min", "month") => Ok(val / 60 / 24 / 30), + ("min", "yr") => Ok(val / 60 / 24 / 365), + ("min", "dec") => Ok(val / 10 / 60 / 24 / 365), + + ("hr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60), + ("hr", "us") => Ok(val * 1000 * 1000 * 60 * 60), + ("hr", "ms") => Ok(val * 1000 * 60 * 60), + ("hr", "sec") => Ok(val * 60 * 60), + ("hr", "min") => Ok(val * 60), + ("hr", "hr") => Ok(val), + ("hr", "day") => Ok(val / 24), + ("hr", "wk") => Ok(val / 24 / 7), + ("hr", "month") => Ok(val / 24 / 30), + ("hr", "yr") => Ok(val / 24 / 365), + ("hr", "dec") => Ok(val / 10 / 24 / 365), + + ("day", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24), + ("day", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24), + ("day", "ms") => Ok(val * 1000 * 60 * 60 * 24), + ("day", "sec") => Ok(val * 60 * 60 * 24), + ("day", "min") => Ok(val * 60 * 24), + ("day", "hr") => Ok(val * 24), + ("day", "day") => Ok(val), + ("day", "wk") => Ok(val / 7), + ("day", "month") => Ok(val / 30), + ("day", "yr") => Ok(val / 365), + ("day", "dec") => Ok(val / 10 / 365), + + ("wk", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7), + ("wk", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 7), + ("wk", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 7), + ("wk", "sec") => Ok(val * 60 * 60 * 24 * 7), + ("wk", "min") => Ok(val * 60 * 24 * 7), + ("wk", "hr") => Ok(val * 24 * 7), + ("wk", "day") => Ok(val * 7), + ("wk", "wk") => Ok(val), + ("wk", "month") => Ok(val / 4), + ("wk", "yr") => Ok(val / 52), + ("wk", "dec") => Ok(val / 10 / 52), + + ("month", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 30), + ("month", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 30), + ("month", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 30), + ("month", "sec") => Ok(val * 60 * 60 * 24 * 30), + ("month", "min") => Ok(val * 60 * 24 * 30), + ("month", "hr") => Ok(val * 24 * 30), + ("month", "day") => Ok(val * 30), + ("month", "wk") => Ok(val * 4), + ("month", "month") => Ok(val), + ("month", "yr") => Ok(val / 12), + ("month", "dec") => Ok(val / 10 / 12), + + ("yr", "ns") => Ok(val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 365), + ("yr", "us") => Ok(val * 1000 * 1000 * 60 * 60 * 24 * 365), + ("yr", "ms") => Ok(val * 1000 * 60 * 60 * 24 * 365), + ("yr", "sec") => Ok(val * 60 * 60 * 24 * 365), + ("yr", "min") => Ok(val * 60 * 24 * 365), + ("yr", "hr") => Ok(val * 24 * 365), + ("yr", "day") => Ok(val * 365), + ("yr", "wk") => Ok(val * 52), + ("yr", "month") => Ok(val * 12), + ("yr", "yr") => Ok(val), + ("yr", "dec") => Ok(val / 10), + + _ => Err(ShellError::CantConvertWithValue( + "string duration".to_string(), + "string duration".to_string(), + to_unit.to_string(), + span, + value_span, + Some( + "supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec" + .to_string(), + ), + )), + } +} + fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result { if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) { if let Expr::ValueWithUnit(value, unit) = expression.expr { @@ -168,20 +330,105 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result Value { +fn string_to_unit_duration( + s: &str, + span: Span, + value_span: Span, +) -> Result<(&str, i64), ShellError> { + if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) { + if let Expr::ValueWithUnit(value, unit) = expression.expr { + if let Expr::Int(x) = value.expr { + match unit.item { + Unit::Nanosecond => return Ok(("ns", x)), + Unit::Microsecond => return Ok(("us", x)), + Unit::Millisecond => return Ok(("ms", x)), + Unit::Second => return Ok(("sec", x)), + Unit::Minute => return Ok(("min", x)), + Unit::Hour => return Ok(("hr", x)), + Unit::Day => return Ok(("day", x)), + Unit::Week => return Ok(("wk", x)), + Unit::Month => return Ok(("month", x)), //30 days to a month + Unit::Year => return Ok(("yr", x)), //365 days to a year + Unit::Decade => return Ok(("dec", x)), //365 days to a year + + _ => return Ok(("ns", 0)), + } + } + } + } + + Err(ShellError::CantConvertWithValue( + "duration".to_string(), + "string".to_string(), + s.to_string(), + span, + value_span, + Some( + "supported units are ns, us, ms, sec, min, hr, day, wk, month, yr and dec".to_string(), + ), + )) +} + +fn action(input: &Value, convert_to_unit: &Option>, span: Span) -> Value { match input { - Value::Duration { .. } => input.clone(), + Value::Duration { + val: _val_num, + span: _value_span, + } => { + if let Some(_to_unit) = convert_to_unit { + Value::Error { + error: ShellError::UnsupportedInput( + "Cannot convert from a Value::Duration right now. Try making it a string." + .into(), + span, + ), + } + } else { + input.clone() + } + } Value::String { val, span: value_span, - } => match string_to_duration(val, span, *value_span) { - Ok(val) => Value::Duration { val, span }, - Err(error) => Value::Error { error }, - }, + } => { + if let Some(to_unit) = convert_to_unit { + if let Ok(dur) = string_to_unit_duration(val, span, *value_span) { + let from_unit = dur.0; + let duration = dur.1; + match convert_str_from_unit_to_unit( + duration, + from_unit, + &to_unit.item, + span, + *value_span, + ) { + Ok(d) => Value::String { + val: format!("{} {}", d, &to_unit.item), + span: *value_span, + }, + Err(e) => Value::Error { error: e }, + } + } else { + Value::Error { + error: ShellError::UnsupportedInput( + "'into duration' does not support this string input".into(), + span, + ), + } + } + } else { + match string_to_duration(val, span, *value_span) { + Ok(val) => Value::Duration { val, span }, + Err(error) => Value::Error { error }, + } + } + } _ => Value::Error { error: ShellError::UnsupportedInput( "'into duration' does not support this input".into(), @@ -207,8 +454,9 @@ mod test { let span = Span::test_data(); let word = Value::test_string("3ns"); let expected = Value::Duration { val: 3, span }; + let convert_duration = None; - let actual = action(&word, span); + let actual = action(&word, &convert_duration, span); assert_eq!(actual, expected); } @@ -220,8 +468,9 @@ mod test { val: 4 * 1000, span, }; + let convert_duration = None; - let actual = action(&word, span); + let actual = action(&word, &convert_duration, span); assert_eq!(actual, expected); } @@ -233,8 +482,9 @@ mod test { val: 5 * 1000 * 1000, span, }; + let convert_duration = None; - let actual = action(&word, span); + let actual = action(&word, &convert_duration, span); assert_eq!(actual, expected); } @@ -246,8 +496,9 @@ mod test { val: 1000 * 1000 * 1000, span, }; + let convert_duration = None; - let actual = action(&word, span); + let actual = action(&word, &convert_duration, span); assert_eq!(actual, expected); } @@ -259,8 +510,9 @@ mod test { val: 7 * 60 * 1000 * 1000 * 1000, span, }; + let convert_duration = None; - let actual = action(&word, span); + let actual = action(&word, &convert_duration, span); assert_eq!(actual, expected); } @@ -272,8 +524,9 @@ mod test { val: 42 * 60 * 60 * 1000 * 1000 * 1000, span, }; + let convert_duration = None; - let actual = action(&word, span); + let actual = action(&word, &convert_duration, span); assert_eq!(actual, expected); } @@ -285,8 +538,9 @@ mod test { val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000, span, }; + let convert_duration = None; - let actual = action(&word, span); + let actual = action(&word, &convert_duration, span); assert_eq!(actual, expected); } @@ -298,8 +552,9 @@ mod test { val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000, span, }; + let convert_duration = None; - let actual = action(&word, span); + let actual = action(&word, &convert_duration, span); assert_eq!(actual, expected); } }