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