ls_option/
option.rs

1use std::{ffi::OsStr, path::Path};
2
3#[derive(Clone, Debug)]
4pub struct ListOption {
5    // if true, list directories
6    dir: bool,
7    // if true, list files
8    file: bool,
9    // if true, show hidden files
10    hidden: bool,
11    // if true,show unhidden files
12    unhidden: bool,
13    // if true, list recursively
14    recursive: bool,
15    // default 1, list only current directory
16    level: usize,
17    // if not empty, list only files with these extensions
18    sufs: Vec<String>,
19}
20// Default implementation for ListOption
21impl Default for ListOption {
22    fn default() -> Self {
23        Self {
24            dir: true,
25            file: true,
26            hidden: false,
27            unhidden: true,
28            recursive: false,
29            level: 1,
30            sufs: Vec::new(),
31        }
32    }
33}
34
35/// specify the list option
36impl ListOption {
37    pub fn new() -> Self {
38        Self::default()
39    }
40    /// set if allow this option to show directories
41    pub fn dir(&mut self, if_show: bool) -> &mut Self {
42        self.dir = if_show;
43        self
44    }
45
46    /// set if allow this option to show files
47    pub fn file(&mut self, if_show: bool) -> &mut Self {
48        self.file = if_show;
49        self
50    }
51
52    /// set if allow this option to show hidden files
53    pub fn hidden(&mut self, if_show: bool) -> &mut Self {
54        self.hidden = if_show;
55        self
56    }
57
58    /// set if allow this option to show unhidden files
59    pub fn unhidden(&mut self, if_show: bool) -> &mut Self {
60        self.unhidden = if_show;
61        self
62    }
63
64    /// set the level of recursion while listing files in some path
65    pub fn level(&mut self, level: usize) -> &mut Self {
66        self.level = level;
67        self
68    }
69
70    /// set if allow this option to list recursively
71    pub fn recursive(&mut self, if_choose: bool) -> &mut Self {
72        self.recursive = if_choose;
73        self
74    }
75
76    /// add one ext to the list of allowed extensions
77    ///
78    /// only files with one of these extensions will be listed
79    ///
80    /// e.g. ext("rs") will allow files with .rs extension to be listed
81    ///
82    /// e.g. ext("rs").ext("toml") will allow files with .rs and .toml extensions to be listed
83    pub fn ext(&mut self, ext: &str) -> &mut Self {
84        self.sufs.push(format!(".{}", ext));
85        self
86    }
87
88    /// add multiple exts to the list of allowed extensions
89    ///
90    /// only files with one of these extensions will be listed
91    ///
92    /// but this function will shadow the previous sufs
93    ///
94    /// e.g. exts(vec!["rs", "toml"]) will allow files with .rs and .toml extensions to be listed
95    ///
96    /// e.g. exts(vec!["rs"]).exts(vec!["toml"]) will only allow files with .toml extensions to be listed
97    ///
98    pub fn exts(&mut self, exts: Vec<&str>) -> &mut Self {
99        self.sufs = exts.iter().map(|s| format!(".{}", s)).collect();
100        self
101    }
102
103    /// add one suf to the list of allowed suffixes,
104    /// only files with one of these suffixes will be listed
105    ///
106    /// e.g. suf("rs") will allow files with rs suffix to be listed
107    ///
108    /// notice that exts function will shadow the previous sufs
109    ///
110    /// e.g. suf(".rs").ext("toml") will only allow files with .rs and .toml extensions to be listed
111    ///
112    /// e.g. suf(".rs").suf(".toml") will only allow files with .toml extensions to be listed
113    ///
114    /// e.g. suf("rs").exts(vec!["toml"]) will only allow files with .toml extensions to be listed
115    pub fn suf(&mut self, suf: &str) -> &mut Self {
116        self.sufs.push(suf.to_string());
117        self
118    }
119
120    /// add multiple sufs to the list of allowed suffixes
121    ///
122    /// only files with one of these suffixes will be listed
123    ///
124    /// but this function will shadow the previous sufs
125    ///
126    /// e.g. sufs(vec!["rs", "toml"]) will allow files with rs and toml suffixes to be listed
127    ///
128    /// e.g. sufs(vec![".rs"]).sufs(vec![".toml"]) will only allow files with .toml extensions to be listed
129    ///
130    /// e.g. sufs(vec![".rs"]).ext("toml") will allow files with .rs or .toml extension to be listed
131    ///
132    /// e.g. sufs(vec!["rs"]).exts(vec!["toml"]) will only allow files with .toml extensions to be listed
133    ///
134    pub fn sufs(&mut self, sufs: Vec<&str>) -> &mut Self {
135        self.sufs = sufs.iter().map(|s| s.to_string()).collect();
136        self
137    }
138}
139
140impl ListOption {
141    pub fn only_dir(&mut self) -> &mut Self {
142        self.file = false;
143        self.dir = true;
144        self
145    }
146    pub fn only_file(&mut self) -> &mut Self {
147        self.file = true;
148        self.dir = false;
149        self
150    }
151    pub fn only_hidden(&mut self) -> &mut Self {
152        self.hidden = true;
153        self.unhidden = false;
154        self
155    }
156    pub fn only_unhidden(&mut self) -> &mut Self {
157        self.hidden = false;
158        self.unhidden = true;
159        self
160    }
161}
162
163/// impl list functionality
164impl ListOption {
165    /// Lists the files and directories at the given path according to the options set in the ListOption
166    ///
167    /// if the path is a file, it will be listed if it matches the options set in the ListOption
168    ///
169    /// if the path is a directory, all files and directories in it will be listed if they match the options set in the ListOption
170    pub fn list<S>(&self, path: &S) -> Vec<String>
171    where
172        S: AsRef<OsStr> + ?Sized,
173    {
174        let mut ret: Vec<String> = Vec::new();
175        if self.level == 0 {
176            return ret;
177        }
178        let path = Path::new(path);
179        if self.would_show(path) {
180            ret.push(path.to_str().unwrap().to_string());
181        }
182        if path.is_file() {
183            return ret;
184        }
185        if path.is_dir() {
186            // if is a directory, list all files and directories in it
187            for entry in path.read_dir().unwrap() {
188                let entry = entry.unwrap();
189                let path = entry.path();
190                let mut sub_option = self.clone();
191                if !self.recursive {
192                    sub_option.level = if self.level == 0 { 0 } else { self.level - 1 };
193                }
194                if self.would_show(&path) {
195                    ret.push(path.to_str().unwrap().to_string());
196                }
197                ret.extend(sub_option.inner_list(&path));
198            }
199        }
200        ret
201    }
202    fn inner_list(&self, path: &Path) -> Vec<String> {
203        let mut ret: Vec<String> = Vec::new();
204        if self.level == 0 {
205            return ret;
206        }
207        if path.is_dir() {
208            // if is a directory, list all files and directories in it
209            for entry in path.read_dir().unwrap() {
210                let entry = entry.unwrap();
211                let path = entry.path();
212                let mut sub_option = self.clone();
213                if !self.recursive {
214                    sub_option.level = if self.level == 0 { 0 } else { self.level - 1 };
215                }
216                if self.would_show(&path) {
217                    ret.push(path.to_str().unwrap().to_string());
218                }
219                ret.extend(sub_option.inner_list(&path));
220            }
221        }
222        ret
223    }
224
225    /// check if the path would be shown according to the options set in the ListOption
226    pub fn would_show<S>(&self, path: &S) -> bool
227    where
228        S: AsRef<OsStr> + ?Sized,
229    {
230        let check_hidden = |path: &Path| {
231            let base_name = path.file_name().unwrap().to_str().unwrap();
232            if self.hidden && base_name.starts_with('.') {
233                true
234            } else {
235                self.unhidden && !base_name.starts_with('.')
236            }
237        };
238        let check_file_dir =
239            |path: &Path| (path.is_file() && self.file) || (path.is_dir() && self.dir);
240        let check_level = || self.recursive || self.level > 0;
241        let check_ext = |path: &Path| {
242            self.sufs.is_empty()
243                || self
244                    .sufs
245                    .iter()
246                    .any(|suf| path.to_str().unwrap().ends_with(suf))
247        };
248        let path = Path::new(path);
249        if !path.exists() {
250            return false;
251        }
252        let path = &path.canonicalize().unwrap();
253        path.exists()
254            && check_hidden(path)
255            && check_file_dir(path)
256            && check_level()
257            && check_ext(path)
258    }
259}