Skip to main content

nu_command/filesystem/
du.rs

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