You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
5.7 KiB
165 lines
5.7 KiB
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
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<IgnoreAction>,
|
|
}
|
|
|
|
|
|
impl IgnoreRules {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
patterns: RegexSet::new(&[] as &[&str]).unwrap(),
|
|
actions: vec![],
|
|
}
|
|
}
|
|
|
|
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::<IgnoreAction>::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<P: AsRef<Utf8Path>>(&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);
|
|
}
|
|
}
|
|
|