Skip to main content

nu_command/strings/format/
filesize.rs

1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3use nu_protocol::{
4    FilesizeFormatter, FilesizeUnit, SUPPORTED_FILESIZE_UNITS, engine::StateWorkingSet,
5};
6
7struct Arguments {
8    unit: FilesizeUnit,
9    float_precision: usize,
10    cell_paths: Option<Vec<CellPath>>,
11}
12
13impl CmdArgument for Arguments {
14    fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
15        self.cell_paths.take()
16    }
17}
18
19#[derive(Clone)]
20pub struct FormatFilesize;
21
22impl Command for FormatFilesize {
23    fn name(&self) -> &str {
24        "format filesize"
25    }
26
27    fn signature(&self) -> Signature {
28        Signature::build("format filesize")
29            .input_output_types(vec![
30                (Type::Filesize, Type::String),
31                (Type::table(), Type::table()),
32                (Type::record(), Type::record()),
33            ])
34            .allow_variants_without_examples(true)
35            .required(
36                "format value",
37                SyntaxShape::String,
38                "The format into which convert the file sizes.",
39            )
40            .rest(
41                "rest",
42                SyntaxShape::CellPath,
43                "For a data structure input, format filesizes at the given cell paths.",
44            )
45            .category(Category::Strings)
46    }
47
48    fn description(&self) -> &str {
49        "Converts a column of filesizes to some specified format."
50    }
51
52    fn extra_description(&self) -> &str {
53        "Decimal precision is controlled by `$env.config.float_precision`."
54    }
55
56    fn search_terms(&self) -> Vec<&str> {
57        vec!["convert", "display"]
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 unit = parse_filesize_unit(call.req::<Spanned<String>>(engine_state, stack, 0)?)?;
72        let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
73        let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
74        // Read runtime config so `$env.config.float_precision` changes are honored.
75        let float_precision = stack.get_config(engine_state).float_precision.max(0) as usize;
76        let arg = Arguments {
77            unit,
78            float_precision,
79            cell_paths,
80        };
81        operate(
82            format_value_impl,
83            arg,
84            input,
85            call.head,
86            engine_state.signals(),
87        )
88    }
89
90    fn run_const(
91        &self,
92        working_set: &StateWorkingSet,
93        call: &Call,
94        input: PipelineData,
95    ) -> Result<PipelineData, ShellError> {
96        let unit = parse_filesize_unit(call.req_const::<Spanned<String>>(working_set, 0)?)?;
97        let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
98        let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
99        let float_precision = working_set.permanent().get_config().float_precision.max(0) as usize;
100        let arg = Arguments {
101            unit,
102            float_precision,
103            cell_paths,
104        };
105        operate(
106            format_value_impl,
107            arg,
108            input,
109            call.head,
110            working_set.permanent().signals(),
111        )
112    }
113
114    fn examples(&self) -> Vec<Example<'_>> {
115        vec![
116            Example {
117                description: "Convert the size column to KB.",
118                example: "ls | format filesize kB size",
119                result: None,
120            },
121            Example {
122                description: "Convert the apparent column to B.",
123                example: "du | format filesize B apparent",
124                result: None,
125            },
126            Example {
127                description: "Convert the size data to MB.",
128                example: "4GB | format filesize MB",
129                result: Some(Value::test_string("4000 MB")),
130            },
131        ]
132    }
133}
134
135fn parse_filesize_unit(format: Spanned<String>) -> Result<FilesizeUnit, ShellError> {
136    format.item.parse().map_err(|_| ShellError::InvalidUnit {
137        supported_units: SUPPORTED_FILESIZE_UNITS.join(", "),
138        span: format.span,
139    })
140}
141
142fn format_value_impl(val: &Value, arg: &Arguments, span: Span) -> Value {
143    let value_span = val.span();
144    match val {
145        Value::Filesize { val, .. } => {
146            // Check if this will produce a fractional result.
147            // If so, apply float_precision; otherwise use None to avoid trailing zeros.
148            let bytes: i64 = (*val).into();
149            let unit_bytes = arg.unit.as_bytes() as i64;
150            let has_remainder =
151                arg.unit != FilesizeUnit::B && unit_bytes > 0 && (bytes % unit_bytes) != 0;
152
153            let precision = if has_remainder {
154                Some(arg.float_precision)
155            } else {
156                None
157            };
158
159            FilesizeFormatter::new()
160                .unit(arg.unit)
161                .precision(precision)
162                .format(*val)
163                .to_string()
164                .into_value(span)
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: value_span,
173            },
174            span,
175        ),
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn test_examples() {
185        use crate::test_examples;
186
187        test_examples(FormatFilesize)
188    }
189}