@@ -30,7 +30,7 @@ use std::io::fs::{mod, PathExtensions};
30
30
use std:: path:: is_sep;
31
31
use std:: string:: String ;
32
32
33
- use PatternToken :: { Char , AnyChar , AnySequence , AnyWithin , AnyExcept } ;
33
+ use PatternToken :: { Char , AnyChar , AnySequence , AnyRecursiveSequence , AnyWithin , AnyExcept } ;
34
34
use CharSpecifier :: { SingleChar , CharRange } ;
35
35
use MatchResult :: { Match , SubPatternDoesntMatch , EntirePatternDoesntMatch } ;
36
36
@@ -193,6 +193,7 @@ enum PatternToken {
193
193
Char ( char ) ,
194
194
AnyChar ,
195
195
AnySequence ,
196
+ AnyRecursiveSequence ,
196
197
AnyWithin ( Vec < CharSpecifier > ) ,
197
198
AnyExcept ( Vec < CharSpecifier > )
198
199
}
@@ -220,6 +221,12 @@ impl Pattern {
220
221
/// of characters, as ordered by Unicode, so e.g. `[0-9]` specifies any
221
222
/// character between 0 and 9 inclusive.
222
223
///
224
+ /// A sequence of two `*` characters, `**`, acts like a single `*` except
225
+ /// that it also matches path separators, making it useful for matching
226
+ /// on arbitrary subdirectories.
227
+ ///
228
+ /// A sequence of more than two consecutive `*` characters is treated literally.
229
+ ///
223
230
/// The metacharacters `?`, `*`, `[`, `]` can be matched by using brackets
224
231
/// (e.g. `[?]`). When a `]` occurs immediately following `[` or `[!` then
225
232
/// it is interpreted as being part of, rather then ending, the character
@@ -242,11 +249,23 @@ impl Pattern {
242
249
i += 1 ;
243
250
}
244
251
'*' => {
245
- // *, **, ***, ****, ... are all equivalent
252
+ let old = i;
253
+
246
254
while i < chars. len ( ) && chars[ i] == '*' {
247
255
i += 1 ;
248
256
}
249
- tokens. push ( AnySequence ) ;
257
+
258
+ let count = i - old;
259
+
260
+ if count > 2 {
261
+ for _ in range ( 0 u, count) {
262
+ tokens. push ( Char ( '*' ) ) ;
263
+ }
264
+ } else if count == 2 {
265
+ tokens. push ( AnyRecursiveSequence ) ;
266
+ } else {
267
+ tokens. push ( AnySequence ) ;
268
+ }
250
269
}
251
270
'[' => {
252
271
@@ -364,7 +383,7 @@ impl Pattern {
364
383
365
384
for ( ti, token) in self . tokens . slice_from ( i) . iter ( ) . enumerate ( ) {
366
385
match * token {
367
- AnySequence => {
386
+ AnySequence | AnyRecursiveSequence => {
368
387
loop {
369
388
match self . matches_from ( prev_char. get ( ) , file, i + ti + 1 , options) {
370
389
SubPatternDoesntMatch => ( ) , // keep trying
@@ -376,9 +395,12 @@ impl Pattern {
376
395
Some ( pair) => pair
377
396
} ;
378
397
379
- if require_literal ( c) {
380
- return SubPatternDoesntMatch ;
398
+ if let AnySequence = * token {
399
+ if require_literal ( c) {
400
+ return SubPatternDoesntMatch ;
401
+ }
381
402
}
403
+
382
404
prev_char. set ( Some ( c) ) ;
383
405
file = next;
384
406
}
@@ -408,7 +430,7 @@ impl Pattern {
408
430
Char ( c2) => {
409
431
chars_eq ( c, c2, options. case_sensitive )
410
432
}
411
- AnySequence => {
433
+ AnySequence | AnyRecursiveSequence => {
412
434
unreachable ! ( )
413
435
}
414
436
} ;
@@ -628,10 +650,9 @@ mod test {
628
650
}
629
651
630
652
#[ test]
631
- fn test_wildcard_optimizations ( ) {
632
- assert ! ( Pattern :: new( "a*b" ) . matches( "a___b " ) ) ;
653
+ fn test_wildcards ( ) {
654
+ assert ! ( Pattern :: new( "a*b" ) . matches( "a_b " ) ) ;
633
655
assert ! ( Pattern :: new( "a**b" ) . matches( "a___b" ) ) ;
634
- assert ! ( Pattern :: new( "a***b" ) . matches( "a___b" ) ) ;
635
656
assert ! ( Pattern :: new( "a*b*c" ) . matches( "abc" ) ) ;
636
657
assert ! ( !Pattern :: new( "a*b*c" ) . matches( "abcd" ) ) ;
637
658
assert ! ( Pattern :: new( "a*b*c" ) . matches( "a_b_c" ) ) ;
@@ -642,6 +663,16 @@ mod test {
642
663
assert ! ( Pattern :: new( "a*b[xyz]c*d" ) . matches( "abxcdbxcddd" ) ) ;
643
664
}
644
665
666
+ #[ test]
667
+ fn test_recursive_wildstars ( ) {
668
+ let pat = Pattern :: new ( "some/**/needle.txt" ) ;
669
+ assert ! ( pat. matches( "some/one/needle.txt" ) ) ;
670
+ assert ! ( pat. matches( "some/one/two/needle.txt" ) ) ;
671
+ assert ! ( pat. matches( "some/other/needle.txt" ) ) ;
672
+ // more than 2 consecutive wildcards and they're all treated literally
673
+ assert ! ( Pattern :: new( "a***b" ) . matches( "a***b" ) ) ;
674
+ }
675
+
645
676
#[ test]
646
677
fn test_lots_of_files ( ) {
647
678
// this is a good test because it touches lots of differently named files
0 commit comments