nu_command/strings/format/
duration.rs

1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3use nu_protocol::SUPPORTED_DURATION_UNITS;
4
5struct Arguments {
6    format_value: Spanned<String>,
7    float_precision: usize,
8    cell_paths: Option<Vec<CellPath>>,
9}
10
11impl CmdArgument for Arguments {
12    fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
13        self.cell_paths.take()
14    }
15}
16
17#[derive(Clone)]
18pub struct FormatDuration;
19
20impl Command for FormatDuration {
21    fn name(&self) -> &str {
22        "format duration"
23    }
24
25    fn signature(&self) -> Signature {
26        Signature::build("format duration")
27            .input_output_types(vec![
28                (Type::Duration, Type::String),
29                (
30                    Type::List(Box::new(Type::Duration)),
31                    Type::List(Box::new(Type::String)),
32                ),
33                (Type::table(), Type::table()),
34            ])
35            .allow_variants_without_examples(true)
36            .required(
37                "format value",
38                SyntaxShape::String,
39                "The unit in which to display the duration.",
40            )
41            .rest(
42                "rest",
43                SyntaxShape::CellPath,
44                "For a data structure input, format duration at the given cell paths.",
45            )
46            .category(Category::Strings)
47    }
48
49    fn description(&self) -> &str {
50        "Outputs duration with a specified unit of time."
51    }
52
53    fn search_terms(&self) -> Vec<&str> {
54        vec!["convert", "display", "pattern", "human readable"]
55    }
56
57    fn is_const(&self) -> bool {
58        true
59    }
60
61    fn run(
62        &self,
63        engine_state: &EngineState,
64        stack: &mut Stack,
65        call: &Call,
66        input: PipelineData,
67    ) -> Result<PipelineData, ShellError> {
68        let format_value = call.req::<Value>(engine_state, stack, 0)?;
69        let format_value_span = format_value.span();
70        let format_value = Spanned {
71            item: format_value.coerce_into_string()?.to_ascii_lowercase(),
72            span: format_value_span,
73        };
74        let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
75        let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
76        let float_precision = engine_state.config.float_precision as usize;
77        let arg = Arguments {
78            format_value,
79            float_precision,
80            cell_paths,
81        };
82        operate(
83            format_value_impl,
84            arg,
85            input,
86            call.head,
87            engine_state.signals(),
88        )
89    }
90
91    fn run_const(
92        &self,
93        working_set: &StateWorkingSet,
94        call: &Call,
95        input: PipelineData,
96    ) -> Result<PipelineData, ShellError> {
97        let format_value = call.req_const::<Value>(working_set, 0)?;
98        let format_value_span = format_value.span();
99        let format_value = Spanned {
100            item: format_value.coerce_into_string()?.to_ascii_lowercase(),
101            span: format_value_span,
102        };
103        let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
104        let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
105        let float_precision = working_set.permanent().config.float_precision as usize;
106        let arg = Arguments {
107            format_value,
108            float_precision,
109            cell_paths,
110        };
111        operate(
112            format_value_impl,
113            arg,
114            input,
115            call.head,
116            working_set.permanent().signals(),
117        )
118    }
119
120    fn examples(&self) -> Vec<Example> {
121        vec![
122            Example {
123                description: "Convert µs duration to the requested second duration as a string",
124                example: "1000000µs | format duration sec",
125                result: Some(Value::test_string("1 sec")),
126            },
127            Example {
128                description: "Convert durations to µs duration as strings",
129                example: "[1sec 2sec] | format duration µs",
130                result: Some(Value::test_list(vec![
131                    Value::test_string("1000000 µs"),
132                    Value::test_string("2000000 µs"),
133                ])),
134            },
135            Example {
136                description: "Convert duration to µs as a string if unit asked for was us",
137                example: "1sec | format duration us",
138                result: Some(Value::test_string("1000000 µs")),
139            },
140        ]
141    }
142}
143
144fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
145    let inner_span = val.span();
146    match val {
147        Value::Duration { val: inner, .. } => {
148            let duration = *inner;
149            let float_precision = arg.float_precision;
150            match convert_inner_to_unit(duration, &arg.format_value.item, arg.format_value.span) {
151                Ok(d) => {
152                    let unit = if &arg.format_value.item == "us" {
153                        "µs"
154                    } else {
155                        &arg.format_value.item
156                    };
157                    if d.fract() == 0.0 {
158                        Value::string(format!("{} {}", d, unit), inner_span)
159                    } else {
160                        Value::string(format!("{:.float_precision$} {}", d, unit), inner_span)
161                    }
162                }
163                Err(e) => Value::error(e, inner_span),
164            }
165        }
166        Value::Error { .. } => val.clone(),
167        _ => Value::error(
168            ShellError::OnlySupportsThisInputType {
169                exp_input_type: "filesize".into(),
170                wrong_type: val.get_type().to_string(),
171                dst_span: span,
172                src_span: val.span(),
173            },
174            span,
175        ),
176    }
177}
178
179fn convert_inner_to_unit(val: i64, to_unit: &str, span: Span) -> Result<f64, ShellError> {
180    match to_unit {
181        "ns" => Ok(val as f64),
182        "us" => Ok(val as f64 / 1000.0),
183        "µs" => Ok(val as f64 / 1000.0), // Micro sign
184        "μs" => Ok(val as f64 / 1000.0), // Greek small letter
185        "ms" => Ok(val as f64 / 1000.0 / 1000.0),
186        "sec" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
187        "min" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
188        "hr" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
189        "day" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
190        "wk" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
191        "month" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
192        "yr" => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
193        "dec" => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
194
195        _ => Err(ShellError::InvalidUnit {
196            span,
197            supported_units: SUPPORTED_DURATION_UNITS.join(", "),
198        }),
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_examples() {
208        use crate::test_examples;
209
210        test_examples(FormatDuration)
211    }
212}