// This file is part of bsv.
//
// bsv is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
// more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with bsv. If not, see .
use camino::Utf8Path;
use regex::RegexSet;
use cas_core::{err, Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IgnoreAction {
Ignore,
Accept,
}
#[derive(Debug)]
pub struct IgnoreRules {
patterns: RegexSet,
actions: Vec,
}
impl IgnoreRules {
pub fn new() -> Self {
Self {
patterns: RegexSet::new(&[] as &[&str]).unwrap(),
actions: vec![],
}
}
pub fn from_source>(source: &str, root: P) -> Result {
assert!(root.as_ref().is_absolute());
let separator = if std::path::MAIN_SEPARATOR == '/' {
"/"
}
else {
assert_eq!(std::path::MAIN_SEPARATOR, '\\');
"\\\\"
};
let mut patterns = Vec::::default();
let mut actions = Vec::::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(IgnoreAction::Accept);
}
else {
actions.push(IgnoreAction::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 action_for>(&self, path: P) -> IgnoreAction {
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(&IgnoreAction::Accept)
}
}
#[cfg(test)]
mod tests {
use camino::Utf8PathBuf;
use super::*;
#[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")), IgnoreAction::Accept);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello/aoeu")), IgnoreAction::Ignore);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello_world/aoeu")), IgnoreAction::Accept);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/world/aoeu")), IgnoreAction::Accept);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello/world/aoeu")), IgnoreAction::Accept);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/world/aoeu")), IgnoreAction::Ignore);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/world")), IgnoreAction::Ignore);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/fooXdir/bar/world")), IgnoreAction::Accept);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/!*/aoeu")), IgnoreAction::Ignore);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/file.bak")), IgnoreAction::Ignore);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/fileXbak")), IgnoreAction::Accept);
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test.bak/file")), IgnoreAction::Ignore);
}
}