PK!MGQQ HISTORY.rstHistory ======= 0.1.0 (2019-03-27) ------------------ - First release on PyPI. PK!z  pathstring.py# Copyright (C) 2019 H. Turgut Uyar """Helpers for file system path operations.""" import os import types from inspect import signature from itertools import dropwhile, zip_longest from pathlib import Path as P from shutil import rmtree __version__ = "0.1.0" def _make_path_type(name): def new_path(cls, *args): return str.__new__(cls, str(P(*args))) def get_property(prop, *, as_path=False): def f(self): result = getattr(P(self), prop) return result if not as_path else Path(result) return f def get_method(meth, *, class_method=False, as_path=False): m = getattr(P, meth) def f(*args, **kwargs): if not class_method: self, *rest = args args = (P(self),) + tuple(rest) result = m(*args, **kwargs) if isinstance(result, types.GeneratorType): return (Path(p) for p in result) return result if not as_path else Path(result) f.__signature__ = signature(m) f.__name__ = m.__name__ f.__doc__ = m.__doc__ return f def relative_to(self, other, strict=True): """Get the relative path of this path starting from another path.""" if strict: return P(self).relative_to(other) parts = zip_longest(other.absolute().parts, self.absolute().parts) path_diff = dropwhile(lambda ps: ps[0] == ps[1], parts) up_parts, down_parts = zip(*path_diff) up_path = Path(*[os.path.pardir for p in up_parts if p is not None]) down_path = Path(*[p for p in down_parts if p is not None]) return Path(up_path, down_path) attrs = {} attrs["__new__"] = new_path for attr in ["parts", "name", "suffix", "stem"]: attrs[attr] = property(get_property(attr), doc=getattr(P, attr).__doc__) for attr in ["parent"]: attrs[attr] = property(get_property(attr, as_path=True), doc=getattr(P, attr).__doc__) for method in ["cwd", "home"]: attrs[method] = get_method(method, class_method=True, as_path=True) for method in ["absolute", "resolve", "with_name", "with_suffix", "glob"]: attrs[method] = get_method(method, as_path=True) for method in [ "is_absolute", "stat", "exists", "is_dir", "is_file", "is_symlink", "mkdir", "rmdir", "symlink_to", "open", "touch", "unlink", "read_bytes", "read_text", "write_bytes", "write_text", "samefile", ]: attrs[method] = get_method(method) attrs["relative_to"] = relative_to attrs["rmtree"] = rmtree return type(name, (str,), attrs) Path = _make_path_type("Path") Path.__doc__ = "A path in the file system." PK!__pathstring.pyifrom typing import Any, Generator, IO, Optional, Tuple, Union import os class Path(str): parts = ... # type: Tuple[str, ...] name = ... # type: str suffix = ... # type: str stem = ... # type: str parent = ... # type: Path def __new__(cls, *args) -> Path: ... @classmethod def cwd(cls) -> Path: ... @classmethod def home(cls) -> Path: ... def absolute(self) -> Path: ... def resolve(self) -> Path: ... def with_name(self, name: str) -> Path: ... def with_suffix(self, suffix: str) -> Path: ... def glob(self, pattern: str) -> Generator[Path]: ... def is_absolute(self) -> bool: ... def stat(self) -> os.stat_result: ... def exists(self) -> bool: ... def is_dir(self) -> bool: ... def is_file(self) -> bool: ... def is_symlink(self) -> bool: ... def mkdir(self, mode: int = ..., parents: bool = ..., exist_ok: bool = ...) -> None: ... def rmdir(self) -> None: ... def symlink_to(self, target: Path, target_is_directory: bool = ...) -> None: ... def open( self, mode: str = ..., buffering: int = ..., encoding: Optional[str] = ..., errors: Optional[str] = ..., newline: Optional[str] = ..., ) -> IO[Any]: ... def touch(self, mode: int = ..., exist_ok: bool = ...) -> None: ... def unlink(self) -> None: ... def read_bytes(self) -> bytes: ... def read_text(self, encoding: Optional[str] = ..., errors: Optional[str] = ...) -> str: ... def write_bytes(self, data: bytes) -> int: ... def write_text( self, data: str, encoding: Optional[str] = ..., errors: Optional[str] = ... ) -> int: ... def samefile(self, other_path: Path) -> bool: ... def relative_to(self, other: Path, strict: Bool = ...) -> Path: ... def rmtree(self, ignore_errors: bool = ..., onerror: bool = ...) -> None: ... PK!tests/test_path.py# flake8: noqa from pytest import raises from os.path import pardir from pkg_resources import get_distribution from pathstring import __version__, Path def test_version(): assert get_distribution("pathstring").version == __version__ def test_relative_to_target_starting_with_parent_folder_should_fail_when_strict(): with raises(ValueError) as e: Path("d1", "d2", "f").relative_to(Path("d1", "d3")) def test_relative_to_target_in_same_folder_should_be_target_name(): assert Path("d1", "f").relative_to(Path("d1")).parts == ("f",) def test_relative_to_target_in_child_folder_should_start_with_child(): assert Path("d1", "d2", "f").relative_to(Path("d1")).parts == ("d2", "f") def test_relative_to_target_in_grandchild_folder_should_start_with_two_children(): assert Path("d1", "d2", "d3", "f").relative_to(Path("d1")).parts == ("d2", "d3", "f") def test_relative_to_target_in_parent_folder_should_start_with_parent(): assert Path("d1", "f").relative_to(Path("d1", "d2"), strict=False).parts == (pardir, "f") def test_relative_to_target_in_grandparent_folder_should_start_with_two_parents(): assert Path("d1", "f").relative_to(Path("d1", "d2", "d3"), strict=False).parts == (pardir, pardir, "f") def test_relative_to_target_in_diagonal_folder_should_go_up_and_down_when_not_stict(): assert Path("d1", "d2", "f").relative_to(Path("d1", "d3"), strict=False).parts == (pardir, "d2", "f") PK!7`&pathstring-0.1.0.dist-info/LICENSE.txtCopyright 2019 H. Turgut Uyar Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PK!Hu)GTU pathstring-0.1.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/R(O-)$qzd&Y)r$UV&UrPK!HAe#pathstring-0.1.0.dist-info/METADATAUMs0WN?( Ц0P Ӵ\jolQ[*=+p!{ov7_d)I&?yet&[J=9+pbV.u ƢP3bbeR5|^10\i硓n$Ĺ*P{ƞOdQ|ָو@qp\Xf|`+UATHx\j\%.wP}>?HgGG^m)n1EMKRe8"MKMK,8 u9R+"pl3[f PD.%Wrrm#\*p#eŌ/d,[`p:nMD.UEsDrACTyj+3hbalTU,^޻g+QzlH)O,\&by~\|]FqWo#wPK!HP!pathstring-0.1.0.dist-info/RECORD}л@gF1؀Kc7 XZi|uf~u$`+^:.te?xkz2^c3( ,S 㾱d%PK!MGQQ HISTORY.rstPK!z  zpathstring.pyPK!__ pathstring.pyiPK!Gtests/test_path.pyPK!7`&pathstring-0.1.0.dist-info/LICENSE.txtPK!Hu)GTU .pathstring-0.1.0.dist-info/WHEELPK!HAe#pathstring-0.1.0.dist-info/METADATAPK!HP!#pathstring-0.1.0.dist-info/RECORDPK2$