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, ¤t_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 ¤t_dir,
149 tag,
150 engine_state.signals().clone(),
151 )?)
152 }
153
154 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 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, ¶ms, 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}