Skip to content

Commit 16ea5fe

Browse files
author
atollk
committed
Properly implemented new filter_glob and exclude_glob parameters in fs.walk.
To do so, fs.wildcard had to undergo a rework to remove the dependency on the `re` module. Unit tests were added for all new/changed code.
1 parent 449580d commit 16ea5fe

File tree

7 files changed

+243
-99
lines changed

7 files changed

+243
-99
lines changed

fs/_ftp_parse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def parse(lines):
7878

7979
def parse_line(line):
8080
for line_re, decode_callable in get_decoders():
81-
match = line_re.match(line)
81+
match = line_re._match(line)
8282
if match is not None:
8383
return decode_callable(line, match)
8484
return None

fs/base.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -539,22 +539,22 @@ def filterdir(
539539
def match_dir(patterns, info):
540540
# type: (Optional[Iterable[Text]], Info) -> bool
541541
"""Pattern match info.name."""
542-
return info.is_file or self.match(patterns, info.name)
542+
return info.is_file or self._match(patterns, info.name)
543543

544544
def match_file(patterns, info):
545545
# type: (Optional[Iterable[Text]], Info) -> bool
546546
"""Pattern match info.name."""
547-
return info.is_dir or self.match(patterns, info.name)
547+
return info.is_dir or self._match(patterns, info.name)
548548

549549
def exclude_dir(patterns, info):
550550
# type: (Optional[Iterable[Text]], Info) -> bool
551551
"""Pattern match info.name."""
552-
return info.is_file or not self.match(patterns, info.name)
552+
return info.is_file or not self._match(patterns, info.name)
553553

554554
def exclude_file(patterns, info):
555555
# type: (Optional[Iterable[Text]], Info) -> bool
556556
"""Pattern match info.name."""
557-
return info.is_dir or not self.match(patterns, info.name)
557+
return info.is_dir or not self._match(patterns, info.name)
558558

559559
if files:
560560
filters.append(partial(match_file, files))
@@ -1543,13 +1543,16 @@ def check(self):
15431543
if self.isclosed():
15441544
raise errors.FilesystemClosed()
15451545

1546-
def match(self, patterns, name):
1547-
# type: (Optional[Iterable[Text]], Text) -> bool
1546+
def match(self, patterns, name, accept_prefix=False):
1547+
# type: (Optional[Iterable[Text]], Text, bool) -> bool
15481548
"""Check if a name matches any of a list of wildcards.
15491549
15501550
Arguments:
15511551
patterns (list): A list of patterns, e.g. ``['*.py']``
15521552
name (str): A file or directory name (not a path)
1553+
accept_prefix (bool): If ``True``, the name is
1554+
not required to match the wildcards themselves
1555+
but only need to be a prefix of a string that does.
15531556
15541557
Returns:
15551558
bool: `True` if ``name`` matches any of the patterns.
@@ -1561,9 +1564,9 @@ def match(self, patterns, name):
15611564
names).
15621565
15631566
Example:
1564-
>>> home_fs.match(['*.py'], '__init__.py')
1567+
>>> home_fs._match(['*.py'], '__init__.py')
15651568
True
1566-
>>> home_fs.match(['*.jpg', '*.png'], 'foo.gif')
1569+
>>> home_fs._match(['*.jpg', '*.png'], 'foo.gif')
15671570
False
15681571
15691572
Note:
@@ -1578,7 +1581,7 @@ def match(self, patterns, name):
15781581
case_sensitive = not typing.cast(
15791582
bool, self.getmeta().get("case_insensitive", False)
15801583
)
1581-
matcher = wildcard.get_matcher(patterns, case_sensitive)
1584+
matcher = wildcard.get_matcher(patterns, case_sensitive, accept_prefix=accept_prefix)
15821585
return matcher(name)
15831586

15841587
def tree(self, **kwargs):

fs/test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,9 +1783,9 @@ def test_movedir(self):
17831783
self.fs.movedir("foo2/foofoo.txt", "foo2/baz/egg")
17841784

17851785
def test_match(self):
1786-
self.assertTrue(self.fs.match(["*.py"], "foo.py"))
1786+
self.assertTrue(self.fs._match(["*.py"], "foo.py"))
17871787
self.assertEqual(
1788-
self.fs.match(["*.py"], "FOO.PY"),
1788+
self.fs._match(["*.py"], "FOO.PY"),
17891789
self.fs.getmeta().get("case_insensitive", False),
17901790
)
17911791

fs/walk.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,12 @@ def __init__(
9797
max_depth (int, optional): Maximum directory depth to walk.
9898
filter_glob (list, optional): If supplied, this parameter
9999
should be a list of path patterns e.g. ``["foo/**/*.py"]``.
100-
Resources will only be returned if their global patah
101-
matches one of the patterns.
100+
Resources will only be returned if their global path or
101+
an extension of it matches one of the patterns.
102102
exclude_glob (list, optional): If supplied, this parameter
103103
should be a list of path patterns e.g. ``["foo/**/*.py"]``.
104-
Resources will only be returned if their global patah
105-
matches one of the patterns.
104+
Resources will not be returned if their global path or
105+
an extension of it matches one of the patterns.
106106
107107
"""
108108
if search not in ("breadth", "depth"):
@@ -215,13 +215,18 @@ def _iter_walk(
215215
def _check_open_dir(self, fs, path, info):
216216
# type: (FS, Text, Info) -> bool
217217
"""Check if a directory should be considered in the walk."""
218+
full_path = ("" if path == "/" else path) + "/" + info.name
218219
if self.exclude_dirs is not None and fs.match(self.exclude_dirs, info.name):
219220
return False
220-
if self.exclude_glob is not None and fs.match(self.exclude_glob, path):
221+
if self.exclude_glob is not None and fs.match(self.exclude_glob, full_path):
221222
return False
222-
if self.filter_dirs is not None and not fs.match(self.filter_dirs, info.name):
223+
if self.filter_dirs is not None and not fs.match(
224+
self.filter_dirs, info.name, accept_prefix=True
225+
):
223226
return False
224-
if self.filter_glob is not None and not fs.match(self.filter_glob, path):
227+
if self.filter_glob is not None and not fs.match(
228+
self.filter_glob, full_path, accept_prefix=True
229+
):
225230
return False
226231
return self.check_open_dir(fs, path, info)
227232

@@ -268,15 +273,23 @@ def check_scan_dir(self, fs, path, info):
268273
"""
269274
return True
270275

271-
def _check_file(self, fs, info):
272-
# type: (FS, Info) -> bool
276+
def _check_file(self, fs, dir_path, info):
277+
# type: (FS, Text, Info) -> bool
273278
"""Check if a filename should be included."""
274279
# Weird check required for backwards compatibility, when _check_file did not exist.
275280
if Walker._check_file == type(self)._check_file:
276281
if self.exclude is not None and fs.match(self.exclude, info.name):
277282
return False
283+
if self.exclude_glob is not None and fs.match(
284+
self.exclude_glob, dir_path + "/" + info.name
285+
):
286+
return False
278287
if self.filter is not None and not fs.match(self.filter, info.name):
279288
return False
289+
if self.filter_glob is not None and not fs.match(
290+
self.filter_glob, dir_path + "/" + info.name, accept_prefix=True
291+
):
292+
return False
280293
return self.check_file(fs, info)
281294

282295
def check_file(self, fs, info):
@@ -462,7 +475,7 @@ def _walk_breadth(
462475
if _check_scan_dir(fs, dir_path, info, _depth):
463476
push(_combine(dir_path, info.name))
464477
else:
465-
if _check_file(fs, info):
478+
if _check_file(fs, dir_path, info):
466479
yield dir_path, info # Found a file
467480
yield dir_path, None # End of directory
468481

@@ -513,7 +526,7 @@ def _walk_depth(
513526
else:
514527
yield dir_path, info
515528
else:
516-
if _check_file(fs, info):
529+
if _check_file(fs, dir_path, info):
517530
yield dir_path, info
518531

519532

0 commit comments

Comments
 (0)