diff --git a/CHANGES.rst b/CHANGES.rst index 3482d02..95c553f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,13 @@ +v3.11.0 +======= + +* #85: Added support for new methods on ``Path``: + + - ``match`` + - ``glob`` and ``rglob`` + - ``relative_to`` + - ``is_symlink`` + v3.10.0 ======= diff --git a/tests/test_zipp.py b/tests/test_zipp.py index cac2cde..1e17870 100644 --- a/tests/test_zipp.py +++ b/tests/test_zipp.py @@ -403,6 +403,52 @@ def test_root_unnamed(self, alpharep): assert sub.name == "b" assert sub.parent + @pass_alpharep + def test_match_and_glob(self, alpharep): + root = zipp.Path(alpharep) + assert not root.match("*.txt") + + assert list(root.glob("b/c.*")) == [zipp.Path(alpharep, "b/c.txt")] + + files = root.glob("**/*.txt") + assert all(each.match("*.txt") for each in files) + + assert list(root.glob("**/*.txt")) == list(root.rglob("*.txt")) + + def test_glob_empty(self): + root = zipp.Path(zipfile.ZipFile(io.BytesIO(), 'w')) + with self.assertRaises(ValueError): + root.glob('') + + @pass_alpharep + def test_eq_hash(self, alpharep): + root = zipp.Path(alpharep) + assert root == zipp.Path(alpharep) + + assert root != (root / "a.txt") + assert (root / "a.txt") == (root / "a.txt") + + root = zipp.Path(alpharep) + assert root in {root} + + @pass_alpharep + def test_is_symlink(self, alpharep): + """ + See python/cpython#82102 for symlink support beyond this object. + """ + + root = zipp.Path(alpharep) + assert not root.is_symlink() + + @pass_alpharep + def test_relative_to(self, alpharep): + root = zipp.Path(alpharep) + relative = root.joinpath("b", "c.txt").relative_to(root / "b") + assert str(relative) == "c.txt" + + relative = root.joinpath("b", "d", "e.txt").relative_to(root / "b") + assert str(relative) == "d/e.txt" + @pass_alpharep def test_inheritance(self, alpharep): cls = type('PathChild', (zipp.Path,), {}) diff --git a/zipp/__init__.py b/zipp/__init__.py index c1f3632..ad01e27 100644 --- a/zipp/__init__.py +++ b/zipp/__init__.py @@ -4,6 +4,8 @@ import itertools import contextlib import pathlib +import re +import fnmatch from .py310compat import text_encoding @@ -243,6 +245,18 @@ def __init__(self, root, at=""): self.root = FastLookup.make(root) self.at = at + def __eq__(self, other): + """ + >>> Path(zipfile.ZipFile(io.BytesIO(), 'w')) == 'foo' + False + """ + if self.__class__ is not other.__class__: + return NotImplemented + return (self.root, self.at) == (other.root, other.at) + + def __hash__(self): + return hash((self.root, self.at)) + def open(self, mode='r', *args, pwd=None, **kwargs): """ Open this entry as text or binary following the semantics @@ -313,6 +327,38 @@ def iterdir(self): subs = map(self._next, self.root.namelist()) return filter(self._is_child, subs) + def match(self, path_pattern): + return pathlib.Path(self.at).match(path_pattern) + + def is_symlink(self): + """ + Return whether this path is a symlink. Always false (python/cpython#82102). + """ + return False + + def _descendants(self): + for child in self.iterdir(): + yield child + if child.is_dir(): + yield from child._descendants() + + def glob(self, pattern): + if not pattern: + raise ValueError("Unacceptable pattern: {!r}".format(pattern)) + + matches = re.compile(fnmatch.translate(pattern)).fullmatch + return ( + child + for child in self._descendants() + if matches(str(child.relative_to(self))) + ) + + def rglob(self, pattern): + return self.glob(f'**/{pattern}') + + def relative_to(self, other, *extra): + return posixpath.relpath(str(self), str(other.joinpath(*extra))) + def __str__(self): return posixpath.join(self.root.filename, self.at)