diff options
| author | robot-piglet <[email protected]> | 2023-12-01 16:59:11 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2023-12-01 19:54:31 +0300 |
| commit | 3715aa9254f65ae1058290101351a72a6d3a67d4 (patch) | |
| tree | 9ac5a1cdab42dfc7cd095a06a362e0681cb1482f /contrib/python/path | |
| parent | b20a8c04fb7e595955ca9d1b943033342b6580cb (diff) | |
Intermediate changes
Diffstat (limited to 'contrib/python/path')
| -rw-r--r-- | contrib/python/path/.dist-info/METADATA | 201 | ||||
| -rw-r--r-- | contrib/python/path/.dist-info/top_level.txt | 1 | ||||
| -rw-r--r-- | contrib/python/path/LICENSE | 17 | ||||
| -rw-r--r-- | contrib/python/path/README.rst | 163 | ||||
| -rw-r--r-- | contrib/python/path/path/__init__.py | 1665 | ||||
| -rw-r--r-- | contrib/python/path/path/classes.py | 27 | ||||
| -rw-r--r-- | contrib/python/path/path/masks.py | 159 | ||||
| -rw-r--r-- | contrib/python/path/path/matchers.py | 59 | ||||
| -rw-r--r-- | contrib/python/path/path/py.typed | 0 | ||||
| -rw-r--r-- | contrib/python/path/ya.make | 30 |
10 files changed, 0 insertions, 2322 deletions
diff --git a/contrib/python/path/.dist-info/METADATA b/contrib/python/path/.dist-info/METADATA deleted file mode 100644 index 7cddb387239..00000000000 --- a/contrib/python/path/.dist-info/METADATA +++ /dev/null @@ -1,201 +0,0 @@ -Metadata-Version: 2.1 -Name: path -Version: 16.7.1 -Summary: A module wrapper for os.path -Home-page: https://github.com/jaraco/path -Author: Jason Orendorff -Author-email: [email protected] -Maintainer: Jason R. Coombs -Maintainer-email: [email protected] -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Operating System :: OS Independent -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Requires-Python: >=3.8 -License-File: LICENSE -Provides-Extra: docs -Requires-Dist: sphinx (>=3.5) ; extra == 'docs' -Requires-Dist: jaraco.packaging (>=9.3) ; extra == 'docs' -Requires-Dist: rst.linker (>=1.9) ; extra == 'docs' -Requires-Dist: furo ; extra == 'docs' -Requires-Dist: sphinx-lint ; extra == 'docs' -Requires-Dist: jaraco.tidelift (>=1.4) ; extra == 'docs' -Provides-Extra: testing -Requires-Dist: pytest (>=6) ; extra == 'testing' -Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing' -Requires-Dist: pytest-cov ; extra == 'testing' -Requires-Dist: pytest-enabler (>=2.2) ; extra == 'testing' -Requires-Dist: pytest-ruff ; extra == 'testing' -Requires-Dist: appdirs ; extra == 'testing' -Requires-Dist: packaging ; extra == 'testing' -Requires-Dist: pygments ; extra == 'testing' -Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing' -Requires-Dist: pytest-mypy (>=0.9.1) ; (platform_python_implementation != "PyPy") and extra == 'testing' -Requires-Dist: pywin32 ; (platform_system == "Windows" and python_version < "3.12") and extra == 'testing' - -.. image:: https://img.shields.io/pypi/v/path.svg - :target: https://pypi.org/project/path - -.. image:: https://img.shields.io/pypi/pyversions/path.svg - -.. image:: https://github.com/jaraco/path/workflows/tests/badge.svg - :target: https://github.com/jaraco/path/actions?query=workflow%3A%22tests%22 - :alt: tests - -.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json - :target: https://github.com/astral-sh/ruff - :alt: Ruff - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code style: Black - -.. image:: https://readthedocs.org/projects/path/badge/?version=latest - :target: https://path.readthedocs.io/en/latest/?badge=latest - -.. image:: https://img.shields.io/badge/skeleton-2023-informational - :target: https://blog.jaraco.com/skeleton - -.. image:: https://tidelift.com/badges/package/pypi/path - :target: https://tidelift.com/subscription/pkg/pypi-path?utm_source=pypi-path&utm_medium=readme - - -``path`` (aka path pie, formerly ``path.py``) implements path -objects as first-class entities, allowing common operations on -files to be invoked on those path objects directly. For example: - -.. code-block:: python - - from path import Path - - d = Path("/home/guido/bin") - for f in d.files("*.py"): - f.chmod(0o755) - - # Globbing - for f in d.files("*.py"): - f.chmod("u+rwx") - - # Changing the working directory: - with Path("somewhere"): - # cwd in now `somewhere` - ... - - # Concatenate paths with / - foo_txt = Path("bar") / "foo.txt" - -Path pie is `hosted at Github <https://github.com/jaraco/path>`_. - -Find `the documentation here <https://path.readthedocs.io>`_. - -Guides and Testimonials -======================= - -Yasoob wrote the Python 101 `Writing a Cleanup Script -<http://freepythontips.wordpress.com/2014/01/23/python-101-writing-a-cleanup-script/>`_ -based on ``path``. - -Advantages -========== - -Python 3.4 introduced -`pathlib <https://docs.python.org/3/library/pathlib.html>`_, -which shares many characteristics with ``path``. In particular, -it provides an object encapsulation for representing filesystem paths. -One may have imagined ``pathlib`` would supersede ``path``. - -But the implementation and the usage quickly diverge, and ``path`` -has several advantages over ``pathlib``: - -- ``path`` implements ``Path`` objects as a subclass of - ``str``, and as a result these ``Path`` - objects may be passed directly to other APIs that expect simple - text representations of paths, whereas with ``pathlib``, one - must first cast values to strings before passing them to - APIs unaware of ``pathlib``. This shortcoming was `addressed - by PEP 519 <https://www.python.org/dev/peps/pep-0519/>`_, - in Python 3.6. -- ``path`` goes beyond exposing basic functionality of a path - and exposes commonly-used behaviors on a path, providing - methods like ``rmtree`` (from shlib) and ``remove_p`` (remove - a file if it exists). -- As a PyPI-hosted package, ``path`` is free to iterate - faster than a stdlib package. Contributions are welcome - and encouraged. -- ``path`` provides a uniform abstraction over its Path object, - freeing the implementer to subclass it readily. One cannot - subclass a ``pathlib.Path`` to add functionality, but must - subclass ``Path``, ``PosixPath``, and ``WindowsPath``, even - if one only wishes to add a ``__dict__`` to the subclass - instances. ``path`` instead allows the ``Path.module`` - object to be overridden by subclasses, defaulting to the - ``os.path``. Even advanced uses of ``path.Path`` that - subclass the model do not need to be concerned with - OS-specific nuances. - -This path project has the explicit aim to provide compatibility -with ``pathlib`` objects where possible, such that a ``path.Path`` -object is a drop-in replacement for ``pathlib.Path*`` objects. -This project welcomes contributions to improve that compatibility -where it's lacking. - -Alternatives -============ - -In addition to -`pathlib <https://docs.python.org/3/library/pathlib.html>`_, the -`pylib project <https://pypi.org/project/py/>`_ implements a -`LocalPath <https://github.com/pytest-dev/py/blob/72601dc8bbb5e11298bf9775bb23b0a395deb09b/py/_path/local.py#L106>`_ -class, which shares some behaviors and interfaces with ``path``. - -Development -=========== - -To install a development version, use the Github links to clone or -download a snapshot of the latest code. Alternatively, if you have git -installed, you may be able to use ``pip`` to install directly from -the repository:: - - pip install git+https://github.com/jaraco/path.git - -Testing -======= - -Tests are invoked with `tox <https://pypi.org/project/tox>`_. After -having installed tox, simply invoke ``tox`` in a checkout of the repo -to invoke the tests. - -Tests are also run in continuous integration. See the badges above -for links to the CI runs. - -Releasing -========= - -Tagged releases are automatically published to PyPI by Azure -Pipelines, assuming the tests pass. - -Origins -======= - -The ``path.py`` project was initially released in 2003 by Jason Orendorff -and has been continuously developed and supported by several maintainers -over the years. - -For Enterprise -============== - -Available as part of the Tidelift Subscription. - -This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. - -`Learn more <https://tidelift.com/subscription/pkg/pypi-path?utm_source=pypi-path&utm_medium=referral&utm_campaign=github>`_. - -Security Contact -================ - -To report a security vulnerability, please use the -`Tidelift security contact <https://tidelift.com/security>`_. -Tidelift will coordinate the fix and disclosure. diff --git a/contrib/python/path/.dist-info/top_level.txt b/contrib/python/path/.dist-info/top_level.txt deleted file mode 100644 index e7a8fd4d0a8..00000000000 --- a/contrib/python/path/.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -path diff --git a/contrib/python/path/LICENSE b/contrib/python/path/LICENSE deleted file mode 100644 index 1bb5a44356f..00000000000 --- a/contrib/python/path/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/contrib/python/path/README.rst b/contrib/python/path/README.rst deleted file mode 100644 index 69aa8737d6b..00000000000 --- a/contrib/python/path/README.rst +++ /dev/null @@ -1,163 +0,0 @@ -.. image:: https://img.shields.io/pypi/v/path.svg - :target: https://pypi.org/project/path - -.. image:: https://img.shields.io/pypi/pyversions/path.svg - -.. image:: https://github.com/jaraco/path/workflows/tests/badge.svg - :target: https://github.com/jaraco/path/actions?query=workflow%3A%22tests%22 - :alt: tests - -.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json - :target: https://github.com/astral-sh/ruff - :alt: Ruff - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code style: Black - -.. image:: https://readthedocs.org/projects/path/badge/?version=latest - :target: https://path.readthedocs.io/en/latest/?badge=latest - -.. image:: https://img.shields.io/badge/skeleton-2023-informational - :target: https://blog.jaraco.com/skeleton - -.. image:: https://tidelift.com/badges/package/pypi/path - :target: https://tidelift.com/subscription/pkg/pypi-path?utm_source=pypi-path&utm_medium=readme - - -``path`` (aka path pie, formerly ``path.py``) implements path -objects as first-class entities, allowing common operations on -files to be invoked on those path objects directly. For example: - -.. code-block:: python - - from path import Path - - d = Path("/home/guido/bin") - for f in d.files("*.py"): - f.chmod(0o755) - - # Globbing - for f in d.files("*.py"): - f.chmod("u+rwx") - - # Changing the working directory: - with Path("somewhere"): - # cwd in now `somewhere` - ... - - # Concatenate paths with / - foo_txt = Path("bar") / "foo.txt" - -Path pie is `hosted at Github <https://github.com/jaraco/path>`_. - -Find `the documentation here <https://path.readthedocs.io>`_. - -Guides and Testimonials -======================= - -Yasoob wrote the Python 101 `Writing a Cleanup Script -<http://freepythontips.wordpress.com/2014/01/23/python-101-writing-a-cleanup-script/>`_ -based on ``path``. - -Advantages -========== - -Python 3.4 introduced -`pathlib <https://docs.python.org/3/library/pathlib.html>`_, -which shares many characteristics with ``path``. In particular, -it provides an object encapsulation for representing filesystem paths. -One may have imagined ``pathlib`` would supersede ``path``. - -But the implementation and the usage quickly diverge, and ``path`` -has several advantages over ``pathlib``: - -- ``path`` implements ``Path`` objects as a subclass of - ``str``, and as a result these ``Path`` - objects may be passed directly to other APIs that expect simple - text representations of paths, whereas with ``pathlib``, one - must first cast values to strings before passing them to - APIs unaware of ``pathlib``. This shortcoming was `addressed - by PEP 519 <https://www.python.org/dev/peps/pep-0519/>`_, - in Python 3.6. -- ``path`` goes beyond exposing basic functionality of a path - and exposes commonly-used behaviors on a path, providing - methods like ``rmtree`` (from shlib) and ``remove_p`` (remove - a file if it exists). -- As a PyPI-hosted package, ``path`` is free to iterate - faster than a stdlib package. Contributions are welcome - and encouraged. -- ``path`` provides a uniform abstraction over its Path object, - freeing the implementer to subclass it readily. One cannot - subclass a ``pathlib.Path`` to add functionality, but must - subclass ``Path``, ``PosixPath``, and ``WindowsPath``, even - if one only wishes to add a ``__dict__`` to the subclass - instances. ``path`` instead allows the ``Path.module`` - object to be overridden by subclasses, defaulting to the - ``os.path``. Even advanced uses of ``path.Path`` that - subclass the model do not need to be concerned with - OS-specific nuances. - -This path project has the explicit aim to provide compatibility -with ``pathlib`` objects where possible, such that a ``path.Path`` -object is a drop-in replacement for ``pathlib.Path*`` objects. -This project welcomes contributions to improve that compatibility -where it's lacking. - -Alternatives -============ - -In addition to -`pathlib <https://docs.python.org/3/library/pathlib.html>`_, the -`pylib project <https://pypi.org/project/py/>`_ implements a -`LocalPath <https://github.com/pytest-dev/py/blob/72601dc8bbb5e11298bf9775bb23b0a395deb09b/py/_path/local.py#L106>`_ -class, which shares some behaviors and interfaces with ``path``. - -Development -=========== - -To install a development version, use the Github links to clone or -download a snapshot of the latest code. Alternatively, if you have git -installed, you may be able to use ``pip`` to install directly from -the repository:: - - pip install git+https://github.com/jaraco/path.git - -Testing -======= - -Tests are invoked with `tox <https://pypi.org/project/tox>`_. After -having installed tox, simply invoke ``tox`` in a checkout of the repo -to invoke the tests. - -Tests are also run in continuous integration. See the badges above -for links to the CI runs. - -Releasing -========= - -Tagged releases are automatically published to PyPI by Azure -Pipelines, assuming the tests pass. - -Origins -======= - -The ``path.py`` project was initially released in 2003 by Jason Orendorff -and has been continuously developed and supported by several maintainers -over the years. - -For Enterprise -============== - -Available as part of the Tidelift Subscription. - -This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. - -`Learn more <https://tidelift.com/subscription/pkg/pypi-path?utm_source=pypi-path&utm_medium=referral&utm_campaign=github>`_. - -Security Contact -================ - -To report a security vulnerability, please use the -`Tidelift security contact <https://tidelift.com/security>`_. -Tidelift will coordinate the fix and disclosure. diff --git a/contrib/python/path/path/__init__.py b/contrib/python/path/path/__init__.py deleted file mode 100644 index eebdc3a0b83..00000000000 --- a/contrib/python/path/path/__init__.py +++ /dev/null @@ -1,1665 +0,0 @@ -""" -Path Pie - -Implements ``path.Path`` - An object representing a -path to a file or directory. - -Example:: - - from path import Path - d = Path('/home/guido/bin') - - # Globbing - for f in d.files('*.py'): - f.chmod(0o755) - - # Changing the working directory: - with Path("somewhere"): - # cwd in now `somewhere` - ... - - # Concatenate paths with / - foo_txt = Path("bar") / "foo.txt" -""" - -import sys -import warnings -import os -import fnmatch -import glob -import shutil -import hashlib -import errno -import tempfile -import functools -import re -import contextlib -import importlib -import itertools -import datetime -from numbers import Number -from typing import Union - -with contextlib.suppress(ImportError): - import win32security - -with contextlib.suppress(ImportError): - import pwd - -with contextlib.suppress(ImportError): - import grp - -from . import matchers -from . import masks -from . import classes - - -__all__ = ['Path', 'TempDir'] - - -LINESEPS = ['\r\n', '\r', '\n'] -U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029'] -B_NEWLINE = re.compile('|'.join(LINESEPS).encode()) -U_NEWLINE = re.compile('|'.join(U_LINESEPS)) -B_NL_END = re.compile(B_NEWLINE.pattern + b'$') -U_NL_END = re.compile(U_NEWLINE.pattern + '$') - -_default_linesep = object() - - -def _make_timestamp_ns(value: Union[Number, datetime.datetime]) -> Number: - timestamp_s = value if isinstance(value, Number) else value.timestamp() - return int(timestamp_s * 10**9) - - -class TreeWalkWarning(Warning): - pass - - -class Traversal: - """ - Wrap a walk result to customize the traversal. - - `follow` is a function that takes an item and returns - True if that item should be followed and False otherwise. - - For example, to avoid traversing into directories that - begin with `.`: - - >>> traverse = Traversal(lambda dir: not dir.startswith('.')) - >>> items = list(traverse(Path('.').walk())) - - Directories beginning with `.` will appear in the results, but - their children will not. - - >>> dot_dir = next(item for item in items if item.isdir() and item.startswith('.')) - >>> any(item.parent == dot_dir for item in items) - False - """ - - def __init__(self, follow): - self.follow = follow - - def __call__(self, walker): - traverse = None - while True: - try: - item = walker.send(traverse) - except StopIteration: - return - yield item - - traverse = functools.partial(self.follow, item) - - -def _strip_newlines(lines): - r""" - >>> list(_strip_newlines(['Hello World\r\n', 'foo'])) - ['Hello World', 'foo'] - """ - return (U_NL_END.sub('', line) for line in lines) - - -class Path(str): - """ - Represents a filesystem path. - - For documentation on individual methods, consult their - counterparts in :mod:`os.path`. - - Some methods are additionally included from :mod:`shutil`. - The functions are linked directly into the class namespace - such that they will be bound to the Path instance. For example, - ``Path(src).copy(target)`` is equivalent to - ``shutil.copy(src, target)``. Therefore, when referencing - the docs for these methods, assume `src` references `self`, - the Path instance. - """ - - module = os.path - """ The path module to use for path operations. - - .. seealso:: :mod:`os.path` - """ - - def __init__(self, other=''): - if other is None: - raise TypeError("Invalid initial value for path: None") - with contextlib.suppress(AttributeError): - self._validate() - - @classmethod - @functools.lru_cache - def using_module(cls, module): - subclass_name = cls.__name__ + '_' + module.__name__ - bases = (cls,) - ns = {'module': module} - return type(subclass_name, bases, ns) - - @classes.ClassProperty - @classmethod - def _next_class(cls): - """ - What class should be used to construct new instances from this class - """ - return cls - - # --- Special Python methods. - - def __repr__(self): - return '{}({})'.format(type(self).__name__, super().__repr__()) - - # Adding a Path and a string yields a Path. - def __add__(self, more): - return self._next_class(super().__add__(more)) - - def __radd__(self, other): - return self._next_class(other.__add__(self)) - - # The / operator joins Paths. - def __div__(self, rel): - """fp.__div__(rel) == fp / rel == fp.joinpath(rel) - - Join two path components, adding a separator character if - needed. - - .. seealso:: :func:`os.path.join` - """ - return self._next_class(self.module.join(self, rel)) - - # Make the / operator work even when true division is enabled. - __truediv__ = __div__ - - # The / operator joins Paths the other way around - def __rdiv__(self, rel): - """fp.__rdiv__(rel) == rel / fp - - Join two path components, adding a separator character if - needed. - - .. seealso:: :func:`os.path.join` - """ - return self._next_class(self.module.join(rel, self)) - - # Make the / operator work even when true division is enabled. - __rtruediv__ = __rdiv__ - - def __enter__(self): - self._old_dir = self.getcwd() - os.chdir(self) - return self - - def __exit__(self, *_): - os.chdir(self._old_dir) - - @classmethod - def getcwd(cls): - """Return the current working directory as a path object. - - .. seealso:: :func:`os.getcwd` - """ - return cls(os.getcwd()) - - # - # --- Operations on Path strings. - - def abspath(self): - """.. seealso:: :func:`os.path.abspath`""" - return self._next_class(self.module.abspath(self)) - - def normcase(self): - """.. seealso:: :func:`os.path.normcase`""" - return self._next_class(self.module.normcase(self)) - - def normpath(self): - """.. seealso:: :func:`os.path.normpath`""" - return self._next_class(self.module.normpath(self)) - - def realpath(self): - """.. seealso:: :func:`os.path.realpath`""" - return self._next_class(self.module.realpath(self)) - - def expanduser(self): - """.. seealso:: :func:`os.path.expanduser`""" - return self._next_class(self.module.expanduser(self)) - - def expandvars(self): - """.. seealso:: :func:`os.path.expandvars`""" - return self._next_class(self.module.expandvars(self)) - - def dirname(self): - """.. seealso:: :attr:`parent`, :func:`os.path.dirname`""" - return self._next_class(self.module.dirname(self)) - - def basename(self): - """.. seealso:: :attr:`name`, :func:`os.path.basename`""" - return self._next_class(self.module.basename(self)) - - def expand(self): - """Clean up a filename by calling :meth:`expandvars()`, - :meth:`expanduser()`, and :meth:`normpath()` on it. - - This is commonly everything needed to clean up a filename - read from a configuration file, for example. - """ - return self.expandvars().expanduser().normpath() - - @property - def stem(self): - """The same as :meth:`name`, but with one file extension stripped off. - - >>> Path('/home/guido/python.tar.gz').stem - 'python.tar' - """ - base, ext = self.module.splitext(self.name) - return base - - @property - def ext(self): - """The file extension, for example ``'.py'``.""" - f, ext = self.module.splitext(self) - return ext - - def with_suffix(self, suffix): - """Return a new path with the file suffix changed (or added, if none) - - >>> Path('/home/guido/python.tar.gz').with_suffix(".foo") - Path('/home/guido/python.tar.foo') - - >>> Path('python').with_suffix('.zip') - Path('python.zip') - - >>> Path('filename.ext').with_suffix('zip') - Traceback (most recent call last): - ... - ValueError: Invalid suffix 'zip' - """ - if not suffix.startswith('.'): - raise ValueError(f"Invalid suffix {suffix!r}") - - return self.stripext() + suffix - - @property - def drive(self): - """The drive specifier, for example ``'C:'``. - - This is always empty on systems that don't use drive specifiers. - """ - drive, r = self.module.splitdrive(self) - return self._next_class(drive) - - parent = property( - dirname, - None, - None, - """ This path's parent directory, as a new Path object. - - For example, - ``Path('/usr/local/lib/libpython.so').parent == - Path('/usr/local/lib')`` - - .. seealso:: :meth:`dirname`, :func:`os.path.dirname` - """, - ) - - name = property( - basename, - None, - None, - """ The name of this file or directory without the full path. - - For example, - ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'`` - - .. seealso:: :meth:`basename`, :func:`os.path.basename` - """, - ) - - def splitpath(self): - """Return two-tuple of ``.parent``, ``.name``. - - .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split` - """ - parent, child = self.module.split(self) - return self._next_class(parent), child - - def splitdrive(self): - """Return two-tuple of ``.drive`` and rest without drive. - - Split the drive specifier from this path. If there is - no drive specifier, :samp:`{p.drive}` is empty, so the return value - is simply ``(Path(''), p)``. This is always the case on Unix. - - .. seealso:: :func:`os.path.splitdrive` - """ - drive, rel = self.module.splitdrive(self) - return self._next_class(drive), self._next_class(rel) - - def splitext(self): - """Return two-tuple of ``.stripext()`` and ``.ext``. - - Split the filename extension from this path and return - the two parts. Either part may be empty. - - The extension is everything from ``'.'`` to the end of the - last path segment. This has the property that if - ``(a, b) == p.splitext()``, then ``a + b == p``. - - .. seealso:: :func:`os.path.splitext` - """ - filename, ext = self.module.splitext(self) - return self._next_class(filename), ext - - def stripext(self): - """Remove one file extension from the path. - - For example, ``Path('/home/guido/python.tar.gz').stripext()`` - returns ``Path('/home/guido/python.tar')``. - """ - return self.splitext()[0] - - @classes.multimethod - def joinpath(cls, first, *others): - """ - Join first to zero or more :class:`Path` components, - adding a separator character (:samp:`{first}.module.sep`) - if needed. Returns a new instance of - :samp:`{first}._next_class`. - - .. seealso:: :func:`os.path.join` - """ - return cls._next_class(cls.module.join(first, *others)) - - def splitall(self): - r"""Return a list of the path components in this path. - - The first item in the list will be a Path. Its value will be - either :data:`os.curdir`, :data:`os.pardir`, empty, or the root - directory of this path (for example, ``'/'`` or ``'C:\\'``). The - other items in the list will be strings. - - ``Path.joinpath(*result)`` will yield the original path. - - >>> Path('/foo/bar/baz').splitall() - [Path('/'), 'foo', 'bar', 'baz'] - """ - return list(self._parts()) - - def parts(self): - """ - >>> Path('/foo/bar/baz').parts() - (Path('/'), 'foo', 'bar', 'baz') - """ - return tuple(self._parts()) - - def _parts(self): - return reversed(tuple(self._parts_iter())) - - def _parts_iter(self): - loc = self - while loc != os.curdir and loc != os.pardir: - prev = loc - loc, child = prev.splitpath() - if loc == prev: - break - yield child - yield loc - - def relpath(self, start='.'): - """Return this path as a relative path, - based from `start`, which defaults to the current working directory. - """ - cwd = self._next_class(start) - return cwd.relpathto(self) - - def relpathto(self, dest): - """Return a relative path from `self` to `dest`. - - If there is no relative path from `self` to `dest`, for example if - they reside on different drives in Windows, then this returns - ``dest.abspath()``. - """ - origin = self.abspath() - dest = self._next_class(dest).abspath() - - orig_list = origin.normcase().splitall() - # Don't normcase dest! We want to preserve the case. - dest_list = dest.splitall() - - if orig_list[0] != self.module.normcase(dest_list[0]): - # Can't get here from there. - return dest - - # Find the location where the two paths start to differ. - i = 0 - for start_seg, dest_seg in zip(orig_list, dest_list): - if start_seg != self.module.normcase(dest_seg): - break - i += 1 - - # Now i is the point where the two paths diverge. - # Need a certain number of "os.pardir"s to work up - # from the origin to the point of divergence. - segments = [os.pardir] * (len(orig_list) - i) - # Need to add the diverging part of dest_list. - segments += dest_list[i:] - if len(segments) == 0: - # If they happen to be identical, use os.curdir. - relpath = os.curdir - else: - relpath = self.module.join(*segments) - return self._next_class(relpath) - - # --- Listing, searching, walking, and matching - - def listdir(self, match=None): - """List of items in this directory. - - Use :meth:`files` or :meth:`dirs` instead if you want a listing - of just files or just subdirectories. - - The elements of the list are Path objects. - - With the optional `match` argument, a callable, - only return items whose names match the given pattern. - - .. seealso:: :meth:`files`, :meth:`dirs` - """ - match = matchers.load(match) - return list(filter(match, (self / child for child in os.listdir(self)))) - - def dirs(self, *args, **kwargs): - """List of this directory's subdirectories. - - The elements of the list are Path objects. - This does not walk recursively into subdirectories - (but see :meth:`walkdirs`). - - Accepts parameters to :meth:`listdir`. - """ - return [p for p in self.listdir(*args, **kwargs) if p.isdir()] - - def files(self, *args, **kwargs): - """List of the files in self. - - The elements of the list are Path objects. - This does not walk into subdirectories (see :meth:`walkfiles`). - - Accepts parameters to :meth:`listdir`. - """ - - return [p for p in self.listdir(*args, **kwargs) if p.isfile()] - - def walk(self, match=None, errors='strict'): - """Iterator over files and subdirs, recursively. - - The iterator yields Path objects naming each child item of - this directory and its descendants. This requires that - ``D.isdir()``. - - This performs a depth-first traversal of the directory tree. - Each directory is returned just before all its children. - - The `errors=` keyword argument controls behavior when an - error occurs. The default is ``'strict'``, which causes an - exception. Other allowed values are ``'warn'`` (which - reports the error via :func:`warnings.warn()`), and ``'ignore'``. - `errors` may also be an arbitrary callable taking a msg parameter. - """ - - errors = Handlers._resolve(errors) - match = matchers.load(match) - - try: - childList = self.listdir() - except Exception as exc: - errors(f"Unable to list directory '{self}': {exc}") - return - - for child in childList: - traverse = None - if match(child): - traverse = yield child - traverse = traverse or child.isdir - try: - do_traverse = traverse() - except Exception as exc: - errors(f"Unable to access '{child}': {exc}") - continue - - if do_traverse: - yield from child.walk(errors=errors, match=match) - - def walkdirs(self, *args, **kwargs): - """Iterator over subdirs, recursively.""" - return (item for item in self.walk(*args, **kwargs) if item.isdir()) - - def walkfiles(self, *args, **kwargs): - """Iterator over files, recursively.""" - return (item for item in self.walk(*args, **kwargs) if item.isfile()) - - def fnmatch(self, pattern, normcase=None): - """Return ``True`` if `self.name` matches the given `pattern`. - - `pattern` - A filename pattern with wildcards, - for example ``'*.py'``. If the pattern contains a `normcase` - attribute, it is applied to the name and path prior to comparison. - - `normcase` - (optional) A function used to normalize the pattern and - filename before matching. Defaults to normcase from - ``self.module``, :func:`os.path.normcase`. - - .. seealso:: :func:`fnmatch.fnmatch` - """ - default_normcase = getattr(pattern, 'normcase', self.module.normcase) - normcase = normcase or default_normcase - name = normcase(self.name) - pattern = normcase(pattern) - return fnmatch.fnmatchcase(name, pattern) - - def glob(self, pattern): - """Return a list of Path objects that match the pattern. - - `pattern` - a path relative to this directory, with wildcards. - - For example, ``Path('/users').glob('*/bin/*')`` returns a list - of all the files users have in their :file:`bin` directories. - - .. seealso:: :func:`glob.glob` - - .. note:: Glob is **not** recursive, even when using ``**``. - To do recursive globbing see :func:`walk`, - :func:`walkdirs` or :func:`walkfiles`. - """ - cls = self._next_class - return [cls(s) for s in glob.glob(self / pattern)] - - def iglob(self, pattern): - """Return an iterator of Path objects that match the pattern. - - `pattern` - a path relative to this directory, with wildcards. - - For example, ``Path('/users').iglob('*/bin/*')`` returns an - iterator of all the files users have in their :file:`bin` - directories. - - .. seealso:: :func:`glob.iglob` - - .. note:: Glob is **not** recursive, even when using ``**``. - To do recursive globbing see :func:`walk`, - :func:`walkdirs` or :func:`walkfiles`. - """ - cls = self._next_class - return (cls(s) for s in glob.iglob(self / pattern)) - - # - # --- Reading or writing an entire file at once. - - def open(self, *args, **kwargs): - """Open this file and return a corresponding file object. - - Keyword arguments work as in :func:`io.open`. If the file cannot be - opened, an :class:`OSError` is raised. - """ - return open(self, *args, **kwargs) - - def bytes(self): - """Open this file, read all bytes, return them as a string.""" - with self.open('rb') as f: - return f.read() - - def chunks(self, size, *args, **kwargs): - """Returns a generator yielding chunks of the file, so it can - be read piece by piece with a simple for loop. - - Any argument you pass after `size` will be passed to :meth:`open`. - - :example: - - >>> hash = hashlib.md5() - >>> for chunk in Path("NEWS.rst").chunks(8192, mode='rb'): - ... hash.update(chunk) - - This will read the file by chunks of 8192 bytes. - """ - with self.open(*args, **kwargs) as f: - yield from iter(lambda: f.read(size) or None, None) - - def write_bytes(self, bytes, append=False): - """Open this file and write the given bytes to it. - - Default behavior is to overwrite any existing file. - Call ``p.write_bytes(bytes, append=True)`` to append instead. - """ - with self.open('ab' if append else 'wb') as f: - f.write(bytes) - - def read_text(self, encoding=None, errors=None): - r"""Open this file, read it in, return the content as a string. - - Optional parameters are passed to :meth:`open`. - - .. seealso:: :meth:`lines` - """ - with self.open(encoding=encoding, errors=errors) as f: - return f.read() - - def read_bytes(self): - r"""Return the contents of this file as bytes.""" - with self.open(mode='rb') as f: - return f.read() - - def text(self, encoding=None, errors='strict'): - r"""Legacy function to read text. - - Converts all newline sequences to ``\n``. - """ - warnings.warn( - ".text is deprecated; use read_text", - DeprecationWarning, - stacklevel=2, - ) - return U_NEWLINE.sub('\n', self.read_text(encoding, errors)) - - def write_text( - self, text, encoding=None, errors='strict', linesep=os.linesep, append=False - ): - r"""Write the given text to this file. - - The default behavior is to overwrite any existing file; - to append instead, use the `append=True` keyword argument. - - There are two differences between :meth:`write_text` and - :meth:`write_bytes`: newline handling and Unicode handling. - See below. - - Parameters: - - `text` - str/bytes - The text to be written. - - `encoding` - str - The text encoding used. - - `errors` - str - How to handle Unicode encoding errors. - Default is ``'strict'``. See ``help(unicode.encode)`` for the - options. Ignored if `text` isn't a Unicode string. - - `linesep` - keyword argument - str/unicode - The sequence of - characters to be used to mark end-of-line. The default is - :data:`os.linesep`. Specify ``None`` to - use newlines unmodified. - - `append` - keyword argument - bool - Specifies what to do if - the file already exists (``True``: append to the end of it; - ``False``: overwrite it). The default is ``False``. - - - --- Newline handling. - - ``write_text()`` converts all standard end-of-line sequences - (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default - end-of-line sequence (see :data:`os.linesep`; on Windows, for example, - the end-of-line marker is ``'\r\n'``). - - To override the platform's default, pass the `linesep=` - keyword argument. To preserve the newlines as-is, pass - ``linesep=None``. - - This handling applies to Unicode text and bytes, except - with Unicode, additional non-ASCII newlines are recognized: - ``\x85``, ``\r\x85``, and ``\u2028``. - - --- Unicode - - If `text` isn't Unicode, then apart from newline handling, the - bytes are written verbatim to the file. The `encoding` and - `errors` arguments are not used and must be omitted. - - If `text` is Unicode, it is first converted to :func:`bytes` using the - specified `encoding` (or the default encoding if `encoding` - isn't specified). The `errors` argument applies only to this - conversion. - """ - if isinstance(text, str): - if linesep is not None: - text = U_NEWLINE.sub(linesep, text) - bytes = text.encode(encoding or sys.getdefaultencoding(), errors) - else: - warnings.warn( - "Writing bytes in write_text is deprecated", - DeprecationWarning, - stacklevel=1, - ) - assert encoding is None - if linesep is not None: - text = B_NEWLINE.sub(linesep.encode(), text) - bytes = text - self.write_bytes(bytes, append=append) - - def lines(self, encoding=None, errors=None, retain=True): - r"""Open this file, read all lines, return them in a list. - - Optional arguments: - `encoding` - The Unicode encoding (or character set) of - the file. The default is ``None``, meaning use - ``locale.getpreferredencoding()``. - `errors` - How to handle Unicode errors; see - `open <https://docs.python.org/3/library/functions.html#open>`_ - for the options. Default is ``None`` meaning "strict". - `retain` - If ``True`` (default), retain newline characters, - but translate all newline - characters to ``\n``. If ``False``, newline characters are - omitted. - - .. seealso:: :meth:`text` - """ - text = U_NEWLINE.sub('\n', self.read_text(encoding, errors)) - return text.splitlines(retain) - - def write_lines( - self, - lines, - encoding=None, - errors='strict', - linesep=_default_linesep, - append=False, - ): - r"""Write the given lines of text to this file. - - By default this overwrites any existing file at this path. - - This puts a platform-specific newline sequence on every line. - See `linesep` below. - - `lines` - A list of strings. - - `encoding` - A Unicode encoding to use. This applies only if - `lines` contains any Unicode strings. - - `errors` - How to handle errors in Unicode encoding. This - also applies only to Unicode strings. - - linesep - (deprecated) The desired line-ending. This line-ending is - applied to every line. If a line already has any - standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``, - ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will - be stripped off and this will be used instead. The - default is os.linesep, which is platform-dependent - (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.). - Specify ``None`` to write the lines as-is, like - ``.writelines`` on a file object. - - Use the keyword argument ``append=True`` to append lines to the - file. The default is to overwrite the file. - """ - mode = 'a' if append else 'w' - with self.open(mode, encoding=encoding, errors=errors, newline='') as f: - f.writelines(self._replace_linesep(lines, linesep)) - - @staticmethod - def _replace_linesep(lines, linesep): - if linesep != _default_linesep: - warnings.warn("linesep is deprecated", DeprecationWarning, stacklevel=3) - else: - linesep = os.linesep - if linesep is None: - return lines - - return (line + linesep for line in _strip_newlines(lines)) - - def read_md5(self): - """Calculate the md5 hash for this file. - - This reads through the entire file. - - .. seealso:: :meth:`read_hash` - """ - return self.read_hash('md5') - - def _hash(self, hash_name): - """Returns a hash object for the file at the current path. - - `hash_name` should be a hash algo name (such as ``'md5'`` - or ``'sha1'``) that's available in the :mod:`hashlib` module. - """ - m = hashlib.new(hash_name) - for chunk in self.chunks(8192, mode="rb"): - m.update(chunk) - return m - - def read_hash(self, hash_name): - """Calculate given hash for this file. - - List of supported hashes can be obtained from :mod:`hashlib` package. - This reads the entire file. - - .. seealso:: :meth:`hashlib.hash.digest` - """ - return self._hash(hash_name).digest() - - def read_hexhash(self, hash_name): - """Calculate given hash for this file, returning hexdigest. - - List of supported hashes can be obtained from :mod:`hashlib` package. - This reads the entire file. - - .. seealso:: :meth:`hashlib.hash.hexdigest` - """ - return self._hash(hash_name).hexdigest() - - # --- Methods for querying the filesystem. - # N.B. On some platforms, the os.path functions may be implemented in C - # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get - # bound. Playing it safe and wrapping them all in method calls. - - def isabs(self): - """ - >>> Path('.').isabs() - False - - .. seealso:: :func:`os.path.isabs` - """ - return self.module.isabs(self) - - def exists(self): - """.. seealso:: :func:`os.path.exists`""" - return self.module.exists(self) - - def isdir(self): - """.. seealso:: :func:`os.path.isdir`""" - return self.module.isdir(self) - - def isfile(self): - """.. seealso:: :func:`os.path.isfile`""" - return self.module.isfile(self) - - def islink(self): - """.. seealso:: :func:`os.path.islink`""" - return self.module.islink(self) - - def ismount(self): - """ - >>> Path('.').ismount() - False - - .. seealso:: :func:`os.path.ismount` - """ - return self.module.ismount(self) - - def samefile(self, other): - """.. seealso:: :func:`os.path.samefile`""" - return self.module.samefile(self, other) - - def getatime(self): - """.. seealso:: :attr:`atime`, :func:`os.path.getatime`""" - return self.module.getatime(self) - - def set_atime(self, value): - mtime_ns = self.stat().st_atime_ns - self.utime(ns=(_make_timestamp_ns(value), mtime_ns)) - - atime = property( - getatime, - set_atime, - None, - """ - Last access time of the file. - - >>> Path('.').atime > 0 - True - - Allows setting: - - >>> some_file = Path(getfixture('tmp_path')).joinpath('file.txt').touch() - >>> MST = datetime.timezone(datetime.timedelta(hours=-7)) - >>> some_file.atime = datetime.datetime(1976, 5, 7, 10, tzinfo=MST) - >>> some_file.atime - 200336400.0 - - .. seealso:: :meth:`getatime`, :func:`os.path.getatime` - """, - ) - - def getmtime(self): - """.. seealso:: :attr:`mtime`, :func:`os.path.getmtime`""" - return self.module.getmtime(self) - - def set_mtime(self, value): - atime_ns = self.stat().st_atime_ns - self.utime(ns=(atime_ns, _make_timestamp_ns(value))) - - mtime = property( - getmtime, - set_mtime, - None, - """ - Last modified time of the file. - - Allows setting: - - >>> some_file = Path(getfixture('tmp_path')).joinpath('file.txt').touch() - >>> MST = datetime.timezone(datetime.timedelta(hours=-7)) - >>> some_file.mtime = datetime.datetime(1976, 5, 7, 10, tzinfo=MST) - >>> some_file.mtime - 200336400.0 - - .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime` - """, - ) - - def getctime(self): - """.. seealso:: :attr:`ctime`, :func:`os.path.getctime`""" - return self.module.getctime(self) - - ctime = property( - getctime, - None, - None, - """ Creation time of the file. - - .. seealso:: :meth:`getctime`, :func:`os.path.getctime` - """, - ) - - def getsize(self): - """.. seealso:: :attr:`size`, :func:`os.path.getsize`""" - return self.module.getsize(self) - - size = property( - getsize, - None, - None, - """ Size of the file, in bytes. - - .. seealso:: :meth:`getsize`, :func:`os.path.getsize` - """, - ) - - @property - def permissions(self) -> masks.Permissions: - """ - Permissions. - - >>> perms = Path('.').permissions - >>> isinstance(perms, int) - True - >>> set(perms.symbolic) <= set('rwx-') - True - >>> perms.symbolic - 'r...' - """ - return masks.Permissions(self.stat().st_mode) - - def access(self, *args, **kwargs): - """ - Return does the real user have access to this path. - - >>> Path('.').access(os.F_OK) - True - - .. seealso:: :func:`os.access` - """ - return os.access(self, *args, **kwargs) - - def stat(self): - """ - Perform a ``stat()`` system call on this path. - - >>> Path('.').stat() - os.stat_result(...) - - .. seealso:: :meth:`lstat`, :func:`os.stat` - """ - return os.stat(self) - - def lstat(self): - """ - Like :meth:`stat`, but do not follow symbolic links. - - >>> Path('.').lstat() == Path('.').stat() - True - - .. seealso:: :meth:`stat`, :func:`os.lstat` - """ - return os.lstat(self) - - def __get_owner_windows(self): # pragma: nocover - r""" - Return the name of the owner of this file or directory. Follow - symbolic links. - - Return a name of the form ``DOMAIN\User Name``; may be a group. - - .. seealso:: :attr:`owner` - """ - desc = win32security.GetFileSecurity( - self, win32security.OWNER_SECURITY_INFORMATION - ) - sid = desc.GetSecurityDescriptorOwner() - account, domain, typecode = win32security.LookupAccountSid(None, sid) - return domain + '\\' + account - - def __get_owner_unix(self): # pragma: nocover - """ - Return the name of the owner of this file or directory. Follow - symbolic links. - - .. seealso:: :attr:`owner` - """ - st = self.stat() - return pwd.getpwuid(st.st_uid).pw_name - - def __get_owner_not_implemented(self): # pragma: nocover - raise NotImplementedError("Ownership not available on this platform.") - - get_owner = ( - __get_owner_windows - if 'win32security' in globals() - else __get_owner_unix - if 'pwd' in globals() - else __get_owner_not_implemented - ) - - owner = property( - get_owner, - None, - None, - """ Name of the owner of this file or directory. - - .. seealso:: :meth:`get_owner`""", - ) - - if hasattr(os, 'statvfs'): - - def statvfs(self): - """Perform a ``statvfs()`` system call on this path. - - .. seealso:: :func:`os.statvfs` - """ - return os.statvfs(self) - - if hasattr(os, 'pathconf'): - - def pathconf(self, name): - """.. seealso:: :func:`os.pathconf`""" - return os.pathconf(self, name) - - # - # --- Modifying operations on files and directories - - def utime(self, *args, **kwargs): - """Set the access and modified times of this file. - - .. seealso:: :func:`os.utime` - """ - os.utime(self, *args, **kwargs) - return self - - def chmod(self, mode): - """ - Set the mode. May be the new mode (os.chmod behavior) or a `symbolic - mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_. - - >>> a_file = Path(getfixture('tmp_path')).joinpath('afile.txt').touch() - >>> a_file.chmod(0o700) - Path(... - >>> a_file.chmod('u+x') - Path(... - - .. seealso:: :func:`os.chmod` - """ - if isinstance(mode, str): - mask = masks.compound(mode) - mode = mask(self.stat().st_mode) - os.chmod(self, mode) - return self - - if hasattr(os, 'chown'): - - def chown(self, uid=-1, gid=-1): - """ - Change the owner and group by names or numbers. - - .. seealso:: :func:`os.chown` - """ - - def resolve_uid(uid): - return uid if isinstance(uid, int) else pwd.getpwnam(uid).pw_uid - - def resolve_gid(gid): - return gid if isinstance(gid, int) else grp.getgrnam(gid).gr_gid - - os.chown(self, resolve_uid(uid), resolve_gid(gid)) - return self - - def rename(self, new): - """.. seealso:: :func:`os.rename`""" - os.rename(self, new) - return self._next_class(new) - - def renames(self, new): - """.. seealso:: :func:`os.renames`""" - os.renames(self, new) - return self._next_class(new) - - # - # --- Create/delete operations on directories - - def mkdir(self, mode=0o777): - """.. seealso:: :func:`os.mkdir`""" - os.mkdir(self, mode) - return self - - def mkdir_p(self, mode=0o777): - """Like :meth:`mkdir`, but does not raise an exception if the - directory already exists.""" - with contextlib.suppress(FileExistsError): - self.mkdir(mode) - return self - - def makedirs(self, mode=0o777): - """.. seealso:: :func:`os.makedirs`""" - os.makedirs(self, mode) - return self - - def makedirs_p(self, mode=0o777): - """Like :meth:`makedirs`, but does not raise an exception if the - directory already exists.""" - with contextlib.suppress(FileExistsError): - self.makedirs(mode) - return self - - def rmdir(self): - """.. seealso:: :func:`os.rmdir`""" - os.rmdir(self) - return self - - def rmdir_p(self): - """Like :meth:`rmdir`, but does not raise an exception if the - directory is not empty or does not exist.""" - suppressed = FileNotFoundError, FileExistsError, DirectoryNotEmpty - with contextlib.suppress(suppressed): - with DirectoryNotEmpty.translate(): - self.rmdir() - return self - - def removedirs(self): - """.. seealso:: :func:`os.removedirs`""" - os.removedirs(self) - return self - - def removedirs_p(self): - """Like :meth:`removedirs`, but does not raise an exception if the - directory is not empty or does not exist.""" - with contextlib.suppress(FileExistsError, DirectoryNotEmpty): - with DirectoryNotEmpty.translate(): - self.removedirs() - return self - - # --- Modifying operations on files - - def touch(self): - """Set the access/modified times of this file to the current time. - Create the file if it does not exist. - """ - os.close(os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)) - os.utime(self, None) - return self - - def remove(self): - """.. seealso:: :func:`os.remove`""" - os.remove(self) - return self - - def remove_p(self): - """Like :meth:`remove`, but does not raise an exception if the - file does not exist.""" - with contextlib.suppress(FileNotFoundError): - self.unlink() - return self - - unlink = remove - unlink_p = remove_p - - # --- Links - - def link(self, newpath): - """Create a hard link at `newpath`, pointing to this file. - - .. seealso:: :func:`os.link` - """ - os.link(self, newpath) - return self._next_class(newpath) - - def symlink(self, newlink=None): - """Create a symbolic link at `newlink`, pointing here. - - If newlink is not supplied, the symbolic link will assume - the name self.basename(), creating the link in the cwd. - - .. seealso:: :func:`os.symlink` - """ - if newlink is None: - newlink = self.basename() - os.symlink(self, newlink) - return self._next_class(newlink) - - def readlink(self): - """Return the path to which this symbolic link points. - - The result may be an absolute or a relative path. - - .. seealso:: :meth:`readlinkabs`, :func:`os.readlink` - """ - return self._next_class(os.readlink(self)) - - def readlinkabs(self): - """Return the path to which this symbolic link points. - - The result is always an absolute path. - - .. seealso:: :meth:`readlink`, :func:`os.readlink` - """ - p = self.readlink() - return p if p.isabs() else (self.parent / p).abspath() - - # High-level functions from shutil - # These functions will be bound to the instance such that - # Path(name).copy(target) will invoke shutil.copy(name, target) - - copyfile = shutil.copyfile - copymode = shutil.copymode - copystat = shutil.copystat - copy = shutil.copy - copy2 = shutil.copy2 - copytree = shutil.copytree - if hasattr(shutil, 'move'): - move = shutil.move - rmtree = shutil.rmtree - - def rmtree_p(self): - """Like :meth:`rmtree`, but does not raise an exception if the - directory does not exist.""" - with contextlib.suppress(FileNotFoundError): - self.rmtree() - return self - - def chdir(self): - """.. seealso:: :func:`os.chdir`""" - os.chdir(self) - - cd = chdir - - def merge_tree( - self, - dst, - symlinks=False, - *, - copy_function=shutil.copy2, - ignore=lambda dir, contents: [], - ): - """ - Copy entire contents of self to dst, overwriting existing - contents in dst with those in self. - - Pass ``symlinks=True`` to copy symbolic links as links. - - Accepts a ``copy_function``, similar to copytree. - - To avoid overwriting newer files, supply a copy function - wrapped in ``only_newer``. For example:: - - src.merge_tree(dst, copy_function=only_newer(shutil.copy2)) - """ - dst = self._next_class(dst) - dst.makedirs_p() - - sources = self.listdir() - _ignored = ignore(self, [item.name for item in sources]) - - def ignored(item): - return item.name in _ignored - - for source in itertools.filterfalse(ignored, sources): - dest = dst / source.name - if symlinks and source.islink(): - target = source.readlink() - target.symlink(dest) - elif source.isdir(): - source.merge_tree( - dest, - symlinks=symlinks, - copy_function=copy_function, - ignore=ignore, - ) - else: - copy_function(source, dest) - - self.copystat(dst) - - # - # --- Special stuff from os - - if hasattr(os, 'chroot'): - - def chroot(self): # pragma: nocover - """.. seealso:: :func:`os.chroot`""" - os.chroot(self) - - if hasattr(os, 'startfile'): - - def startfile(self, *args, **kwargs): # pragma: nocover - """.. seealso:: :func:`os.startfile`""" - os.startfile(self, *args, **kwargs) - return self - - # in-place re-writing, courtesy of Martijn Pieters - # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ - @contextlib.contextmanager - def in_place( - self, - mode='r', - buffering=-1, - encoding=None, - errors=None, - newline=None, - backup_extension=None, - ): - """ - A context in which a file may be re-written in-place with - new content. - - Yields a tuple of :samp:`({readable}, {writable})` file - objects, where `writable` replaces `readable`. - - If an exception occurs, the old file is restored, removing the - written data. - - Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only - read-only-modes are allowed. A :exc:`ValueError` is raised - on invalid modes. - - For example, to add line numbers to a file:: - - p = Path(filename) - assert p.isfile() - with p.in_place() as (reader, writer): - for number, line in enumerate(reader, 1): - writer.write('{0:3}: '.format(number))) - writer.write(line) - - Thereafter, the file at `filename` will have line numbers in it. - """ - if set(mode).intersection('wa+'): - raise ValueError('Only read-only file modes can be used') - - # move existing file to backup, create new file with same permissions - # borrowed extensively from the fileinput module - backup_fn = self + (backup_extension or os.extsep + 'bak') - backup_fn.remove_p() - self.rename(backup_fn) - readable = open( - backup_fn, - mode, - buffering=buffering, - encoding=encoding, - errors=errors, - newline=newline, - ) - try: - perm = os.fstat(readable.fileno()).st_mode - except OSError: - writable = self.open( - 'w' + mode.replace('r', ''), - buffering=buffering, - encoding=encoding, - errors=errors, - newline=newline, - ) - else: - os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC - os_mode |= getattr(os, 'O_BINARY', 0) - fd = os.open(self, os_mode, perm) - writable = open( - fd, - "w" + mode.replace('r', ''), - buffering=buffering, - encoding=encoding, - errors=errors, - newline=newline, - ) - with contextlib.suppress(OSError, AttributeError): - self.chmod(perm) - try: - yield readable, writable - except Exception: - # move backup back - readable.close() - writable.close() - self.remove_p() - backup_fn.rename(self) - raise - else: - readable.close() - writable.close() - finally: - backup_fn.remove_p() - - @classes.ClassProperty - @classmethod - def special(cls): - """ - Return a SpecialResolver object suitable referencing a suitable - directory for the relevant platform for the given - type of content. - - For example, to get a user config directory, invoke: - - dir = Path.special().user.config - - Uses the `appdirs - <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve - the paths in a platform-friendly way. - - To create a config directory for 'My App', consider: - - dir = Path.special("My App").user.config.makedirs_p() - - If the ``appdirs`` module is not installed, invocation - of special will raise an ImportError. - """ - return functools.partial(SpecialResolver, cls) - - -class DirectoryNotEmpty(OSError): - @staticmethod - @contextlib.contextmanager - def translate(): - try: - yield - except OSError as exc: - if exc.errno == errno.ENOTEMPTY: - raise DirectoryNotEmpty(*exc.args) from exc - raise - - -def only_newer(copy_func): - """ - Wrap a copy function (like shutil.copy2) to return - the dst if it's newer than the source. - """ - - @functools.wraps(copy_func) - def wrapper(src, dst, *args, **kwargs): - is_newer_dst = dst.exists() and dst.getmtime() >= src.getmtime() - if is_newer_dst: - return dst - return copy_func(src, dst, *args, **kwargs) - - return wrapper - - -class ExtantPath(Path): - """ - >>> ExtantPath('.') - ExtantPath('.') - >>> ExtantPath('does-not-exist') - Traceback (most recent call last): - OSError: does-not-exist does not exist. - """ - - def _validate(self): - if not self.exists(): - raise OSError(f"{self} does not exist.") - - -class ExtantFile(Path): - """ - >>> ExtantFile('.') - Traceback (most recent call last): - FileNotFoundError: . does not exist as a file. - >>> ExtantFile('does-not-exist') - Traceback (most recent call last): - FileNotFoundError: does-not-exist does not exist as a file. - """ - - def _validate(self): - if not self.isfile(): - raise FileNotFoundError(f"{self} does not exist as a file.") - - -class SpecialResolver: - class ResolverScope: - def __init__(self, paths, scope): - self.paths = paths - self.scope = scope - - def __getattr__(self, class_): - return self.paths.get_dir(self.scope, class_) - - def __init__(self, path_class, *args, **kwargs): - appdirs = importlib.import_module('appdirs') - - vars(self).update( - path_class=path_class, wrapper=appdirs.AppDirs(*args, **kwargs) - ) - - def __getattr__(self, scope): - return self.ResolverScope(self, scope) - - def get_dir(self, scope, class_): - """ - Return the callable function from appdirs, but with the - result wrapped in self.path_class - """ - prop_name = f'{scope}_{class_}_dir' - value = getattr(self.wrapper, prop_name) - MultiPath = Multi.for_class(self.path_class) - return MultiPath.detect(value) - - -class Multi: - """ - A mix-in for a Path which may contain multiple Path separated by pathsep. - """ - - @classmethod - def for_class(cls, path_cls): - name = 'Multi' + path_cls.__name__ - return type(name, (cls, path_cls), {}) - - @classmethod - def detect(cls, input): - if os.pathsep not in input: - cls = cls._next_class - return cls(input) - - def __iter__(self): - return iter(map(self._next_class, self.split(os.pathsep))) - - @classes.ClassProperty - @classmethod - def _next_class(cls): - """ - Multi-subclasses should use the parent class - """ - return next(class_ for class_ in cls.__mro__ if not issubclass(class_, Multi)) - - -class TempDir(Path): - """ - A temporary directory via :func:`tempfile.mkdtemp`, and - constructed with the same parameters that you can use - as a context manager. - - For example: - - >>> with TempDir() as d: - ... d.isdir() and isinstance(d, Path) - True - - The directory is deleted automatically. - - >>> d.isdir() - False - - .. seealso:: :func:`tempfile.mkdtemp` - """ - - @classes.ClassProperty - @classmethod - def _next_class(cls): - return Path - - def __new__(cls, *args, **kwargs): - dirname = tempfile.mkdtemp(*args, **kwargs) - return super().__new__(cls, dirname) - - def __init__(self, *args, **kwargs): - pass - - def __enter__(self): - # TempDir should return a Path version of itself and not itself - # so that a second context manager does not create a second - # temporary directory, but rather changes CWD to the location - # of the temporary directory. - return self._next_class(self) - - def __exit__(self, exc_type, exc_value, traceback): - self.rmtree() - - -class Handlers: - def strict(msg): - raise - - def warn(msg): - warnings.warn(msg, TreeWalkWarning) - - def ignore(msg): - pass - - @classmethod - def _resolve(cls, param): - if not callable(param) and param not in vars(Handlers): - raise ValueError("invalid errors parameter") - return vars(cls).get(param, param) diff --git a/contrib/python/path/path/classes.py b/contrib/python/path/path/classes.py deleted file mode 100644 index b6101d0a7e2..00000000000 --- a/contrib/python/path/path/classes.py +++ /dev/null @@ -1,27 +0,0 @@ -import functools - - -class ClassProperty(property): - def __get__(self, cls, owner): - return self.fget.__get__(None, owner)() - - -class multimethod: - """ - Acts like a classmethod when invoked from the class and like an - instancemethod when invoked from the instance. - """ - - def __init__(self, func): - self.func = func - - def __get__(self, instance, owner): - """ - If called on an instance, pass the instance as the first - argument. - """ - return ( - functools.partial(self.func, owner) - if instance is None - else functools.partial(self.func, owner, instance) - ) diff --git a/contrib/python/path/path/masks.py b/contrib/python/path/path/masks.py deleted file mode 100644 index e7037e96033..00000000000 --- a/contrib/python/path/path/masks.py +++ /dev/null @@ -1,159 +0,0 @@ -import re -import functools -import operator -import itertools - - -# from jaraco.functools -def compose(*funcs): - compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs)) # noqa - return functools.reduce(compose_two, funcs) - - -# from jaraco.structures.binary -def gen_bit_values(number): - """ - Return a zero or one for each bit of a numeric value up to the most - significant 1 bit, beginning with the least significant bit. - - >>> list(gen_bit_values(16)) - [0, 0, 0, 0, 1] - """ - digits = bin(number)[2:] - return map(int, reversed(digits)) - - -# from more_itertools -def padded(iterable, fillvalue=None, n=None, next_multiple=False): - """Yield the elements from *iterable*, followed by *fillvalue*, such that - at least *n* items are emitted. - - >>> list(padded([1, 2, 3], '?', 5)) - [1, 2, 3, '?', '?'] - - If *next_multiple* is ``True``, *fillvalue* will be emitted until the - number of items emitted is a multiple of *n*:: - - >>> list(padded([1, 2, 3, 4], n=3, next_multiple=True)) - [1, 2, 3, 4, None, None] - - If *n* is ``None``, *fillvalue* will be emitted indefinitely. - - """ - it = iter(iterable) - if n is None: - yield from itertools.chain(it, itertools.repeat(fillvalue)) - elif n < 1: - raise ValueError('n must be at least 1') - else: - item_count = 0 - for item in it: - yield item - item_count += 1 - - remaining = (n - item_count) % n if next_multiple else n - item_count - for _ in range(remaining): - yield fillvalue - - -def compound(mode): - """ - Support multiple, comma-separated Unix chmod symbolic modes. - - >>> oct(compound('a=r,u+w')(0)) - '0o644' - """ - return compose(*map(simple, reversed(mode.split(',')))) - - -def simple(mode): - """ - Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function - suitable for applying to a mask to affect that change. - - >>> mask = simple('ugo+rwx') - >>> mask(0o554) == 0o777 - True - - >>> simple('go-x')(0o777) == 0o766 - True - - >>> simple('o-x')(0o445) == 0o444 - True - - >>> simple('a+x')(0) == 0o111 - True - - >>> simple('a=rw')(0o057) == 0o666 - True - - >>> simple('u=x')(0o666) == 0o166 - True - - >>> simple('g=')(0o157) == 0o107 - True - - >>> simple('gobbledeegook') - Traceback (most recent call last): - ValueError: ('Unrecognized symbolic mode', 'gobbledeegook') - """ - # parse the symbolic mode - parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode) - if not parsed: - raise ValueError("Unrecognized symbolic mode", mode) - - # generate a mask representing the specified permission - spec_map = dict(r=4, w=2, x=1) - specs = (spec_map[perm] for perm in parsed.group('what')) - spec = functools.reduce(operator.or_, specs, 0) - - # now apply spec to each subject in who - shift_map = dict(u=6, g=3, o=0) - who = parsed.group('who').replace('a', 'ugo') - masks = (spec << shift_map[subj] for subj in who) - mask = functools.reduce(operator.or_, masks) - - op = parsed.group('op') - - # if op is -, invert the mask - if op == '-': - mask ^= 0o777 - - # if op is =, retain extant values for unreferenced subjects - if op == '=': - masks = (0o7 << shift_map[subj] for subj in who) - retain = functools.reduce(operator.or_, masks) ^ 0o777 - - op_map = { - '+': operator.or_, - '-': operator.and_, - '=': lambda mask, target: target & retain ^ mask, - } - return functools.partial(op_map[op], mask) - - -class Permissions(int): - """ - >>> perms = Permissions(0o764) - >>> oct(perms) - '0o764' - >>> perms.symbolic - 'rwxrw-r--' - >>> str(perms) - 'rwxrw-r--' - >>> str(Permissions(0o222)) - '-w--w--w-' - """ - - @property - def symbolic(self): - return ''.join( - ['-', val][bit] for val, bit in zip(itertools.cycle('rwx'), self.bits) - ) - - @property - def bits(self): - return reversed(tuple(padded(gen_bit_values(self), 0, n=9))) - - def __str__(self): - return self.symbolic diff --git a/contrib/python/path/path/matchers.py b/contrib/python/path/path/matchers.py deleted file mode 100644 index 63ca218a804..00000000000 --- a/contrib/python/path/path/matchers.py +++ /dev/null @@ -1,59 +0,0 @@ -import ntpath -import fnmatch - - -def load(param): - """ - If the supplied parameter is a string, assume it's a simple - pattern. - """ - return ( - Pattern(param) - if isinstance(param, str) - else param - if param is not None - else Null() - ) - - -class Base: - pass - - -class Null(Base): - def __call__(self, path): - return True - - -class Pattern(Base): - def __init__(self, pattern): - self.pattern = pattern - - def get_pattern(self, normcase): - try: - return self._pattern - except AttributeError: - pass - self._pattern = normcase(self.pattern) - return self._pattern - - def __call__(self, path): - normcase = getattr(self, 'normcase', path.module.normcase) - pattern = self.get_pattern(normcase) - return fnmatch.fnmatchcase(normcase(path.name), pattern) - - -class CaseInsensitive(Pattern): - """ - A Pattern with a ``'normcase'`` property, suitable for passing to - :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`, - :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive. - - For example, to get all files ending in .py, .Py, .pY, or .PY in the - current directory:: - - from path import Path, matchers - Path('.').files(matchers.CaseInsensitive('*.py')) - """ - - normcase = staticmethod(ntpath.normcase) diff --git a/contrib/python/path/path/py.typed b/contrib/python/path/path/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 --- a/contrib/python/path/path/py.typed +++ /dev/null diff --git a/contrib/python/path/ya.make b/contrib/python/path/ya.make deleted file mode 100644 index aa9cb072502..00000000000 --- a/contrib/python/path/ya.make +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by devtools/yamaker (pypi). - -PY3_LIBRARY() - -VERSION(16.7.1) - -LICENSE(MIT) - -NO_LINT() - -PY_SRCS( - TOP_LEVEL - path/__init__.py - path/__init__.pyi - path/classes.py - path/classes.pyi - path/masks.py - path/masks.pyi - path/matchers.py - path/matchers.pyi -) - -RESOURCE_FILES( - PREFIX contrib/python/path/ - .dist-info/METADATA - .dist-info/top_level.txt - path/py.typed -) - -END() |
