1mod path;
2
3pub fn path_matches(path: &str, files: &[&str]) -> Option<usize> {
4 if files.is_empty() {
5 return None;
6 }
7
8 let parsed_path = path::parse(path);
10
11 if matches!(parsed_path.segments.first(), Some(path::Segment::Negation)) {
13 return None;
14 }
15
16 files
18 .iter()
19 .enumerate()
20 .find(|(_, file)| match_path(&parsed_path.segments, file))
21 .map(|(i, _)| i)
22}
23
24fn match_path(segments: &[path::Segment], text: &str) -> bool {
25 match_path_recursive(segments, text, 0, 0, false)
26}
27
28fn match_path_recursive(segments: &[path::Segment], text: &str, seg_idx: usize, t_idx: usize, is_after_zero_double_star: bool) -> bool {
29 if seg_idx >= segments.len() && t_idx >= text.len() {
30 return true;
31 }
32 if seg_idx >= segments.len() {
33 return false;
34 }
35
36 match &segments[seg_idx] {
37 path::Segment::Literal(lit) => {
38 if lit.starts_with("/") && is_after_zero_double_star {
39 let without_slash = &lit[1..];
41 if t_idx + without_slash.len() <= text.len()
42 && &text[t_idx..t_idx + without_slash.len()] == without_slash
43 && match_path_recursive(segments, text, seg_idx + 1, t_idx + without_slash.len(), false)
44 {
45 return true;
46 }
47 }
48 if t_idx + lit.len() <= text.len() && &text[t_idx..t_idx + lit.len()] == lit {
50 match_path_recursive(segments, text, seg_idx + 1, t_idx + lit.len(), false)
51 } else {
52 false
53 }
54 }
55 path::Segment::SingleStar => {
56 let mut end = text.len();
57 if let Some(pos) = text[t_idx..].find('/') {
58 end = t_idx + pos;
59 }
60 for i in t_idx..=end {
61 if match_path_recursive(segments, text, seg_idx + 1, i, false) {
62 return true;
63 }
64 }
65 false
66 }
67 path::Segment::DoubleStar => {
68 for i in t_idx..=text.len() {
69 let after_zero = i == t_idx;
70 if match_path_recursive(segments, text, seg_idx + 1, i, after_zero) {
71 return true;
72 }
73 }
74 false
75 }
76 path::Segment::QuestionMark(c) => {
77 if match_path_recursive(segments, text, seg_idx + 1, t_idx, false) {
79 return true;
80 }
81 if t_idx < text.len() && text.chars().nth(t_idx) == Some(*c) {
83 return match_path_recursive(segments, text, seg_idx + 1, t_idx + 1, false);
84 }
85 false
86 }
87 path::Segment::Plus(c) => {
88 let mut count = 0;
89 let mut curr_t_idx = t_idx;
90 while curr_t_idx < text.len() && text.chars().nth(curr_t_idx) == Some(*c) {
91 count += 1;
92 curr_t_idx += 1;
93 }
94 if count == 0 {
95 return false;
96 }
97 match_path_recursive(segments, text, seg_idx + 1, curr_t_idx, false)
98 }
99 path::Segment::Bracket(b) => {
100 if t_idx >= text.len() {
101 return false;
102 }
103 let ch = text.chars().nth(t_idx).unwrap();
104 if b.singles.contains(&ch) || b.ranges.iter().any(|(start, end)| ch >= *start && ch <= *end) {
105 match_path_recursive(segments, text, seg_idx + 1, t_idx + 1, false)
106 } else {
107 false
108 }
109 }
110 path::Segment::Negation => false,
111 }
112}