|
|
|
@ -15,12 +15,12 @@ |
|
|
|
|
|
|
|
|
|
|
|
use camino::{Utf8Path, Utf8PathBuf}; |
|
|
|
use globset::GlobSet; |
|
|
|
use regex::RegexSet; |
|
|
|
|
|
|
|
use cas_core::{err, Error, Result}; |
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
|
|
|
pub enum Action { |
|
|
|
Ignore, |
|
|
|
Accept, |
|
|
|
@ -29,7 +29,7 @@ pub enum Action { |
|
|
|
|
|
|
|
#[derive(Debug)] |
|
|
|
pub struct IgnoreRules { |
|
|
|
patterns: GlobSet, |
|
|
|
patterns: RegexSet, |
|
|
|
actions: Vec<Action>, |
|
|
|
} |
|
|
|
|
|
|
|
@ -37,21 +37,101 @@ pub struct IgnoreRules { |
|
|
|
impl IgnoreRules { |
|
|
|
pub fn new() -> Self { |
|
|
|
Self { |
|
|
|
patterns: GlobSet::default(), |
|
|
|
patterns: RegexSet::new(&[] as &[&str]).unwrap(), |
|
|
|
actions: vec![], |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pub fn from_vec(vec: Vec<(Action, Utf8PathBuf)>) -> Result<Self> { |
|
|
|
err!("Todo") |
|
|
|
} |
|
|
|
pub fn from_source<P: AsRef<Utf8Path>>(source: &str, root: P) -> Result<Self> { |
|
|
|
assert!(root.as_ref().is_absolute()); |
|
|
|
|
|
|
|
pub fn from_ignore_file(ignore_file: &str, root: &Utf8Path) -> Result<Self> { |
|
|
|
err!("Todo") |
|
|
|
} |
|
|
|
let separator = if std::path::MAIN_SEPARATOR == '/' { |
|
|
|
"/" |
|
|
|
} |
|
|
|
else { |
|
|
|
assert_eq!(std::path::MAIN_SEPARATOR, '\\'); |
|
|
|
"\\\\" |
|
|
|
}; |
|
|
|
|
|
|
|
let mut patterns = Vec::<String>::default(); |
|
|
|
let mut actions = Vec::<Action>::default(); |
|
|
|
|
|
|
|
for line in source.lines() { |
|
|
|
let rule = line.trim(); |
|
|
|
let mut rule_it = rule.chars().peekable(); |
|
|
|
if rule_it.peek().is_none() || rule_it.peek() == Some(&'#') { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if rule_it.peek() == Some(&'!') { |
|
|
|
rule_it.next(); |
|
|
|
actions.push(Action::Accept); |
|
|
|
} |
|
|
|
else { |
|
|
|
actions.push(Action::Ignore); |
|
|
|
} |
|
|
|
|
|
|
|
let mut pat = String::new(); |
|
|
|
let mut last_is_sep = false; |
|
|
|
|
|
|
|
if rule_it.peek() == Some(&'/') { |
|
|
|
pat.push_str("^"); |
|
|
|
pat.push_str(®ex::escape(root.as_ref().as_str())); |
|
|
|
} |
|
|
|
|
|
|
|
while let Some(c) = rule_it.next() { |
|
|
|
if c == '/' { |
|
|
|
pat.push_str(separator); |
|
|
|
last_is_sep = true; |
|
|
|
continue; |
|
|
|
} |
|
|
|
else if c == '*' { |
|
|
|
if rule_it.peek() == Some(&'*') { |
|
|
|
rule_it.next(); |
|
|
|
if !last_is_sep || (!rule_it.peek().is_none() && rule_it.peek() != Some(&'/')) { |
|
|
|
return err!("** pattern can only be used as a whole path segment"); |
|
|
|
} |
|
|
|
pat.push_str(".*"); |
|
|
|
} |
|
|
|
else { |
|
|
|
pat.push_str("[^/]*"); |
|
|
|
} |
|
|
|
} |
|
|
|
else if c == '\\' { |
|
|
|
let c2 = rule_it.next().ok_or(Error::unknown("invalid \\ at end of rule"))?; |
|
|
|
let mut buf = [0u8; 4]; |
|
|
|
pat.push_str(®ex::escape(c2.encode_utf8(&mut buf))); |
|
|
|
} |
|
|
|
else { |
|
|
|
let mut buf = [0u8; 4]; |
|
|
|
pat.push_str(®ex::escape(c.encode_utf8(&mut buf))); |
|
|
|
} |
|
|
|
last_is_sep = false; |
|
|
|
} |
|
|
|
|
|
|
|
if last_is_sep { |
|
|
|
pat.pop(); |
|
|
|
} |
|
|
|
pat.push_str("(/.*)?$"); |
|
|
|
|
|
|
|
dbg!(&pat); |
|
|
|
patterns.push(pat); |
|
|
|
} |
|
|
|
|
|
|
|
pub fn is_ignored<P: AsRef<Utf8PathBuf>>(&self, path: P) -> bool { |
|
|
|
Ok(Self { |
|
|
|
patterns: RegexSet::new(patterns) |
|
|
|
.or_else(|err| err!("failed to compile ignore rules: {err}"))?, |
|
|
|
actions: actions, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn action_for<P: AsRef<Utf8Path>>(&self, path: P) -> Action { |
|
|
|
assert!(path.as_ref().is_absolute()); |
|
|
|
let index = self.patterns.matches(path.as_ref().as_str()) |
|
|
|
.iter() |
|
|
|
.next() |
|
|
|
.unwrap_or(self.actions.len()); |
|
|
|
*self.actions.get(index).unwrap_or(&Action::Accept) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -63,5 +143,21 @@ mod tests { |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn test_path_map() { |
|
|
|
let root = Utf8PathBuf::from("/foo.dir/bar"); |
|
|
|
let patterns = "!hello/world\nhello\n/world/\n\\!\\*\n*.bak"; |
|
|
|
let ignore = IgnoreRules::from_source(patterns, root).unwrap(); |
|
|
|
|
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test")), Action::Accept); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello/aoeu")), Action::Ignore); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello_world/aoeu")), Action::Accept); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/world/aoeu")), Action::Accept); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello/world/aoeu")), Action::Accept); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/world/aoeu")), Action::Ignore); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/world")), Action::Ignore); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/fooXdir/bar/world")), Action::Accept); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/!*/aoeu")), Action::Ignore); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/file.bak")), Action::Ignore); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/fileXbak")), Action::Accept); |
|
|
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test.bak/file")), Action::Ignore); |
|
|
|
} |
|
|
|
} |
|
|
|
|