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