1use filesize::file_real_size_fast;
2use nu_protocol::{ShellError, Signals, Span, Value, record, shell_error::io::IoError};
3use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone)]
8pub enum ExcludeGlob {
9 Legacy(nu_glob::Pattern),
10 DcGlob(nu_glob::dc_glob::DcPattern),
11}
12
13impl ExcludeGlob {
14 pub fn matches_path(&self, path: &Path) -> bool {
15 match self {
16 ExcludeGlob::Legacy(p) => p.matches_path(path),
17 ExcludeGlob::DcGlob(p) => p.matches_path(path),
18 }
19 }
20}
21
22#[derive(Debug, Clone)]
23pub struct DirBuilder {
24 pub tag: Span,
25 pub min: Option<u64>,
26 pub deref: bool,
27 pub exclude: Option<ExcludeGlob>,
28 pub long: bool,
29}
30
31impl DirBuilder {
32 pub fn new(
33 tag: Span,
34 min: Option<u64>,
35 deref: bool,
36 exclude: Option<ExcludeGlob>,
37 long: bool,
38 ) -> DirBuilder {
39 DirBuilder {
40 tag,
41 min,
42 deref,
43 exclude,
44 long,
45 }
46 }
47}
48
49#[derive(Debug, Clone)]
50pub struct DirInfo {
51 dirs: Vec<DirInfo>,
52 files: Vec<FileInfo>,
53 errors: Vec<ShellError>,
54 size: u64,
55 blocks: u64,
56 path: PathBuf,
57 tag: Span,
58 long: bool,
59}
60
61#[derive(Debug, Clone)]
62pub struct FileInfo {
63 path: PathBuf,
64 size: u64,
65 blocks: Option<u64>,
66 tag: Span,
67 long: bool,
68}
69
70impl FileInfo {
71 pub fn new(
72 path: impl Into<PathBuf>,
73 deref: bool,
74 tag: Span,
75 long: bool,
76 ) -> Result<Self, ShellError> {
77 let path = path.into();
78 let m = if deref {
79 std::fs::metadata(&path)
80 } else {
81 std::fs::symlink_metadata(&path)
82 };
83
84 match m {
85 Ok(d) => {
86 let block_size = file_real_size_fast(&path, &d).ok();
87
88 Ok(FileInfo {
89 path,
90 blocks: block_size,
91 size: d.len(),
92 tag,
93 long,
94 })
95 }
96 Err(e) => Err(IoError::new(e, tag, path).into()),
97 }
98 }
99}
100
101impl DirInfo {
102 pub fn new(
103 path: impl Into<PathBuf>,
104 params: &DirBuilder,
105 depth: Option<u64>,
106 span: Span,
107 signals: &Signals,
108 ) -> Result<Self, ShellError> {
109 let path = path.into();
110 let from_io_error = IoError::factory(span, path.as_path());
111
112 let mut s = Self {
113 dirs: Vec::new(),
114 errors: Vec::new(),
115 files: Vec::new(),
116 size: 0,
117 blocks: 0,
118 tag: params.tag,
119 path: path.clone(),
120 long: params.long,
121 };
122
123 match std::fs::metadata(&s.path) {
124 Ok(d) => {
125 s.size = d.len(); s.blocks = file_real_size_fast(&s.path, &d).ok().unwrap_or(0);
127 }
128 Err(e) => s = s.add_error(from_io_error(e).into()),
129 };
130
131 match std::fs::read_dir(&s.path) {
132 Ok(d) => {
133 for f in d {
134 signals.check(&span)?;
135
136 match f {
137 Ok(i) => match i.file_type() {
138 Ok(t) if t.is_dir() => {
139 s = s.add_dir(i.path(), depth, params, span, signals)?
140 }
141 Ok(_t) => s = s.add_file(i.path(), params),
142 Err(e) => s = s.add_error(from_io_error(e).into()),
143 },
144 Err(e) => s = s.add_error(from_io_error(e).into()),
145 }
146 }
147 }
148 Err(e) => s = s.add_error(from_io_error(e).into()),
149 }
150 Ok(s)
151 }
152
153 fn add_dir(
154 mut self,
155 path: impl Into<PathBuf>,
156 mut depth: Option<u64>,
157 params: &DirBuilder,
158 span: Span,
159 signals: &Signals,
160 ) -> Result<Self, ShellError> {
161 if let Some(current) = depth {
162 if let Some(new) = current.checked_sub(1) {
163 depth = Some(new);
164 } else {
165 return Ok(self);
166 }
167 }
168
169 let d = DirInfo::new(path, params, depth, span, signals)?;
170 self.size += d.size;
171 self.blocks += d.blocks;
172 self.dirs.push(d);
173 Ok(self)
174 }
175
176 fn add_file(mut self, f: impl Into<PathBuf>, params: &DirBuilder) -> Self {
177 let f = f.into();
178 let include = params.exclude.as_ref().is_none_or(|x| !x.matches_path(&f));
179 if include {
180 match FileInfo::new(f, params.deref, self.tag, self.long) {
181 Ok(file) => {
182 let inc = params.min.is_none_or(|s| file.size >= s);
183 if inc {
184 self.size += file.size;
185 self.blocks += file.blocks.unwrap_or(0);
186 if params.long {
187 self.files.push(file);
188 }
189 }
190 }
191 Err(e) => self = self.add_error(e),
192 }
193 }
194 self
195 }
196
197 fn add_error(mut self, e: ShellError) -> Self {
198 self.errors.push(e);
199 self
200 }
201
202 pub fn get_size(&self) -> u64 {
203 self.size
204 }
205}
206
207impl From<DirInfo> for Value {
208 fn from(d: DirInfo) -> Self {
209 if d.long {
224 Value::record(
225 record! {
226 "path" => Value::string(d.path.display().to_string(), d.tag),
227 "apparent" => Value::filesize(d.size as i64, d.tag),
228 "physical" => Value::filesize(d.blocks as i64, d.tag),
229 "directories" => value_from_vec(d.dirs, d.tag),
230 "files" => value_from_vec(d.files, d.tag)
231 },
232 d.tag,
233 )
234 } else {
235 Value::record(
236 record! {
237 "path" => Value::string(d.path.display().to_string(), d.tag),
238 "apparent" => Value::filesize(d.size as i64, d.tag),
239 "physical" => Value::filesize(d.blocks as i64, d.tag),
240 },
241 d.tag,
242 )
243 }
244 }
245}
246
247impl From<FileInfo> for Value {
248 fn from(f: FileInfo) -> Self {
249 if f.long {
253 Value::record(
254 record! {
255 "path" => Value::string(f.path.display().to_string(), f.tag),
256 "apparent" => Value::filesize(f.size as i64, f.tag),
257 "physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag),
258 "directories" => Value::nothing(f.tag),
259 "files" => Value::nothing(f.tag),
260 },
261 f.tag,
262 )
263 } else {
264 Value::record(
265 record! {
266 "path" => Value::string(f.path.display().to_string(), f.tag),
267 "apparent" => Value::filesize(f.size as i64, f.tag),
268 "physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag),
269 },
270 f.tag,
271 )
272 }
273 }
274}
275
276fn value_from_vec<V>(vec: Vec<V>, tag: Span) -> Value
277where
278 V: Into<Value>,
279{
280 if vec.is_empty() {
281 Value::nothing(tag)
282 } else {
283 let values = vec.into_iter().map(Into::into).collect::<Vec<Value>>();
284 Value::list(values, tag)
285 }
286}