use std::borrow::Cow;
use std::fs;
use std::iter::empty;
use std::path::Path;
use globset::{GlobBuilder, GlobSet, GlobSetBuilder};
use super::*;
#[derive(Clone, Debug)]
pub struct Exclude {
globset: GlobSet,
}
impl Exclude {
pub fn from_strings<I: IntoIterator<Item = S>, S: AsRef<str>>(excludes: I) -> Result<Exclude> {
Exclude::from_patterns_and_files(excludes, empty::<&Path>())
}
pub fn from_patterns_and_files<I1, A, I2, P>(exclude: I1, exclude_from: I2) -> Result<Exclude>
where
I1: IntoIterator<Item = A>,
A: AsRef<str>,
I2: IntoIterator<Item = P>,
P: AsRef<Path>,
{
let mut gsb = GlobSetBuilder::new();
for pat in exclude {
add_pattern(&mut gsb, pat.as_ref())?;
}
for path in exclude_from {
add_patterns_from_file(&mut gsb, path.as_ref())?;
}
Ok(Exclude {
globset: gsb.build()?,
})
}
pub fn nothing() -> Exclude {
Exclude {
globset: GlobSet::empty(),
}
}
pub fn matches<'a, A>(&self, apath: &'a A) -> bool
where
&'a A: Into<Apath> + 'a,
A: ?Sized,
{
let apath: Apath = apath.into();
self.globset.is_match(apath)
}
}
fn add_pattern(gsb: &mut GlobSetBuilder, pattern: &str) -> Result<()> {
let pattern: Cow<str> = if pattern.starts_with('/') {
Cow::Borrowed(pattern)
} else {
Cow::Owned(format!("**/{pattern}"))
};
gsb.add(
GlobBuilder::new(&pattern)
.literal_separator(true)
.build()
.map_err(|source| Error::ParseGlob { source })?,
);
gsb.add(
GlobBuilder::new(&format!("{pattern}/**"))
.literal_separator(true)
.build()
.map_err(|source| Error::ParseGlob { source })?,
);
Ok(())
}
fn add_patterns_from_file(gsb: &mut GlobSetBuilder, path: &Path) -> Result<()> {
for pat in fs::read_to_string(path)?
.lines()
.map(str::trim)
.filter(|s| !s.starts_with('#') && !s.is_empty())
{
add_pattern(gsb, pat)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::super::*;
#[test]
fn simple_globs() {
let vec = vec!["fo*", "foo", "bar*"];
let exclude = Exclude::from_strings(vec).unwrap();
assert!(exclude.matches("/foo"));
assert!(exclude.matches("/foobar"));
assert!(exclude.matches("/barBaz"));
assert!(!exclude.matches("/bazBar"));
assert!(exclude.matches("/subdir/foo"));
assert!(exclude.matches("/subdir/foobar"));
assert!(exclude.matches("/subdir/barBaz"));
assert!(!exclude.matches("/subdir/bazBar"));
}
#[test]
fn rooted_pattern() {
let exclude = Exclude::from_strings(["/exc"]).unwrap();
assert!(exclude.matches("/exc"));
assert!(!exclude.matches("/excellent"));
assert!(!exclude.matches("/sub/excellent"));
assert!(!exclude.matches("/sub/exc"));
}
#[test]
fn path_parse() {
let exclude = Exclude::from_strings(["fo*/bar/baz*"]).unwrap();
assert!(exclude.matches("/foo/bar/baz.rs"))
}
#[test]
fn extended_pattern_parse() {
let exclude = Exclude::from_strings(["fo?", "ba[abc]", "[!a-z]"]).unwrap();
assert!(exclude.matches("/foo"));
assert!(!exclude.matches("/fo"));
assert!(exclude.matches("/baa"));
assert!(exclude.matches("/1"));
assert!(!exclude.matches("/a"));
}
#[test]
fn nothing_parse() {
let exclude = Exclude::nothing();
assert!(!exclude.matches("/a"));
}
}