Skip to content

Commit 398e093

Browse files
committed
Fix ** globs.
1. Don't match `.` if require_literal_leading_dot is true. 2. Only match full path segments. (also, just clean this up a bit)
1 parent 3ed4935 commit 398e093

File tree

1 file changed

+65
-62
lines changed

1 file changed

+65
-62
lines changed

src/lib.rs

Lines changed: 65 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#![cfg_attr(all(test, windows), feature(std_misc))]
2727

2828
use std::ascii::AsciiExt;
29-
use std::cell::Cell;
3029
use std::cmp;
3130
use std::fmt;
3231
use std::fs;
@@ -614,7 +613,7 @@ impl Pattern {
614613
/// Return if the given `str` matches this `Pattern` using the specified
615614
/// match options.
616615
pub fn matches_with(&self, str: &str, options: &MatchOptions) -> bool {
617-
self.matches_from(None, str, 0, options) == Match
616+
self.matches_from(true, str.chars(), 0, options) == Match
618617
}
619618

620619
/// Return if the given `Path`, when converted to a `str`, matches this
@@ -630,81 +629,67 @@ impl Pattern {
630629
pub fn as_str<'a>(&'a self) -> &'a str { &self.original }
631630

632631
fn matches_from(&self,
633-
prev_char: Option<char>,
634-
mut file: &str,
632+
mut follows_separator: bool,
633+
mut file: std::str::Chars,
635634
i: usize,
636635
options: &MatchOptions) -> MatchResult {
637636

638-
let prev_char = Cell::new(prev_char);
639-
640-
let require_literal = |c| {
641-
(options.require_literal_separator && path::is_separator(c)) ||
642-
(options.require_literal_leading_dot && c == '.'
643-
&& path::is_separator(prev_char.get().unwrap_or('/')))
644-
};
645-
646637
for (ti, token) in self.tokens[i..].iter().enumerate() {
647638
match *token {
648-
AnySequence | AnyRecursiveSequence => {
649-
loop {
650-
match self.matches_from(prev_char.get(), file,
651-
i + ti + 1, options) {
639+
AnySequence|AnyRecursiveSequence => {
640+
// ** must be at the start.
641+
debug_assert!(match *token { AnyRecursiveSequence => follows_separator, _ => true });
642+
643+
// Empty match
644+
match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) {
645+
SubPatternDoesntMatch => (), // keep trying
646+
m => return m,
647+
};
648+
649+
while let Some(c) = file.next() {
650+
if follows_separator && options.require_literal_leading_dot && c == '.' {
651+
return SubPatternDoesntMatch;
652+
}
653+
follows_separator = path::is_separator(c);
654+
match *token {
655+
AnyRecursiveSequence if !follows_separator => continue,
656+
AnySequence if options.require_literal_separator && follows_separator => return SubPatternDoesntMatch,
657+
_ => (),
658+
}
659+
match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) {
652660
SubPatternDoesntMatch => (), // keep trying
653661
m => return m,
654662
}
655-
656-
if file.len() == 0 { return EntirePatternDoesntMatch }
657-
let c = file.chars().next().unwrap();
658-
let next = &file[c.len_utf8()..];
659-
660-
if let AnySequence = *token {
661-
if require_literal(c) {
662-
return SubPatternDoesntMatch;
663-
}
664-
}
665-
666-
prev_char.set(Some(c));
667-
file = next;
668663
}
669-
}
664+
},
670665
_ => {
671-
if file.len() == 0 { return EntirePatternDoesntMatch }
672-
let c = file.chars().next().unwrap();
673-
let next = &file[c.len_utf8()..];
674-
675-
let matches = match *token {
676-
AnyChar => {
677-
!require_literal(c)
678-
}
679-
AnyWithin(ref specifiers) => {
680-
!require_literal(c) &&
681-
in_char_specifiers(&specifiers,
682-
c,
683-
options)
684-
}
685-
AnyExcept(ref specifiers) => {
686-
!require_literal(c) &&
687-
!in_char_specifiers(&specifiers,
688-
c,
689-
options)
690-
}
691-
Char(c2) => {
692-
chars_eq(c, c2, options.case_sensitive)
693-
}
694-
AnySequence | AnyRecursiveSequence => {
695-
unreachable!()
696-
}
666+
let c = match file.next() {
667+
Some(c) => c,
668+
None => return EntirePatternDoesntMatch,
697669
};
698-
if !matches {
670+
671+
let is_sep = path::is_separator(c);
672+
673+
if !match *token {
674+
AnyChar|AnyWithin(..)|AnyExcept(..)
675+
if (options.require_literal_separator && is_sep)
676+
|| (follows_separator && options.require_literal_leading_dot && c == '.')
677+
=> false,
678+
AnyChar => true,
679+
AnyWithin(ref specifiers) => in_char_specifiers(&specifiers, c, options),
680+
AnyExcept(ref specifiers) => !in_char_specifiers(&specifiers, c, options),
681+
Char(c2) => chars_eq(c, c2, options.case_sensitive),
682+
AnySequence | AnyRecursiveSequence => unreachable!(),
683+
} {
699684
return SubPatternDoesntMatch;
700685
}
701-
prev_char.set(Some(c));
702-
file = next;
686+
follows_separator = is_sep;
703687
}
704688
}
705689
}
706690

707-
if file.is_empty() {
691+
// Iter is fused.
692+
if file.next().is_none() {
708693
Match
709694
} else {
710695
SubPatternDoesntMatch
@@ -885,7 +870,7 @@ pub struct MatchOptions {
885870
pub require_literal_separator: bool,
886871

887872
/// If this is true then paths that contain components that start with a `.`
888-
/// will not match unless the `.` appears literally in the pattern: `*`, `?`
873+
/// will not match unless the `.` appears literally in the pattern: `*`, `?`, `**`,
889874
/// or `[...]` will not match. This is useful because such files are
890875
/// conventionally considered hidden on Unix systems and it might be
891876
/// desirable to skip them when listing files.
@@ -1022,7 +1007,14 @@ mod test {
10221007
assert!(!pat.matches("some/other/notthis.txt"));
10231008

10241009
// a single ** should be valid, for globs
1025-
assert!(Pattern::new("**").unwrap().is_recursive);
1010+
// Should accept anything
1011+
let pat = Pattern::new("**").unwrap();
1012+
assert!(pat.is_recursive);
1013+
assert!(pat.matches("abcde"));
1014+
assert!(pat.matches(""));
1015+
assert!(pat.matches(".asdf"));
1016+
assert!(pat.matches("/x/.asdf"));
1017+
10261018

10271019
// collapse consecutive wildcards
10281020
let pat = Pattern::new("some/**/**/needle.txt").unwrap();
@@ -1045,6 +1037,13 @@ mod test {
10451037
assert!(pat.matches("/test"));
10461038
assert!(!pat.matches("/one/notthis"));
10471039
assert!(!pat.matches("/notthis"));
1040+
1041+
// Only start sub-patterns on start of path segment.
1042+
let pat = Pattern::new("**/.*").unwrap();
1043+
assert!(pat.matches(".abc"));
1044+
assert!(pat.matches("abc/.abc"));
1045+
assert!(!pat.matches("ab.c"));
1046+
assert!(!pat.matches("abc/ab.c"));
10481047
}
10491048

10501049
#[test]
@@ -1234,6 +1233,10 @@ mod test {
12341233
let f = |options| Pattern::new("aaa/[.]bbb").unwrap().matches_with("aaa/.bbb", options);
12351234
assert!(f(&options_not_require_literal_leading_dot));
12361235
assert!(!f(&options_require_literal_leading_dot));
1236+
1237+
let f = |options| Pattern::new("**/*").unwrap().matches_with(".bbb", options);
1238+
assert!(f(&options_not_require_literal_leading_dot));
1239+
assert!(!f(&options_require_literal_leading_dot));
12371240
}
12381241

12391242
#[test]

0 commit comments

Comments
 (0)