nu_command/strings/format/
duration.rs1use 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), "μs" => Ok(val as f64 / 1000.0), "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}