nu_command/filesystem/
du.rs

1use crate::{DirBuilder, DirInfo, FileInfo};
2use nu_engine::command_prelude::*;
3use nu_glob::{MatchOptions, Pattern};
4use nu_protocol::{NuGlob, Signals};
5use serde::Deserialize;
6use std::path::Path;
7
8#[derive(Clone)]
9pub struct Du;
10
11#[derive(Deserialize, Clone, Debug)]
12pub struct DuArgs {
13    path: Option<Spanned<NuGlob>>,
14    deref: bool,
15    long: bool,
16    all: bool,
17    exclude: Option<Spanned<NuGlob>>,
18    #[serde(rename = "max-depth")]
19    max_depth: Option<Spanned<i64>>,
20    #[serde(rename = "min-size")]
21    min_size: Option<Spanned<i64>>,
22}
23
24impl Command for Du {
25    fn name(&self) -> &str {
26        "du"
27    }
28
29    fn description(&self) -> &str {
30        "Find disk usage sizes of specified items."
31    }
32
33    fn signature(&self) -> Signature {
34        Signature::build("du")
35            .input_output_types(vec![(Type::Nothing, Type::table())])
36            .allow_variants_without_examples(true)
37            .rest(
38                "path",
39                SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
40                "Starting directory.",
41            )
42            .switch(
43                "deref",
44                "Dereference symlinks to their targets for size",
45                Some('r'),
46            )
47            .switch(
48                "long",
49                "Get underlying directories and files for each entry",
50                Some('l'),
51            )
52            .named(
53                "exclude",
54                SyntaxShape::GlobPattern,
55                "Exclude these file names",
56                Some('x'),
57            )
58            .named(
59                "max-depth",
60                SyntaxShape::Int,
61                "Directory recursion limit",
62                Some('d'),
63            )
64            .named(
65                "min-size",
66                SyntaxShape::Int,
67                "Exclude files below this size",
68                Some('m'),
69            )
70            .switch("all", "move hidden files if '*' is provided", Some('a'))
71            .category(Category::FileSystem)
72    }
73
74    fn run(
75        &self,
76        engine_state: &EngineState,
77        stack: &mut Stack,
78        call: &Call,
79        _input: PipelineData,
80    ) -> Result<PipelineData, ShellError> {
81        let tag = call.head;
82        let min_size: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "min-size")?;
83        let max_depth: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "max-depth")?;
84        if let Some(ref max_depth) = max_depth
85            && max_depth.item < 0
86        {
87            return Err(ShellError::NeedsPositiveValue {
88                span: max_depth.span,
89            });
90        }
91        if let Some(ref min_size) = min_size
92            && min_size.item < 0
93        {
94            return Err(ShellError::NeedsPositiveValue {
95                span: min_size.span,
96            });
97        }
98        let deref = call.has_flag(engine_state, stack, "deref")?;
99        let long = call.has_flag(engine_state, stack, "long")?;
100        let exclude = call.get_flag(engine_state, stack, "exclude")?;
101        let current_dir = engine_state.cwd(Some(stack))?.into_std_path_buf();
102        let all = call.has_flag(engine_state, stack, "all")?;
103
104        let paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
105        let paths = if !call.has_positional_args(stack, 0) {
106            None
107        } else {
108            Some(paths)
109        };
110
111        match paths {
112            None => {
113                let args = DuArgs {
114                    path: None,
115                    deref,
116                    long,
117                    all,
118                    exclude,
119                    max_depth,
120                    min_size,
121                };
122                Ok(
123                    du_for_one_pattern(args, &current_dir, tag, engine_state.signals().clone())?
124                        .into_pipeline_data(tag, engine_state.signals().clone()),
125                )
126            }
127            Some(paths) => {
128                let mut result_iters = vec![];
129                for p in paths {
130                    let args = DuArgs {
131                        path: Some(p),
132                        deref,
133                        long,
134                        all,
135                        exclude: exclude.clone(),
136                        max_depth,
137                        min_size,
138                    };
139                    result_iters.push(du_for_one_pattern(
140                        args,
141                        &current_dir,
142                        tag,
143                        engine_state.signals().clone(),
144                    )?)
145                }
146
147                // chain all iterators on result.
148                Ok(result_iters
149                    .into_iter()
150                    .flatten()
151                    .into_pipeline_data(tag, engine_state.signals().clone()))
152            }
153        }
154    }
155
156    fn examples(&self) -> Vec<Example<'_>> {
157        vec![Example {
158            description: "Disk usage of the current directory",
159            example: "du",
160            result: None,
161        }]
162    }
163}
164
165fn du_for_one_pattern(
166    args: DuArgs,
167    current_dir: &Path,
168    span: Span,
169    signals: Signals,
170) -> Result<impl Iterator<Item = Value> + Send + use<>, ShellError> {
171    let exclude = args.exclude.map_or(Ok(None), move |x| {
172        Pattern::new(x.item.as_ref())
173            .map(Some)
174            .map_err(|e| ShellError::InvalidGlobPattern {
175                msg: e.msg.into(),
176                span: x.span,
177            })
178    })?;
179    let glob_options = if args.all {
180        None
181    } else {
182        let glob_options = MatchOptions {
183            require_literal_leading_dot: true,
184            ..Default::default()
185        };
186        Some(glob_options)
187    };
188    let paths = match args.path {
189        Some(p) => nu_engine::glob_from(&p, current_dir, span, glob_options, signals.clone()),
190
191        // The * pattern should never fail.
192        None => nu_engine::glob_from(
193            &Spanned {
194                item: NuGlob::Expand("*".into()),
195                span: Span::unknown(),
196            },
197            current_dir,
198            span,
199            None,
200            signals.clone(),
201        ),
202    }
203    .map(|f| f.1)?;
204
205    let deref = args.deref;
206    let long = args.long;
207    let max_depth = args.max_depth.map(|f| f.item as u64);
208    let min_size = args.min_size.map(|f| f.item as u64);
209
210    let params = DirBuilder {
211        tag: span,
212        min: min_size,
213        deref,
214        exclude,
215        long,
216    };
217
218    Ok(paths.filter_map(move |p| match p {
219        Ok(a) => {
220            if a.is_dir() {
221                match DirInfo::new(a, &params, max_depth, span, &signals) {
222                    Ok(v) => Some(Value::from(v)),
223                    Err(_) => None,
224                }
225            } else {
226                match FileInfo::new(a, deref, span, params.long) {
227                    Ok(v) => Some(Value::from(v)),
228                    Err(_) => None,
229                }
230            }
231        }
232        Err(e) => Some(Value::error(e, span)),
233    }))
234}
235
236#[cfg(test)]
237mod tests {
238    use super::Du;
239
240    #[test]
241    fn examples_work_as_expected() {
242        use crate::test_examples;
243        test_examples(Du {})
244    }
245}