1use globset::{Glob, GlobMatcher};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct GlobPattern {
11 pattern: String,
12 #[serde(skip)]
13 matcher: Option<GlobMatcher>,
14}
15
16impl GlobPattern {
17 pub fn new(pattern: &str) -> Self {
19 let matcher = Glob::new(pattern).ok().map(|g| g.compile_matcher());
20 GlobPattern {
21 pattern: pattern.to_string(),
22 matcher,
23 }
24 }
25
26 pub fn matches(&self, text: &str) -> bool {
28 if self.pattern == "-" {
30 return true;
31 }
32 if let Some(ref m) = self.matcher {
33 return m.is_match(text);
34 }
35 self.pattern == text
37 }
38
39 pub fn ensure_compiled(&mut self) {
41 if self.matcher.is_none() {
42 self.matcher = Glob::new(&self.pattern).ok().map(|g| g.compile_matcher());
43 }
44 }
45
46 pub fn as_str(&self) -> &str {
48 &self.pattern
49 }
50}
51
52impl PartialEq for GlobPattern {
53 fn eq(&self, other: &Self) -> bool {
54 self.pattern == other.pattern
55 }
56}
57
58impl std::fmt::Display for GlobPattern {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.write_str(&self.pattern)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn test_exact_match() {
70 let pat = GlobPattern::new("src/main.rs");
71 assert!(pat.matches("src/main.rs"));
72 assert!(!pat.matches("src/lib.rs"));
73 }
74
75 #[test]
76 fn test_wildcard_all() {
77 let pat = GlobPattern::new("**");
78 assert!(pat.matches("anything"));
79 assert!(pat.matches("src/foo/bar.rs"));
80 }
81
82 #[test]
83 fn test_directory_glob() {
84 let pat = GlobPattern::new("src/**");
85 assert!(pat.matches("src/main.rs"));
86 assert!(pat.matches("src/foo/bar.rs"));
87 assert!(!pat.matches("tests/test.rs"));
88 }
89
90 #[test]
91 fn test_dash_means_everything() {
92 let pat = GlobPattern::new("-");
93 assert!(pat.matches("anything"));
94 }
95
96 #[test]
97 fn test_star_glob() {
98 let pat = GlobPattern::new("*.rs");
99 assert!(pat.matches("main.rs"));
100 assert!(pat.matches("src/main.rs"));
102 }
103
104 #[test]
105 fn test_complex_glob() {
106 let pat = GlobPattern::new("src/**/*.rs");
107 assert!(pat.matches("src/weave/mod.rs"));
108 assert!(pat.matches("src/a/b/c.rs"));
109 assert!(!pat.matches("tests/test.rs"));
110 }
111
112 #[test]
113 fn test_serde_roundtrip() {
114 let pat = GlobPattern::new("src/**");
115 let json = serde_json::to_string(&pat).unwrap();
116 let mut parsed: GlobPattern = serde_json::from_str(&json).unwrap();
117 parsed.ensure_compiled();
118 assert!(parsed.matches("src/main.rs"));
119 assert!(!parsed.matches("tests/test.rs"));
120 }
121}