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 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", "Include 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_with_metadata(
125                            tag,
126                            engine_state.signals().clone(),
127                            PipelineMetadata {
128                                path_columns: vec![String::from("path")],
129                                ..Default::default()
130                            },
131                        ),
132                )
133            }
134            Some(paths) => {
135                let mut result_iters = vec![];
136                for p in paths {
137                    let args = DuArgs {
138                        path: Some(p),
139                        deref,
140                        long,
141                        all,
142                        exclude: exclude.clone(),
143                        max_depth,
144                        min_size,
145                    };
146                    result_iters.push(du_for_one_pattern(
147                        args,
148                        &current_dir,
149                        tag,
150                        engine_state.signals().clone(),
151                    )?)
152                }
153
154                // chain all iterators on result.
155                Ok(result_iters
156                    .into_iter()
157                    .flatten()
158                    .into_pipeline_data_with_metadata(
159                        tag,
160                        engine_state.signals().clone(),
161                        PipelineMetadata {
162                            path_columns: vec![String::from("path")],
163                            ..Default::default()
164                        },
165                    ))
166            }
167        }
168    }
169
170    fn examples(&self) -> Vec<Example<'_>> {
171        vec![Example {
172            description: "Disk usage of the current directory.",
173            example: "du",
174            result: None,
175        }]
176    }
177}
178
179fn du_for_one_pattern(
180    args: DuArgs,
181    current_dir: &Path,
182    span: Span,
183    signals: Signals,
184) -> Result<impl Iterator<Item = Value> + Send + use<>, ShellError> {
185    let exclude = args.exclude.map_or(Ok(None), move |x| {
186        Pattern::new(x.item.as_ref())
187            .map(Some)
188            .map_err(|e| ShellError::InvalidGlobPattern {
189                msg: e.msg.into(),
190                span: x.span,
191            })
192    })?;
193    let glob_options = if args.all {
194        None
195    } else {
196        let glob_options = MatchOptions {
197            require_literal_leading_dot: true,
198            ..Default::default()
199        };
200        Some(glob_options)
201    };
202    let paths = match args.path {
203        Some(p) => nu_engine::glob_from(&p, current_dir, span, glob_options, signals.clone()),
204
205        // The * pattern should never fail.
206        None => nu_engine::glob_from(
207            &Spanned {
208                item: NuGlob::Expand("*".into()),
209                span: Span::unknown(),
210            },
211            current_dir,
212            span,
213            None,
214            signals.clone(),
215        ),
216    }
217    .map(|f| f.1)?;
218
219    let deref = args.deref;
220    let long = args.long;
221    let max_depth = args.max_depth.map(|f| f.item as u64);
222    let min_size = args.min_size.map(|f| f.item as u64);
223
224    let params = DirBuilder {
225        tag: span,
226        min: min_size,
227        deref,
228        exclude,
229        long,
230    };
231
232    Ok(paths.filter_map(move |p| match p {
233        Ok(a) => {
234            if a.is_dir() {
235                match DirInfo::new(a, &params, max_depth, span, &signals) {
236                    Ok(v) => Some(Value::from(v)),
237                    Err(_) => None,
238                }
239            } else {
240                match FileInfo::new(a, deref, span, params.long) {
241                    Ok(v) => Some(Value::from(v)),
242                    Err(_) => None,
243                }
244            }
245        }
246        Err(e) => Some(Value::error(e, span)),
247    }))
248}
249
250#[cfg(test)]
251mod tests {
252    use super::Du;
253
254    #[test]
255    fn examples_work_as_expected() {
256        use crate::test_examples;
257        test_examples(Du {})
258    }
259}