use std::path;
use crate::{utils, Builder, GlobSet, Matcher};
fn extract_patterns<T>(candidates: Vec<Result<T, String>>) -> Result<Vec<T>, String> {
let failures: Vec<_> = candidates
.iter()
.filter_map(|f| match f {
Ok(_) => None,
Err(e) => Some(e),
})
.collect();
if !failures.is_empty() {
return Err(format!(
"Failed to compile patterns: \n{}",
failures
.iter()
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n"),
));
}
Ok(candidates.into_iter().flatten().collect())
}
pub fn build_matchers<'a, P>(
globs: &[&'a str],
root: P,
) -> Result<Vec<Matcher<'a, path::PathBuf>>, String>
where
P: AsRef<path::Path>,
{
let candidates: Vec<Result<_, String>> = globs
.iter()
.map(|pattern| {
Builder::new(pattern)
.case_sensitive(!cfg!(windows))
.build(root.as_ref())
})
.collect();
let candidates = extract_patterns(candidates)?;
Ok(candidates)
}
pub fn build_glob_set<'a>(
paths: &Option<Vec<&'a str>>,
case_sensitive: bool,
) -> Result<Option<Vec<GlobSet<'a>>>, String> {
let paths = match paths {
None => None,
Some(paths_) => {
let candidates: Vec<Result<_, String>> = paths_
.iter()
.map(|pattern| {
Builder::new(pattern)
.case_sensitive(case_sensitive)
.build_glob_set()
})
.collect();
Some(extract_patterns(candidates)?)
}
};
Ok(paths)
}
pub fn match_paths<P>(
candidates: Vec<Matcher<'_, P>>,
filter_entry: Option<Vec<GlobSet<'_>>>,
filter_post: Option<Vec<GlobSet<'_>>>,
) -> (Vec<path::PathBuf>, Vec<path::PathBuf>)
where
P: AsRef<path::Path>,
{
let mut filtered = vec![];
let paths = candidates
.into_iter()
.flat_map(|m| {
m.into_iter()
.filter_entry(|path| {
match &filter_entry {
Some(patterns) => {
let do_filter = patterns
.iter()
.try_for_each(|glob| match glob.is_match(path) {
true => None, false => Some(()), })
.is_none(); !do_filter
}
_ => !utils::is_hidden_entry(path), }
})
.flatten()
.collect::<Vec<_>>()
})
.filter(|path| match &filter_post {
None => true,
Some(patterns) => {
let do_filter = patterns
.iter()
.try_for_each(|glob| match glob.is_match(path) {
true => None, false => Some(()), })
.is_none(); if do_filter {
filtered.push(path::PathBuf::from(path));
}
!do_filter
}
});
let mut paths: Vec<_> = paths.collect();
paths.sort_unstable();
paths.dedup();
log::debug!(
"paths \n{}",
paths
.iter()
.map(|p| format!("{}", p.canonicalize().unwrap().to_string_lossy()))
.collect::<Vec<_>>()
.join("\n")
);
filtered.sort_unstable();
filtered.dedup();
if !filtered.is_empty() {
log::debug!(
"filtered \n{}",
filtered
.iter()
.map(|p| format!("{}", p.canonicalize().unwrap().to_string_lossy()))
.collect::<Vec<_>>()
.join("\n")
);
}
(paths, filtered)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_foreach() {
let items = vec![0u8, 1u8, 2u8];
let filter: Vec<u8> = vec![];
let filter_zero: Vec<_> = items
.iter()
.filter(|item| {
let do_filter = filter
.iter()
.try_for_each(|filter_item| {
if *filter_item == **item {
None } else {
Some(()) }
})
.is_none(); !do_filter
})
.cloned()
.collect();
assert_eq!(filter_zero, items);
}
#[test]
fn test_usecase() -> Result<(), String> {
fn log_paths<P>(paths: &[P])
where
P: AsRef<path::Path>,
{
println!(
"paths:\n{}",
paths
.iter()
.map(|p| format!("{}", p.as_ref().to_string_lossy()))
.collect::<Vec<_>>()
.join("\n")
);
}
let root = env!("CARGO_MANIFEST_DIR");
let patterns = vec![
"test-files/c-simple/**/[aA]*.txt",
"test-files/c-simple/**/*.md",
];
let filter_entry = Some(vec![".*"]);
let filter_post = Some(vec![
"test-files/c-simple/**/a1/*.txt",
"test-files/c-simple/**/a0/*.*",
]);
let candidates = build_matchers(&patterns, root)?;
let filter_pre = build_glob_set(&filter_entry, !cfg!(windows))?;
let filter_post = build_glob_set(&filter_post, !cfg!(windows))?;
let (paths, filtered) = match_paths(candidates, filter_pre, filter_post);
log_paths(&paths);
log_paths(&filtered);
assert_eq!(1, paths.len());
assert_eq!(5, filtered.len());
Ok(())
}
}