##// END OF EJS Templates
rust-filepatterns: also normalize RelPath...
Spencer Baugh -
r51751:df6dfad5 default
parent child Browse files
Show More
@@ -1,807 +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 | PatternSyntax::RelPath
320 321 | PatternSyntax::RootFiles => normalize_path_bytes(pattern),
321 322 PatternSyntax::Include | PatternSyntax::SubInclude => {
322 323 return Err(PatternError::NonRegexPattern(entry.clone()))
323 324 }
324 325 _ => pattern.to_owned(),
325 326 };
326 327 let is_simple_rootglob = *syntax == PatternSyntax::RootGlob
327 328 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b));
328 329 if is_simple_rootglob || syntax == &PatternSyntax::FilePath {
329 330 Ok(None)
330 331 } else {
331 332 let mut entry = entry.clone();
332 333 entry.pattern = pattern;
333 334 Ok(Some(_build_single_regex(&entry)))
334 335 }
335 336 }
336 337
337 338 lazy_static! {
338 339 static ref SYNTAXES: FastHashMap<&'static [u8], PatternSyntax> = {
339 340 let mut m = FastHashMap::default();
340 341
341 342 m.insert(b"re:".as_ref(), PatternSyntax::Regexp);
342 343 m.insert(b"regexp:".as_ref(), PatternSyntax::Regexp);
343 344 m.insert(b"path:".as_ref(), PatternSyntax::Path);
344 345 m.insert(b"filepath:".as_ref(), PatternSyntax::FilePath);
345 346 m.insert(b"relpath:".as_ref(), PatternSyntax::RelPath);
346 347 m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFiles);
347 348 m.insert(b"relglob:".as_ref(), PatternSyntax::RelGlob);
348 349 m.insert(b"relre:".as_ref(), PatternSyntax::RelRegexp);
349 350 m.insert(b"glob:".as_ref(), PatternSyntax::Glob);
350 351 m.insert(b"rootglob:".as_ref(), PatternSyntax::RootGlob);
351 352 m.insert(b"include:".as_ref(), PatternSyntax::Include);
352 353 m.insert(b"subinclude:".as_ref(), PatternSyntax::SubInclude);
353 354
354 355 m
355 356 };
356 357 }
357 358
358 359 #[derive(Debug)]
359 360 pub enum PatternFileWarning {
360 361 /// (file path, syntax bytes)
361 362 InvalidSyntax(PathBuf, Vec<u8>),
362 363 /// File path
363 364 NoSuchFile(PathBuf),
364 365 }
365 366
366 367 pub fn parse_one_pattern(
367 368 pattern: &[u8],
368 369 source: &Path,
369 370 default: PatternSyntax,
370 371 ) -> IgnorePattern {
371 372 let mut pattern_bytes: &[u8] = pattern;
372 373 let mut syntax = default;
373 374
374 375 for (s, val) in SYNTAXES.iter() {
375 376 if let Some(rest) = pattern_bytes.drop_prefix(s) {
376 377 syntax = val.clone();
377 378 pattern_bytes = rest;
378 379 break;
379 380 }
380 381 }
381 382
382 383 let pattern = pattern_bytes.to_vec();
383 384
384 385 IgnorePattern {
385 386 syntax,
386 387 pattern,
387 388 source: source.to_owned(),
388 389 }
389 390 }
390 391
391 392 pub fn parse_pattern_file_contents(
392 393 lines: &[u8],
393 394 file_path: &Path,
394 395 default_syntax_override: Option<PatternSyntax>,
395 396 warn: bool,
396 397 relativize: bool,
397 398 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
398 399 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
399 400
400 401 #[allow(clippy::trivial_regex)]
401 402 let comment_escape_regex = Regex::new(r"\\#").unwrap();
402 403 let mut inputs: Vec<IgnorePattern> = vec![];
403 404 let mut warnings: Vec<PatternFileWarning> = vec![];
404 405
405 406 let mut current_syntax =
406 407 default_syntax_override.unwrap_or(PatternSyntax::RelRegexp);
407 408
408 409 for mut line in lines.split(|c| *c == b'\n') {
409 410 let line_buf;
410 411 if line.contains(&b'#') {
411 412 if let Some(cap) = comment_regex.captures(line) {
412 413 line = &line[..cap.get(1).unwrap().end()]
413 414 }
414 415 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
415 416 line = &line_buf;
416 417 }
417 418
418 419 let line = line.trim_end();
419 420
420 421 if line.is_empty() {
421 422 continue;
422 423 }
423 424
424 425 if let Some(syntax) = line.drop_prefix(b"syntax:") {
425 426 let syntax = syntax.trim();
426 427
427 428 if let Some(parsed) =
428 429 SYNTAXES.get([syntax, &b":"[..]].concat().as_slice())
429 430 {
430 431 current_syntax = parsed.clone();
431 432 } else if warn {
432 433 warnings.push(PatternFileWarning::InvalidSyntax(
433 434 file_path.to_owned(),
434 435 syntax.to_owned(),
435 436 ));
436 437 }
437 438 } else {
438 439 let pattern = parse_one_pattern(
439 440 line,
440 441 file_path,
441 442 current_syntax.clone(),
442 443 );
443 444 inputs.push(if relativize {
444 445 pattern.to_relative()
445 446 } else {
446 447 pattern
447 448 })
448 449 }
449 450 }
450 451 Ok((inputs, warnings))
451 452 }
452 453
453 454 pub fn read_pattern_file(
454 455 file_path: &Path,
455 456 warn: bool,
456 457 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
457 458 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
458 459 match std::fs::read(file_path) {
459 460 Ok(contents) => {
460 461 inspect_pattern_bytes(file_path, &contents);
461 462 parse_pattern_file_contents(&contents, file_path, None, warn, true)
462 463 }
463 464 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
464 465 vec![],
465 466 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
466 467 )),
467 468 Err(e) => Err(e.into()),
468 469 }
469 470 }
470 471
471 472 /// Represents an entry in an "ignore" file.
472 473 #[derive(Debug, Eq, PartialEq, Clone)]
473 474 pub struct IgnorePattern {
474 475 pub syntax: PatternSyntax,
475 476 pub pattern: Vec<u8>,
476 477 pub source: PathBuf,
477 478 }
478 479
479 480 impl IgnorePattern {
480 481 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
481 482 Self {
482 483 syntax,
483 484 pattern: pattern.to_owned(),
484 485 source: source.to_owned(),
485 486 }
486 487 }
487 488
488 489 pub fn to_relative(self) -> Self {
489 490 let Self {
490 491 syntax,
491 492 pattern,
492 493 source,
493 494 } = self;
494 495 Self {
495 496 syntax: match syntax {
496 497 PatternSyntax::Regexp => PatternSyntax::RelRegexp,
497 498 PatternSyntax::Glob => PatternSyntax::RelGlob,
498 499 x => x,
499 500 },
500 501 pattern,
501 502 source,
502 503 }
503 504 }
504 505 }
505 506
506 507 pub type PatternResult<T> = Result<T, PatternError>;
507 508
508 509 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
509 510 /// and `subinclude:` patterns.
510 511 ///
511 512 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
512 513 /// is used for the latter to form a tree of patterns.
513 514 pub fn get_patterns_from_file(
514 515 pattern_file: &Path,
515 516 root_dir: &Path,
516 517 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
517 518 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
518 519 let (patterns, mut warnings) =
519 520 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
520 521 let patterns = patterns
521 522 .into_iter()
522 523 .flat_map(|entry| -> PatternResult<_> {
523 524 Ok(match &entry.syntax {
524 525 PatternSyntax::Include => {
525 526 let inner_include =
526 527 root_dir.join(get_path_from_bytes(&entry.pattern));
527 528 let (inner_pats, inner_warnings) = get_patterns_from_file(
528 529 &inner_include,
529 530 root_dir,
530 531 inspect_pattern_bytes,
531 532 )?;
532 533 warnings.extend(inner_warnings);
533 534 inner_pats
534 535 }
535 536 PatternSyntax::SubInclude => {
536 537 let mut sub_include = SubInclude::new(
537 538 root_dir,
538 539 &entry.pattern,
539 540 &entry.source,
540 541 )?;
541 542 let (inner_patterns, inner_warnings) =
542 543 get_patterns_from_file(
543 544 &sub_include.path,
544 545 &sub_include.root,
545 546 inspect_pattern_bytes,
546 547 )?;
547 548 sub_include.included_patterns = inner_patterns;
548 549 warnings.extend(inner_warnings);
549 550 vec![IgnorePattern {
550 551 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
551 552 sub_include,
552 553 )),
553 554 ..entry
554 555 }]
555 556 }
556 557 _ => vec![entry],
557 558 })
558 559 })
559 560 .flatten()
560 561 .collect();
561 562
562 563 Ok((patterns, warnings))
563 564 }
564 565
565 566 /// Holds all the information needed to handle a `subinclude:` pattern.
566 567 #[derive(Debug, PartialEq, Eq, Clone)]
567 568 pub struct SubInclude {
568 569 /// Will be used for repository (hg) paths that start with this prefix.
569 570 /// It is relative to the current working directory, so comparing against
570 571 /// repository paths is painless.
571 572 pub prefix: HgPathBuf,
572 573 /// The file itself, containing the patterns
573 574 pub path: PathBuf,
574 575 /// Folder in the filesystem where this it applies
575 576 pub root: PathBuf,
576 577
577 578 pub included_patterns: Vec<IgnorePattern>,
578 579 }
579 580
580 581 impl SubInclude {
581 582 pub fn new(
582 583 root_dir: &Path,
583 584 pattern: &[u8],
584 585 source: &Path,
585 586 ) -> Result<SubInclude, HgPathError> {
586 587 let normalized_source =
587 588 normalize_path_bytes(&get_bytes_from_path(source));
588 589
589 590 let source_root = get_path_from_bytes(&normalized_source);
590 591 let source_root =
591 592 source_root.parent().unwrap_or_else(|| source_root.deref());
592 593
593 594 let path = source_root.join(get_path_from_bytes(pattern));
594 595 let new_root = path.parent().unwrap_or_else(|| path.deref());
595 596
596 597 let prefix = canonical_path(root_dir, root_dir, new_root)?;
597 598
598 599 Ok(Self {
599 600 prefix: path_to_hg_path_buf(prefix).map(|mut p| {
600 601 if !p.is_empty() {
601 602 p.push_byte(b'/');
602 603 }
603 604 p
604 605 })?,
605 606 path: path.to_owned(),
606 607 root: new_root.to_owned(),
607 608 included_patterns: Vec::new(),
608 609 })
609 610 }
610 611 }
611 612
612 613 /// Separate and pre-process subincludes from other patterns for the "ignore"
613 614 /// phase.
614 615 pub fn filter_subincludes(
615 616 ignore_patterns: Vec<IgnorePattern>,
616 617 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
617 618 let mut subincludes = vec![];
618 619 let mut others = vec![];
619 620
620 621 for pattern in ignore_patterns {
621 622 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
622 623 {
623 624 subincludes.push(*sub_include);
624 625 } else {
625 626 others.push(pattern)
626 627 }
627 628 }
628 629 Ok((subincludes, others))
629 630 }
630 631
631 632 #[cfg(test)]
632 633 mod tests {
633 634 use super::*;
634 635 use pretty_assertions::assert_eq;
635 636
636 637 #[test]
637 638 fn escape_pattern_test() {
638 639 let untouched =
639 640 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
640 641 assert_eq!(escape_pattern(untouched), untouched.to_vec());
641 642 // All escape codes
642 643 assert_eq!(
643 644 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
644 645 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
645 646 .to_vec()
646 647 );
647 648 }
648 649
649 650 #[test]
650 651 fn glob_test() {
651 652 assert_eq!(glob_to_re(br#"?"#), br#"."#);
652 653 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
653 654 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
654 655 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
655 656 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
656 657 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
657 658 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
658 659 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
659 660 }
660 661
661 662 #[test]
662 663 fn test_parse_pattern_file_contents() {
663 664 let lines = b"syntax: glob\n*.elc";
664 665
665 666 assert_eq!(
666 667 parse_pattern_file_contents(
667 668 lines,
668 669 Path::new("file_path"),
669 670 None,
670 671 false,
671 672 true,
672 673 )
673 674 .unwrap()
674 675 .0,
675 676 vec![IgnorePattern::new(
676 677 PatternSyntax::RelGlob,
677 678 b"*.elc",
678 679 Path::new("file_path")
679 680 )],
680 681 );
681 682
682 683 let lines = b"syntax: include\nsyntax: glob";
683 684
684 685 assert_eq!(
685 686 parse_pattern_file_contents(
686 687 lines,
687 688 Path::new("file_path"),
688 689 None,
689 690 false,
690 691 true,
691 692 )
692 693 .unwrap()
693 694 .0,
694 695 vec![]
695 696 );
696 697 let lines = b"glob:**.o";
697 698 assert_eq!(
698 699 parse_pattern_file_contents(
699 700 lines,
700 701 Path::new("file_path"),
701 702 None,
702 703 false,
703 704 true,
704 705 )
705 706 .unwrap()
706 707 .0,
707 708 vec![IgnorePattern::new(
708 709 PatternSyntax::RelGlob,
709 710 b"**.o",
710 711 Path::new("file_path")
711 712 )]
712 713 );
713 714 }
714 715
715 716 #[test]
716 717 fn test_build_single_regex() {
717 718 assert_eq!(
718 719 build_single_regex(&IgnorePattern::new(
719 720 PatternSyntax::RelGlob,
720 721 b"rust/target/",
721 722 Path::new("")
722 723 ))
723 724 .unwrap(),
724 725 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
725 726 );
726 727 assert_eq!(
727 728 build_single_regex(&IgnorePattern::new(
728 729 PatternSyntax::Regexp,
729 730 br"rust/target/\d+",
730 731 Path::new("")
731 732 ))
732 733 .unwrap(),
733 734 Some(br"rust/target/\d+".to_vec()),
734 735 );
735 736 }
736 737
737 738 #[test]
738 739 fn test_build_single_regex_shortcut() {
739 740 assert_eq!(
740 741 build_single_regex(&IgnorePattern::new(
741 742 PatternSyntax::RootGlob,
742 743 b"",
743 744 Path::new("")
744 745 ))
745 746 .unwrap(),
746 747 None,
747 748 );
748 749 assert_eq!(
749 750 build_single_regex(&IgnorePattern::new(
750 751 PatternSyntax::RootGlob,
751 752 b"whatever",
752 753 Path::new("")
753 754 ))
754 755 .unwrap(),
755 756 None,
756 757 );
757 758 assert_eq!(
758 759 build_single_regex(&IgnorePattern::new(
759 760 PatternSyntax::RootGlob,
760 761 b"*.o",
761 762 Path::new("")
762 763 ))
763 764 .unwrap(),
764 765 Some(br"[^/]*\.o(?:/|$)".to_vec()),
765 766 );
766 767 }
767 768
768 769 #[test]
769 770 fn test_build_single_relregex() {
770 771 assert_eq!(
771 772 build_single_regex(&IgnorePattern::new(
772 773 PatternSyntax::RelRegexp,
773 774 b"^ba{2}r",
774 775 Path::new("")
775 776 ))
776 777 .unwrap(),
777 778 Some(b"^ba{2}r".to_vec()),
778 779 );
779 780 assert_eq!(
780 781 build_single_regex(&IgnorePattern::new(
781 782 PatternSyntax::RelRegexp,
782 783 b"ba{2}r",
783 784 Path::new("")
784 785 ))
785 786 .unwrap(),
786 787 Some(b".*ba{2}r".to_vec()),
787 788 );
788 789 assert_eq!(
789 790 build_single_regex(&IgnorePattern::new(
790 791 PatternSyntax::RelRegexp,
791 792 b"(?ia)ba{2}r",
792 793 Path::new("")
793 794 ))
794 795 .unwrap(),
795 796 Some(b"(?ia:.*ba{2}r)".to_vec()),
796 797 );
797 798 assert_eq!(
798 799 build_single_regex(&IgnorePattern::new(
799 800 PatternSyntax::RelRegexp,
800 801 b"(?ia)^ba{2}r",
801 802 Path::new("")
802 803 ))
803 804 .unwrap(),
804 805 Some(b"(?ia:^ba{2}r)".to_vec()),
805 806 );
806 807 }
807 808 }
General Comments 0
You need to be logged in to leave comments. Login now