feat(nsignore): implement nsignore

This commit is contained in:
grimhilt 2024-08-31 19:42:44 +02:00
parent 5a70590eee
commit 2364cadfd5

144
src/store/nsignore.rs Normal file
View 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);
}
}