ktrs_cli/ktlint/
java_glob.rs1use regex::Regex;
4
5const REGEX_META_CHARS: &str = ".^$+{[]|()";
6const GLOB_META_CHARS: &str = "\\*?[{";
7
8fn is_regex_meta(c: char) -> bool {
9 REGEX_META_CHARS.contains(c)
10}
11
12fn is_glob_meta(c: char) -> bool {
13 GLOB_META_CHARS.contains(c)
14}
15
16pub fn to_unix_regex_pattern(glob: &str) -> Result<String, String> {
18 let chars: Vec<char> = glob.chars().collect();
19 let next = |i: usize| chars.get(i).copied();
20 let mut in_group = false;
21 let mut regex = String::from("^");
22 let mut i = 0;
23 while i < chars.len() {
24 let c = chars[i];
25 i += 1;
26 match c {
27 '\\' => {
28 let Some(next) = next(i) else { return Err("No character to escape".to_owned()) };
29 if is_glob_meta(next) || is_regex_meta(next) {
30 regex.push('\\');
31 }
32 regex.push(next);
33 i += 1;
34 }
35 '/' => regex.push(c),
36 '[' => {
37 regex.push_str("[[^/]&&[");
38 if next(i) == Some('^') {
39 regex.push_str("\\^");
40 i += 1;
41 } else {
42 if next(i) == Some('!') {
43 regex.push('^');
44 i += 1;
45 }
46 if next(i) == Some('-') {
47 regex.push('-');
48 i += 1;
49 }
50 }
51 let mut has_range_start = false;
52 let mut last = '\0';
53 let mut c = '\0';
54 while i < chars.len() {
55 c = chars[i];
56 i += 1;
57 if c == ']' {
58 break;
59 }
60 if c == '/' {
61 return Err("Explicit 'name separator' in class".to_owned());
62 }
63 if c == '\\' || c == '[' || c == '&' && next(i) == Some('&') {
64 regex.push('\\');
65 }
66 regex.push(c);
67 if c == '-' {
68 if !has_range_start {
69 return Err("Invalid range".to_owned());
70 }
71 let end = next(i);
72 i += 1;
73 match end {
74 None | Some(']') => {
75 c = end.unwrap_or('\0');
76 break;
77 }
78 Some(end) if end < last => return Err("Invalid range".to_owned()),
79 Some(end) => {
80 c = end;
81 regex.push(end);
82 }
83 }
84 has_range_start = false;
85 } else {
86 has_range_start = true;
87 last = c;
88 }
89 }
90 if c != ']' {
91 return Err("Missing ']".to_owned());
92 }
93 regex.push_str("]]");
94 }
95 '{' => {
96 if in_group {
97 return Err("Cannot nest groups".to_owned());
98 }
99 regex.push_str("(?:(?:");
100 in_group = true;
101 }
102 '}' if in_group => {
103 regex.push_str("))");
104 in_group = false;
105 }
106 ',' if in_group => regex.push_str(")|(?:"),
107 '*' if next(i) == Some('*') => {
108 regex.push_str(".*");
109 i += 1;
110 }
111 '*' => regex.push_str("[^/]*"),
112 '?' => regex.push_str("[^/]"),
113 c => {
114 if is_regex_meta(c) {
115 regex.push('\\');
116 }
117 regex.push(c);
118 }
119 }
120 }
121 if in_group {
122 return Err("Missing '}".to_owned());
123 }
124 regex.push('$');
125 Ok(regex)
126}
127
128#[derive(Clone, Debug)]
130pub struct PathMatcher {
131 glob: String,
132 regex: Regex,
133}
134
135impl PathMatcher {
136 pub fn new(glob: &str, case_insensitive: bool) -> Result<PathMatcher, String> {
137 let pattern = to_unix_regex_pattern(glob)?;
138 let pattern = if case_insensitive { format!("(?i){pattern}") } else { pattern };
139 let regex = Regex::new(&pattern).map_err(|e| e.to_string())?;
140 Ok(PathMatcher { glob: glob.to_owned(), regex })
141 }
142
143 pub fn matches(&self, path: &str) -> bool {
144 self.regex.is_match(path)
145 }
146
147 pub fn glob(&self) -> &str {
149 &self.glob
150 }
151}