// 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, Utf8PathBuf}; use regex::RegexSet; use cas_core::{err, Error, Result}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Action { 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(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 action_for>(&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) } } #[cfg(test)] mod tests { 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")), 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); } }