feat(nsignore): implement nsignore
This commit is contained in:
parent
5a70590eee
commit
2364cadfd5
144
src/store/nsignore.rs
Normal file
144
src/store/nsignore.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use regex::Regex;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Search .nsignore or .nextsyncignore in this path level
|
||||
///
|
||||
/// * `level_path`: the dir we want to find the ignore file in
|
||||
pub fn get_nsignore_file(level_path: &PathBuf) -> Option<PathBuf> {
|
||||
let mut path = level_path.to_path_buf();
|
||||
|
||||
path.push(".nextsyncignore");
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
path.pop();
|
||||
path.push(".nsignore");
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_nsignore_rules(nsignore_path: &PathBuf) -> Vec<String> {
|
||||
let file = File::open(nsignore_path).expect(&format!(
|
||||
"fatal: failed to open nextsyncignore file in '{}'",
|
||||
nsignore_path.display()
|
||||
));
|
||||
|
||||
let reader = BufReader::new(file);
|
||||
reader
|
||||
.lines()
|
||||
.map(|rule| normalize_rule(rule.unwrap()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn normalize_rule(l: String) -> String {
|
||||
let mut line = l;
|
||||
|
||||
// define / as root
|
||||
let re = Regex::new("^(!)?/").unwrap();
|
||||
line = re.replace_all(&line, "$1^").to_string();
|
||||
|
||||
// escape .
|
||||
let re = Regex::new(r"\.").unwrap();
|
||||
line = re.replace_all(&line, r"\.").to_string();
|
||||
|
||||
// add . before *
|
||||
let re = Regex::new(r"\*").unwrap();
|
||||
line = re.replace_all(&line, r".*").to_string();
|
||||
|
||||
// add optional .* at the end of /
|
||||
let re = Regex::new(r"/$").unwrap();
|
||||
line = re.replace_all(&line, r"(/.*)?").to_string();
|
||||
line
|
||||
}
|
||||
|
||||
pub fn is_file_ignored(path: &str, rules: &Vec<String>) -> bool {
|
||||
let mut ignored = false;
|
||||
for rule in rules {
|
||||
if rule.starts_with("!") {
|
||||
if !ignored {
|
||||
continue;
|
||||
}
|
||||
|
||||
let strip_rule = rule.strip_prefix("!").unwrap();
|
||||
let re = Regex::new(&strip_rule).unwrap();
|
||||
if re.is_match(path) {
|
||||
ignored = false;
|
||||
}
|
||||
} else if !ignored {
|
||||
let re = Regex::new(&rule).unwrap();
|
||||
if re.is_match(path) {
|
||||
ignored = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ignored
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Cursor;
|
||||
|
||||
fn test_multiple(rules: Vec<&str>, tests: Vec<(&str, bool)>) {
|
||||
let rules = rules
|
||||
.iter()
|
||||
.map(|rule| normalize_rule(rule.to_string()))
|
||||
.collect();
|
||||
for (file, res) in tests.into_iter() {
|
||||
assert_eq!(is_file_ignored(file, &rules), res, "{}", file);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_files() {
|
||||
let rules = vec![
|
||||
"*.log",
|
||||
"exclude",
|
||||
"/logs/",
|
||||
"/build/target/",
|
||||
"**/*.swp",
|
||||
"secret/",
|
||||
];
|
||||
|
||||
let tests = vec![
|
||||
("error.log", true),
|
||||
("./error.log", true),
|
||||
("dir/error.log", true),
|
||||
("exclude", true),
|
||||
("dir/exclude", true),
|
||||
("logs/dir/file2", true),
|
||||
("dir/logs/dir/file2", false),
|
||||
("build/target/dir/file1", true),
|
||||
("build", false),
|
||||
("build/target", true),
|
||||
("dir/build/target", false),
|
||||
("dir/file.swp", true),
|
||||
(".swp", false),
|
||||
("secret", true),
|
||||
("dir/secret", true),
|
||||
("dir/secret/file", true),
|
||||
];
|
||||
|
||||
test_multiple(rules, tests);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_files_negation() {
|
||||
let rules = vec!["*", "!*.log", "!*log.*"];
|
||||
let tests = vec![
|
||||
("file", true),
|
||||
("dir/file", true),
|
||||
("file.log", false),
|
||||
("log.file", false),
|
||||
("dir/file.log", false),
|
||||
("dir/log.file", false),
|
||||
];
|
||||
test_multiple(rules, tests);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user