Skip to content

Commit

Permalink
feat: add support for -I
Browse files Browse the repository at this point in the history
- also add test case
- remove `fn remove_empty_directories_from_cache` as `tree` command
  displays empty directories
- doing the above fixed another test case
- change type of CLI parameter for `-P` from `Option<String>` to
  `Vec<String>` to be in accordance with `tree` command
- fix expected result test `test_filter_txt_files`

TODO:
Test `test_filter_txt_files` still breaks. The `expected` value is based
on `tree`'s output, therefore it's correct. The logic in this crate
needs to be validated and corrected
  • Loading branch information
ByteBaker committed Sep 3, 2024
1 parent 986ce7c commit 1117d65
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 36 deletions.
19 changes: 3 additions & 16 deletions src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,6 @@ impl FilteredIterator {
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;
}
}
}
}

impl Iterator for FilteredIterator {
Expand All @@ -51,14 +40,12 @@ impl Iterator for FilteredIterator {
return Some(next_item);
}

while let Some(item) = self.source.next() {
self.remove_empty_directories_from_cache(&item);

for item in self.source.by_ref() {
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 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);
Expand Down
30 changes: 21 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ struct Args {
dir: String,
/// List only those files matching <`include_pattern`>
#[clap(short = 'P')]
include_pattern: Option<String>,
include_pattern: Vec<String>,
/// Exclude any files matching <`exclude_pattern`>
#[clap(short = 'I')]
exclude_pattern: Vec<String>,
/// Descend only <level> directories deep
#[clap(short = 'L', long = "level", default_value_t = usize::max_value())]
max_level: usize,
Expand All @@ -48,20 +51,29 @@ impl TryFrom<&Args> for Config {
type Error = String;

fn try_from(value: &Args) -> Result<Self, Self::Error> {
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,
})
}
}
Expand Down
15 changes: 10 additions & 5 deletions src/pathiterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ pub struct FileIteratorConfig {
pub show_hidden: bool,
pub show_only_dirs: bool,
pub max_level: usize,
pub include_glob: Option<GlobMatcher>,
pub include_globs: Vec<GlobMatcher>,
pub exlude_globs: Vec<GlobMatcher>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -117,6 +121,7 @@ impl Iterator for FileIterator {
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.queue.pop_back() {
if item.is_dir() && item.level < self.config.max_level {
// println!("{:?}", item.file_name);
self.push_dir(&item);
}

Expand Down
37 changes: 35 additions & 2 deletions src/tests/test_simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions src/tree_printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ pub struct Config {
pub show_hidden: bool,
pub show_only_dirs: bool,
pub max_level: usize,
pub include_glob: Option<GlobMatcher>,
pub include_globs: Vec<GlobMatcher>,
pub exlude_globs: Vec<GlobMatcher>,
}

impl Default for Config {
Expand All @@ -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(),
}
}
}
Expand Down Expand Up @@ -164,15 +166,16 @@ impl<'a, T: Terminal<Output = W>, 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() {
if self.config.include_globs.is_empty() && self.config.exlude_globs.is_empty() {
list.skip_filter();
}

Expand Down

0 comments on commit 1117d65

Please sign in to comment.