1pub fn matches_any_file(path: &str, files: &[&str]) -> bool {
2 if files.is_empty() {
3 return false;
4 }
5
6 let parse_paths: Vec<(Path, bool)> = parse_path(path);
8
9 for file in files {
11 let file_segments = if file.is_empty() { vec![] } else { file.split('/').collect() };
12
13 let mut matched = false;
15
16 for (parsed_path, is_negation) in &parse_paths {
17 if match_segments(&parsed_path.segments, &file_segments, 0, 0) {
18 if *is_negation {
19 matched = false; } else {
21 matched = true; }
23 }
24 }
25
26 if matched {
28 return true;
29 }
30 }
31
32 false
34}
35
36#[derive(Debug, Clone)]
37struct Path {
38 segments: Vec<Segment>,
39}
40
41#[derive(Debug, Clone)]
42enum Segment {
43 Literal(String), Pattern(String), DoubleStar, DoubleStarWithSuffix(String), }
48
49fn parse_path(pattern: &str) -> Vec<(Path, bool)> {
50 let (actual_pattern, is_negation) = match pattern.strip_prefix('!') {
51 Some(rest) => (rest, true),
52 None => (pattern, false),
53 };
54
55 expand_optionals(actual_pattern)
57 .into_iter()
58 .map(|expanded_pattern| {
59 let parts: Vec<&str> = expanded_pattern.split('/').collect();
60 let mut segments = Vec::new();
61
62 for part in parts {
63 if part == "**" {
64 segments.push(Segment::DoubleStar);
65 } else if let Some(suffix) = part.strip_prefix("**") {
66 segments.push(Segment::DoubleStarWithSuffix(suffix.to_string()));
67 } else if part.contains('*') || part.contains('+') || part.contains('[') {
68 segments.push(Segment::Pattern(part.to_string()));
69 } else {
70 segments.push(Segment::Literal(part.to_string()));
71 }
72 }
73
74 (Path { segments }, is_negation)
75 })
76 .collect()
77}
78
79fn expand_optionals(pattern: &str) -> Vec<String> {
80 if let Some(question_pos) = pattern.find('?') {
81 if question_pos == 0 {
82 return vec![pattern.to_string()]; }
84
85 let optional_char = pattern.chars().nth(question_pos - 1).unwrap();
86 let before_optional = &pattern[..question_pos - 1];
87 let after_optional = &pattern[question_pos + 1..];
88
89 let pattern_without = format!("{}{}", before_optional, after_optional);
91 let pattern_with = format!("{}{}{}", before_optional, optional_char, after_optional);
92
93 let mut results = Vec::new();
95 results.extend(expand_optionals(&pattern_without));
96 results.extend(expand_optionals(&pattern_with));
97 results
98 } else {
99 vec![pattern.to_string()]
101 }
102}
103
104fn match_segments(segments: &[Segment], path_parts: &[&str], seg_idx: usize, path_idx: usize) -> bool {
105 if seg_idx >= segments.len() && path_idx >= path_parts.len() {
107 return true;
108 }
109
110 if seg_idx >= segments.len() {
112 return false;
113 }
114
115 match &segments[seg_idx] {
116 Segment::Literal(literal) => {
117 if path_idx >= path_parts.len() || path_parts[path_idx] != literal {
118 return false;
119 }
120 match_segments(segments, path_parts, seg_idx + 1, path_idx + 1)
121 }
122
123 Segment::Pattern(pattern) => {
124 if path_idx >= path_parts.len() {
125 return false;
126 }
127
128 if glob_match(pattern, path_parts[path_idx]) {
129 match_segments(segments, path_parts, seg_idx + 1, path_idx + 1)
130 } else {
131 false
132 }
133 }
134
135 Segment::DoubleStar => {
136 if match_segments(segments, path_parts, seg_idx + 1, path_idx) {
138 return true;
139 }
140
141 for i in (path_idx + 1)..=path_parts.len() {
142 if match_segments(segments, path_parts, seg_idx + 1, i) {
143 return true;
144 }
145 }
146 false
147 }
148
149 Segment::DoubleStarWithSuffix(suffix) => {
150 for i in path_idx..path_parts.len() {
151 if path_parts[i].ends_with(suffix) && match_segments(segments, path_parts, seg_idx + 1, i + 1) {
152 return true;
153 }
154 }
155 false
156 }
157 }
158}
159
160fn glob_match(pattern: &str, text: &str) -> bool {
162 glob_match_recursive(pattern, text, 0, 0)
163}
164
165fn glob_match_recursive(pattern: &str, text: &str, p_idx: usize, t_idx: usize) -> bool {
166 let p_chars: Vec<char> = pattern.chars().collect();
167 let t_chars: Vec<char> = text.chars().collect();
168
169 if p_idx >= p_chars.len() && t_idx >= t_chars.len() {
171 return true; }
173 if p_idx >= p_chars.len() {
174 return false; }
176
177 match p_chars[p_idx] {
178 '*' => {
179 for i in t_idx..=t_chars.len() {
181 if glob_match_recursive(pattern, text, p_idx + 1, i) {
182 return true;
183 }
184 }
185 false
186 }
187
188 '+' => {
189 if p_idx == 0 {
190 return false; }
192
193 let char_to_repeat = p_chars[p_idx - 1];
194
195 let mut repeat_count = 0;
199 let mut curr_t_idx = t_idx;
200
201 while curr_t_idx < t_chars.len() && t_chars[curr_t_idx] == char_to_repeat {
203 repeat_count += 1;
204 curr_t_idx += 1;
205 }
206
207 if repeat_count == 0 {
208 return false; }
210
211 glob_match_recursive(pattern, text, p_idx + 1, curr_t_idx)
213 }
214
215 '[' => {
216 if t_idx >= t_chars.len() {
217 return false;
218 }
219
220 let mut bracket_end = p_idx + 1;
222 while bracket_end < p_chars.len() && p_chars[bracket_end] != ']' {
223 bracket_end += 1;
224 }
225
226 if bracket_end >= p_chars.len() {
227 return false; }
229
230 let bracket_content: String = p_chars[(p_idx + 1)..bracket_end].iter().collect();
232
233 if matches_bracket_content(&bracket_content, t_chars[t_idx]) {
234 glob_match_recursive(pattern, text, bracket_end + 1, t_idx + 1)
235 } else {
236 false
237 }
238 }
239
240 '?' => {
241 if p_idx == 0 {
242 return false; }
244
245 if glob_match_recursive(pattern, text, p_idx + 1, t_idx) {
248 return true;
249 }
250
251 if t_idx < t_chars.len() {
253 let optional_char = p_chars[p_idx - 1];
254 if t_chars[t_idx] == optional_char {
255 return glob_match_recursive(pattern, text, p_idx + 1, t_idx + 1);
256 }
257 }
258
259 false
260 }
261
262 c => {
263 if p_idx + 1 < p_chars.len() && p_chars[p_idx + 1] == '+' {
265 let mut repeat_count = 0;
267 let mut curr_t_idx = t_idx;
268
269 while curr_t_idx < t_chars.len() && t_chars[curr_t_idx] == c {
270 repeat_count += 1;
271 curr_t_idx += 1;
272 }
273
274 if repeat_count == 0 {
275 return false; }
277
278 glob_match_recursive(pattern, text, p_idx + 2, curr_t_idx)
280 } else {
281 if t_idx >= t_chars.len() || t_chars[t_idx] != c {
283 return false;
284 }
285 glob_match_recursive(pattern, text, p_idx + 1, t_idx + 1)
286 }
287 }
288 }
289}
290
291fn matches_bracket_content(bracket_content: &str, ch: char) -> bool {
292 let chars: Vec<char> = bracket_content.chars().collect();
293 let mut i = 0;
294
295 while i < chars.len() {
296 if i + 2 < chars.len() && chars[i + 1] == '-' {
298 let start_char = chars[i];
299 let end_char = chars[i + 2];
300
301 if ch >= start_char && ch <= end_char {
302 return true;
303 }
304
305 i += 3; } else {
307 if chars[i] == ch {
309 return true;
310 }
311 i += 1;
312 }
313 }
314
315 false
316}