Skip to main content

om_context/
ignore.rs

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    use proptest::prelude::*;
65    use std::io::Write;
66    use tempfile::NamedTempFile;
67
68    proptest! {
69        #[test]
70        fn test_is_ignored_never_panics(s in "\\PC*") {
71            let patterns = vec![Pattern::new("**/node_modules/**").unwrap()];
72            let ignore = IgnorePatterns { patterns };
73            ignore.is_ignored(&s);
74        }
75    }
76
77    #[test]
78    fn test_pattern_matching() {
79        let patterns = vec![
80            Pattern::new("**/*.lock").unwrap(),
81            Pattern::new("**/*-lock.*").unwrap(),
82            Pattern::new("**/node_modules/**").unwrap(),
83            Pattern::new("dist/**").unwrap(),
84        ];
85
86        let ignore = IgnorePatterns { patterns };
87
88        assert!(ignore.is_ignored("package-lock.json"));
89        assert!(ignore.is_ignored("Cargo.lock"));
90        assert!(ignore.is_ignored("src/node_modules/foo/bar.js"));
91        assert!(ignore.is_ignored("dist/bundle.js"));
92        assert!(!ignore.is_ignored("src/main.rs"));
93    }
94
95    #[test]
96    fn test_parse_file_logic() {
97        let mut tmp = NamedTempFile::new().unwrap();
98        writeln!(tmp, "# comment").unwrap();
99        writeln!(tmp, "  ").unwrap();
100        writeln!(tmp, "target/").unwrap();
101        writeln!(tmp, "*.log").unwrap();
102        writeln!(tmp, "foo.txt").unwrap();
103        writeln!(tmp, "**/bar/*").unwrap();
104
105        let patterns = IgnorePatterns::parse_file(tmp.path()).unwrap();
106        let ignore = IgnorePatterns { patterns };
107
108        assert!(ignore.is_ignored("target/debug/exe"));
109        assert!(ignore.is_ignored("some/path/test.log"));
110        assert!(ignore.is_ignored("foo.txt"));
111        assert!(ignore.is_ignored("a/bar/b"));
112        assert!(!ignore.is_ignored("src/main.rs"));
113
114        assert_eq!(ignore.patterns.len(), 4);
115    }
116
117    #[test]
118    fn test_directory_pattern_expansion() {
119        let mut tmp = NamedTempFile::new().unwrap();
120        writeln!(tmp, "dir/").unwrap();
121
122        let patterns = IgnorePatterns::parse_file(tmp.path()).unwrap();
123        let ignore = IgnorePatterns { patterns };
124
125        assert!(ignore.is_ignored("dir/file.txt"));
126        assert!(ignore.is_ignored("dir/subdir/file.txt"));
127    }
128}