Implement ignore rules.
This commit is contained in:
@@ -7,7 +7,7 @@ license = "AGPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
toml = "0.5.8"
|
||||
camino = { version = "1.0.7" }
|
||||
globset = "0.4.9"
|
||||
camino = "1.0.7"
|
||||
regex = "1.6.0"
|
||||
cas-core = { path = "../cas-core" }
|
||||
cas-simple = { path = "../cas-simple" }
|
||||
@@ -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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
patterns: RegexSet::new(patterns)
|
||||
.or_else(|err| err!("failed to compile ignore rules: {err}"))?,
|
||||
actions: actions,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_ignore_file(ignore_file: &str, root: &Utf8Path) -> Result<Self> {
|
||||
err!("Todo")
|
||||
}
|
||||
|
||||
pub fn is_ignored<P: AsRef<Utf8PathBuf>>(&self, path: P) -> bool {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
extern crate toml;
|
||||
extern crate camino;
|
||||
extern crate globset;
|
||||
extern crate regex;
|
||||
|
||||
extern crate cas_core;
|
||||
|
||||
@@ -26,7 +26,7 @@ mod tree_item;
|
||||
// mod tree_walker;
|
||||
// mod config;
|
||||
mod path_map;
|
||||
// mod ignore;
|
||||
mod ignore;
|
||||
mod repository;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user