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