ta_changeset/
uri_pattern.rs1use glob::{MatchOptions, Pattern};
14
15const FS_PREFIX: &str = "fs://workspace/";
17
18pub fn matches_uri(pattern: &str, uri: &str) -> bool {
27 if pattern.contains("://") {
28 let pattern_scheme = scheme_of(pattern);
30 let uri_scheme = scheme_of(uri);
31 if pattern_scheme != uri_scheme {
32 return false;
33 }
34 glob_match(pattern, uri)
35 } else {
36 if !uri.starts_with(FS_PREFIX) {
38 return false;
39 }
40 let full_pattern = format!("{}{}", FS_PREFIX, pattern);
41 glob_match(&full_pattern, uri)
42 }
43}
44
45fn scheme_of(uri: &str) -> &str {
47 uri.split("://").next().unwrap_or("")
48}
49
50fn glob_match(pattern: &str, target: &str) -> bool {
52 let opts = MatchOptions {
53 require_literal_separator: true,
54 ..Default::default()
55 };
56 match Pattern::new(pattern) {
57 Ok(p) => p.matches_with(target, opts),
58 Err(_) => false,
59 }
60}
61
62pub fn resolve_pattern(pattern: &str) -> String {
65 if pattern.contains("://") {
66 pattern.to_string()
67 } else {
68 format!("{}{}", FS_PREFIX, pattern)
69 }
70}
71
72pub fn filter_uris<'a>(patterns: &[&str], uris: &[&'a str]) -> Vec<&'a str> {
74 uris.iter()
75 .filter(|uri| patterns.iter().any(|pat| matches_uri(pat, uri)))
76 .copied()
77 .collect()
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
87 fn bare_pattern_matches_fs_uri() {
88 assert!(matches_uri("src/**", "fs://workspace/src/main.rs"));
89 assert!(matches_uri("src/*.rs", "fs://workspace/src/lib.rs"));
90 assert!(matches_uri("README.md", "fs://workspace/README.md"));
91 }
92
93 #[test]
94 fn bare_pattern_does_not_match_other_schemes() {
95 assert!(!matches_uri("src/**", "gmail://inbox/src/draft"));
97 assert!(!matches_uri("src/**", "drive://docs/src/readme"));
98 assert!(!matches_uri("*.rs", "db://tables/schema.rs"));
99 }
100
101 #[test]
102 fn bare_pattern_no_match_outside_scope() {
103 assert!(!matches_uri("src/**", "fs://workspace/tests/test.rs"));
104 assert!(!matches_uri("src/*.rs", "fs://workspace/src/sub/deep.rs"));
105 }
106
107 #[test]
110 fn explicit_fs_pattern_matches() {
111 assert!(matches_uri(
112 "fs://workspace/src/**",
113 "fs://workspace/src/main.rs"
114 ));
115 assert!(matches_uri(
116 "fs://workspace/**",
117 "fs://workspace/Cargo.toml"
118 ));
119 }
120
121 #[test]
122 fn explicit_gmail_pattern_matches() {
123 assert!(matches_uri("gmail://inbox/*", "gmail://inbox/msg-456"));
125 assert!(matches_uri("gmail://**", "gmail://inbox/msg-123"));
127 assert!(!matches_uri("gmail://*", "gmail://inbox/msg-123"));
129 }
130
131 #[test]
132 fn scheme_mismatch_never_matches() {
133 assert!(!matches_uri("fs://workspace/**", "gmail://inbox/msg-123"));
135 assert!(!matches_uri("gmail://*", "fs://workspace/src/main.rs"));
137 }
138
139 #[test]
142 fn exact_path_match() {
143 assert!(matches_uri("src/main.rs", "fs://workspace/src/main.rs"));
144 assert!(!matches_uri("src/main.rs", "fs://workspace/src/lib.rs"));
145 }
146
147 #[test]
148 fn double_star_matches_deep_paths() {
149 assert!(matches_uri(
150 "src/**",
151 "fs://workspace/src/deeply/nested/file.rs"
152 ));
153 }
154
155 #[test]
156 fn invalid_glob_pattern_never_matches() {
157 assert!(!matches_uri("[invalid", "fs://workspace/src/main.rs"));
159 }
160
161 #[test]
162 fn empty_pattern_does_not_match() {
163 assert!(!matches_uri("", "fs://workspace/src/main.rs"));
164 }
165
166 #[test]
169 fn resolve_bare_pattern() {
170 assert_eq!(resolve_pattern("src/**"), "fs://workspace/src/**");
171 }
172
173 #[test]
174 fn resolve_explicit_pattern_unchanged() {
175 assert_eq!(resolve_pattern("gmail://*"), "gmail://*");
176 }
177
178 #[test]
179 fn filter_uris_selects_matching() {
180 let uris = vec![
181 "fs://workspace/src/main.rs",
182 "fs://workspace/src/lib.rs",
183 "fs://workspace/tests/test.rs",
184 "gmail://inbox/msg-1",
185 ];
186 let matched = filter_uris(&["src/**"], &uris);
187 assert_eq!(matched.len(), 2);
188 assert!(matched.contains(&"fs://workspace/src/main.rs"));
189 assert!(matched.contains(&"fs://workspace/src/lib.rs"));
190 }
191
192 #[test]
193 fn filter_uris_multiple_patterns() {
194 let uris = vec![
195 "fs://workspace/src/main.rs",
196 "fs://workspace/tests/test.rs",
197 "gmail://inbox/msg-1",
198 ];
199 let matched = filter_uris(&["src/**", "gmail://**"], &uris);
200 assert_eq!(matched.len(), 2);
201 assert!(matched.contains(&"fs://workspace/src/main.rs"));
202 assert!(matched.contains(&"gmail://inbox/msg-1"));
203 }
204}