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, ¤t_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 ¤t_dir,
142 tag,
143 engine_state.signals().clone(),
144 )?)
145 }
146
147 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 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, ¶ms, 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}