Skip to content

Commit

Permalink
Merge pull request #24 from ByteBaker/master
Browse files Browse the repository at this point in the history
feat: add support for `-I`
  • Loading branch information
sighol committed Sep 9, 2024
2 parents 986ce7c + 40bdadc commit 813546a
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 86 deletions.
57 changes: 2 additions & 55 deletions src/filter.rs
Original file line number Diff line number Diff line change
@@ -1,72 +1,19 @@
use std::collections::VecDeque;

use crate::pathiterator::{FileIterator, IteratorItem};

pub struct FilteredIterator {
pub source: FileIterator,
cache: VecDeque<IteratorItem>,
skip: bool,
next_item: Option<IteratorItem>,
}

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 }
}
}

impl Iterator for FilteredIterator {
type Item = IteratorItem;

fn next(&mut self) -> Option<Self::Item> {
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()
}
}
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
22 changes: 12 additions & 10 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 @@ -115,14 +119,12 @@ impl Iterator for FileIterator {
type Item = IteratorItem;

fn next(&mut self) -> Option<Self::Item> {
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
})
}
}
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
18 changes: 8 additions & 10 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,19 +166,15 @@ 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() {
list.skip_filter();
}

list
let iterator = pathiterator::FileIterator::new(path, config);
filter::FilteredIterator::new(iterator)
}

/// # Errors
Expand Down

0 comments on commit 813546a

Please sign in to comment.