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