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