3333
3434_PATTERN_CACHE = LRUCache (
3535 1000
36- ) # type: LRUCache[Tuple[Text, bool], Tuple[int, bool , Pattern]]
36+ ) # type: LRUCache[Tuple[Text, bool], Tuple[Optional[ int] , Pattern]]
3737
3838
39- def _split_pattern_by_rec (pattern ):
39+ def _split_pattern_by_sep (pattern ):
4040 # type: (Text) -> List[Text]
4141 """Split a glob pattern at its directory seperators (/).
4242
@@ -56,28 +56,27 @@ def _split_pattern_by_rec(pattern):
5656 return [pattern [i + 1 : j ] for i , j in zip (indices [:- 1 ], indices [1 :])]
5757
5858
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.
6262
6363 There is no way to quote meta-characters.
64+
6465 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.
6867
6968 Returns:
7069 str: A regex equivalent to the given pattern.
7170
7271 """
73- if not case_sensitive :
74- pattern = pattern .lower ()
7572 i , n = 0 , len (pattern )
7673 res = []
7774 while i < n :
7875 c = pattern [i ]
7976 i = i + 1
8077 if c == "*" :
78+ if i < n and pattern [i ] == "*" :
79+ raise ValueError ("glob._translate does not support '**' patterns." )
8180 res .append ("[^/]*" )
8281 elif c == "?" :
8382 res .append ("[^/]" )
@@ -95,7 +94,7 @@ def _translate(pattern, case_sensitive=True):
9594 stuff = pattern [i :j ].replace ("\\ " , "\\ \\ " )
9695 i = j + 1
9796 if stuff [0 ] == "!" :
98- stuff = "^" + stuff [1 :]
97+ stuff = "^/ " + stuff [1 :]
9998 elif stuff [0 ] == "^" :
10099 stuff = "\\ " + stuff
101100 res .append ("[%s]" % stuff )
@@ -104,27 +103,35 @@ def _translate(pattern, case_sensitive=True):
104103 return "" .join (res )
105104
106105
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+ """
109123 recursive = False
110124 re_patterns = ["" ]
111125 for component in iteratepath (pattern ):
112126 if "**" in component :
113127 recursive = True
114128 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 ]
116130 re_patterns .append ("/?" + ".*/?" .join (split_re ))
117131 else :
118- re_patterns .append (
119- "/" + _translate (component , case_sensitive = case_sensitive )
120- )
121- levels += 1
132+ re_patterns .append ("/" + _translate (component ))
122133 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
128135
129136
130137def match (pattern , path ):
@@ -146,10 +153,11 @@ def match(pattern, path):
146153
147154 """
148155 try :
149- levels , recursive , re_pattern = _PATTERN_CACHE [(pattern , True )]
156+ levels , re_pattern = _PATTERN_CACHE [(pattern , True )]
150157 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 )
153161 if path and path [0 ] != "/" :
154162 path = "/" + path
155163 return bool (re_pattern .match (path ))
@@ -168,10 +176,11 @@ def imatch(pattern, path):
168176
169177 """
170178 try :
171- levels , recursive , re_pattern = _PATTERN_CACHE [(pattern , False )]
179+ levels , re_pattern = _PATTERN_CACHE [(pattern , False )]
172180 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 )
175184 if path and path [0 ] != "/" :
176185 path = "/" + path
177186 return bool (re_pattern .match (path ))
@@ -186,7 +195,7 @@ def match_any(patterns, path):
186195 Arguments:
187196 patterns (list): A list of wildcard pattern, e.g ``["*.py",
188197 "*.pyc"]``
189- name (str): A filename .
198+ path (str): A resource path .
190199
191200 Returns:
192201 bool: `True` if the path matches at least one of the patterns.
@@ -206,7 +215,7 @@ def imatch_any(patterns, path):
206215 Arguments:
207216 patterns (list): A list of wildcard pattern, e.g ``["*.py",
208217 "*.pyc"]``
209- name (str): A filename .
218+ path (str): A resource path .
210219
211220 Returns:
212221 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):
227236 case_sensitive (bool): If ``True``, then the callable will be case
228237 sensitive, otherwise it will be case insensitive.
229238 accept_prefix (bool): If ``True``, the name is
230- not required to match the wildcards themselves
239+ not required to match the patterns themselves
231240 but only need to be a prefix of a string that does.
232241
233242 Returns:
234243 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.
236246
237247 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)
240250 >>> is_python('__init__.py')
241251 True
242252 >>> is_python('foo.txt')
243253 False
244254
245255 """
246256 if not patterns :
247- return lambda name : True
257+ return lambda path : True
248258
249259 if accept_prefix :
250260 new_patterns = []
251261 for pattern in patterns :
252- split = _split_pattern_by_rec (pattern )
262+ split = _split_pattern_by_sep (pattern )
253263 for i in range (1 , len (split )):
254264 new_pattern = "/" .join (split [:i ])
255265 new_patterns .append (new_pattern )
@@ -309,18 +319,15 @@ def __repr__(self):
309319 def _make_iter (self , search = "breadth" , namespaces = None ):
310320 # type: (str, List[str]) -> Iterator[GlobMatch]
311321 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 )]
315323 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 )
319326
320327 for path , info in self .fs .walk .info (
321328 path = self .path ,
322329 namespaces = namespaces or self .namespaces ,
323- max_depth = None if recursive else levels ,
330+ max_depth = levels ,
324331 search = search ,
325332 exclude_dirs = self .exclude_dirs ,
326333 ):
0 commit comments