34
34
35
35
_PATTERN_CACHE = LRUCache (
36
36
1000
37
- ) # type: LRUCache[Tuple[Text, bool], Tuple[int, bool , Pattern]]
37
+ ) # type: LRUCache[Tuple[Text, bool], Tuple[Optional[ int] , Pattern]]
38
38
39
39
40
- def _split_pattern_by_rec (pattern ):
40
+ def _split_pattern_by_sep (pattern ):
41
41
# type: (Text) -> List[Text]
42
42
"""Split a glob pattern at its directory seperators (/).
43
43
@@ -57,28 +57,27 @@ def _split_pattern_by_rec(pattern):
57
57
return [pattern [i + 1 : j ] for i , j in zip (indices [:- 1 ], indices [1 :])]
58
58
59
59
60
- def _translate (pattern , case_sensitive = True ):
61
- # type: (Text, bool ) -> Text
62
- """Translate a wildcard pattern to a regular expression.
60
+ def _translate (pattern ):
61
+ # type: (Text) -> Text
62
+ """Translate a glob pattern without '**' to a regular expression.
63
63
64
64
There is no way to quote meta-characters.
65
+
65
66
Arguments:
66
- pattern (str): A wildcard pattern.
67
- case_sensitive (bool): Set to `False` to use a case
68
- insensitive regex (default `True`).
67
+ pattern (str): A glob pattern.
69
68
70
69
Returns:
71
70
str: A regex equivalent to the given pattern.
72
71
73
72
"""
74
- if not case_sensitive :
75
- pattern = pattern .lower ()
76
73
i , n = 0 , len (pattern )
77
74
res = []
78
75
while i < n :
79
76
c = pattern [i ]
80
77
i = i + 1
81
78
if c == "*" :
79
+ if i < n and pattern [i ] == "*" :
80
+ raise ValueError ("glob._translate does not support '**' patterns." )
82
81
res .append ("[^/]*" )
83
82
elif c == "?" :
84
83
res .append ("[^/]" )
@@ -96,7 +95,7 @@ def _translate(pattern, case_sensitive=True):
96
95
stuff = pattern [i :j ].replace ("\\ " , "\\ \\ " )
97
96
i = j + 1
98
97
if stuff [0 ] == "!" :
99
- stuff = "^" + stuff [1 :]
98
+ stuff = "^/ " + stuff [1 :]
100
99
elif stuff [0 ] == "^" :
101
100
stuff = "\\ " + stuff
102
101
res .append ("[%s]" % stuff )
@@ -105,27 +104,35 @@ def _translate(pattern, case_sensitive=True):
105
104
return "" .join (res )
106
105
107
106
108
- def _translate_glob (pattern , case_sensitive = True ):
109
- levels = 0
107
+ def _translate_glob (pattern ):
108
+ # type: (Text) -> Tuple[Optional[int], Text]
109
+ """Translate a glob pattern to a regular expression.
110
+
111
+ There is no way to quote meta-characters.
112
+
113
+ Arguments:
114
+ pattern (str): A glob pattern.
115
+
116
+ Returns:
117
+ Tuple[Optional[int], Text]: The first component describes the levels
118
+ of depth this glob pattern goes to; basically the number of "/" in
119
+ the pattern. If there is a "**" in the glob pattern, the depth is
120
+ basically unbounded, and this component is `None` instead.
121
+ The second component is the regular expression.
122
+
123
+ """
110
124
recursive = False
111
125
re_patterns = ["" ]
112
126
for component in iteratepath (pattern ):
113
127
if "**" in component :
114
128
recursive = True
115
129
split = component .split ("**" )
116
- split_re = [_translate (s , case_sensitive = case_sensitive ) for s in split ]
130
+ split_re = [_translate (s ) for s in split ]
117
131
re_patterns .append ("/?" + ".*/?" .join (split_re ))
118
132
else :
119
- re_patterns .append (
120
- "/" + _translate (component , case_sensitive = case_sensitive )
121
- )
122
- levels += 1
133
+ re_patterns .append ("/" + _translate (component ))
123
134
re_glob = "(?ms)^" + "" .join (re_patterns ) + ("/$" if pattern .endswith ("/" ) else "$" )
124
- return (
125
- levels ,
126
- recursive ,
127
- re .compile (re_glob , 0 if case_sensitive else re .IGNORECASE ),
128
- )
135
+ return pattern .count ("/" ) + 1 if not recursive else None , re_glob
129
136
130
137
131
138
def match (pattern , path ):
@@ -147,10 +154,11 @@ def match(pattern, path):
147
154
148
155
"""
149
156
try :
150
- levels , recursive , re_pattern = _PATTERN_CACHE [(pattern , True )]
157
+ levels , re_pattern = _PATTERN_CACHE [(pattern , True )]
151
158
except KeyError :
152
- levels , recursive , re_pattern = _translate_glob (pattern , case_sensitive = True )
153
- _PATTERN_CACHE [(pattern , True )] = (levels , recursive , re_pattern )
159
+ levels , re_str = _translate_glob (pattern )
160
+ re_pattern = re .compile (re_str )
161
+ _PATTERN_CACHE [(pattern , True )] = (levels , re_pattern )
154
162
if path and path [0 ] != "/" :
155
163
path = "/" + path
156
164
return bool (re_pattern .match (path ))
@@ -169,10 +177,11 @@ def imatch(pattern, path):
169
177
170
178
"""
171
179
try :
172
- levels , recursive , re_pattern = _PATTERN_CACHE [(pattern , False )]
180
+ levels , re_pattern = _PATTERN_CACHE [(pattern , False )]
173
181
except KeyError :
174
- levels , recursive , re_pattern = _translate_glob (pattern , case_sensitive = True )
175
- _PATTERN_CACHE [(pattern , False )] = (levels , recursive , re_pattern )
182
+ levels , re_str = _translate_glob (pattern )
183
+ re_pattern = re .compile (re_str , re .IGNORECASE )
184
+ _PATTERN_CACHE [(pattern , False )] = (levels , re_pattern )
176
185
if path and path [0 ] != "/" :
177
186
path = "/" + path
178
187
return bool (re_pattern .match (path ))
@@ -187,7 +196,7 @@ def match_any(patterns, path):
187
196
Arguments:
188
197
patterns (list): A list of wildcard pattern, e.g ``["*.py",
189
198
"*.pyc"]``
190
- name (str): A filename .
199
+ path (str): A resource path .
191
200
192
201
Returns:
193
202
bool: `True` if the path matches at least one of the patterns.
@@ -207,7 +216,7 @@ def imatch_any(patterns, path):
207
216
Arguments:
208
217
patterns (list): A list of wildcard pattern, e.g ``["*.py",
209
218
"*.pyc"]``
210
- name (str): A filename .
219
+ path (str): A resource path .
211
220
212
221
Returns:
213
222
bool: `True` if the path matches at least one of the patterns.
@@ -228,29 +237,30 @@ def get_matcher(patterns, case_sensitive, accept_prefix=False):
228
237
case_sensitive (bool): If ``True``, then the callable will be case
229
238
sensitive, otherwise it will be case insensitive.
230
239
accept_prefix (bool): If ``True``, the name is
231
- not required to match the wildcards themselves
240
+ not required to match the patterns themselves
232
241
but only need to be a prefix of a string that does.
233
242
234
243
Returns:
235
244
callable: a matcher that will return `True` if the paths given as
236
- an argument matches any of the given patterns.
245
+ an argument matches any of the given patterns, or if no patterns
246
+ exist.
237
247
238
248
Example:
239
- >>> from fs import wildcard
240
- >>> is_python = wildcard .get_matcher(['*.py'], True)
249
+ >>> from fs import glob
250
+ >>> is_python = glob .get_matcher(['*.py'], True)
241
251
>>> is_python('__init__.py')
242
252
True
243
253
>>> is_python('foo.txt')
244
254
False
245
255
246
256
"""
247
257
if not patterns :
248
- return lambda name : True
258
+ return lambda path : True
249
259
250
260
if accept_prefix :
251
261
new_patterns = []
252
262
for pattern in patterns :
253
- split = _split_pattern_by_rec (pattern )
263
+ split = _split_pattern_by_sep (pattern )
254
264
for i in range (1 , len (split )):
255
265
new_pattern = "/" .join (split [:i ])
256
266
new_patterns .append (new_pattern )
@@ -310,18 +320,15 @@ def __repr__(self):
310
320
def _make_iter (self , search = "breadth" , namespaces = None ):
311
321
# type: (str, List[str]) -> Iterator[GlobMatch]
312
322
try :
313
- levels , recursive , re_pattern = _PATTERN_CACHE [
314
- (self .pattern , self .case_sensitive )
315
- ]
323
+ levels , re_pattern = _PATTERN_CACHE [(self .pattern , self .case_sensitive )]
316
324
except KeyError :
317
- levels , recursive , re_pattern = _translate_glob (
318
- self .pattern , case_sensitive = self .case_sensitive
319
- )
325
+ levels , re_str = _translate_glob (self .pattern )
326
+ re_pattern = re .compile (re_str , 0 if self .case_sensitive else re .IGNORECASE )
320
327
321
328
for path , info in self .fs .walk .info (
322
329
path = self .path ,
323
330
namespaces = namespaces or self .namespaces ,
324
- max_depth = None if recursive else levels ,
331
+ max_depth = levels ,
325
332
search = search ,
326
333
exclude_dirs = self .exclude_dirs ,
327
334
):
0 commit comments