nu_command/filesystem/
du.rs

1use crate::{DirBuilder, DirInfo, FileInfo};
2#[allow(deprecated)]
3use nu_engine::{command_prelude::*, current_dir};
4use nu_glob::Pattern;
5use nu_protocol::{NuGlob, Signals};
6use serde::Deserialize;
7use std::path::Path;
8
9#[derive(Clone)]
10pub struct Du;
11
12#[derive(Deserialize, Clone, Debug)]
13pub struct DuArgs {
14    path: Option<Spanned<NuGlob>>,
15    deref: bool,
16    long: 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            .category(Category::FileSystem)
71    }
72
73    fn run(
74        &self,
75        engine_state: &EngineState,
76        stack: &mut Stack,
77        call: &Call,
78        _input: PipelineData,
79    ) -> Result<PipelineData, ShellError> {
80        let tag = call.head;
81        let min_size: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "min-size")?;
82        let max_depth: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "max-depth")?;
83        if let Some(ref max_depth) = max_depth {
84            if max_depth.item < 0 {
85                return Err(ShellError::NeedsPositiveValue {
86                    span: max_depth.span,
87                });
88            }
89        }
90        if let Some(ref min_size) = min_size {
91            if min_size.item < 0 {
92                return Err(ShellError::NeedsPositiveValue {
93                    span: min_size.span,
94                });
95            }
96        }
97        let deref = call.has_flag(engine_state, stack, "deref")?;
98        let long = call.has_flag(engine_state, stack, "long")?;
99        let exclude = call.get_flag(engine_state, stack, "exclude")?;
100        #[allow(deprecated)]
101        let current_dir = current_dir(engine_state, stack)?;
102
103        let paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
104        let paths = if !call.has_positional_args(stack, 0) {
105            None
106        } else {
107            Some(paths)
108        };
109
110        match paths {
111            None => {
112                let args = DuArgs {
113                    path: None,
114                    deref,
115                    long,
116                    exclude,
117                    max_depth,
118                    min_size,
119                };
120                Ok(
121                    du_for_one_pattern(args, &current_dir, tag, engine_state.signals().clone())?
122                        .into_pipeline_data(tag, engine_state.signals().clone()),
123                )
124            }
125            Some(paths) => {
126                let mut result_iters = vec![];
127                for p in paths {
128                    let args = DuArgs {
129                        path: Some(p),
130                        deref,
131                        long,
132                        exclude: exclude.clone(),
133                        max_depth,
134                        min_size,
135                    };
136                    result_iters.push(du_for_one_pattern(
137                        args,
138                        &current_dir,
139                        tag,
140                        engine_state.signals().clone(),
141                    )?)
142                }
143
144                // chain all iterators on result.
145                Ok(result_iters
146                    .into_iter()
147                    .flatten()
148                    .into_pipeline_data(tag, engine_state.signals().clone()))
149            }
150        }
151    }
152
153    fn examples(&self) -> Vec<Example> {
154        vec![Example {
155            description: "Disk usage of the current directory",
156            example: "du",
157            result: None,
158        }]
159    }
160}
161
162fn du_for_one_pattern(
163    args: DuArgs,
164    current_dir: &Path,
165    span: Span,
166    signals: Signals,
167) -> Result<impl Iterator<Item = Value> + Send + use<>, ShellError> {
168    let exclude = args.exclude.map_or(Ok(None), move |x| {
169        Pattern::new(x.item.as_ref())
170            .map(Some)
171            .map_err(|e| ShellError::InvalidGlobPattern {
172                msg: e.msg.into(),
173                span: x.span,
174            })
175    })?;
176
177    let paths = match args.path {
178        Some(p) => nu_engine::glob_from(&p, current_dir, span, None, signals.clone()),
179
180        // The * pattern should never fail.
181        None => nu_engine::glob_from(
182            &Spanned {
183                item: NuGlob::Expand("*".into()),
184                span: Span::unknown(),
185            },
186            current_dir,
187            span,
188            None,
189            signals.clone(),
190        ),
191    }
192    .map(|f| f.1)?;
193
194    let deref = args.deref;
195    let long = args.long;
196    let max_depth = args.max_depth.map(|f| f.item as u64);
197    let min_size = args.min_size.map(|f| f.item as u64);
198
199    let params = DirBuilder {
200        tag: span,
201        min: min_size,
202        deref,
203        exclude,
204        long,
205    };
206
207    Ok(paths.filter_map(move |p| match p {
208        Ok(a) => {
209            if a.is_dir() {
210                match DirInfo::new(a, &params, max_depth, span, &signals) {
211                    Ok(v) => Some(Value::from(v)),
212                    Err(_) => None,
213                }
214            } else {
215                match FileInfo::new(a, deref, span, params.long) {
216                    Ok(v) => Some(Value::from(v)),
217                    Err(_) => None,
218                }
219            }
220        }
221        Err(e) => Some(Value::error(e, span)),
222    }))
223}
224
225#[cfg(test)]
226mod tests {
227    use super::Du;
228
229    #[test]
230    fn examples_work_as_expected() {
231        use crate::test_examples;
232        test_examples(Du {})
233    }
234}