Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for -I #24

Merged
merged 2 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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