diff --git a/src/filter.rs b/src/filter.rs index 286bf9a..130909a 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,37 +1,12 @@ -use std::collections::VecDeque; - use crate::pathiterator::{FileIterator, IteratorItem}; pub struct FilteredIterator { pub source: FileIterator, - cache: VecDeque, - skip: bool, - next_item: Option, } impl FilteredIterator { pub fn new(iterator: FileIterator) -> Self { - FilteredIterator { - source: iterator, - cache: VecDeque::new(), - skip: false, - next_item: None, - } - } - - pub fn skip_filter(&mut self) { - self.skip = true; - } - - /// Remove previous directories from cache that shouldn't be - /// shown because they are empty. - fn remove_empty_directories_from_cache(&mut self, item: &IteratorItem) { - while let Some(last) = self.cache.pop_back() { - if last.level < item.level { - self.cache.push_back(last); - break; - } - } + FilteredIterator { source: iterator } } } @@ -39,34 +14,6 @@ impl Iterator for FilteredIterator { type Item = IteratorItem; fn next(&mut self) -> Option { - if self.skip { - return self.source.next(); - } - - if let Some(cache_item) = self.cache.pop_front() { - return Some(cache_item); - } - - if let Some(next_item) = self.next_item.take() { - return Some(next_item); - } - - while let Some(item) = self.source.next() { - self.remove_empty_directories_from_cache(&item); - - if item.is_dir() { - self.cache.push_back(item); - } else { - // If the cache already contains a folder, start emptying cache, and - // save the item. - if let Some(cache_front) = self.cache.pop_front() { - self.next_item = Some(item); - return Some(cache_front); - } - return Some(item); - } - } - - None + self.source.next() } } diff --git a/src/main.rs b/src/main.rs index 48d8021..b5db7d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,10 @@ struct Args { dir: String, /// List only those files matching <`include_pattern`> #[clap(short = 'P')] - include_pattern: Option, + include_pattern: Vec, + /// Exclude any files matching <`exclude_pattern`> + #[clap(short = 'I')] + exclude_pattern: Vec, /// Descend only directories deep #[clap(short = 'L', long = "level", default_value_t = usize::max_value())] max_level: usize, @@ -48,20 +51,29 @@ impl TryFrom<&Args> for Config { type Error = String; fn try_from(value: &Args) -> Result { - let include_glob = value - .include_pattern - .as_deref() - .map(Glob::new) - .transpose() - .map_err(|e| format!("`include_pattern` is not valid: {e}"))? - .map(|glob| glob.compile_matcher()); + let mut include_globs = Vec::with_capacity(value.include_pattern.len()); + + for pattern in &value.include_pattern { + let glob = + Glob::new(pattern).map_err(|e| format!("`include_pattern` is not valid: {e}"))?; + include_globs.push(glob.compile_matcher()); + } + + let mut exlude_globs = Vec::with_capacity(value.exclude_pattern.len()); + + for pattern in &value.exclude_pattern { + let glob = + Glob::new(pattern).map_err(|e| format!("`exclude_pattern` is not valid: {e}"))?; + exlude_globs.push(glob.compile_matcher()); + } Ok(Config { use_color: value.color_on || !value.color_off, show_hidden: value.show_all, show_only_dirs: value.only_dirs, max_level: value.max_level, - include_glob, + include_globs, + exlude_globs, }) } } diff --git a/src/pathiterator.rs b/src/pathiterator.rs index 7b7adde..6e8ebe7 100644 --- a/src/pathiterator.rs +++ b/src/pathiterator.rs @@ -45,7 +45,8 @@ pub struct FileIteratorConfig { pub show_hidden: bool, pub show_only_dirs: bool, pub max_level: usize, - pub include_glob: Option, + pub include_globs: Vec, + pub exlude_globs: Vec, } #[derive(Debug)] @@ -82,10 +83,13 @@ impl FileIterator { } fn is_glob_included(&self, file_name: &str) -> bool { - self.config - .include_glob - .as_ref() - .map_or(true, |glob| glob.is_match(file_name)) + let incl = &self.config.include_globs; + let excl = &self.config.exlude_globs; + + let not_exclude = excl.is_empty() || excl.iter().all(|glob| !glob.is_match(file_name)); + let include = incl.is_empty() || incl.iter().any(|glob| glob.is_match(file_name)); + + not_exclude && include } fn is_included(&self, name: &str, is_dir: bool) -> bool { @@ -115,14 +119,12 @@ impl Iterator for FileIterator { type Item = IteratorItem; fn next(&mut self) -> Option { - if let Some(item) = self.queue.pop_back() { + self.queue.pop_back().map(|item| { if item.is_dir() && item.level < self.config.max_level { self.push_dir(&item); } - Some(item) - } else { - None - } + item + }) } } diff --git a/src/tests/test_simple.rs b/src/tests/test_simple.rs index 085c14b..c20325c 100644 --- a/src/tests/test_simple.rs +++ b/src/tests/test_simple.rs @@ -64,22 +64,55 @@ fn test_max_depth() { fn test_filter_txt_files() { let expected = r#"simple └── yyy + ├── k + ├── s ├── test.txt + └── zz + └── a + └── b "#; let (output, summary) = run_cmd( Path::new("tests/simple"), Config { - include_glob: Some(Glob::new("*.txt").unwrap().compile_matcher()), + include_globs: vec![Glob::new("*.txt").unwrap().compile_matcher()], ..Default::default() }, ); - assert_eq!(1, summary.num_folders); + + assert_eq!(6, summary.num_folders); assert_eq!(1, summary.num_files); assert_eq!(expected, output); } +#[test] +fn test_exclude_txt_files() { + let expected = r#"simple +└── yyy + ├── k + ├── s + │   ├── a + │   └── t + └── zz + └── a + └── b + └── c +"#; + + let (output, summary) = run_cmd( + Path::new("tests/simple"), + Config { + exlude_globs: vec![Glob::new("*.txt").unwrap().compile_matcher()], + ..Default::default() + }, + ); + + assert_eq!(6, summary.num_folders); + assert_eq!(3, summary.num_files); + assert_eq!(expected, output); +} + #[test] fn test_only_directories() { let expected = r#"simple diff --git a/src/tree_printer.rs b/src/tree_printer.rs index 81ecd31..5e46df1 100644 --- a/src/tree_printer.rs +++ b/src/tree_printer.rs @@ -118,7 +118,8 @@ pub struct Config { pub show_hidden: bool, pub show_only_dirs: bool, pub max_level: usize, - pub include_glob: Option, + pub include_globs: Vec, + pub exlude_globs: Vec, } impl Default for Config { @@ -128,7 +129,8 @@ impl Default for Config { show_hidden: false, show_only_dirs: false, max_level: usize::MAX, - include_glob: None, + include_globs: Vec::new(), + exlude_globs: Vec::new(), } } } @@ -164,19 +166,15 @@ impl<'a, T: Terminal, W: std::io::Write> TreePrinter<'a, T, W> { fn get_iterator(&self, path: &Path) -> filter::FilteredIterator { let config = pathiterator::FileIteratorConfig { - include_glob: self.config.include_glob.clone(), + include_globs: self.config.include_globs.clone(), + exlude_globs: self.config.exlude_globs.clone(), max_level: self.config.max_level, show_hidden: self.config.show_hidden, show_only_dirs: self.config.show_only_dirs, }; - let list = pathiterator::FileIterator::new(path, config); - let mut list = filter::FilteredIterator::new(list); - if self.config.include_glob.is_none() { - list.skip_filter(); - } - - list + let iterator = pathiterator::FileIterator::new(path, config); + filter::FilteredIterator::new(iterator) } /// # Errors