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 search_terms(&self) -> Vec<&str> {
34 vec!["disk", "usage", "size", "space"]
35 }
36
37 fn signature(&self) -> Signature {
38 Signature::build("du")
39 .input_output_types(vec![(Type::Nothing, Type::table())])
40 .allow_variants_without_examples(true)
41 .rest(
42 "path",
43 SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
44 "Starting directory.",
45 )
46 .switch(
47 "deref",
48 "Dereference symlinks to their targets for size.",
49 Some('r'),
50 )
51 .switch(
52 "long",
53 "Get underlying directories and files for each entry.",
54 Some('l'),
55 )
56 .named(
57 "exclude",
58 SyntaxShape::GlobPattern,
59 "Exclude these file names.",
60 Some('x'),
61 )
62 .named(
63 "max-depth",
64 SyntaxShape::Int,
65 "Directory recursion limit.",
66 Some('d'),
67 )
68 .named(
69 "min-size",
70 SyntaxShape::Int,
71 "Exclude files below this size.",
72 Some('m'),
73 )
74 .switch("all", "Include hidden files if '*' is provided.", Some('a'))
75 .category(Category::FileSystem)
76 }
77
78 fn run(
79 &self,
80 engine_state: &EngineState,
81 stack: &mut Stack,
82 call: &Call,
83 _input: PipelineData,
84 ) -> Result<PipelineData, ShellError> {
85 let tag = call.head;
86 let min_size: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "min-size")?;
87 let max_depth: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "max-depth")?;
88 if let Some(ref max_depth) = max_depth
89 && max_depth.item < 0
90 {
91 return Err(ShellError::NeedsPositiveValue {
92 span: max_depth.span,
93 });
94 }
95 if let Some(ref min_size) = min_size
96 && min_size.item < 0
97 {
98 return Err(ShellError::NeedsPositiveValue {
99 span: min_size.span,
100 });
101 }
102 let deref = call.has_flag(engine_state, stack, "deref")?;
103 let long = call.has_flag(engine_state, stack, "long")?;
104 let exclude = call.get_flag(engine_state, stack, "exclude")?;
105 let current_dir = engine_state.cwd(Some(stack))?.into_std_path_buf();
106 let all = call.has_flag(engine_state, stack, "all")?;
107
108 let paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
109 let paths = if !call.has_positional_args(stack, 0) {
110 None
111 } else {
112 Some(paths)
113 };
114
115 match paths {
116 None => {
117 let args = DuArgs {
118 path: None,
119 deref,
120 long,
121 all,
122 exclude,
123 max_depth,
124 min_size,
125 };
126 Ok(
127 du_for_one_pattern(args, ¤t_dir, tag, engine_state.signals().clone())?
128 .into_pipeline_data_with_metadata(
129 tag,
130 engine_state.signals().clone(),
131 PipelineMetadata {
132 path_columns: vec![String::from("path")],
133 ..Default::default()
134 },
135 ),
136 )
137 }
138 Some(paths) => {
139 let mut result_iters = vec![];
140 for p in paths {
141 let args = DuArgs {
142 path: Some(p),
143 deref,
144 long,
145 all,
146 exclude: exclude.clone(),
147 max_depth,
148 min_size,
149 };
150 result_iters.push(du_for_one_pattern(
151 args,
152 ¤t_dir,
153 tag,
154 engine_state.signals().clone(),
155 )?)
156 }
157
158 Ok(result_iters
160 .into_iter()
161 .flatten()
162 .into_pipeline_data_with_metadata(
163 tag,
164 engine_state.signals().clone(),
165 PipelineMetadata {
166 path_columns: vec![String::from("path")],
167 ..Default::default()
168 },
169 ))
170 }
171 }
172 }
173
174 fn examples(&self) -> Vec<Example<'_>> {
175 vec![Example {
176 description: "Disk usage of the current directory.",
177 example: "du",
178 result: None,
179 }]
180 }
181}
182
183fn du_for_one_pattern(
184 args: DuArgs,
185 current_dir: &Path,
186 span: Span,
187 signals: Signals,
188) -> Result<impl Iterator<Item = Value> + Send + use<>, ShellError> {
189 let exclude = args.exclude.map_or(Ok(None), move |x| {
190 Pattern::new(x.item.as_ref())
191 .map(Some)
192 .map_err(|e| ShellError::InvalidGlobPattern {
193 msg: e.msg.into(),
194 span: x.span,
195 })
196 })?;
197 let glob_options = if args.all {
198 None
199 } else {
200 let glob_options = MatchOptions {
201 require_literal_leading_dot: true,
202 ..Default::default()
203 };
204 Some(glob_options)
205 };
206 let paths = match args.path {
207 Some(p) => nu_engine::glob_from(&p, current_dir, span, glob_options, signals.clone()),
208
209 None => nu_engine::glob_from(
211 &Spanned {
212 item: NuGlob::Expand("*".into()),
213 span,
214 },
215 current_dir,
216 span,
217 None,
218 signals.clone(),
219 ),
220 }
221 .map(|f| f.1)?;
222
223 let deref = args.deref;
224 let long = args.long;
225 let max_depth = args.max_depth.map(|f| f.item as u64);
226 let min_size = args.min_size.map(|f| f.item as u64);
227
228 let params = DirBuilder {
229 tag: span,
230 min: min_size,
231 deref,
232 exclude,
233 long,
234 };
235
236 Ok(paths.filter_map(move |p| match p {
237 Ok(a) => {
238 if a.is_dir() {
239 match DirInfo::new(a, ¶ms, max_depth, span, &signals) {
240 Ok(v) => Some(Value::from(v)),
241 Err(_) => None,
242 }
243 } else {
244 match FileInfo::new(a, deref, span, params.long) {
245 Ok(v) => Some(Value::from(v)),
246 Err(_) => None,
247 }
248 }
249 }
250 Err(e) => Some(Value::error(e, span)),
251 }))
252}
253
254#[cfg(test)]
255mod tests {
256 use super::Du;
257
258 #[test]
259 fn examples_work_as_expected() -> nu_test_support::Result {
260 nu_test_support::test().examples(Du)
261 }
262}