##// END OF EJS Templates
rust: don't escape spaces in regex...
Spencer Baugh -
r51752:2b4bcdc9 default
parent child Browse files
Show More
@@ -1,808 +1,808 b''
1 1 // filepatterns.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Handling of Mercurial-specific patterns.
9 9
10 10 use crate::{
11 11 utils::{
12 12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes},
13 13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError},
14 14 SliceExt,
15 15 },
16 16 FastHashMap, PatternError,
17 17 };
18 18 use lazy_static::lazy_static;
19 19 use regex::bytes::{NoExpand, Regex};
20 20 use std::ops::Deref;
21 21 use std::path::{Path, PathBuf};
22 22 use std::vec::Vec;
23 23
24 24 lazy_static! {
25 25 static ref RE_ESCAPE: Vec<Vec<u8>> = {
26 26 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
27 27 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
28 28 for byte in to_escape {
29 29 v[*byte as usize].insert(0, b'\\');
30 30 }
31 31 v
32 32 };
33 33 }
34 34
35 35 /// These are matched in order
36 36 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
37 37 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
38 38
39 39 /// Appended to the regexp of globs
40 40 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
41 41
42 42 #[derive(Debug, Clone, PartialEq, Eq)]
43 43 pub enum PatternSyntax {
44 44 /// A regular expression
45 45 Regexp,
46 46 /// Glob that matches at the front of the path
47 47 RootGlob,
48 48 /// Glob that matches at any suffix of the path (still anchored at
49 49 /// slashes)
50 50 Glob,
51 51 /// a path relative to repository root, which is matched recursively
52 52 Path,
53 53 /// a single exact path relative to repository root
54 54 FilePath,
55 55 /// A path relative to cwd
56 56 RelPath,
57 57 /// an unrooted glob (*.rs matches Rust files in all dirs)
58 58 RelGlob,
59 59 /// A regexp that needn't match the start of a name
60 60 RelRegexp,
61 61 /// A path relative to repository root, which is matched non-recursively
62 62 /// (will not match subdirectories)
63 63 RootFiles,
64 64 /// A file of patterns to read and include
65 65 Include,
66 66 /// A file of patterns to match against files under the same directory
67 67 SubInclude,
68 68 /// SubInclude with the result of parsing the included file
69 69 ///
70 70 /// Note: there is no ExpandedInclude because that expansion can be done
71 71 /// in place by replacing the Include pattern by the included patterns.
72 72 /// SubInclude requires more handling.
73 73 ///
74 74 /// Note: `Box` is used to minimize size impact on other enum variants
75 75 ExpandedSubInclude(Box<SubInclude>),
76 76 }
77 77
78 78 /// Transforms a glob pattern into a regex
79 79 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
80 80 let mut input = pat;
81 81 let mut res: Vec<u8> = vec![];
82 82 let mut group_depth = 0;
83 83
84 84 while let Some((c, rest)) = input.split_first() {
85 85 input = rest;
86 86
87 87 match c {
88 88 b'*' => {
89 89 for (source, repl) in GLOB_REPLACEMENTS {
90 90 if let Some(rest) = input.drop_prefix(source) {
91 91 input = rest;
92 92 res.extend(*repl);
93 93 break;
94 94 }
95 95 }
96 96 }
97 97 b'?' => res.extend(b"."),
98 98 b'[' => {
99 99 match input.iter().skip(1).position(|b| *b == b']') {
100 100 None => res.extend(b"\\["),
101 101 Some(end) => {
102 102 // Account for the one we skipped
103 103 let end = end + 1;
104 104
105 105 res.extend(b"[");
106 106
107 107 for (i, b) in input[..end].iter().enumerate() {
108 108 if *b == b'!' && i == 0 {
109 109 res.extend(b"^")
110 110 } else if *b == b'^' && i == 0 {
111 111 res.extend(b"\\^")
112 112 } else if *b == b'\\' {
113 113 res.extend(b"\\\\")
114 114 } else {
115 115 res.push(*b)
116 116 }
117 117 }
118 118 res.extend(b"]");
119 119 input = &input[end + 1..];
120 120 }
121 121 }
122 122 }
123 123 b'{' => {
124 124 group_depth += 1;
125 125 res.extend(b"(?:")
126 126 }
127 127 b'}' if group_depth > 0 => {
128 128 group_depth -= 1;
129 129 res.extend(b")");
130 130 }
131 131 b',' if group_depth > 0 => res.extend(b"|"),
132 132 b'\\' => {
133 133 let c = {
134 134 if let Some((c, rest)) = input.split_first() {
135 135 input = rest;
136 136 c
137 137 } else {
138 138 c
139 139 }
140 140 };
141 141 res.extend(&RE_ESCAPE[*c as usize])
142 142 }
143 143 _ => res.extend(&RE_ESCAPE[*c as usize]),
144 144 }
145 145 }
146 146 res
147 147 }
148 148
149 149 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
150 150 pattern
151 151 .iter()
152 152 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
153 153 .collect()
154 154 }
155 155
156 156 pub fn parse_pattern_syntax(
157 157 kind: &[u8],
158 158 ) -> Result<PatternSyntax, PatternError> {
159 159 match kind {
160 160 b"re:" => Ok(PatternSyntax::Regexp),
161 161 b"path:" => Ok(PatternSyntax::Path),
162 162 b"filepath:" => Ok(PatternSyntax::FilePath),
163 163 b"relpath:" => Ok(PatternSyntax::RelPath),
164 164 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
165 165 b"relglob:" => Ok(PatternSyntax::RelGlob),
166 166 b"relre:" => Ok(PatternSyntax::RelRegexp),
167 167 b"glob:" => Ok(PatternSyntax::Glob),
168 168 b"rootglob:" => Ok(PatternSyntax::RootGlob),
169 169 b"include:" => Ok(PatternSyntax::Include),
170 170 b"subinclude:" => Ok(PatternSyntax::SubInclude),
171 171 _ => Err(PatternError::UnsupportedSyntax(
172 172 String::from_utf8_lossy(kind).to_string(),
173 173 )),
174 174 }
175 175 }
176 176
177 177 lazy_static! {
178 178 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
179 179 }
180 180
181 181 /// Builds the regex that corresponds to the given pattern.
182 182 /// If within a `syntax: regexp` context, returns the pattern,
183 183 /// otherwise, returns the corresponding regex.
184 184 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
185 185 let IgnorePattern {
186 186 syntax, pattern, ..
187 187 } = entry;
188 188 if pattern.is_empty() {
189 189 return vec![];
190 190 }
191 191 match syntax {
192 192 PatternSyntax::Regexp => pattern.to_owned(),
193 193 PatternSyntax::RelRegexp => {
194 194 // The `regex` crate accepts `**` while `re2` and Python's `re`
195 195 // do not. Checking for `*` correctly triggers the same error all
196 196 // engines.
197 197 if pattern[0] == b'^'
198 198 || pattern[0] == b'*'
199 199 || pattern.starts_with(b".*")
200 200 {
201 201 return pattern.to_owned();
202 202 }
203 203 match FLAG_RE.find(pattern) {
204 204 Some(mat) => {
205 205 let s = mat.start();
206 206 let e = mat.end();
207 207 [
208 208 &b"(?"[..],
209 209 &pattern[s + 2..e - 1],
210 210 &b":"[..],
211 211 if pattern[e] == b'^'
212 212 || pattern[e] == b'*'
213 213 || pattern[e..].starts_with(b".*")
214 214 {
215 215 &b""[..]
216 216 } else {
217 217 &b".*"[..]
218 218 },
219 219 &pattern[e..],
220 220 &b")"[..],
221 221 ]
222 222 .concat()
223 223 }
224 224 None => [&b".*"[..], pattern].concat(),
225 225 }
226 226 }
227 227 PatternSyntax::Path | PatternSyntax::RelPath => {
228 228 if pattern == b"." {
229 229 return vec![];
230 230 }
231 231 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
232 232 }
233 233 PatternSyntax::RootFiles => {
234 234 let mut res = if pattern == b"." {
235 235 vec![]
236 236 } else {
237 237 // Pattern is a directory name.
238 238 [escape_pattern(pattern).as_slice(), b"/"].concat()
239 239 };
240 240
241 241 // Anything after the pattern must be a non-directory.
242 242 res.extend(b"[^/]+$");
243 243 res
244 244 }
245 245 PatternSyntax::RelGlob => {
246 246 let glob_re = glob_to_re(pattern);
247 247 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
248 248 [b".*", rest, GLOB_SUFFIX].concat()
249 249 } else {
250 250 [b"(?:.*/)?", glob_re.as_slice(), GLOB_SUFFIX].concat()
251 251 }
252 252 }
253 253 PatternSyntax::Glob | PatternSyntax::RootGlob => {
254 254 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
255 255 }
256 256 PatternSyntax::Include
257 257 | PatternSyntax::SubInclude
258 258 | PatternSyntax::ExpandedSubInclude(_)
259 259 | PatternSyntax::FilePath => unreachable!(),
260 260 }
261 261 }
262 262
263 263 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
264 264 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
265 265
266 266 /// TODO support other platforms
267 267 #[cfg(unix)]
268 268 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
269 269 if bytes.is_empty() {
270 270 return b".".to_vec();
271 271 }
272 272 let sep = b'/';
273 273
274 274 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
275 275 if initial_slashes > 2 {
276 276 // POSIX allows one or two initial slashes, but treats three or more
277 277 // as single slash.
278 278 initial_slashes = 1;
279 279 }
280 280 let components = bytes
281 281 .split(|b| *b == sep)
282 282 .filter(|c| !(c.is_empty() || c == b"."))
283 283 .fold(vec![], |mut acc, component| {
284 284 if component != b".."
285 285 || (initial_slashes == 0 && acc.is_empty())
286 286 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
287 287 {
288 288 acc.push(component)
289 289 } else if !acc.is_empty() {
290 290 acc.pop();
291 291 }
292 292 acc
293 293 });
294 294 let mut new_bytes = components.join(&sep);
295 295
296 296 if initial_slashes > 0 {
297 297 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
298 298 buf.extend(new_bytes);
299 299 new_bytes = buf;
300 300 }
301 301 if new_bytes.is_empty() {
302 302 b".".to_vec()
303 303 } else {
304 304 new_bytes
305 305 }
306 306 }
307 307
308 308 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
309 309 /// that don't need to be transformed into a regex.
310 310 pub fn build_single_regex(
311 311 entry: &IgnorePattern,
312 312 ) -> Result<Option<Vec<u8>>, PatternError> {
313 313 let IgnorePattern {
314 314 pattern, syntax, ..
315 315 } = entry;
316 316 let pattern = match syntax {
317 317 PatternSyntax::RootGlob
318 318 | PatternSyntax::Path
319 319 | PatternSyntax::RelGlob
320 320 | PatternSyntax::RelPath
321 321 | PatternSyntax::RootFiles => normalize_path_bytes(pattern),
322 322 PatternSyntax::Include | PatternSyntax::SubInclude => {
323 323 return Err(PatternError::NonRegexPattern(entry.clone()))
324 324 }
325 325 _ => pattern.to_owned(),
326 326 };
327 327 let is_simple_rootglob = *syntax == PatternSyntax::RootGlob
328 328 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b));
329 329 if is_simple_rootglob || syntax == &PatternSyntax::FilePath {
330 330 Ok(None)
331 331 } else {
332 332 let mut entry = entry.clone();
333 333 entry.pattern = pattern;
334 334 Ok(Some(_build_single_regex(&entry)))
335 335 }
336 336 }
337 337
338 338 lazy_static! {
339 339 static ref SYNTAXES: FastHashMap<&'static [u8], PatternSyntax> = {
340 340 let mut m = FastHashMap::default();
341 341
342 342 m.insert(b"re:".as_ref(), PatternSyntax::Regexp);
343 343 m.insert(b"regexp:".as_ref(), PatternSyntax::Regexp);
344 344 m.insert(b"path:".as_ref(), PatternSyntax::Path);
345 345 m.insert(b"filepath:".as_ref(), PatternSyntax::FilePath);
346 346 m.insert(b"relpath:".as_ref(), PatternSyntax::RelPath);
347 347 m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFiles);
348 348 m.insert(b"relglob:".as_ref(), PatternSyntax::RelGlob);
349 349 m.insert(b"relre:".as_ref(), PatternSyntax::RelRegexp);
350 350 m.insert(b"glob:".as_ref(), PatternSyntax::Glob);
351 351 m.insert(b"rootglob:".as_ref(), PatternSyntax::RootGlob);
352 352 m.insert(b"include:".as_ref(), PatternSyntax::Include);
353 353 m.insert(b"subinclude:".as_ref(), PatternSyntax::SubInclude);
354 354
355 355 m
356 356 };
357 357 }
358 358
359 359 #[derive(Debug)]
360 360 pub enum PatternFileWarning {
361 361 /// (file path, syntax bytes)
362 362 InvalidSyntax(PathBuf, Vec<u8>),
363 363 /// File path
364 364 NoSuchFile(PathBuf),
365 365 }
366 366
367 367 pub fn parse_one_pattern(
368 368 pattern: &[u8],
369 369 source: &Path,
370 370 default: PatternSyntax,
371 371 ) -> IgnorePattern {
372 372 let mut pattern_bytes: &[u8] = pattern;
373 373 let mut syntax = default;
374 374
375 375 for (s, val) in SYNTAXES.iter() {
376 376 if let Some(rest) = pattern_bytes.drop_prefix(s) {
377 377 syntax = val.clone();
378 378 pattern_bytes = rest;
379 379 break;
380 380 }
381 381 }
382 382
383 383 let pattern = pattern_bytes.to_vec();
384 384
385 385 IgnorePattern {
386 386 syntax,
387 387 pattern,
388 388 source: source.to_owned(),
389 389 }
390 390 }
391 391
392 392 pub fn parse_pattern_file_contents(
393 393 lines: &[u8],
394 394 file_path: &Path,
395 395 default_syntax_override: Option<PatternSyntax>,
396 396 warn: bool,
397 397 relativize: bool,
398 398 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
399 399 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
400 400
401 401 #[allow(clippy::trivial_regex)]
402 402 let comment_escape_regex = Regex::new(r"\\#").unwrap();
403 403 let mut inputs: Vec<IgnorePattern> = vec![];
404 404 let mut warnings: Vec<PatternFileWarning> = vec![];
405 405
406 406 let mut current_syntax =
407 407 default_syntax_override.unwrap_or(PatternSyntax::RelRegexp);
408 408
409 409 for mut line in lines.split(|c| *c == b'\n') {
410 410 let line_buf;
411 411 if line.contains(&b'#') {
412 412 if let Some(cap) = comment_regex.captures(line) {
413 413 line = &line[..cap.get(1).unwrap().end()]
414 414 }
415 415 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
416 416 line = &line_buf;
417 417 }
418 418
419 419 let line = line.trim_end();
420 420
421 421 if line.is_empty() {
422 422 continue;
423 423 }
424 424
425 425 if let Some(syntax) = line.drop_prefix(b"syntax:") {
426 426 let syntax = syntax.trim();
427 427
428 428 if let Some(parsed) =
429 429 SYNTAXES.get([syntax, &b":"[..]].concat().as_slice())
430 430 {
431 431 current_syntax = parsed.clone();
432 432 } else if warn {
433 433 warnings.push(PatternFileWarning::InvalidSyntax(
434 434 file_path.to_owned(),
435 435 syntax.to_owned(),
436 436 ));
437 437 }
438 438 } else {
439 439 let pattern = parse_one_pattern(
440 440 line,
441 441 file_path,
442 442 current_syntax.clone(),
443 443 );
444 444 inputs.push(if relativize {
445 445 pattern.to_relative()
446 446 } else {
447 447 pattern
448 448 })
449 449 }
450 450 }
451 451 Ok((inputs, warnings))
452 452 }
453 453
454 454 pub fn read_pattern_file(
455 455 file_path: &Path,
456 456 warn: bool,
457 457 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
458 458 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
459 459 match std::fs::read(file_path) {
460 460 Ok(contents) => {
461 461 inspect_pattern_bytes(file_path, &contents);
462 462 parse_pattern_file_contents(&contents, file_path, None, warn, true)
463 463 }
464 464 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
465 465 vec![],
466 466 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
467 467 )),
468 468 Err(e) => Err(e.into()),
469 469 }
470 470 }
471 471
472 472 /// Represents an entry in an "ignore" file.
473 473 #[derive(Debug, Eq, PartialEq, Clone)]
474 474 pub struct IgnorePattern {
475 475 pub syntax: PatternSyntax,
476 476 pub pattern: Vec<u8>,
477 477 pub source: PathBuf,
478 478 }
479 479
480 480 impl IgnorePattern {
481 481 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
482 482 Self {
483 483 syntax,
484 484 pattern: pattern.to_owned(),
485 485 source: source.to_owned(),
486 486 }
487 487 }
488 488
489 489 pub fn to_relative(self) -> Self {
490 490 let Self {
491 491 syntax,
492 492 pattern,
493 493 source,
494 494 } = self;
495 495 Self {
496 496 syntax: match syntax {
497 497 PatternSyntax::Regexp => PatternSyntax::RelRegexp,
498 498 PatternSyntax::Glob => PatternSyntax::RelGlob,
499 499 x => x,
500 500 },
501 501 pattern,
502 502 source,
503 503 }
504 504 }
505 505 }
506 506
507 507 pub type PatternResult<T> = Result<T, PatternError>;
508 508
509 509 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
510 510 /// and `subinclude:` patterns.
511 511 ///
512 512 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
513 513 /// is used for the latter to form a tree of patterns.
514 514 pub fn get_patterns_from_file(
515 515 pattern_file: &Path,
516 516 root_dir: &Path,
517 517 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
518 518 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
519 519 let (patterns, mut warnings) =
520 520 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
521 521 let patterns = patterns
522 522 .into_iter()
523 523 .flat_map(|entry| -> PatternResult<_> {
524 524 Ok(match &entry.syntax {
525 525 PatternSyntax::Include => {
526 526 let inner_include =
527 527 root_dir.join(get_path_from_bytes(&entry.pattern));
528 528 let (inner_pats, inner_warnings) = get_patterns_from_file(
529 529 &inner_include,
530 530 root_dir,
531 531 inspect_pattern_bytes,
532 532 )?;
533 533 warnings.extend(inner_warnings);
534 534 inner_pats
535 535 }
536 536 PatternSyntax::SubInclude => {
537 537 let mut sub_include = SubInclude::new(
538 538 root_dir,
539 539 &entry.pattern,
540 540 &entry.source,
541 541 )?;
542 542 let (inner_patterns, inner_warnings) =
543 543 get_patterns_from_file(
544 544 &sub_include.path,
545 545 &sub_include.root,
546 546 inspect_pattern_bytes,
547 547 )?;
548 548 sub_include.included_patterns = inner_patterns;
549 549 warnings.extend(inner_warnings);
550 550 vec![IgnorePattern {
551 551 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
552 552 sub_include,
553 553 )),
554 554 ..entry
555 555 }]
556 556 }
557 557 _ => vec![entry],
558 558 })
559 559 })
560 560 .flatten()
561 561 .collect();
562 562
563 563 Ok((patterns, warnings))
564 564 }
565 565
566 566 /// Holds all the information needed to handle a `subinclude:` pattern.
567 567 #[derive(Debug, PartialEq, Eq, Clone)]
568 568 pub struct SubInclude {
569 569 /// Will be used for repository (hg) paths that start with this prefix.
570 570 /// It is relative to the current working directory, so comparing against
571 571 /// repository paths is painless.
572 572 pub prefix: HgPathBuf,
573 573 /// The file itself, containing the patterns
574 574 pub path: PathBuf,
575 575 /// Folder in the filesystem where this it applies
576 576 pub root: PathBuf,
577 577
578 578 pub included_patterns: Vec<IgnorePattern>,
579 579 }
580 580
581 581 impl SubInclude {
582 582 pub fn new(
583 583 root_dir: &Path,
584 584 pattern: &[u8],
585 585 source: &Path,
586 586 ) -> Result<SubInclude, HgPathError> {
587 587 let normalized_source =
588 588 normalize_path_bytes(&get_bytes_from_path(source));
589 589
590 590 let source_root = get_path_from_bytes(&normalized_source);
591 591 let source_root =
592 592 source_root.parent().unwrap_or_else(|| source_root.deref());
593 593
594 594 let path = source_root.join(get_path_from_bytes(pattern));
595 595 let new_root = path.parent().unwrap_or_else(|| path.deref());
596 596
597 597 let prefix = canonical_path(root_dir, root_dir, new_root)?;
598 598
599 599 Ok(Self {
600 600 prefix: path_to_hg_path_buf(prefix).map(|mut p| {
601 601 if !p.is_empty() {
602 602 p.push_byte(b'/');
603 603 }
604 604 p
605 605 })?,
606 606 path: path.to_owned(),
607 607 root: new_root.to_owned(),
608 608 included_patterns: Vec::new(),
609 609 })
610 610 }
611 611 }
612 612
613 613 /// Separate and pre-process subincludes from other patterns for the "ignore"
614 614 /// phase.
615 615 pub fn filter_subincludes(
616 616 ignore_patterns: Vec<IgnorePattern>,
617 617 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
618 618 let mut subincludes = vec![];
619 619 let mut others = vec![];
620 620
621 621 for pattern in ignore_patterns {
622 622 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
623 623 {
624 624 subincludes.push(*sub_include);
625 625 } else {
626 626 others.push(pattern)
627 627 }
628 628 }
629 629 Ok((subincludes, others))
630 630 }
631 631
632 632 #[cfg(test)]
633 633 mod tests {
634 634 use super::*;
635 635 use pretty_assertions::assert_eq;
636 636
637 637 #[test]
638 638 fn escape_pattern_test() {
639 639 let untouched =
640 640 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
641 641 assert_eq!(escape_pattern(untouched), untouched.to_vec());
642 642 // All escape codes
643 643 assert_eq!(
644 644 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
645 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
645 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\\t\\n\\r\\v\\f"#
646 646 .to_vec()
647 647 );
648 648 }
649 649
650 650 #[test]
651 651 fn glob_test() {
652 652 assert_eq!(glob_to_re(br#"?"#), br#"."#);
653 653 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
654 654 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
655 655 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
656 656 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
657 657 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
658 658 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
659 659 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
660 660 }
661 661
662 662 #[test]
663 663 fn test_parse_pattern_file_contents() {
664 664 let lines = b"syntax: glob\n*.elc";
665 665
666 666 assert_eq!(
667 667 parse_pattern_file_contents(
668 668 lines,
669 669 Path::new("file_path"),
670 670 None,
671 671 false,
672 672 true,
673 673 )
674 674 .unwrap()
675 675 .0,
676 676 vec![IgnorePattern::new(
677 677 PatternSyntax::RelGlob,
678 678 b"*.elc",
679 679 Path::new("file_path")
680 680 )],
681 681 );
682 682
683 683 let lines = b"syntax: include\nsyntax: glob";
684 684
685 685 assert_eq!(
686 686 parse_pattern_file_contents(
687 687 lines,
688 688 Path::new("file_path"),
689 689 None,
690 690 false,
691 691 true,
692 692 )
693 693 .unwrap()
694 694 .0,
695 695 vec![]
696 696 );
697 697 let lines = b"glob:**.o";
698 698 assert_eq!(
699 699 parse_pattern_file_contents(
700 700 lines,
701 701 Path::new("file_path"),
702 702 None,
703 703 false,
704 704 true,
705 705 )
706 706 .unwrap()
707 707 .0,
708 708 vec![IgnorePattern::new(
709 709 PatternSyntax::RelGlob,
710 710 b"**.o",
711 711 Path::new("file_path")
712 712 )]
713 713 );
714 714 }
715 715
716 716 #[test]
717 717 fn test_build_single_regex() {
718 718 assert_eq!(
719 719 build_single_regex(&IgnorePattern::new(
720 720 PatternSyntax::RelGlob,
721 721 b"rust/target/",
722 722 Path::new("")
723 723 ))
724 724 .unwrap(),
725 725 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
726 726 );
727 727 assert_eq!(
728 728 build_single_regex(&IgnorePattern::new(
729 729 PatternSyntax::Regexp,
730 730 br"rust/target/\d+",
731 731 Path::new("")
732 732 ))
733 733 .unwrap(),
734 734 Some(br"rust/target/\d+".to_vec()),
735 735 );
736 736 }
737 737
738 738 #[test]
739 739 fn test_build_single_regex_shortcut() {
740 740 assert_eq!(
741 741 build_single_regex(&IgnorePattern::new(
742 742 PatternSyntax::RootGlob,
743 743 b"",
744 744 Path::new("")
745 745 ))
746 746 .unwrap(),
747 747 None,
748 748 );
749 749 assert_eq!(
750 750 build_single_regex(&IgnorePattern::new(
751 751 PatternSyntax::RootGlob,
752 752 b"whatever",
753 753 Path::new("")
754 754 ))
755 755 .unwrap(),
756 756 None,
757 757 );
758 758 assert_eq!(
759 759 build_single_regex(&IgnorePattern::new(
760 760 PatternSyntax::RootGlob,
761 761 b"*.o",
762 762 Path::new("")
763 763 ))
764 764 .unwrap(),
765 765 Some(br"[^/]*\.o(?:/|$)".to_vec()),
766 766 );
767 767 }
768 768
769 769 #[test]
770 770 fn test_build_single_relregex() {
771 771 assert_eq!(
772 772 build_single_regex(&IgnorePattern::new(
773 773 PatternSyntax::RelRegexp,
774 774 b"^ba{2}r",
775 775 Path::new("")
776 776 ))
777 777 .unwrap(),
778 778 Some(b"^ba{2}r".to_vec()),
779 779 );
780 780 assert_eq!(
781 781 build_single_regex(&IgnorePattern::new(
782 782 PatternSyntax::RelRegexp,
783 783 b"ba{2}r",
784 784 Path::new("")
785 785 ))
786 786 .unwrap(),
787 787 Some(b".*ba{2}r".to_vec()),
788 788 );
789 789 assert_eq!(
790 790 build_single_regex(&IgnorePattern::new(
791 791 PatternSyntax::RelRegexp,
792 792 b"(?ia)ba{2}r",
793 793 Path::new("")
794 794 ))
795 795 .unwrap(),
796 796 Some(b"(?ia:.*ba{2}r)".to_vec()),
797 797 );
798 798 assert_eq!(
799 799 build_single_regex(&IgnorePattern::new(
800 800 PatternSyntax::RelRegexp,
801 801 b"(?ia)^ba{2}r",
802 802 Path::new("")
803 803 ))
804 804 .unwrap(),
805 805 Some(b"(?ia:^ba{2}r)".to_vec()),
806 806 );
807 807 }
808 808 }
General Comments 0
You need to be logged in to leave comments. Login now