nu_command/platform/
dir_info.rs

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