use {
anyhow::Result,
glob::Pattern,
std::path::{
Path,
PathBuf,
},
};
mod git_ignorer;
mod glob_ignorer;
pub use {
git_ignorer::GitIgnorer,
glob_ignorer::GlobIgnorer,
};
pub(crate) fn build_glob_patterns(
pattern: &str,
root: &Path,
) -> Result<Vec<Pattern>> {
let patterns = expand_patterns(pattern, root);
let mut glob_patterns = Vec::with_capacity(patterns.len());
for p in patterns {
glob_patterns.push(Pattern::new(&p)?);
}
Ok(glob_patterns)
}
fn expand_patterns(
pattern: &str,
root: &Path,
) -> Vec<String> {
let mut patterns_over_start = Vec::new();
let starts_with_slash = pattern.starts_with('/');
let ends_in_slash = pattern.ends_with('/');
if starts_with_slash {
let without_slash = pattern.trim_start_matches('/');
patterns_over_start.push(root.join(without_slash).to_string_lossy().to_string());
patterns_over_start.push(pattern.to_string());
} else if pattern.starts_with("**/") {
patterns_over_start.push(pattern.to_string());
} else {
patterns_over_start.push(format!("**/{pattern}"));
}
let mut patterns = Vec::new();
for p in patterns_over_start {
if !pattern.ends_with('*') {
let complement = if ends_in_slash { "**" } else { "/**" };
patterns.push(format!("{p}{complement}"));
}
if !ends_in_slash {
patterns.push(p);
}
}
patterns
}
pub trait Ignorer {
fn excludes(
&mut self,
paths: &Path,
) -> Result<bool>;
}
#[derive(Default)]
pub struct IgnorerSet {
ignorers: Vec<Box<dyn Ignorer + Send>>,
override_globs: Vec<Pattern>,
}
impl IgnorerSet {
pub fn add(
&mut self,
ignorer: Box<dyn Ignorer + Send>,
) {
self.ignorers.push(ignorer);
}
pub fn add_override(
&mut self,
pattern: &str,
root: &Path,
) -> Result<()> {
self.override_globs
.extend(build_glob_patterns(pattern, root)?);
Ok(())
}
fn is_overridden(
&self,
path: &Path,
) -> bool {
for glob in &self.override_globs {
if glob.matches_path(path) {
return true;
}
}
false
}
pub fn excludes_all_pathbufs(
&mut self,
paths: &[PathBuf],
) -> Result<bool> {
if self.ignorers.is_empty() {
return Ok(false);
}
for path in paths {
if self.is_overridden(path) {
return Ok(false);
}
let mut excluded = false;
for ignorer in &mut self.ignorers {
if ignorer.excludes(path)? {
excluded = true;
break;
}
}
if !excluded {
return Ok(false);
}
}
Ok(true)
}
}
#[test]
fn test_expand_patterns() {
assert_eq!(
expand_patterns("foo/bar", Path::new("/root")),
vec!["**/foo/bar/**".to_string(), "**/foo/bar".to_string(),]
);
assert_eq!(
expand_patterns("foo/bar/", Path::new("/root")),
vec!["**/foo/bar/**".to_string(),]
);
assert_eq!(
expand_patterns("/foo", Path::new("/root")),
vec![
"/root/foo/**".to_string(),
"/root/foo".to_string(),
"/foo/**".to_string(),
"/foo".to_string(),
]
);
assert_eq!(
expand_patterns("**/toto", Path::new("/root")),
vec!["**/toto/**".to_string(), "**/toto".to_string(),]
);
assert_eq!(
expand_patterns("toto/**", Path::new("/root")),
vec!["**/toto/**".to_string(),]
);
assert_eq!(
expand_patterns("/foo/bar/*", Path::new("/root")),
vec!["/root/foo/bar/*".to_string(), "/foo/bar/*".to_string(),]
);
assert_eq!(
expand_patterns("foo/**/bar/*", Path::new("/root")),
vec!["**/foo/**/bar/*".to_string(),]
);
}