1use glob::Pattern;
2use std::fs;
3use std::path::Path;
4
5pub struct IgnorePatterns {
6 patterns: Vec<Pattern>,
7}
8
9impl IgnorePatterns {
10 pub fn load(root: &Path) -> Self {
11 let mut patterns = Vec::new();
12
13 if let Some(home) = dirs::home_dir() {
14 let global_ignore = home.join(".omignore");
15 if let Ok(ps) = Self::parse_file(&global_ignore) {
16 patterns.extend(ps);
17 }
18 }
19
20 let local_ignore = root.join(".omignore");
21 if let Ok(ps) = Self::parse_file(&local_ignore) {
22 patterns.extend(ps);
23 }
24
25 IgnorePatterns { patterns }
26 }
27
28 fn parse_file(path: &Path) -> Result<Vec<Pattern>, Box<dyn std::error::Error>> {
29 let content = fs::read_to_string(path)?;
30 let mut patterns = Vec::new();
31
32 for line in content.lines() {
33 let line = line.trim();
34 if line.is_empty() || line.starts_with('#') {
35 continue;
36 }
37
38 let pattern = if line.starts_with("**/") {
39 line.to_string()
40 } else if line.ends_with('/') {
41 format!("{}**", line)
42 } else if line.contains('*') || line.contains('?') {
43 format!("**/{}", line)
44 } else {
45 line.to_string()
46 };
47
48 if let Ok(p) = Pattern::new(&pattern) {
49 patterns.push(p);
50 }
51 }
52
53 Ok(patterns)
54 }
55
56 pub fn is_ignored(&self, path: &str) -> bool {
57 self.patterns.iter().any(|p| p.matches(path))
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn test_pattern_matching() {
67 let patterns = vec![
68 Pattern::new("**/*.lock").unwrap(),
69 Pattern::new("**/*-lock.*").unwrap(),
70 Pattern::new("**/node_modules/**").unwrap(),
71 Pattern::new("dist/**").unwrap(),
72 ];
73
74 let ignore = IgnorePatterns { patterns };
75
76 assert!(ignore.is_ignored("package-lock.json"));
77 assert!(ignore.is_ignored("Cargo.lock"));
78 assert!(ignore.is_ignored("src/node_modules/foo/bar.js"));
79 assert!(ignore.is_ignored("dist/bundle.js"));
80 assert!(!ignore.is_ignored("src/main.rs"));
81 }
82}