3434
3535_PATTERN_CACHE = LRUCache (
3636 1000
37- ) # type: LRUCache[Tuple[Text, bool], Tuple[int, bool , Pattern]]
37+ ) # type: LRUCache[Tuple[Text, bool], Tuple[Optional[ int] , Pattern]]
3838
3939
40- def _split_pattern_by_rec (pattern ):
40+ def _split_pattern_by_sep (pattern ):
4141 # type: (Text) -> List[Text]
4242 """Split a glob pattern at its directory seperators (/).
4343
@@ -57,28 +57,27 @@ def _split_pattern_by_rec(pattern):
5757 return [pattern [i + 1 : j ] for i , j in zip (indices [:- 1 ], indices [1 :])]
5858
5959
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.
6363
6464 There is no way to quote meta-characters.
65+
6566 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.
6968
7069 Returns:
7170 str: A regex equivalent to the given pattern.
7271
7372 """
74- if not case_sensitive :
75- pattern = pattern .lower ()
7673 i , n = 0 , len (pattern )
7774 res = []
7875 while i < n :
7976 c = pattern [i ]
8077 i = i + 1
8178 if c == "*" :
79+ if i < n and pattern [i ] == "*" :
80+ raise ValueError ("glob._translate does not support '**' patterns." )
8281 res .append ("[^/]*" )
8382 elif c == "?" :
8483 res .append ("[^/]" )
@@ -96,7 +95,7 @@ def _translate(pattern, case_sensitive=True):
9695 stuff = pattern [i :j ].replace ("\\ " , "\\ \\ " )
9796 i = j + 1
9897 if stuff [0 ] == "!" :
99- stuff = "^" + stuff [1 :]
98+ stuff = "^/ " + stuff [1 :]
10099 elif stuff [0 ] == "^" :
101100 stuff = "\\ " + stuff
102101 res .append ("[%s]" % stuff )
@@ -105,27 +104,35 @@ def _translate(pattern, case_sensitive=True):
105104 return "" .join (res )
106105
107106
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+ """
110124 recursive = False
111125 re_patterns = ["" ]
112126 for component in iteratepath (pattern ):
113127 if "**" in component :
114128 recursive = True
115129 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 ]
117131 re_patterns .append ("/?" + ".*/?" .join (split_re ))
118132 else :
119- re_patterns .append (
120- "/" + _translate (component , case_sensitive = case_sensitive )
121- )
122- levels += 1
133+ re_patterns .append ("/" + _translate (component ))
123134 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
129136
130137
131138def match (pattern , path ):
@@ -147,10 +154,11 @@ def match(pattern, path):
147154
148155 """
149156 try :
150- levels , recursive , re_pattern = _PATTERN_CACHE [(pattern , True )]
157+ levels , re_pattern = _PATTERN_CACHE [(pattern , True )]
151158 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 )
154162 if path and path [0 ] != "/" :
155163 path = "/" + path
156164 return bool (re_pattern .match (path ))
@@ -169,10 +177,11 @@ def imatch(pattern, path):
169177
170178 """
171179 try :
172- levels , recursive , re_pattern = _PATTERN_CACHE [(pattern , False )]
180+ levels , re_pattern = _PATTERN_CACHE [(pattern , False )]
173181 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 )
176185 if path and path [0 ] != "/" :
177186 path = "/" + path
178187 return bool (re_pattern .match (path ))
@@ -187,7 +196,7 @@ def match_any(patterns, path):
187196 Arguments:
188197 patterns (list): A list of wildcard pattern, e.g ``["*.py",
189198 "*.pyc"]``
190- name (str): A filename .
199+ path (str): A resource path .
191200
192201 Returns:
193202 bool: `True` if the path matches at least one of the patterns.
@@ -207,7 +216,7 @@ def imatch_any(patterns, path):
207216 Arguments:
208217 patterns (list): A list of wildcard pattern, e.g ``["*.py",
209218 "*.pyc"]``
210- name (str): A filename .
219+ path (str): A resource path .
211220
212221 Returns:
213222 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):
228237 case_sensitive (bool): If ``True``, then the callable will be case
229238 sensitive, otherwise it will be case insensitive.
230239 accept_prefix (bool): If ``True``, the name is
231- not required to match the wildcards themselves
240+ not required to match the patterns themselves
232241 but only need to be a prefix of a string that does.
233242
234243 Returns:
235244 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.
237247
238248 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)
241251 >>> is_python('__init__.py')
242252 True
243253 >>> is_python('foo.txt')
244254 False
245255
246256 """
247257 if not patterns :
248- return lambda name : True
258+ return lambda path : True
249259
250260 if accept_prefix :
251261 new_patterns = []
252262 for pattern in patterns :
253- split = _split_pattern_by_rec (pattern )
263+ split = _split_pattern_by_sep (pattern )
254264 for i in range (1 , len (split )):
255265 new_pattern = "/" .join (split [:i ])
256266 new_patterns .append (new_pattern )
@@ -310,18 +320,15 @@ def __repr__(self):
310320 def _make_iter (self , search = "breadth" , namespaces = None ):
311321 # type: (str, List[str]) -> Iterator[GlobMatch]
312322 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 )]
316324 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 )
320327
321328 for path , info in self .fs .walk .info (
322329 path = self .path ,
323330 namespaces = namespaces or self .namespaces ,
324- max_depth = None if recursive else levels ,
331+ max_depth = levels ,
325332 search = search ,
326333 exclude_dirs = self .exclude_dirs ,
327334 ):
0 commit comments