##// END OF EJS Templates
rust-regex: fix shortcut for exact matches...
Raphaël Gomès -
r42631:48f1f864 default
parent child Browse files
Show More
@@ -1,354 +1,372 b''
1 use crate::{LineNumber, PatternError, PatternFileError};
1 use crate::{LineNumber, PatternError, PatternFileError};
2 use regex::bytes::Regex;
2 use regex::bytes::Regex;
3 use std::collections::HashMap;
3 use std::collections::HashMap;
4 use std::fs::File;
4 use std::fs::File;
5 use std::io::Read;
5 use std::io::Read;
6 use std::vec::Vec;
6 use std::vec::Vec;
7 use utils::files::get_path_from_bytes;
7 use utils::files::get_path_from_bytes;
8 use utils::{replace_slice, SliceExt};
8 use utils::{replace_slice, SliceExt};
9
9
10 lazy_static! {
10 lazy_static! {
11 static ref reescape: Vec<Vec<u8>> = {
11 static ref reescape: Vec<Vec<u8>> = {
12 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
12 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
13 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
13 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
14 for byte in to_escape {
14 for byte in to_escape {
15 v[*byte as usize].insert(0, b'\\');
15 v[*byte as usize].insert(0, b'\\');
16 }
16 }
17 v
17 v
18 };
18 };
19 }
19 }
20
20
21 /// These are matched in order
21 /// These are matched in order
22 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
22 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
23 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
23 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
24
24
25 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
25 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
26 pub enum PatternSyntax {
26 pub enum PatternSyntax {
27 Regexp,
27 Regexp,
28 /// Glob that matches at the front of the path
28 /// Glob that matches at the front of the path
29 RootGlob,
29 RootGlob,
30 /// Glob that matches at any suffix of the path (still anchored at slashes)
30 /// Glob that matches at any suffix of the path (still anchored at slashes)
31 Glob,
31 Glob,
32 Path,
32 Path,
33 RelPath,
33 RelPath,
34 RelGlob,
34 RelGlob,
35 RelRegexp,
35 RelRegexp,
36 RootFiles,
36 RootFiles,
37 }
37 }
38
38
39 /// Transforms a glob pattern into a regex
39 /// Transforms a glob pattern into a regex
40 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
40 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
41 let mut input = pat;
41 let mut input = pat;
42 let mut res: Vec<u8> = vec![];
42 let mut res: Vec<u8> = vec![];
43 let mut group_depth = 0;
43 let mut group_depth = 0;
44
44
45 while let Some((c, rest)) = input.split_first() {
45 while let Some((c, rest)) = input.split_first() {
46 input = rest;
46 input = rest;
47
47
48 match c {
48 match c {
49 b'*' => {
49 b'*' => {
50 for (source, repl) in GLOB_REPLACEMENTS {
50 for (source, repl) in GLOB_REPLACEMENTS {
51 if input.starts_with(source) {
51 if input.starts_with(source) {
52 input = &input[source.len()..];
52 input = &input[source.len()..];
53 res.extend(*repl);
53 res.extend(*repl);
54 break;
54 break;
55 }
55 }
56 }
56 }
57 }
57 }
58 b'?' => res.extend(b"."),
58 b'?' => res.extend(b"."),
59 b'[' => {
59 b'[' => {
60 match input.iter().skip(1).position(|b| *b == b']') {
60 match input.iter().skip(1).position(|b| *b == b']') {
61 None => res.extend(b"\\["),
61 None => res.extend(b"\\["),
62 Some(end) => {
62 Some(end) => {
63 // Account for the one we skipped
63 // Account for the one we skipped
64 let end = end + 1;
64 let end = end + 1;
65
65
66 res.extend(b"[");
66 res.extend(b"[");
67
67
68 for (i, b) in input[..end].iter().enumerate() {
68 for (i, b) in input[..end].iter().enumerate() {
69 if *b == b'!' && i == 0 {
69 if *b == b'!' && i == 0 {
70 res.extend(b"^")
70 res.extend(b"^")
71 } else if *b == b'^' && i == 0 {
71 } else if *b == b'^' && i == 0 {
72 res.extend(b"\\^")
72 res.extend(b"\\^")
73 } else if *b == b'\\' {
73 } else if *b == b'\\' {
74 res.extend(b"\\\\")
74 res.extend(b"\\\\")
75 } else {
75 } else {
76 res.push(*b)
76 res.push(*b)
77 }
77 }
78 }
78 }
79 res.extend(b"]");
79 res.extend(b"]");
80 input = &input[end + 1..];
80 input = &input[end + 1..];
81 }
81 }
82 }
82 }
83 }
83 }
84 b'{' => {
84 b'{' => {
85 group_depth += 1;
85 group_depth += 1;
86 res.extend(b"(?:")
86 res.extend(b"(?:")
87 }
87 }
88 b'}' if group_depth > 0 => {
88 b'}' if group_depth > 0 => {
89 group_depth -= 1;
89 group_depth -= 1;
90 res.extend(b")");
90 res.extend(b")");
91 }
91 }
92 b',' if group_depth > 0 => res.extend(b"|"),
92 b',' if group_depth > 0 => res.extend(b"|"),
93 b'\\' => {
93 b'\\' => {
94 let c = {
94 let c = {
95 if let Some((c, rest)) = input.split_first() {
95 if let Some((c, rest)) = input.split_first() {
96 input = rest;
96 input = rest;
97 c
97 c
98 } else {
98 } else {
99 c
99 c
100 }
100 }
101 };
101 };
102 res.extend(&reescape[*c as usize])
102 res.extend(&reescape[*c as usize])
103 }
103 }
104 _ => res.extend(&reescape[*c as usize]),
104 _ => res.extend(&reescape[*c as usize]),
105 }
105 }
106 }
106 }
107 res
107 res
108 }
108 }
109
109
110 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
110 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
111 pattern
111 pattern
112 .iter()
112 .iter()
113 .flat_map(|c| reescape[*c as usize].clone())
113 .flat_map(|c| reescape[*c as usize].clone())
114 .collect()
114 .collect()
115 }
115 }
116
116
117 fn parse_pattern_syntax(kind: &[u8]) -> Result<PatternSyntax, PatternError> {
117 fn parse_pattern_syntax(kind: &[u8]) -> Result<PatternSyntax, PatternError> {
118 match kind {
118 match kind {
119 b"re" => Ok(PatternSyntax::Regexp),
119 b"re" => Ok(PatternSyntax::Regexp),
120 b"path" => Ok(PatternSyntax::Path),
120 b"path" => Ok(PatternSyntax::Path),
121 b"relpath" => Ok(PatternSyntax::RelPath),
121 b"relpath" => Ok(PatternSyntax::RelPath),
122 b"rootfilesin" => Ok(PatternSyntax::RootFiles),
122 b"rootfilesin" => Ok(PatternSyntax::RootFiles),
123 b"relglob" => Ok(PatternSyntax::RelGlob),
123 b"relglob" => Ok(PatternSyntax::RelGlob),
124 b"relre" => Ok(PatternSyntax::RelRegexp),
124 b"relre" => Ok(PatternSyntax::RelRegexp),
125 b"glob" => Ok(PatternSyntax::Glob),
125 b"glob" => Ok(PatternSyntax::Glob),
126 b"rootglob" => Ok(PatternSyntax::RootGlob),
126 b"rootglob" => Ok(PatternSyntax::RootGlob),
127 _ => Err(PatternError::UnsupportedSyntax(
127 _ => Err(PatternError::UnsupportedSyntax(
128 String::from_utf8_lossy(kind).to_string(),
128 String::from_utf8_lossy(kind).to_string(),
129 )),
129 )),
130 }
130 }
131 }
131 }
132
132
133 /// Builds the regex that corresponds to the given pattern.
133 /// Builds the regex that corresponds to the given pattern.
134 /// If within a `syntax: regexp` context, returns the pattern,
134 /// If within a `syntax: regexp` context, returns the pattern,
135 /// otherwise, returns the corresponding regex.
135 /// otherwise, returns the corresponding regex.
136 fn _build_single_regex(
136 fn _build_single_regex(
137 syntax: PatternSyntax,
137 syntax: PatternSyntax,
138 pattern: &[u8],
138 pattern: &[u8],
139 globsuffix: &[u8],
139 globsuffix: &[u8],
140 ) -> Vec<u8> {
140 ) -> Vec<u8> {
141 if pattern.is_empty() {
141 if pattern.is_empty() {
142 return vec![];
142 return vec![];
143 }
143 }
144 match syntax {
144 match syntax {
145 PatternSyntax::Regexp => pattern.to_owned(),
145 PatternSyntax::Regexp => pattern.to_owned(),
146 PatternSyntax::RelRegexp => {
146 PatternSyntax::RelRegexp => {
147 if pattern[0] == b'^' {
147 if pattern[0] == b'^' {
148 return pattern.to_owned();
148 return pattern.to_owned();
149 }
149 }
150 let mut res = b".*".to_vec();
150 let mut res = b".*".to_vec();
151 res.extend(pattern);
151 res.extend(pattern);
152 res
152 res
153 }
153 }
154 PatternSyntax::Path | PatternSyntax::RelPath => {
154 PatternSyntax::Path | PatternSyntax::RelPath => {
155 if pattern == b"." {
155 if pattern == b"." {
156 return vec![];
156 return vec![];
157 }
157 }
158 let mut pattern = escape_pattern(pattern);
158 let mut pattern = escape_pattern(pattern);
159 pattern.extend(b"(?:/|$)");
159 pattern.extend(b"(?:/|$)");
160 pattern
160 pattern
161 }
161 }
162 PatternSyntax::RootFiles => {
162 PatternSyntax::RootFiles => {
163 let mut res = if pattern == b"." {
163 let mut res = if pattern == b"." {
164 vec![]
164 vec![]
165 } else {
165 } else {
166 // Pattern is a directory name.
166 // Pattern is a directory name.
167 let mut as_vec: Vec<u8> = escape_pattern(pattern);
167 let mut as_vec: Vec<u8> = escape_pattern(pattern);
168 as_vec.push(b'/');
168 as_vec.push(b'/');
169 as_vec
169 as_vec
170 };
170 };
171
171
172 // Anything after the pattern must be a non-directory.
172 // Anything after the pattern must be a non-directory.
173 res.extend(b"[^/]+$");
173 res.extend(b"[^/]+$");
174 res
174 res
175 }
175 }
176 PatternSyntax::Glob
176 PatternSyntax::Glob
177 | PatternSyntax::RelGlob
177 | PatternSyntax::RelGlob
178 | PatternSyntax::RootGlob => {
178 | PatternSyntax::RootGlob => {
179 let mut res: Vec<u8> = vec![];
179 let mut res: Vec<u8> = vec![];
180 if syntax == PatternSyntax::RelGlob {
180 if syntax == PatternSyntax::RelGlob {
181 res.extend(b"(?:|.*/)");
181 res.extend(b"(?:|.*/)");
182 }
182 }
183
183
184 res.extend(glob_to_re(pattern));
184 res.extend(glob_to_re(pattern));
185 res.extend(globsuffix.iter());
185 res.extend(globsuffix.iter());
186 res
186 res
187 }
187 }
188 }
188 }
189 }
189 }
190
190
191 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
191 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
192 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
192 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
193
193
194 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
194 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
195 /// that don't need to be transformed into a regex.
195 /// that don't need to be transformed into a regex.
196 pub fn build_single_regex(
196 pub fn build_single_regex(
197 kind: &[u8],
197 kind: &[u8],
198 pat: &[u8],
198 pat: &[u8],
199 globsuffix: &[u8],
199 globsuffix: &[u8],
200 ) -> Result<Vec<u8>, PatternError> {
200 ) -> Result<Vec<u8>, PatternError> {
201 let enum_kind = parse_pattern_syntax(kind)?;
201 let enum_kind = parse_pattern_syntax(kind)?;
202 if enum_kind == PatternSyntax::RootGlob
202 if enum_kind == PatternSyntax::RootGlob
203 && pat.iter().all(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
203 && !pat.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
204 {
204 {
205 Ok(pat.to_vec())
205 let mut escaped = escape_pattern(pat);
206 escaped.extend(b"(?:/|$)");
207 Ok(escaped)
206 } else {
208 } else {
207 Ok(_build_single_regex(enum_kind, pat, globsuffix))
209 Ok(_build_single_regex(enum_kind, pat, globsuffix))
208 }
210 }
209 }
211 }
210
212
211 lazy_static! {
213 lazy_static! {
212 static ref SYNTAXES: HashMap<&'static [u8], &'static [u8]> = {
214 static ref SYNTAXES: HashMap<&'static [u8], &'static [u8]> = {
213 let mut m = HashMap::new();
215 let mut m = HashMap::new();
214
216
215 m.insert(b"re".as_ref(), b"relre:".as_ref());
217 m.insert(b"re".as_ref(), b"relre:".as_ref());
216 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
218 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
217 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
219 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
218 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
220 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
219 m.insert(b"include".as_ref(), b"include".as_ref());
221 m.insert(b"include".as_ref(), b"include".as_ref());
220 m.insert(b"subinclude".as_ref(), b"subinclude".as_ref());
222 m.insert(b"subinclude".as_ref(), b"subinclude".as_ref());
221 m
223 m
222 };
224 };
223 }
225 }
224
226
225 pub type PatternTuple = (Vec<u8>, LineNumber, Vec<u8>);
227 pub type PatternTuple = (Vec<u8>, LineNumber, Vec<u8>);
226 type WarningTuple = (String, String);
228 type WarningTuple = (String, String);
227
229
228 pub fn parse_pattern_file_contents(
230 pub fn parse_pattern_file_contents(
229 lines: &[u8],
231 lines: &[u8],
230 file_path: &[u8],
232 file_path: &[u8],
231 warn: bool,
233 warn: bool,
232 ) -> (Vec<PatternTuple>, Vec<WarningTuple>) {
234 ) -> (Vec<PatternTuple>, Vec<WarningTuple>) {
233 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
235 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
234 let mut inputs: Vec<PatternTuple> = vec![];
236 let mut inputs: Vec<PatternTuple> = vec![];
235 let mut warnings: Vec<WarningTuple> = vec![];
237 let mut warnings: Vec<WarningTuple> = vec![];
236
238
237 let mut current_syntax = b"relre:".as_ref();
239 let mut current_syntax = b"relre:".as_ref();
238
240
239 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
241 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
240 let line_number = line_number + 1;
242 let line_number = line_number + 1;
241
243
242 if line.contains(&('#' as u8)) {
244 if line.contains(&('#' as u8)) {
243 if let Some(cap) = comment_regex.captures(line) {
245 if let Some(cap) = comment_regex.captures(line) {
244 line = &line[..cap.get(1).unwrap().end()]
246 line = &line[..cap.get(1).unwrap().end()]
245 }
247 }
246 let mut line = line.to_owned();
248 let mut line = line.to_owned();
247 replace_slice(&mut line, br"\#", b"#");
249 replace_slice(&mut line, br"\#", b"#");
248 }
250 }
249
251
250 let mut line = line.trim_end();
252 let mut line = line.trim_end();
251
253
252 if line.is_empty() {
254 if line.is_empty() {
253 continue;
255 continue;
254 }
256 }
255
257
256 if line.starts_with(b"syntax:") {
258 if line.starts_with(b"syntax:") {
257 let syntax = line[b"syntax:".len()..].trim();
259 let syntax = line[b"syntax:".len()..].trim();
258
260
259 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
261 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
260 current_syntax = rel_syntax;
262 current_syntax = rel_syntax;
261 } else if warn {
263 } else if warn {
262 warnings.push((
264 warnings.push((
263 String::from_utf8_lossy(file_path).to_string(),
265 String::from_utf8_lossy(file_path).to_string(),
264 String::from_utf8_lossy(syntax).to_string(),
266 String::from_utf8_lossy(syntax).to_string(),
265 ));
267 ));
266 }
268 }
267 continue;
269 continue;
268 }
270 }
269
271
270 let mut line_syntax: &[u8] = &current_syntax;
272 let mut line_syntax: &[u8] = &current_syntax;
271
273
272 for (s, rels) in SYNTAXES.iter() {
274 for (s, rels) in SYNTAXES.iter() {
273 if line.starts_with(rels) {
275 if line.starts_with(rels) {
274 line_syntax = rels;
276 line_syntax = rels;
275 line = &line[rels.len()..];
277 line = &line[rels.len()..];
276 break;
278 break;
277 } else if line.starts_with(&[s, b":".as_ref()].concat()) {
279 } else if line.starts_with(&[s, b":".as_ref()].concat()) {
278 line_syntax = rels;
280 line_syntax = rels;
279 line = &line[s.len() + 1..];
281 line = &line[s.len() + 1..];
280 break;
282 break;
281 }
283 }
282 }
284 }
283
285
284 inputs.push((
286 inputs.push((
285 [line_syntax, line].concat(),
287 [line_syntax, line].concat(),
286 line_number,
288 line_number,
287 line.to_owned(),
289 line.to_owned(),
288 ));
290 ));
289 }
291 }
290 (inputs, warnings)
292 (inputs, warnings)
291 }
293 }
292
294
293 pub fn read_pattern_file(
295 pub fn read_pattern_file(
294 file_path: &[u8],
296 file_path: &[u8],
295 warn: bool,
297 warn: bool,
296 ) -> Result<(Vec<PatternTuple>, Vec<WarningTuple>), PatternFileError> {
298 ) -> Result<(Vec<PatternTuple>, Vec<WarningTuple>), PatternFileError> {
297 let mut f = File::open(get_path_from_bytes(file_path))?;
299 let mut f = File::open(get_path_from_bytes(file_path))?;
298 let mut contents = Vec::new();
300 let mut contents = Vec::new();
299
301
300 f.read_to_end(&mut contents)?;
302 f.read_to_end(&mut contents)?;
301
303
302 Ok(parse_pattern_file_contents(&contents, file_path, warn))
304 Ok(parse_pattern_file_contents(&contents, file_path, warn))
303 }
305 }
304
306
305 #[cfg(test)]
307 #[cfg(test)]
306 mod tests {
308 mod tests {
307 use super::*;
309 use super::*;
308
310
309 #[test]
311 #[test]
310 fn escape_pattern_test() {
312 fn escape_pattern_test() {
311 let untouched = br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
313 let untouched = br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
312 assert_eq!(escape_pattern(untouched), untouched.to_vec());
314 assert_eq!(escape_pattern(untouched), untouched.to_vec());
313 // All escape codes
315 // All escape codes
314 assert_eq!(
316 assert_eq!(
315 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
317 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
316 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
318 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
317 .to_vec()
319 .to_vec()
318 );
320 );
319 }
321 }
320
322
321 #[test]
323 #[test]
322 fn glob_test() {
324 fn glob_test() {
323 assert_eq!(glob_to_re(br#"?"#), br#"."#);
325 assert_eq!(glob_to_re(br#"?"#), br#"."#);
324 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
326 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
325 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
327 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
326 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
328 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
327 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
329 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
328 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
330 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
329 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
331 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
330 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
332 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
331 }
333 }
332
334
333 #[test]
335 #[test]
334 fn test_parse_pattern_file_contents() {
336 fn test_parse_pattern_file_contents() {
335 let lines = b"syntax: glob\n*.elc";
337 let lines = b"syntax: glob\n*.elc";
336
338
337 assert_eq!(
339 assert_eq!(
338 vec![(b"relglob:*.elc".to_vec(), 2, b"*.elc".to_vec())],
340 vec![(b"relglob:*.elc".to_vec(), 2, b"*.elc".to_vec())],
339 parse_pattern_file_contents(lines, b"file_path", false).0,
341 parse_pattern_file_contents(lines, b"file_path", false).0,
340 );
342 );
341
343
342 let lines = b"syntax: include\nsyntax: glob";
344 let lines = b"syntax: include\nsyntax: glob";
343
345
344 assert_eq!(
346 assert_eq!(
345 parse_pattern_file_contents(lines, b"file_path", false).0,
347 parse_pattern_file_contents(lines, b"file_path", false).0,
346 vec![]
348 vec![]
347 );
349 );
348 let lines = b"glob:**.o";
350 let lines = b"glob:**.o";
349 assert_eq!(
351 assert_eq!(
350 parse_pattern_file_contents(lines, b"file_path", false).0,
352 parse_pattern_file_contents(lines, b"file_path", false).0,
351 vec![(b"relglob:**.o".to_vec(), 1, b"**.o".to_vec())]
353 vec![(b"relglob:**.o".to_vec(), 1, b"**.o".to_vec())]
352 );
354 );
353 }
355 }
356
357 #[test]
358 fn test_build_single_regex_shortcut() {
359 assert_eq!(
360 br"(?:/|$)".to_vec(),
361 build_single_regex(b"rootglob", b"", b"").unwrap()
362 );
363 assert_eq!(
364 br"whatever(?:/|$)".to_vec(),
365 build_single_regex(b"rootglob", b"whatever", b"").unwrap()
366 );
367 assert_eq!(
368 br"[^/]*\.o".to_vec(),
369 build_single_regex(b"rootglob", b"*.o", b"").unwrap()
370 );
371 }
354 }
372 }
General Comments 0
You need to be logged in to leave comments. Login now