scanner code v1 & new libs
This commit is contained in:
1
lib/pathlib-1.0.1.dist-info/INSTALLER
Normal file
1
lib/pathlib-1.0.1.dist-info/INSTALLER
Normal file
@@ -0,0 +1 @@
|
||||
pip
|
||||
19
lib/pathlib-1.0.1.dist-info/LICENSE.txt
Normal file
19
lib/pathlib-1.0.1.dist-info/LICENSE.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012-2014 Antoine Pitrou and contributors
|
||||
|
||||
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.
|
||||
180
lib/pathlib-1.0.1.dist-info/METADATA
Normal file
180
lib/pathlib-1.0.1.dist-info/METADATA
Normal file
@@ -0,0 +1,180 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: pathlib
|
||||
Version: 1.0.1
|
||||
Summary: Object-oriented filesystem paths
|
||||
Home-page: https://pathlib.readthedocs.org/
|
||||
Download-URL: https://pypi.python.org/pypi/pathlib/
|
||||
Author: Antoine Pitrou
|
||||
Author-email: solipsis@pitrou.net
|
||||
License: MIT License
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: System :: Filesystems
|
||||
License-File: LICENSE.txt
|
||||
|
||||
pathlib offers a set of classes to handle filesystem paths. It offers the
|
||||
following advantages over using string objects:
|
||||
|
||||
* No more cumbersome use of os and os.path functions. Everything can be
|
||||
done easily through operators, attribute accesses, and method calls.
|
||||
|
||||
* Embodies the semantics of different path types. For example, comparing
|
||||
Windows paths ignores casing.
|
||||
|
||||
* Well-defined semantics, eliminating any warts or ambiguities (forward vs.
|
||||
backward slashes, etc.).
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Python 3.2 or later is recommended, but pathlib is also usable with Python 2.7
|
||||
and 2.6.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
In Python 3.4, pathlib is now part of the standard library. For Python 3.3
|
||||
and earlier, ``easy_install pathlib`` or ``pip install pathlib`` should do
|
||||
the trick.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Importing the module classes::
|
||||
|
||||
>>> from pathlib import *
|
||||
|
||||
Listing Python source files in a directory::
|
||||
|
||||
>>> list(p.glob('*.py'))
|
||||
[PosixPath('test_pathlib.py'), PosixPath('setup.py'),
|
||||
PosixPath('pathlib.py')]
|
||||
|
||||
Navigating inside a directory tree::
|
||||
|
||||
>>> p = Path('/etc')
|
||||
>>> q = p / 'init.d' / 'reboot'
|
||||
>>> q
|
||||
PosixPath('/etc/init.d/reboot')
|
||||
>>> q.resolve()
|
||||
PosixPath('/etc/rc.d/init.d/halt')
|
||||
|
||||
Querying path properties::
|
||||
|
||||
>>> q.exists()
|
||||
True
|
||||
>>> q.is_dir()
|
||||
False
|
||||
|
||||
Opening a file::
|
||||
|
||||
>>> with q.open() as f: f.readline()
|
||||
...
|
||||
'#!/bin/bash\n'
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The full documentation can be read at `Read the Docs
|
||||
<https://pathlib.readthedocs.org/>`_.
|
||||
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Main development now takes place in the Python standard library: see
|
||||
the `Python developer's guide <http://docs.python.org/devguide/>`_, and
|
||||
report issues on the `Python bug tracker <http://bugs.python.org/>`_.
|
||||
|
||||
However, if you find an issue specific to prior versions of Python
|
||||
(such as 2.7 or 3.2), you can post an issue on the
|
||||
`BitBucket project page <https://bitbucket.org/pitrou/pathlib/>`_.
|
||||
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
Version 1.0.1
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
- Pull requestion #4: Python 2.6 compatibility by eevee.
|
||||
|
||||
Version 1.0
|
||||
^^^^^^^^^^^
|
||||
|
||||
This version brings ``pathlib`` up to date with the official Python 3.4
|
||||
release, and also fixes a couple of 2.7-specific issues.
|
||||
|
||||
- Python issue #20765: Add missing documentation for PurePath.with_name()
|
||||
and PurePath.with_suffix().
|
||||
- Fix test_mkdir_parents when the working directory has additional bits
|
||||
set (such as the setgid or sticky bits).
|
||||
- Python issue #20111: pathlib.Path.with_suffix() now sanity checks the
|
||||
given suffix.
|
||||
- Python issue #19918: Fix PurePath.relative_to() under Windows.
|
||||
- Python issue #19921: When Path.mkdir() is called with parents=True, any
|
||||
missing parent is created with the default permissions, ignoring the mode
|
||||
argument (mimicking the POSIX "mkdir -p" command).
|
||||
- Python issue #19887: Improve the Path.resolve() algorithm to support
|
||||
certain symlink chains.
|
||||
- Make pathlib usable under Python 2.7 with unicode pathnames (only pure
|
||||
ASCII, though).
|
||||
- Issue #21: fix TypeError under Python 2.7 when using new division.
|
||||
- Add tox support for easier testing.
|
||||
|
||||
Version 0.97
|
||||
^^^^^^^^^^^^
|
||||
|
||||
This version brings ``pathlib`` up to date with the final API specified
|
||||
in :pep:`428`. The changes are too long to list here, it is recommended
|
||||
to read the `documentation <https://pathlib.readthedocs.org/>`_.
|
||||
|
||||
.. warning::
|
||||
The API in this version is partially incompatible with pathlib 0.8 and
|
||||
earlier. Be sure to check your code for possible breakage!
|
||||
|
||||
Version 0.8
|
||||
^^^^^^^^^^^
|
||||
|
||||
- Add PurePath.name and PurePath.anchor.
|
||||
- Add Path.owner and Path.group.
|
||||
- Add Path.replace().
|
||||
- Add Path.as_uri().
|
||||
- Issue #10: when creating a file with Path.open(), don't set the executable
|
||||
bit.
|
||||
- Issue #11: fix comparisons with non-Path objects.
|
||||
|
||||
Version 0.7
|
||||
^^^^^^^^^^^
|
||||
|
||||
- Add '**' (recursive) patterns to Path.glob().
|
||||
- Fix openat() support after the API refactoring in Python 3.3 beta1.
|
||||
- Add a *target_is_directory* argument to Path.symlink_to()
|
||||
|
||||
Version 0.6
|
||||
^^^^^^^^^^^
|
||||
|
||||
- Add Path.is_file() and Path.is_symlink()
|
||||
- Add Path.glob() and Path.rglob()
|
||||
- Add PurePath.match()
|
||||
|
||||
Version 0.5
|
||||
^^^^^^^^^^^
|
||||
|
||||
- Add Path.mkdir().
|
||||
- Add Python 2.7 compatibility by Michele Lacchia.
|
||||
- Make parent() raise ValueError when the level is greater than the path
|
||||
length.
|
||||
|
||||
|
||||
9
lib/pathlib-1.0.1.dist-info/RECORD
Normal file
9
lib/pathlib-1.0.1.dist-info/RECORD
Normal file
@@ -0,0 +1,9 @@
|
||||
__pycache__/pathlib.cpython-314.pyc,,
|
||||
pathlib-1.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
pathlib-1.0.1.dist-info/LICENSE.txt,sha256=7FK0RgeHp9ofbbcLvp4eZJxz98sU7rrxDl25_n9-nNk,1080
|
||||
pathlib-1.0.1.dist-info/METADATA,sha256=NrN3I2q6qf7u2iD0kK0Or6JrsqSP-WXXK41ZAkQznuI,5149
|
||||
pathlib-1.0.1.dist-info/RECORD,,
|
||||
pathlib-1.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
pathlib-1.0.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
||||
pathlib-1.0.1.dist-info/top_level.txt,sha256=zhefsaNuASYaoZF-ELG3TPFn6S2cuB1fd8PlNFLrMh0,8
|
||||
pathlib.py,sha256=9MfboQTEcuscgm7ZALBMTDpLwD-2PCTSUyhlA2tcvqw,41481
|
||||
0
lib/pathlib-1.0.1.dist-info/REQUESTED
Normal file
0
lib/pathlib-1.0.1.dist-info/REQUESTED
Normal file
5
lib/pathlib-1.0.1.dist-info/WHEEL
Normal file
5
lib/pathlib-1.0.1.dist-info/WHEEL
Normal file
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.37.1)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
1
lib/pathlib-1.0.1.dist-info/top_level.txt
Normal file
1
lib/pathlib-1.0.1.dist-info/top_level.txt
Normal file
@@ -0,0 +1 @@
|
||||
pathlib
|
||||
1280
lib/pathlib.py
Normal file
1280
lib/pathlib.py
Normal file
File diff suppressed because it is too large
Load Diff
1
lib/pathspec-1.0.4.dist-info/INSTALLER
Normal file
1
lib/pathspec-1.0.4.dist-info/INSTALLER
Normal file
@@ -0,0 +1 @@
|
||||
pip
|
||||
356
lib/pathspec-1.0.4.dist-info/METADATA
Normal file
356
lib/pathspec-1.0.4.dist-info/METADATA
Normal file
@@ -0,0 +1,356 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: pathspec
|
||||
Version: 1.0.4
|
||||
Summary: Utility library for gitignore style pattern matching of file paths.
|
||||
Author-email: "Caleb P. Burns" <cpburnz@gmail.com>
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/x-rst
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Utilities
|
||||
License-File: LICENSE
|
||||
Requires-Dist: hyperscan >=0.7 ; extra == "hyperscan"
|
||||
Requires-Dist: typing-extensions >=4 ; extra == "optional"
|
||||
Requires-Dist: google-re2 >=1.1 ; extra == "re2"
|
||||
Requires-Dist: pytest >=9 ; extra == "tests"
|
||||
Requires-Dist: typing-extensions >=4.15 ; extra == "tests"
|
||||
Project-URL: Documentation, https://python-path-specification.readthedocs.io/en/latest/index.html
|
||||
Project-URL: Issue Tracker, https://github.com/cpburnz/python-pathspec/issues
|
||||
Project-URL: Source Code, https://github.com/cpburnz/python-pathspec
|
||||
Provides-Extra: hyperscan
|
||||
Provides-Extra: optional
|
||||
Provides-Extra: re2
|
||||
Provides-Extra: tests
|
||||
|
||||
|
||||
PathSpec
|
||||
========
|
||||
|
||||
*pathspec* is a utility library for pattern matching of file paths. So far this
|
||||
only includes Git's `gitignore`_ pattern matching.
|
||||
|
||||
.. _`gitignore`: http://git-scm.com/docs/gitignore
|
||||
|
||||
|
||||
Tutorial
|
||||
--------
|
||||
|
||||
Say you have a "Projects" directory and you want to back it up, but only
|
||||
certain files, and ignore others depending on certain conditions::
|
||||
|
||||
>>> from pathspec import PathSpec
|
||||
>>> # The gitignore-style patterns for files to select, but we're including
|
||||
>>> # instead of ignoring.
|
||||
>>> spec_text = """
|
||||
...
|
||||
... # This is a comment because the line begins with a hash: "#"
|
||||
...
|
||||
... # Include several project directories (and all descendants) relative to
|
||||
... # the current directory. To reference only a directory you must end with a
|
||||
... # slash: "/"
|
||||
... /project-a/
|
||||
... /project-b/
|
||||
... /project-c/
|
||||
...
|
||||
... # Patterns can be negated by prefixing with exclamation mark: "!"
|
||||
...
|
||||
... # Ignore temporary files beginning or ending with "~" and ending with
|
||||
... # ".swp".
|
||||
... !~*
|
||||
... !*~
|
||||
... !*.swp
|
||||
...
|
||||
... # These are python projects so ignore compiled python files from
|
||||
... # testing.
|
||||
... !*.pyc
|
||||
...
|
||||
... # Ignore the build directories but only directly under the project
|
||||
... # directories.
|
||||
... !/*/build/
|
||||
...
|
||||
... """
|
||||
|
||||
The ``PathSpec`` class provides an abstraction around pattern implementations,
|
||||
and we want to compile our patterns as "gitignore" patterns. You could call it a
|
||||
wrapper for a list of compiled patterns::
|
||||
|
||||
>>> spec = PathSpec.from_lines('gitignore', spec_text.splitlines())
|
||||
|
||||
If we wanted to manually compile the patterns, we can use the ``GitIgnoreBasicPattern``
|
||||
class directly. It is used in the background for "gitignore" which internally
|
||||
converts patterns to regular expressions::
|
||||
|
||||
>>> from pathspec.patterns.gitignore.basic import GitIgnoreBasicPattern
|
||||
>>> patterns = map(GitIgnoreBasicPattern, spec_text.splitlines())
|
||||
>>> spec = PathSpec(patterns)
|
||||
|
||||
``PathSpec.from_lines()`` is a class method which simplifies that.
|
||||
|
||||
If you want to load the patterns from file, you can pass the file object
|
||||
directly as well::
|
||||
|
||||
>>> with open('patterns.list', 'r') as fh:
|
||||
>>> spec = PathSpec.from_lines('gitignore', fh)
|
||||
|
||||
You can perform matching on a whole directory tree with::
|
||||
|
||||
>>> matches = set(spec.match_tree_files('path/to/directory'))
|
||||
|
||||
Or you can perform matching on a specific set of file paths with::
|
||||
|
||||
>>> matches = set(spec.match_files(file_paths))
|
||||
|
||||
Or check to see if an individual file matches::
|
||||
|
||||
>>> is_matched = spec.match_file(file_path)
|
||||
|
||||
There's actually two implementations of "gitignore". The basic implementation is
|
||||
used by ``PathSpec`` and follows patterns as documented by `gitignore`_.
|
||||
However, Git's behavior differs from the documented patterns. There's some
|
||||
edge-cases, and in particular, Git allows including files from excluded
|
||||
directories which appears to contradict the documentation. ``GitIgnoreSpec``
|
||||
handles these cases to more closely replicate Git's behavior::
|
||||
|
||||
>>> from pathspec import GitIgnoreSpec
|
||||
>>> spec = GitIgnoreSpec.from_lines(spec_text.splitlines())
|
||||
|
||||
You do not specify the style of pattern for ``GitIgnoreSpec`` because it should
|
||||
always use ``GitIgnoreSpecPattern`` internally.
|
||||
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
Running lots of regular expression matches against thousands of files in Python
|
||||
is slow. Alternate regular expression backends can be used to improve
|
||||
performance. ``PathSpec`` and ``GitIgnoreSpec`` both accept a ``backend``
|
||||
parameter to control the backend. The default is "best" to automatically choose
|
||||
the best available backend. There are currently 3 backends.
|
||||
|
||||
The "simple" backend is the default and it simply uses Python's ``re.Pattern``
|
||||
objects that are normally created. This can be the fastest when there's only 1
|
||||
or 2 patterns.
|
||||
|
||||
The "hyperscan" backend uses the `hyperscan`_ library. Hyperscan tends to be at
|
||||
least 2 times faster than "simple", and generally slower than "re2". This can be
|
||||
faster than "re2" under the right conditions with pattern counts of 1-25.
|
||||
|
||||
The "re2" backend uses the `google-re2`_ library (not to be confused with the
|
||||
*re2* library on PyPI which is unrelated and abandoned). Google's re2 tends to
|
||||
be significantly faster than "simple", and 3 times faster than "hyperscan" at
|
||||
high pattern counts.
|
||||
|
||||
See `benchmarks_backends.md`_ for comparisons between native Python regular
|
||||
expressions and the optional backends.
|
||||
|
||||
|
||||
.. _`benchmarks_backends.md`: https://github.com/cpburnz/python-pathspec/blob/master/benchmarks_backends.md
|
||||
.. _`google-re2`: https://pypi.org/project/google-re2/
|
||||
.. _`hyperscan`: https://pypi.org/project/hyperscan/
|
||||
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
|
||||
1. How do I ignore files like *.gitignore*?
|
||||
+++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
``GitIgnoreSpec`` (and ``PathSpec``) positively match files by default. To find
|
||||
the files to keep, and exclude files like *.gitignore*, you need to set
|
||||
``negate=True`` to flip the results::
|
||||
|
||||
>>> from pathspec import GitIgnoreSpec
|
||||
>>> spec = GitIgnoreSpec.from_lines([...])
|
||||
>>> keep_files = set(spec.match_tree_files('path/to/directory', negate=True))
|
||||
>>> ignore_files = set(spec.match_tree_files('path/to/directory'))
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
*pathspec* is licensed under the `Mozilla Public License Version 2.0`_. See
|
||||
`LICENSE`_ or the `FAQ`_ for more information.
|
||||
|
||||
In summary, you may use *pathspec* with any closed or open source project
|
||||
without affecting the license of the larger work so long as you:
|
||||
|
||||
- give credit where credit is due,
|
||||
|
||||
- and release any custom changes made to *pathspec*.
|
||||
|
||||
.. _`Mozilla Public License Version 2.0`: http://www.mozilla.org/MPL/2.0
|
||||
.. _`LICENSE`: LICENSE
|
||||
.. _`FAQ`: http://www.mozilla.org/MPL/2.0/FAQ.html
|
||||
|
||||
|
||||
Source
|
||||
------
|
||||
|
||||
The source code for *pathspec* is available from the GitHub repo
|
||||
`cpburnz/python-pathspec`_.
|
||||
|
||||
.. _`cpburnz/python-pathspec`: https://github.com/cpburnz/python-pathspec
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
*pathspec* is available for install through `PyPI`_::
|
||||
|
||||
pip install pathspec
|
||||
|
||||
*pathspec* can also be built from source. The following packages will be
|
||||
required:
|
||||
|
||||
- `build`_ (>=0.6.0)
|
||||
|
||||
*pathspec* can then be built and installed with::
|
||||
|
||||
python -m build
|
||||
pip install dist/pathspec-*-py3-none-any.whl
|
||||
|
||||
The following optional dependencies can be installed:
|
||||
|
||||
- `google-re2`_: Enables optional "re2" backend.
|
||||
- `hyperscan`_: Enables optional "hyperscan" backend.
|
||||
- `typing-extensions`_: Improves some type hints.
|
||||
|
||||
.. _`PyPI`: http://pypi.python.org/pypi/pathspec
|
||||
.. _`build`: https://pypi.org/project/build/
|
||||
.. _`typing-extensions`: https://pypi.org/project/typing-extensions/
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation for *pathspec* is available on `Read the Docs`_.
|
||||
|
||||
The full change history can be found in `CHANGES.rst`_ and `Change History`_.
|
||||
|
||||
An upgrade guide is available in `UPGRADING.rst`_ and `Upgrade Guide`_.
|
||||
|
||||
.. _`CHANGES.rst`: https://github.com/cpburnz/python-pathspec/blob/master/CHANGES.rst
|
||||
.. _`Change History`: https://python-path-specification.readthedocs.io/en/stable/changes.html
|
||||
.. _`Read the Docs`: https://python-path-specification.readthedocs.io
|
||||
.. _`UPGRADING.rst`: https://github.com/cpburnz/python-pathspec/blob/master/UPGRADING.rst
|
||||
.. _`Upgrade Guide`: https://python-path-specification.readthedocs.io/en/stable/upgrading.html
|
||||
|
||||
|
||||
Other Languages
|
||||
---------------
|
||||
|
||||
The related project `pathspec-ruby`_ (by *highb*) provides a similar library as
|
||||
a `Ruby gem`_.
|
||||
|
||||
.. _`pathspec-ruby`: https://github.com/highb/pathspec-ruby
|
||||
.. _`Ruby gem`: https://rubygems.org/gems/pathspec
|
||||
|
||||
|
||||
Change History
|
||||
==============
|
||||
|
||||
|
||||
1.0.4 (2026-01-26)
|
||||
------------------
|
||||
|
||||
- `Issue #103`_: Using re2 fails if pyre2 is also installed.
|
||||
|
||||
.. _`Issue #103`: https://github.com/cpburnz/python-pathspec/issues/103
|
||||
|
||||
|
||||
1.0.3 (2026-01-09)
|
||||
------------------
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- `Issue #101`_: pyright strict errors with pathspec >= 1.0.0.
|
||||
- `Issue #102`_: No module named 'tomllib'.
|
||||
|
||||
|
||||
.. _`Issue #101`: https://github.com/cpburnz/python-pathspec/issues/101
|
||||
.. _`Issue #102`: https://github.com/cpburnz/python-pathspec/issues/102
|
||||
|
||||
|
||||
1.0.2 (2026-01-07)
|
||||
------------------
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Type hint `collections.abc.Callable` does not properly replace `typing.Callable` until Python 3.9.2.
|
||||
|
||||
|
||||
1.0.1 (2026-01-06)
|
||||
------------------
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- `Issue #100`_: ValueError(f"{patterns=!r} cannot be empty.") when using black.
|
||||
|
||||
|
||||
.. _`Issue #100`: https://github.com/cpburnz/python-pathspec/issues/100
|
||||
|
||||
|
||||
1.0.0 (2026-01-05)
|
||||
------------------
|
||||
|
||||
Major changes:
|
||||
|
||||
- `Issue #91`_: Dropped support of EoL Python 3.8.
|
||||
- Added concept of backends to allow for faster regular expression matching. The backend can be controlled using the `backend` argument to `PathSpec()`, `PathSpec.from_lines()`, `GitIgnoreSpec()`, and `GitIgnoreSpec.from_lines()`.
|
||||
- Renamed "gitwildmatch" pattern back to "gitignore". The "gitignore" pattern behaves slightly differently when used with `PathSpec` (*gitignore* as documented) than with `GitIgnoreSpec` (replicates *Git*'s edge cases).
|
||||
|
||||
API changes:
|
||||
|
||||
- Breaking: protected method `pathspec.pathspec.PathSpec._match_file()` (with a leading underscore) has been removed and replaced by backends. This does not affect normal usage of `PathSpec` or `GitIgnoreSpec`. Only custom subclasses will be affected. If this breaks your usage, let me know by `opening an issue <https://github.com/cpburnz/python-pathspec/issues>`_.
|
||||
- Deprecated: "gitwildmatch" is now an alias for "gitignore".
|
||||
- Deprecated: `pathspec.patterns.GitWildMatchPattern` is now an alias for `pathspec.patterns.gitignore.spec.GitIgnoreSpecPattern`.
|
||||
- Deprecated: `pathspec.patterns.gitwildmatch` module has been replaced by the `pathspec.patterns.gitignore` package.
|
||||
- Deprecated: `pathspec.patterns.gitwildmatch.GitWildMatchPattern` is now an alias for `pathspec.patterns.gitignore.spec.GitIgnoreSpecPattern`.
|
||||
- Deprecated: `pathspec.patterns.gitwildmatch.GitWildMatchPatternError` is now an alias for `pathspec.patterns.gitignore.GitIgnorePatternError`.
|
||||
- Removed: `pathspec.patterns.gitwildmatch.GitIgnorePattern` has been deprecated since v0.4 (2016-07-15).
|
||||
- Signature of method `pathspec.pattern.RegexPattern.match_file()` has been changed from `def match_file(self, file: str) -> RegexMatchResult | None` to `def match_file(self, file: AnyStr) -> RegexMatchResult | None` to reflect usage.
|
||||
- Signature of class method `pathspec.pattern.RegexPattern.pattern_to_regex()` has been changed from `def pattern_to_regex(cls, pattern: str) -> tuple[str, bool]` to `def pattern_to_regex(cls, pattern: AnyStr) -> tuple[AnyStr | None, bool | None]` to reflect usage and documentation.
|
||||
|
||||
New features:
|
||||
|
||||
- Added optional "hyperscan" backend using `hyperscan`_ library. It will automatically be used when installed. This dependency can be installed with ``pip install 'pathspec[hyperscan]'``.
|
||||
- Added optional "re2" backend using the `google-re2`_ library. It will automatically be used when installed. This dependency can be installed with ``pip install 'pathspec[re2]'``.
|
||||
- Added optional dependency on `typing-extensions`_ library to improve some type hints.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- `Issue #93`_: Do not remove leading spaces.
|
||||
- `Issue #95`_: Matching for files inside folder does not seem to behave like .gitignore's.
|
||||
- `Issue #98`_: UnboundLocalError in RegexPattern when initialized with `pattern=None`.
|
||||
- Type hint on return value of `pathspec.pattern.RegexPattern.match_file()` to match documentation.
|
||||
|
||||
Improvements:
|
||||
|
||||
- Mark Python 3.13 and 3.14 as supported.
|
||||
- No-op patterns are now filtered out when matching files, slightly improving performance.
|
||||
- Fix performance regression in `iter_tree_files()` from v0.10.
|
||||
|
||||
|
||||
.. _`Issue #38`: https://github.com/cpburnz/python-pathspec/issues/38
|
||||
.. _`Issue #91`: https://github.com/cpburnz/python-pathspec/issues/91
|
||||
.. _`Issue #93`: https://github.com/cpburnz/python-pathspec/issues/93
|
||||
.. _`Issue #95`: https://github.com/cpburnz/python-pathspec/issues/95
|
||||
.. _`Issue #98`: https://github.com/cpburnz/python-pathspec/issues/98
|
||||
.. _`google-re2`: https://pypi.org/project/google-re2/
|
||||
.. _`hyperscan`: https://pypi.org/project/hyperscan/
|
||||
.. _`typing-extensions`: https://pypi.org/project/typing-extensions/
|
||||
|
||||
69
lib/pathspec-1.0.4.dist-info/RECORD
Normal file
69
lib/pathspec-1.0.4.dist-info/RECORD
Normal file
@@ -0,0 +1,69 @@
|
||||
pathspec-1.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
pathspec-1.0.4.dist-info/METADATA,sha256=pekHVZjpp_VHVlDo7U032-fIhSGEbY_V8jjmYrEgaWM,13755
|
||||
pathspec-1.0.4.dist-info/RECORD,,
|
||||
pathspec-1.0.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
pathspec-1.0.4.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
||||
pathspec-1.0.4.dist-info/licenses/LICENSE,sha256=-rPda9qyJvHAhjCx3ZF-Efy07F4eAg4sFvg6ChOGPoU,16726
|
||||
pathspec/__init__.py,sha256=0PnZCecVo4UjsfA0EFGsAUikyz1jSDFmQP9gCoKXW_Y,1408
|
||||
pathspec/__pycache__/__init__.cpython-314.pyc,,
|
||||
pathspec/__pycache__/_meta.cpython-314.pyc,,
|
||||
pathspec/__pycache__/_typing.cpython-314.pyc,,
|
||||
pathspec/__pycache__/_version.cpython-314.pyc,,
|
||||
pathspec/__pycache__/backend.cpython-314.pyc,,
|
||||
pathspec/__pycache__/gitignore.cpython-314.pyc,,
|
||||
pathspec/__pycache__/pathspec.cpython-314.pyc,,
|
||||
pathspec/__pycache__/pattern.cpython-314.pyc,,
|
||||
pathspec/__pycache__/util.cpython-314.pyc,,
|
||||
pathspec/_backends/__init__.py,sha256=CjgX4uSPMC5UH4iy_IrdFXrcLQ_gwK8MKW5Qbspz_uE,130
|
||||
pathspec/_backends/__pycache__/__init__.cpython-314.pyc,,
|
||||
pathspec/_backends/__pycache__/_utils.cpython-314.pyc,,
|
||||
pathspec/_backends/__pycache__/agg.cpython-314.pyc,,
|
||||
pathspec/_backends/_utils.py,sha256=mDjbGpndOyVkt9Fue0WDWKTkk-jVqOejof9Bv9pzArE,1066
|
||||
pathspec/_backends/agg.py,sha256=naHFqYXMR53hwtgHtEHrwNJEBFpbUWbdMbF0zguxHlE,2505
|
||||
pathspec/_backends/hyperscan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
pathspec/_backends/hyperscan/__pycache__/__init__.cpython-314.pyc,,
|
||||
pathspec/_backends/hyperscan/__pycache__/_base.cpython-314.pyc,,
|
||||
pathspec/_backends/hyperscan/__pycache__/base.cpython-314.pyc,,
|
||||
pathspec/_backends/hyperscan/__pycache__/gitignore.cpython-314.pyc,,
|
||||
pathspec/_backends/hyperscan/__pycache__/pathspec.cpython-314.pyc,,
|
||||
pathspec/_backends/hyperscan/_base.py,sha256=b8E_kClW6Wtkdserr3qZzMPWVomrI4yhfxSlGVYdT3c,1719
|
||||
pathspec/_backends/hyperscan/base.py,sha256=BclDnsbCH6Fvx58YT6wqxGDcfWKNUQAcy_9jV63WkCI,563
|
||||
pathspec/_backends/hyperscan/gitignore.py,sha256=OyqtXEoZWrMB3Uh_2xNzY0aGK5UdBBjkFeGAFKQh7Oo,6761
|
||||
pathspec/_backends/hyperscan/pathspec.py,sha256=74RsGQt9x3nTxjz5S5grEQI34x8eFew78wluiIzhOpw,6500
|
||||
pathspec/_backends/re2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
pathspec/_backends/re2/__pycache__/__init__.cpython-314.pyc,,
|
||||
pathspec/_backends/re2/__pycache__/_base.cpython-314.pyc,,
|
||||
pathspec/_backends/re2/__pycache__/base.cpython-314.pyc,,
|
||||
pathspec/_backends/re2/__pycache__/gitignore.cpython-314.pyc,,
|
||||
pathspec/_backends/re2/__pycache__/pathspec.cpython-314.pyc,,
|
||||
pathspec/_backends/re2/_base.py,sha256=VDThfjwEOnrDOfri_EnPifXH8pOYt71nxq3tUQAScfU,2149
|
||||
pathspec/_backends/re2/base.py,sha256=0sCZzhDpvyZLg9imO7BdE9KOmy3L0mgyHuzPhHWNbRU,462
|
||||
pathspec/_backends/re2/gitignore.py,sha256=0RPjCzg1vxE_6qDOL29V4qAyi9UnMKT2bb3k2XDimew,5094
|
||||
pathspec/_backends/re2/pathspec.py,sha256=aUtY_DdVHQyxHMbMGiovmXTIpuLKgIAeGtZerMVHIhI,4871
|
||||
pathspec/_backends/simple/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
pathspec/_backends/simple/__pycache__/__init__.cpython-314.pyc,,
|
||||
pathspec/_backends/simple/__pycache__/gitignore.cpython-314.pyc,,
|
||||
pathspec/_backends/simple/__pycache__/pathspec.cpython-314.pyc,,
|
||||
pathspec/_backends/simple/gitignore.py,sha256=45SfH2SM-YF7CppdSrQ15z7A4GUAesFzLWs8QaKdER4,2865
|
||||
pathspec/_backends/simple/pathspec.py,sha256=Zzebst2evN8-juZr5w6VBwIox7LToYT4K2zD4Jp3M7U,2095
|
||||
pathspec/_meta.py,sha256=3sxdG_ghfAmwhV7AGeJS9VUZptsmaBFVSPhQqVLpiMk,2937
|
||||
pathspec/_typing.py,sha256=xega7efBH3B4StmBzxpGvrk-yJWYKnD6Lk5Id0IiHzc,1642
|
||||
pathspec/_version.py,sha256=iV7XOjXu_8FpfpC966oeh6PC-5XA35XwWlO7oI-p2ys,64
|
||||
pathspec/backend.py,sha256=QXFus8SgZ1hKH8LZ8eOnZcyGNTO1_YQYwRM_kTkvi2M,1161
|
||||
pathspec/gitignore.py,sha256=oFWfSgeecaJFSCgI0TwdYxz0jluQxztgf-T779OxIN8,5263
|
||||
pathspec/pathspec.py,sha256=5JhgxfZTyzUcG0bEUN91xTdcvF_S9sdhXGK59nIpDOY,15151
|
||||
pathspec/pattern.py,sha256=smqkNSWc9LmPZS1MqYBGjXFXZRteiSpwF8iAy9250DY,6695
|
||||
pathspec/patterns/__init__.py,sha256=6pfTpyrSIJxN8A12hKWpa9JFvVMTR39FV3QE1HBQbho,404
|
||||
pathspec/patterns/__pycache__/__init__.cpython-314.pyc,,
|
||||
pathspec/patterns/__pycache__/gitwildmatch.cpython-314.pyc,,
|
||||
pathspec/patterns/gitignore/__init__.py,sha256=MaSAZd0DDg0vCH9k1LslaJjBJw5DkX4ty-FuLmB1z_4,422
|
||||
pathspec/patterns/gitignore/__pycache__/__init__.cpython-314.pyc,,
|
||||
pathspec/patterns/gitignore/__pycache__/base.cpython-314.pyc,,
|
||||
pathspec/patterns/gitignore/__pycache__/basic.cpython-314.pyc,,
|
||||
pathspec/patterns/gitignore/__pycache__/spec.cpython-314.pyc,,
|
||||
pathspec/patterns/gitignore/base.py,sha256=mkLYm-prSD2SXNDpxnFhL0FRV8FRPAsIBVeXyNOWjCI,4688
|
||||
pathspec/patterns/gitignore/basic.py,sha256=0pTlzzJt8qMpy-SnGHhozZVWVDH9ErPDy29MV3Q8UOw,9924
|
||||
pathspec/patterns/gitignore/spec.py,sha256=8jB3Q7Wbb6fLvtIfNax89tEtw2UZgATbAKnpGQleU8Q,10186
|
||||
pathspec/patterns/gitwildmatch.py,sha256=bF2PUtc9gOFHuFwHJ035x91y3R8An5dIY5oRibylsco,1463
|
||||
pathspec/py.typed,sha256=wq7wwDeyBungK6DsiV4O-IujgKzARwHz94uQshdpdEU,68
|
||||
pathspec/util.py,sha256=KbG9seqfTOBLPoSJ8I4CdeDFVof6rDGCMy69cZb4Du4,24728
|
||||
0
lib/pathspec-1.0.4.dist-info/REQUESTED
Normal file
0
lib/pathspec-1.0.4.dist-info/REQUESTED
Normal file
4
lib/pathspec-1.0.4.dist-info/WHEEL
Normal file
4
lib/pathspec-1.0.4.dist-info/WHEEL
Normal file
@@ -0,0 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: flit 3.12.0
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
373
lib/pathspec-1.0.4.dist-info/licenses/LICENSE
Normal file
373
lib/pathspec-1.0.4.dist-info/licenses/LICENSE
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
68
lib/pathspec/__init__.py
Normal file
68
lib/pathspec/__init__.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
The *pathspec* package provides pattern matching for file paths. So far this
|
||||
only includes Git's *gitignore* patterns.
|
||||
|
||||
The following classes are imported and made available from the root of the
|
||||
`pathspec` package:
|
||||
|
||||
- :class:`pathspec.gitignore.GitIgnoreSpec`
|
||||
|
||||
- :class:`pathspec.pathspec.PathSpec`
|
||||
|
||||
- :class:`pathspec.pattern.Pattern`
|
||||
|
||||
- :class:`pathspec.pattern.RegexPattern`
|
||||
|
||||
- :class:`pathspec.util.RecursionError`
|
||||
|
||||
The following functions are also imported:
|
||||
|
||||
- :func:`pathspec.util.lookup_pattern`
|
||||
|
||||
The following deprecated functions are also imported to maintain backward
|
||||
compatibility:
|
||||
|
||||
- :func:`pathspec.util.iter_tree`
|
||||
|
||||
- :func:`pathspec.util.match_files`
|
||||
"""
|
||||
|
||||
from .gitignore import (
|
||||
GitIgnoreSpec)
|
||||
from .pathspec import (
|
||||
PathSpec)
|
||||
from .pattern import (
|
||||
Pattern,
|
||||
RegexPattern)
|
||||
from .util import (
|
||||
RecursionError,
|
||||
iter_tree, # Deprecated since 0.10.0.
|
||||
lookup_pattern,
|
||||
match_files) # Deprecated since 0.10.0.
|
||||
|
||||
from ._meta import (
|
||||
__author__,
|
||||
__copyright__,
|
||||
__credits__,
|
||||
__license__)
|
||||
from ._version import (
|
||||
__version__)
|
||||
|
||||
# Load pattern implementations.
|
||||
from . import patterns
|
||||
|
||||
# Declare private imports as part of the public interface. Deprecated imports
|
||||
# are deliberately excluded.
|
||||
__all__ = [
|
||||
'GitIgnoreSpec',
|
||||
'PathSpec',
|
||||
'Pattern',
|
||||
'RecursionError',
|
||||
'RegexPattern',
|
||||
'__author__',
|
||||
'__copyright__',
|
||||
'__credits__',
|
||||
'__license__',
|
||||
'__version__',
|
||||
'lookup_pattern',
|
||||
]
|
||||
BIN
lib/pathspec/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/__pycache__/_meta.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/_meta.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/__pycache__/_typing.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/_typing.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/__pycache__/_version.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/_version.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/__pycache__/backend.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/backend.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/__pycache__/gitignore.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/gitignore.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/__pycache__/pathspec.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/pathspec.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/__pycache__/pattern.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/pattern.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/__pycache__/util.cpython-314.pyc
Normal file
BIN
lib/pathspec/__pycache__/util.cpython-314.pyc
Normal file
Binary file not shown.
4
lib/pathspec/_backends/__init__.py
Normal file
4
lib/pathspec/_backends/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
WARNING: The *pathspec._backends* package is not part of the public API. Its
|
||||
contents and structure are likely to change.
|
||||
"""
|
||||
BIN
lib/pathspec/_backends/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
lib/pathspec/_backends/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/_backends/__pycache__/_utils.cpython-314.pyc
Normal file
BIN
lib/pathspec/_backends/__pycache__/_utils.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/_backends/__pycache__/agg.cpython-314.pyc
Normal file
BIN
lib/pathspec/_backends/__pycache__/agg.cpython-314.pyc
Normal file
Binary file not shown.
45
lib/pathspec/_backends/_utils.py
Normal file
45
lib/pathspec/_backends/_utils.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
This module provides private utility functions for backends.
|
||||
|
||||
WARNING: The *pathspec._backends* package is not part of the public API. Its
|
||||
contents and structure are likely to change.
|
||||
"""
|
||||
|
||||
from collections.abc import (
|
||||
Iterable)
|
||||
from typing import (
|
||||
TypeVar)
|
||||
|
||||
from pathspec.pattern import (
|
||||
Pattern)
|
||||
|
||||
TPattern = TypeVar("TPattern", bound=Pattern)
|
||||
|
||||
|
||||
def enumerate_patterns(
|
||||
patterns: Iterable[TPattern],
|
||||
filter: bool,
|
||||
reverse: bool,
|
||||
) -> list[tuple[int, TPattern]]:
|
||||
"""
|
||||
Enumerate the patterns.
|
||||
|
||||
*patterns* (:class:`Iterable` of :class:`.Pattern`) contains the patterns.
|
||||
|
||||
*filter* (:class:`bool`) is whether to remove no-op patterns (:data:`True`),
|
||||
or keep them (:data:`False`).
|
||||
|
||||
*reverse* (:class:`bool`) is whether to reverse the pattern order
|
||||
(:data:`True`), or keep the order (:data:`True`).
|
||||
|
||||
Returns the enumerated patterns (:class:`list` of :class:`tuple`).
|
||||
"""
|
||||
out_patterns = [
|
||||
(__i, __pat)
|
||||
for __i, __pat in enumerate(patterns)
|
||||
if not filter or __pat.include is not None
|
||||
]
|
||||
if reverse:
|
||||
out_patterns.reverse()
|
||||
|
||||
return out_patterns
|
||||
104
lib/pathspec/_backends/agg.py
Normal file
104
lib/pathspec/_backends/agg.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
This module provides aggregated private data and utilities functions about the
|
||||
available backends.
|
||||
|
||||
WARNING: The *pathspec._backends* package is not part of the public API. Its
|
||||
contents and structure are likely to change.
|
||||
"""
|
||||
|
||||
from collections.abc import (
|
||||
Sequence)
|
||||
from typing import (
|
||||
cast)
|
||||
|
||||
from pathspec.backend import (
|
||||
BackendNamesHint,
|
||||
_Backend)
|
||||
from pathspec.pattern import (
|
||||
Pattern,
|
||||
RegexPattern)
|
||||
|
||||
from .hyperscan.base import (
|
||||
hyperscan_error)
|
||||
from .hyperscan.gitignore import (
|
||||
HyperscanGiBackend)
|
||||
from .hyperscan.pathspec import (
|
||||
HyperscanPsBackend)
|
||||
from .re2.base import (
|
||||
re2_error)
|
||||
from .re2.gitignore import (
|
||||
Re2GiBackend)
|
||||
from .re2.pathspec import (
|
||||
Re2PsBackend)
|
||||
from .simple.gitignore import (
|
||||
SimpleGiBackend)
|
||||
from .simple.pathspec import (
|
||||
SimplePsBackend)
|
||||
|
||||
_BEST_BACKEND: BackendNamesHint
|
||||
"""
|
||||
The best available backend.
|
||||
"""
|
||||
|
||||
if re2_error is None:
|
||||
_BEST_BACKEND = 're2'
|
||||
elif hyperscan_error is None:
|
||||
_BEST_BACKEND = 'hyperscan'
|
||||
else:
|
||||
_BEST_BACKEND = 'simple'
|
||||
|
||||
|
||||
def make_gitignore_backend(
|
||||
name: BackendNamesHint,
|
||||
patterns: Sequence[Pattern],
|
||||
) -> _Backend:
|
||||
"""
|
||||
Create the specified backend with the supplied patterns for
|
||||
:class:`~pathspec.gitignore.GitIgnoreSpec`.
|
||||
|
||||
*name* (:class:`str`) is the name of the backend.
|
||||
|
||||
*patterns* (:class:`.Iterable` of :class:`.Pattern`) contains the compiled
|
||||
patterns.
|
||||
|
||||
Returns the backend (:class:`._Backend`).
|
||||
"""
|
||||
if name == 'best':
|
||||
name = _BEST_BACKEND
|
||||
|
||||
if name == 'hyperscan':
|
||||
return HyperscanGiBackend(cast(Sequence[RegexPattern], patterns))
|
||||
elif name == 're2':
|
||||
return Re2GiBackend(cast(Sequence[RegexPattern], patterns))
|
||||
elif name == 'simple':
|
||||
return SimpleGiBackend(cast(Sequence[RegexPattern], patterns))
|
||||
else:
|
||||
raise ValueError(f"Backend {name=!r} is invalid.")
|
||||
|
||||
|
||||
def make_pathspec_backend(
|
||||
name: BackendNamesHint,
|
||||
patterns: Sequence[Pattern],
|
||||
) -> _Backend:
|
||||
"""
|
||||
Create the specified backend with the supplied patterns for
|
||||
:class:`~pathspec.pathspec.PathSpec`.
|
||||
|
||||
*name* (:class:`str`) is the name of the backend.
|
||||
|
||||
*patterns* (:class:`Iterable` of :class:`Pattern`) contains the compiled
|
||||
patterns.
|
||||
|
||||
Returns the backend (:class:`._Backend`).
|
||||
"""
|
||||
if name == 'best':
|
||||
name = _BEST_BACKEND
|
||||
|
||||
if name == 'hyperscan':
|
||||
return HyperscanPsBackend(cast(Sequence[RegexPattern], patterns))
|
||||
elif name == 're2':
|
||||
return Re2PsBackend(cast(Sequence[RegexPattern], patterns))
|
||||
elif name == 'simple':
|
||||
return SimplePsBackend(patterns)
|
||||
else:
|
||||
raise ValueError(f"Backend {name=!r} is invalid.")
|
||||
0
lib/pathspec/_backends/hyperscan/__init__.py
Normal file
0
lib/pathspec/_backends/hyperscan/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
78
lib/pathspec/_backends/hyperscan/_base.py
Normal file
78
lib/pathspec/_backends/hyperscan/_base.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
This module provides private data for the base implementation for the
|
||||
:module:`hyperscan` library.
|
||||
|
||||
WARNING: The *pathspec._backends.hyperscan* package is not part of the public
|
||||
API. Its contents and structure are likely to change.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import (
|
||||
dataclass)
|
||||
from typing import (
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
try:
|
||||
import hyperscan
|
||||
except ModuleNotFoundError:
|
||||
hyperscan = None
|
||||
HS_FLAGS = 0
|
||||
else:
|
||||
HS_FLAGS = hyperscan.HS_FLAG_SINGLEMATCH | hyperscan.HS_FLAG_UTF8
|
||||
|
||||
HS_FLAGS: int
|
||||
"""
|
||||
The hyperscan flags to use:
|
||||
|
||||
- HS_FLAG_SINGLEMATCH is needed to ensure the partial patterns only match once.
|
||||
|
||||
- HS_FLAG_UTF8 is required to support unicode paths.
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HyperscanExprDat(object):
|
||||
"""
|
||||
The :class:`HyperscanExprDat` class is used to store data related to an
|
||||
expression.
|
||||
"""
|
||||
|
||||
# The slots argument is not supported until Python 3.10.
|
||||
__slots__ = [
|
||||
'include',
|
||||
'index',
|
||||
'is_dir_pattern',
|
||||
]
|
||||
|
||||
include: bool
|
||||
"""
|
||||
*include* (:class:`bool`) is whether is whether the matched files should be
|
||||
included (:data:`True`), or excluded (:data:`False`).
|
||||
"""
|
||||
|
||||
index: int
|
||||
"""
|
||||
*index* (:class:`int`) is the pattern index.
|
||||
"""
|
||||
|
||||
is_dir_pattern: bool
|
||||
"""
|
||||
*is_dir_pattern* (:class:`bool`) is whether the pattern is a directory
|
||||
pattern for gitignore.
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HyperscanExprDebug(HyperscanExprDat):
|
||||
"""
|
||||
The :class:`HyperscanExprDebug` class stores additional debug information
|
||||
related to an expression.
|
||||
"""
|
||||
|
||||
# The slots argument is not supported until Python 3.10.
|
||||
__slots__ = ['regex']
|
||||
|
||||
regex: Union[str, bytes]
|
||||
"""
|
||||
*regex* (:class:`str` or :class:`bytes`) is the regular expression.
|
||||
"""
|
||||
24
lib/pathspec/_backends/hyperscan/base.py
Normal file
24
lib/pathspec/_backends/hyperscan/base.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
This module provides the base implementation for the :module:`hyperscan`
|
||||
backend.
|
||||
|
||||
WARNING: The *pathspec._backends.hyperscan* package is not part of the public
|
||||
API. Its contents and structure are likely to change.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import (
|
||||
Optional)
|
||||
|
||||
try:
|
||||
import hyperscan
|
||||
hyperscan_error = None
|
||||
except ModuleNotFoundError as e:
|
||||
hyperscan = None
|
||||
hyperscan_error = e
|
||||
|
||||
hyperscan_error: Optional[ModuleNotFoundError]
|
||||
"""
|
||||
*hyperscan_error* (:class:`ModuleNotFoundError` or :data:`None`) is the
|
||||
hyperscan import error.
|
||||
"""
|
||||
245
lib/pathspec/_backends/hyperscan/gitignore.py
Normal file
245
lib/pathspec/_backends/hyperscan/gitignore.py
Normal file
@@ -0,0 +1,245 @@
|
||||
"""
|
||||
This module provides the :module:`hyperscan` backend for :class:`~pathspec.gitignore.GitIgnoreSpec`.
|
||||
|
||||
WARNING: The *pathspec._backends.hyperscan* package is not part of the public
|
||||
API. Its contents and structure are likely to change.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import (
|
||||
Sequence)
|
||||
from typing import (
|
||||
Any,
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
try:
|
||||
import hyperscan
|
||||
except ModuleNotFoundError:
|
||||
hyperscan = None
|
||||
|
||||
from pathspec.pattern import (
|
||||
RegexPattern)
|
||||
from pathspec.patterns.gitignore.spec import (
|
||||
GitIgnoreSpecPattern,
|
||||
_BYTES_ENCODING,
|
||||
_DIR_MARK_CG,
|
||||
_DIR_MARK_OPT)
|
||||
from pathspec._typing import (
|
||||
override) # Added in 3.12.
|
||||
|
||||
from ._base import (
|
||||
HS_FLAGS,
|
||||
HyperscanExprDat,
|
||||
HyperscanExprDebug)
|
||||
from .pathspec import (
|
||||
HyperscanPsBackend)
|
||||
|
||||
|
||||
class HyperscanGiBackend(HyperscanPsBackend):
|
||||
"""
|
||||
The :class:`HyperscanGiBackend` class is the :module:`hyperscan`
|
||||
implementation used by :class:`~pathspec.gitignore.GitIgnoreSpec`. The
|
||||
Hyperscan database uses block mode for matching files.
|
||||
"""
|
||||
|
||||
# Change type hint.
|
||||
_out: tuple[Optional[bool], int, int]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
patterns: Sequence[RegexPattern],
|
||||
*,
|
||||
_debug_exprs: Optional[bool] = None,
|
||||
_test_sort: Optional[Callable[[list], None]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the :class:`HyperscanMatcher` instance.
|
||||
|
||||
*patterns* (:class:`Sequence` of :class:`.RegexPattern`) contains the
|
||||
compiled patterns.
|
||||
"""
|
||||
super().__init__(patterns, _debug_exprs=_debug_exprs, _test_sort=_test_sort)
|
||||
|
||||
self._out = (None, -1, 0)
|
||||
"""
|
||||
*_out* (:class:`tuple`) stores the current match:
|
||||
|
||||
- *0* (:class:`bool` or :data:`None`) is the match include.
|
||||
|
||||
- *1* (:class:`int`) is the match index.
|
||||
|
||||
- *2* (:class:`int`) is the match priority.
|
||||
"""
|
||||
|
||||
@override
|
||||
@staticmethod
|
||||
def _init_db(
|
||||
db: hyperscan.Database,
|
||||
debug: bool,
|
||||
patterns: list[tuple[int, RegexPattern]],
|
||||
sort_ids: Optional[Callable[[list[int]], None]],
|
||||
) -> list[HyperscanExprDat]:
|
||||
"""
|
||||
Create the Hyperscan database from the given patterns.
|
||||
|
||||
*db* (:class:`hyperscan.Hyperscan`) is the Hyperscan database.
|
||||
|
||||
*debug* (:class:`bool`) is whether to include additional debugging
|
||||
information for the expressions.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`.RegexPattern`)
|
||||
contains the patterns.
|
||||
|
||||
*sort_ids* (:class:`callable` or :data:`None`) is a function used to sort
|
||||
the compiled expression ids. This is used during testing to ensure the order
|
||||
of expressions is not accidentally relied on.
|
||||
|
||||
Returns a :class:`list` indexed by expression id (:class:`int`) to its data
|
||||
(:class:`HyperscanExprDat`).
|
||||
"""
|
||||
# WARNING: Hyperscan raises a `hyperscan.error` exception when compiled with
|
||||
# zero elements.
|
||||
assert patterns, patterns
|
||||
|
||||
# Prepare patterns.
|
||||
expr_data: list[HyperscanExprDat] = []
|
||||
exprs: list[bytes] = []
|
||||
for pattern_index, pattern in patterns:
|
||||
assert pattern.include is not None, (pattern_index, pattern)
|
||||
|
||||
# Encode regex.
|
||||
assert isinstance(pattern, RegexPattern), pattern
|
||||
regex = pattern.regex.pattern
|
||||
|
||||
use_regexes: list[tuple[Union[str, bytes], bool]] = []
|
||||
if isinstance(pattern, GitIgnoreSpecPattern):
|
||||
# GitIgnoreSpecPattern uses capture groups for its directory marker but
|
||||
# Hyperscan does not support capture groups. Handle this scenario.
|
||||
regex_str: str
|
||||
if isinstance(regex, str):
|
||||
regex_str: str = regex
|
||||
else:
|
||||
assert isinstance(regex, bytes), regex
|
||||
regex_str = regex.decode(_BYTES_ENCODING)
|
||||
|
||||
if _DIR_MARK_CG in regex_str:
|
||||
# Found directory marker.
|
||||
if regex_str.endswith(_DIR_MARK_OPT):
|
||||
# Regex has optional directory marker. Split regex into directory
|
||||
# and file variants.
|
||||
base_regex = regex_str[:-len(_DIR_MARK_OPT)]
|
||||
use_regexes.append((f'{base_regex}/', True))
|
||||
use_regexes.append((f'{base_regex}$', False))
|
||||
else:
|
||||
# Remove capture group.
|
||||
base_regex = regex_str.replace(_DIR_MARK_CG, '/')
|
||||
use_regexes.append((base_regex, True))
|
||||
|
||||
if not use_regexes:
|
||||
# No special case for regex.
|
||||
use_regexes.append((regex, False))
|
||||
|
||||
for regex, is_dir_pattern in use_regexes:
|
||||
if isinstance(regex, bytes):
|
||||
regex_bytes = regex
|
||||
else:
|
||||
assert isinstance(regex, str), regex
|
||||
regex_bytes = regex.encode('utf8')
|
||||
|
||||
if debug:
|
||||
expr_data.append(HyperscanExprDebug(
|
||||
include=pattern.include,
|
||||
index=pattern_index,
|
||||
is_dir_pattern=is_dir_pattern,
|
||||
regex=regex,
|
||||
))
|
||||
else:
|
||||
expr_data.append(HyperscanExprDat(
|
||||
include=pattern.include,
|
||||
index=pattern_index,
|
||||
is_dir_pattern=is_dir_pattern,
|
||||
))
|
||||
|
||||
exprs.append(regex_bytes)
|
||||
|
||||
# Sort expressions.
|
||||
ids = list(range(len(exprs)))
|
||||
if sort_ids is not None:
|
||||
sort_ids(ids)
|
||||
exprs = [exprs[__id] for __id in ids]
|
||||
|
||||
# Compile patterns.
|
||||
db.compile(
|
||||
expressions=exprs,
|
||||
ids=ids,
|
||||
elements=len(exprs),
|
||||
flags=HS_FLAGS,
|
||||
)
|
||||
return expr_data
|
||||
|
||||
@override
|
||||
def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to check.
|
||||
|
||||
Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
|
||||
or :data:`None`), and the index of the last matched pattern (:class:`int` or
|
||||
:data:`None`).
|
||||
"""
|
||||
# NOTICE: According to benchmarking, a method callback is 13% faster than
|
||||
# using a closure here.
|
||||
db = self._db
|
||||
if self._db is None:
|
||||
# Database was not initialized because there were no patterns. Return no
|
||||
# match.
|
||||
return (None, None)
|
||||
|
||||
self._out = (None, -1, 0)
|
||||
db.scan(file.encode('utf8'), match_event_handler=self.__on_match)
|
||||
|
||||
out_include, out_index = self._out[:2]
|
||||
if out_index == -1:
|
||||
out_index = None
|
||||
|
||||
return (out_include, out_index)
|
||||
|
||||
@override
|
||||
def __on_match(
|
||||
self,
|
||||
expr_id: int,
|
||||
_from: int,
|
||||
_to: int,
|
||||
_flags: int,
|
||||
_context: Any,
|
||||
) -> Optional[bool]:
|
||||
"""
|
||||
Called on each match.
|
||||
|
||||
*expr_id* (:class:`int`) is the expression id (index) of the matched
|
||||
pattern.
|
||||
"""
|
||||
expr_dat = self._expr_data[expr_id]
|
||||
|
||||
is_dir_pattern = expr_dat.is_dir_pattern
|
||||
if is_dir_pattern:
|
||||
# Pattern matched by a directory pattern.
|
||||
priority = 1
|
||||
else:
|
||||
# Pattern matched by a file pattern.
|
||||
priority = 2
|
||||
|
||||
# WARNING: Hyperscan does not guarantee matches will be produced in order!
|
||||
include = expr_dat.include
|
||||
index = expr_dat.index
|
||||
prev_index = self._out[1]
|
||||
prev_priority = self._out[2]
|
||||
if (
|
||||
(include and is_dir_pattern and index > prev_index)
|
||||
or (priority == prev_priority and index > prev_index)
|
||||
or priority > prev_priority
|
||||
):
|
||||
self._out = (include, expr_dat.index, priority)
|
||||
251
lib/pathspec/_backends/hyperscan/pathspec.py
Normal file
251
lib/pathspec/_backends/hyperscan/pathspec.py
Normal file
@@ -0,0 +1,251 @@
|
||||
"""
|
||||
This module provides the :module:`hyperscan` backend for :class:`~pathspec.pathspec.PathSpec`.
|
||||
|
||||
WARNING: The *pathspec._backends.hyperscan* package is not part of the public
|
||||
API. Its contents and structure are likely to change.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import (
|
||||
Sequence)
|
||||
from typing import (
|
||||
Any,
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
|
||||
Optional) # Replaced by `X | None` in 3.10.
|
||||
|
||||
try:
|
||||
import hyperscan
|
||||
except ModuleNotFoundError:
|
||||
hyperscan = None
|
||||
|
||||
from pathspec.backend import (
|
||||
_Backend)
|
||||
from pathspec.pattern import (
|
||||
RegexPattern)
|
||||
from pathspec._typing import (
|
||||
override) # Added in 3.12.
|
||||
|
||||
from .._utils import (
|
||||
enumerate_patterns)
|
||||
|
||||
from .base import (
|
||||
hyperscan_error)
|
||||
from ._base import (
|
||||
HS_FLAGS,
|
||||
HyperscanExprDat,
|
||||
HyperscanExprDebug)
|
||||
|
||||
|
||||
class HyperscanPsBackend(_Backend):
|
||||
"""
|
||||
The :class:`HyperscanPsBackend` class is the :module:`hyperscan`
|
||||
implementation used by :class:`~pathspec.pathspec.PathSpec` for matching
|
||||
files. The Hyperscan database uses block mode for matching files.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
patterns: Sequence[RegexPattern],
|
||||
*,
|
||||
_debug_exprs: Optional[bool] = None,
|
||||
_test_sort: Optional[Callable[[list], None]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the :class:`HyperscanPsBackend` instance.
|
||||
|
||||
*patterns* (:class:`Sequence` of :class:`.RegexPattern`) contains the
|
||||
compiled patterns.
|
||||
"""
|
||||
if hyperscan is None:
|
||||
raise hyperscan_error
|
||||
|
||||
if patterns and not isinstance(patterns[0], RegexPattern):
|
||||
raise TypeError(f"{patterns[0]=!r} must be a RegexPattern.")
|
||||
|
||||
use_patterns = enumerate_patterns(
|
||||
patterns, filter=True, reverse=False,
|
||||
)
|
||||
|
||||
debug_exprs = bool(_debug_exprs)
|
||||
if use_patterns:
|
||||
db = self._make_db()
|
||||
expr_data = self._init_db(
|
||||
db=db,
|
||||
debug=debug_exprs,
|
||||
patterns=use_patterns,
|
||||
sort_ids=_test_sort,
|
||||
)
|
||||
else:
|
||||
# WARNING: The hyperscan database cannot be initialized with zero
|
||||
# patterns.
|
||||
db = None
|
||||
expr_data = []
|
||||
|
||||
self._db: Optional[hyperscan.Database] = db
|
||||
"""
|
||||
*_db* (:class:`hyperscan.Database`) is the Hyperscan database.
|
||||
"""
|
||||
|
||||
self._debug_exprs = debug_exprs
|
||||
"""
|
||||
*_debug_exprs* (:class:`bool`) is whether to include additional debugging
|
||||
information for the expressions.
|
||||
"""
|
||||
|
||||
self._expr_data: list[HyperscanExprDat] = expr_data
|
||||
"""
|
||||
*_expr_data* (:class:`list`) maps expression index (:class:`int`) to
|
||||
expression data (:class:`:class:`HyperscanExprDat`).
|
||||
"""
|
||||
|
||||
self._out: tuple[Optional[bool], int] = (None, -1)
|
||||
"""
|
||||
*_out* (:class:`tuple`) stores the current match:
|
||||
|
||||
- *0* (:class:`bool` or :data:`None`) is the match include.
|
||||
|
||||
- *1* (:class:`int`) is the match index.
|
||||
"""
|
||||
|
||||
self._patterns: dict[int, RegexPattern] = dict(use_patterns)
|
||||
"""
|
||||
*_patterns* (:class:`dict`) maps pattern index (:class:`int`) to pattern
|
||||
(:class:`RegexPattern`).
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _init_db(
|
||||
db: hyperscan.Database,
|
||||
debug: bool,
|
||||
patterns: list[tuple[int, RegexPattern]],
|
||||
sort_ids: Optional[Callable[[list[int]], None]],
|
||||
) -> list[HyperscanExprDat]:
|
||||
"""
|
||||
Initialize the Hyperscan database from the given patterns.
|
||||
|
||||
*db* (:class:`hyperscan.Hyperscan`) is the Hyperscan database.
|
||||
|
||||
*debug* (:class:`bool`) is whether to include additional debugging
|
||||
information for the expressions.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`.RegexPattern`)
|
||||
contains the patterns.
|
||||
|
||||
*sort_ids* (:class:`callable` or :data:`None`) is a function used to sort
|
||||
the compiled expression ids. This is used during testing to ensure the order
|
||||
of expressions is not accidentally relied on.
|
||||
|
||||
Returns a :class:`list` indexed by expression id (:class:`int`) to its data
|
||||
(:class:`HyperscanExprDat`).
|
||||
"""
|
||||
# WARNING: Hyperscan raises a `hyperscan.error` exception when compiled with
|
||||
# zero elements.
|
||||
assert patterns, patterns
|
||||
|
||||
# Prepare patterns.
|
||||
expr_data: list[HyperscanExprDat] = []
|
||||
exprs: list[bytes] = []
|
||||
for pattern_index, pattern in patterns:
|
||||
assert pattern.include is not None, (pattern_index, pattern)
|
||||
|
||||
# Encode regex.
|
||||
assert isinstance(pattern, RegexPattern), pattern
|
||||
regex = pattern.regex.pattern
|
||||
|
||||
if isinstance(regex, bytes):
|
||||
regex_bytes = regex
|
||||
else:
|
||||
assert isinstance(regex, str), regex
|
||||
regex_bytes = regex.encode('utf8')
|
||||
|
||||
if debug:
|
||||
expr_data.append(HyperscanExprDebug(
|
||||
include=pattern.include,
|
||||
index=pattern_index,
|
||||
is_dir_pattern=False,
|
||||
regex=regex,
|
||||
))
|
||||
else:
|
||||
expr_data.append(HyperscanExprDat(
|
||||
include=pattern.include,
|
||||
index=pattern_index,
|
||||
is_dir_pattern=False,
|
||||
))
|
||||
|
||||
exprs.append(regex_bytes)
|
||||
|
||||
# Sort expressions.
|
||||
ids = list(range(len(exprs)))
|
||||
if sort_ids is not None:
|
||||
sort_ids(ids)
|
||||
exprs = [exprs[__id] for __id in ids]
|
||||
|
||||
# Compile patterns.
|
||||
db.compile(
|
||||
expressions=exprs,
|
||||
ids=ids,
|
||||
elements=len(exprs),
|
||||
flags=HS_FLAGS,
|
||||
)
|
||||
|
||||
return expr_data
|
||||
|
||||
@override
|
||||
def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to check.
|
||||
|
||||
Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
|
||||
or :data:`None`), and the index of the last matched pattern (:class:`int` or
|
||||
:data:`None`).
|
||||
"""
|
||||
# NOTICE: According to benchmarking, a method callback is 20% faster than
|
||||
# using a closure here.
|
||||
db = self._db
|
||||
if self._db is None:
|
||||
# Database was not initialized because there were no patterns. Return no
|
||||
# match.
|
||||
return (None, None)
|
||||
|
||||
self._out = (None, -1)
|
||||
db.scan(file.encode('utf8'), match_event_handler=self.__on_match)
|
||||
|
||||
out_include, out_index = self._out
|
||||
if out_index == -1:
|
||||
out_index = None
|
||||
|
||||
return (out_include, out_index)
|
||||
|
||||
@staticmethod
|
||||
def _make_db() -> hyperscan.Database:
|
||||
"""
|
||||
Create the Hyperscan database.
|
||||
|
||||
Returns the database (:class:`hyperscan.Database`).
|
||||
"""
|
||||
return hyperscan.Database(mode=hyperscan.HS_MODE_BLOCK)
|
||||
|
||||
def __on_match(
|
||||
self,
|
||||
expr_id: int,
|
||||
_from: int,
|
||||
_to: int,
|
||||
_flags: int,
|
||||
_context: Any,
|
||||
) -> Optional[bool]:
|
||||
"""
|
||||
Called on each match.
|
||||
|
||||
*expr_id* (:class:`int`) is the expression id (index) of the matched
|
||||
pattern.
|
||||
"""
|
||||
# Store match.
|
||||
# - WARNING: Hyperscan does not guarantee matches will be produced in order!
|
||||
# Later expressions have higher priority.
|
||||
expr_dat = self._expr_data[expr_id]
|
||||
index = expr_dat.index
|
||||
prev_index = self._out[1]
|
||||
if index > prev_index:
|
||||
self._out = (expr_dat.include, index)
|
||||
0
lib/pathspec/_backends/re2/__init__.py
Normal file
0
lib/pathspec/_backends/re2/__init__.py
Normal file
BIN
lib/pathspec/_backends/re2/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
lib/pathspec/_backends/re2/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/_backends/re2/__pycache__/_base.cpython-314.pyc
Normal file
BIN
lib/pathspec/_backends/re2/__pycache__/_base.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/_backends/re2/__pycache__/base.cpython-314.pyc
Normal file
BIN
lib/pathspec/_backends/re2/__pycache__/base.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/_backends/re2/__pycache__/gitignore.cpython-314.pyc
Normal file
BIN
lib/pathspec/_backends/re2/__pycache__/gitignore.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/_backends/re2/__pycache__/pathspec.cpython-314.pyc
Normal file
BIN
lib/pathspec/_backends/re2/__pycache__/pathspec.cpython-314.pyc
Normal file
Binary file not shown.
95
lib/pathspec/_backends/re2/_base.py
Normal file
95
lib/pathspec/_backends/re2/_base.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
This module provides private data for the base implementation for the
|
||||
:module:`re2` library.
|
||||
|
||||
WARNING: The *pathspec._backends.re2* package is not part of the public API. Its
|
||||
contents and structure are likely to change.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import (
|
||||
dataclass)
|
||||
from typing import (
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
try:
|
||||
import re2
|
||||
re2_error = None
|
||||
except ModuleNotFoundError as e:
|
||||
re2 = None
|
||||
re2_error = e
|
||||
RE2_OPTIONS = None
|
||||
else:
|
||||
# Both the `google-re2` and `pyre2` libraries use the `re2` namespace.
|
||||
# `google-re2` is the only one currently supported.
|
||||
try:
|
||||
RE2_OPTIONS = re2.Options()
|
||||
RE2_OPTIONS.log_errors = False
|
||||
RE2_OPTIONS.never_capture = True
|
||||
except Exception as e:
|
||||
re2_error = e
|
||||
RE2_OPTIONS = None
|
||||
|
||||
RE2_OPTIONS: re2.Options
|
||||
"""
|
||||
The re2 options to use:
|
||||
|
||||
- `log_errors=False` disables logging to stderr.
|
||||
|
||||
- `never_capture=True` disables capture groups because they effectively cannot
|
||||
be utilized with :class:`re2.Set`.
|
||||
"""
|
||||
|
||||
re2_error: Optional[Exception]
|
||||
"""
|
||||
*re2_error* (:class:`Exception` or :data:`None`) is the re2 import error.
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Re2RegexDat(object):
|
||||
"""
|
||||
The :class:`Re2RegexDat` class is used to store data related to a regular
|
||||
expression.
|
||||
"""
|
||||
|
||||
# The slots argument is not supported until Python 3.10.
|
||||
__slots__ = [
|
||||
'include',
|
||||
'index',
|
||||
'is_dir_pattern',
|
||||
]
|
||||
|
||||
include: bool
|
||||
"""
|
||||
*include* (:class:`bool`) is whether is whether the matched files should be
|
||||
included (:data:`True`), or excluded (:data:`False`).
|
||||
"""
|
||||
|
||||
index: int
|
||||
"""
|
||||
*index* (:class:`int`) is the pattern index.
|
||||
"""
|
||||
|
||||
is_dir_pattern: bool
|
||||
"""
|
||||
*is_dir_pattern* (:class:`bool`) is whether the pattern is a directory
|
||||
pattern for gitignore.
|
||||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Re2RegexDebug(Re2RegexDat):
|
||||
"""
|
||||
The :class:`Re2RegexDebug` class stores additional debug information related
|
||||
to a regular expression.
|
||||
"""
|
||||
|
||||
# The slots argument is not supported until Python 3.10.
|
||||
__slots__ = ['regex']
|
||||
|
||||
regex: Union[str, bytes]
|
||||
"""
|
||||
*regex* (:class:`str` or :class:`bytes`) is the regular expression.
|
||||
"""
|
||||
18
lib/pathspec/_backends/re2/base.py
Normal file
18
lib/pathspec/_backends/re2/base.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
This module provides the base implementation for the :module:`re2` backend.
|
||||
|
||||
WARNING: The *pathspec._backends.re2* package is not part of the public API. Its
|
||||
contents and structure are likely to change.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import (
|
||||
Optional) # Replaced by `X | None` in 3.10.
|
||||
|
||||
from ._base import (
|
||||
re2_error)
|
||||
|
||||
re2_error: Optional[Exception]
|
||||
"""
|
||||
*re2_error* (:class:`Exception` or :data:`None`) is the re2 import error.
|
||||
"""
|
||||
179
lib/pathspec/_backends/re2/gitignore.py
Normal file
179
lib/pathspec/_backends/re2/gitignore.py
Normal file
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
This module provides the :module:`re2` backend for :class:`~pathspec.gitignore.GitIgnoreSpec`.
|
||||
|
||||
WARNING: The *pathspec._backends.re2* package is not part of the public API. Its
|
||||
contents and structure are likely to change.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import (
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
try:
|
||||
import re2
|
||||
except ModuleNotFoundError:
|
||||
re2 = None
|
||||
|
||||
from pathspec.pattern import (
|
||||
RegexPattern)
|
||||
from pathspec.patterns.gitignore.spec import (
|
||||
GitIgnoreSpecPattern,
|
||||
_BYTES_ENCODING,
|
||||
_DIR_MARK_CG,
|
||||
_DIR_MARK_OPT)
|
||||
from pathspec._typing import (
|
||||
override) # Added in 3.12.
|
||||
|
||||
from ._base import (
|
||||
Re2RegexDat,
|
||||
Re2RegexDebug)
|
||||
from .pathspec import (
|
||||
Re2PsBackend)
|
||||
|
||||
|
||||
class Re2GiBackend(Re2PsBackend):
|
||||
"""
|
||||
The :class:`Re2GiBackend` class is the :module:`re2` implementation used by
|
||||
:class:`~pathspec.gitignore.GitIgnoreSpec` for matching files.
|
||||
"""
|
||||
|
||||
@override
|
||||
@staticmethod
|
||||
def _init_set(
|
||||
debug: bool,
|
||||
patterns: dict[int, RegexPattern],
|
||||
regex_set: re2.Set,
|
||||
sort_indices: Optional[Callable[[list[int]], None]],
|
||||
) -> list[Re2RegexDat]:
|
||||
"""
|
||||
Create the re2 regex set.
|
||||
|
||||
*debug* (:class:`bool`) is whether to include additional debugging
|
||||
information for the regular expressions.
|
||||
|
||||
*patterns* (:class:`dict`) maps pattern index (:class:`int`) to pattern
|
||||
(:class:`.RegexPattern`).
|
||||
|
||||
*regex_set* (:class:`re2.Set`) is the regex set.
|
||||
|
||||
*sort_indices* (:class:`callable` or :data:`None`) is a function used to
|
||||
sort the patterns by index. This is used during testing to ensure the order
|
||||
of patterns is not accidentally relied on.
|
||||
|
||||
Returns a :class:`list` indexed by regex id (:class:`int`) to its data
|
||||
(:class:`Re2RegexDat`).
|
||||
"""
|
||||
# Sort patterns.
|
||||
indices = list(patterns.keys())
|
||||
if sort_indices is not None:
|
||||
sort_indices(indices)
|
||||
|
||||
# Prepare patterns.
|
||||
regex_data: list[Re2RegexDat] = []
|
||||
for pattern_index in indices:
|
||||
pattern = patterns[pattern_index]
|
||||
if pattern.include is None:
|
||||
continue
|
||||
|
||||
assert isinstance(pattern, RegexPattern), pattern
|
||||
regex = pattern.regex.pattern
|
||||
|
||||
use_regexes: list[tuple[Union[str, bytes], bool]] = []
|
||||
if isinstance(pattern, GitIgnoreSpecPattern):
|
||||
# GitIgnoreSpecPattern uses capture groups for its directory marker. Re2
|
||||
# supports capture groups, but they cannot be utilized when using
|
||||
# `re2.Set`. Handle this scenario.
|
||||
regex_str: str
|
||||
if isinstance(regex, str):
|
||||
regex_str = regex
|
||||
else:
|
||||
assert isinstance(regex, bytes), regex
|
||||
regex_str = regex.decode(_BYTES_ENCODING)
|
||||
|
||||
if _DIR_MARK_CG in regex_str:
|
||||
# Found directory marker.
|
||||
if regex_str.endswith(_DIR_MARK_OPT):
|
||||
# Regex has optional directory marker. Split regex into directory
|
||||
# and file variants.
|
||||
base_regex = regex_str[:-len(_DIR_MARK_OPT)]
|
||||
use_regexes.append((f'{base_regex}/', True))
|
||||
use_regexes.append((f'{base_regex}$', False))
|
||||
else:
|
||||
# Remove capture group.
|
||||
base_regex = regex_str.replace(_DIR_MARK_CG, '/')
|
||||
use_regexes.append((base_regex, True))
|
||||
|
||||
if not use_regexes:
|
||||
# No special case for regex.
|
||||
use_regexes.append((regex, False))
|
||||
|
||||
for regex, is_dir_pattern in use_regexes:
|
||||
if debug:
|
||||
regex_data.append(Re2RegexDebug(
|
||||
include=pattern.include,
|
||||
index=pattern_index,
|
||||
is_dir_pattern=is_dir_pattern,
|
||||
regex=regex,
|
||||
))
|
||||
else:
|
||||
regex_data.append(Re2RegexDat(
|
||||
include=pattern.include,
|
||||
index=pattern_index,
|
||||
is_dir_pattern=is_dir_pattern,
|
||||
))
|
||||
|
||||
regex_set.Add(regex)
|
||||
|
||||
# Compile patterns.
|
||||
regex_set.Compile()
|
||||
return regex_data
|
||||
|
||||
@override
|
||||
def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to check.
|
||||
|
||||
Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
|
||||
or :data:`None`), and the index of the last matched pattern (:class:`int` or
|
||||
:data:`None`).
|
||||
"""
|
||||
# Find best match.
|
||||
match_ids: Optional[list[int]] = self._set.Match(file)
|
||||
if not match_ids:
|
||||
return (None, None)
|
||||
|
||||
out_include: Optional[bool] = None
|
||||
out_index: int = -1
|
||||
out_priority = -1
|
||||
|
||||
regex_data = self._regex_data
|
||||
for regex_id in match_ids:
|
||||
regex_dat = regex_data[regex_id]
|
||||
|
||||
is_dir_pattern = regex_dat.is_dir_pattern
|
||||
if is_dir_pattern:
|
||||
# Pattern matched by a directory pattern.
|
||||
priority = 1
|
||||
else:
|
||||
# Pattern matched by a file pattern.
|
||||
priority = 2
|
||||
|
||||
# WARNING: According to the documentation on `RE2::Set::Match()`, there is
|
||||
# no guarantee matches will be produced in order!
|
||||
include = regex_dat.include
|
||||
index = regex_dat.index
|
||||
if (
|
||||
(include and is_dir_pattern and index > out_index)
|
||||
or (priority == out_priority and index > out_index)
|
||||
or priority > out_priority
|
||||
):
|
||||
out_include = include
|
||||
out_index = index
|
||||
out_priority = priority
|
||||
|
||||
assert out_index != -1, (out_index, out_include, out_priority)
|
||||
return (out_include, out_index)
|
||||
187
lib/pathspec/_backends/re2/pathspec.py
Normal file
187
lib/pathspec/_backends/re2/pathspec.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
This module provides the :module:`re2` backend for :class:`~pathspec.pathspec.PathSpec`.
|
||||
|
||||
WARNING: The *pathspec._backends.re2* package is not part of the public API. Its
|
||||
contents and structure are likely to change.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import (
|
||||
Sequence)
|
||||
from typing import (
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
|
||||
Optional) # Replaced by `X | None` in 3.10.
|
||||
|
||||
try:
|
||||
import re2
|
||||
except ModuleNotFoundError:
|
||||
re2 = None
|
||||
|
||||
from pathspec.backend import (
|
||||
_Backend)
|
||||
from pathspec.pattern import (
|
||||
RegexPattern)
|
||||
from pathspec._typing import (
|
||||
override) # Added in 3.12.
|
||||
|
||||
from .._utils import (
|
||||
enumerate_patterns)
|
||||
|
||||
from .base import (
|
||||
re2_error)
|
||||
from ._base import (
|
||||
RE2_OPTIONS,
|
||||
Re2RegexDat,
|
||||
Re2RegexDebug)
|
||||
|
||||
|
||||
class Re2PsBackend(_Backend):
|
||||
"""
|
||||
The :class:`Re2PsBackend` class is the :module:`re2` implementation used by
|
||||
:class:`~pathspec.pathspec.PathSpec` for matching files.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
patterns: Sequence[RegexPattern],
|
||||
*,
|
||||
_debug_regex: Optional[bool] = None,
|
||||
_test_sort: Optional[Callable[[list], None]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the :class:`Re2PsBackend` instance.
|
||||
|
||||
*patterns* (:class:`Sequence` of :class:`.RegexPattern`) contains the
|
||||
compiled patterns.
|
||||
"""
|
||||
if re2_error is not None:
|
||||
raise re2_error
|
||||
|
||||
if patterns and not isinstance(patterns[0], RegexPattern):
|
||||
raise TypeError(f"{patterns[0]=!r} must be a RegexPattern.")
|
||||
|
||||
use_patterns = dict(enumerate_patterns(
|
||||
patterns, filter=True, reverse=False,
|
||||
))
|
||||
regex_set = self._make_set()
|
||||
|
||||
self._debug_regex = bool(_debug_regex)
|
||||
"""
|
||||
*_debug_regex* (:class:`bool`) is whether to include additional debugging
|
||||
information for the regular expressions.
|
||||
"""
|
||||
|
||||
self._patterns: dict[int, RegexPattern] = use_patterns
|
||||
"""
|
||||
*_patterns* (:class:`dict`) maps pattern index (:class:`int`) to pattern
|
||||
(:class:`RegexPattern`).
|
||||
"""
|
||||
|
||||
self._regex_data: list[Re2RegexDat] = self._init_set(
|
||||
debug=self._debug_regex,
|
||||
patterns=use_patterns,
|
||||
regex_set=regex_set,
|
||||
sort_indices=_test_sort,
|
||||
)
|
||||
"""
|
||||
*_regex_data* (:class:`list`) maps regex index (:class:`int`) to regex data
|
||||
(:class:`Re2RegexDat`).
|
||||
"""
|
||||
|
||||
self._set: re2.Set = regex_set
|
||||
"""
|
||||
*_set* (:class:`re2.Set`) is the re2 regex set.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _init_set(
|
||||
debug: bool,
|
||||
patterns: dict[int, RegexPattern],
|
||||
regex_set: re2.Set,
|
||||
sort_indices: Optional[Callable[[list[int]], None]],
|
||||
) -> list[Re2RegexDat]:
|
||||
"""
|
||||
Create the re2 regex set.
|
||||
|
||||
*debug* (:class:`bool`) is whether to include additional debugging
|
||||
information for the regular expressions.
|
||||
|
||||
*patterns* (:class:`dict`) maps pattern index (:class:`int`) to pattern
|
||||
(:class:`.RegexPattern`).
|
||||
|
||||
*regex_set* (:class:`re2.Set`) is the regex set.
|
||||
|
||||
*sort_indices* (:class:`callable` or :data:`None`) is a function used to
|
||||
sort the patterns by index. This is used during testing to ensure the order
|
||||
of patterns is not accidentally relied on.
|
||||
|
||||
Returns a :class:`list` indexed by regex id (:class:`int`) to its data
|
||||
(:class:`Re2RegexDat`).
|
||||
"""
|
||||
# Sort patterns.
|
||||
indices = list(patterns.keys())
|
||||
if sort_indices is not None:
|
||||
sort_indices(indices)
|
||||
|
||||
# Prepare patterns.
|
||||
regex_data: list[Re2RegexDat] = []
|
||||
for pattern_index in indices:
|
||||
pattern = patterns[pattern_index]
|
||||
if pattern.include is None:
|
||||
continue
|
||||
|
||||
assert isinstance(pattern, RegexPattern), pattern
|
||||
regex = pattern.regex.pattern
|
||||
|
||||
if debug:
|
||||
regex_data.append(Re2RegexDebug(
|
||||
include=pattern.include,
|
||||
index=pattern_index,
|
||||
is_dir_pattern=False,
|
||||
regex=regex,
|
||||
))
|
||||
else:
|
||||
regex_data.append(Re2RegexDat(
|
||||
include=pattern.include,
|
||||
index=pattern_index,
|
||||
is_dir_pattern=False,
|
||||
))
|
||||
|
||||
regex_set.Add(regex)
|
||||
|
||||
# Compile patterns.
|
||||
regex_set.Compile()
|
||||
return regex_data
|
||||
|
||||
@staticmethod
|
||||
def _make_set() -> re2.Set:
|
||||
"""
|
||||
Create the re2 regex set.
|
||||
|
||||
Returns the set (:class:`re2.Set`).
|
||||
"""
|
||||
return re2.Set.SearchSet(RE2_OPTIONS)
|
||||
|
||||
@override
|
||||
def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to check.
|
||||
|
||||
Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
|
||||
or :data:`None`), and the index of the last matched pattern (:class:`int` or
|
||||
:data:`None`).
|
||||
"""
|
||||
# Find best match.
|
||||
# - WARNING: According to the documentation on `RE2::Set::Match()`, there is
|
||||
# no guarantee matches will be produced in order! Later expressions have
|
||||
# higher priority.
|
||||
match_ids: Optional[list[int]] = self._set.Match(file)
|
||||
if not match_ids:
|
||||
return (None, None)
|
||||
|
||||
regex_data = self._regex_data
|
||||
pattern_index = max(regex_data[__id].index for __id in match_ids)
|
||||
pattern = self._patterns[pattern_index]
|
||||
return (pattern.include, pattern_index)
|
||||
0
lib/pathspec/_backends/simple/__init__.py
Normal file
0
lib/pathspec/_backends/simple/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
104
lib/pathspec/_backends/simple/gitignore.py
Normal file
104
lib/pathspec/_backends/simple/gitignore.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
This module provides the simple backend for :class:`~pathspec.gitignore.GitIgnoreSpec`.
|
||||
|
||||
WARNING: The *pathspec._backends.simple* package is not part of the public API.
|
||||
Its contents and structure are likely to change.
|
||||
"""
|
||||
|
||||
from collections.abc import (
|
||||
Sequence)
|
||||
from typing import (
|
||||
Optional) # Replaced by `X | None` in 3.10.
|
||||
|
||||
from pathspec.pattern import (
|
||||
RegexPattern)
|
||||
from pathspec.patterns.gitignore.spec import (
|
||||
_DIR_MARK)
|
||||
from pathspec._typing import (
|
||||
override) # Added in 3.12.
|
||||
|
||||
from .pathspec import (
|
||||
SimplePsBackend)
|
||||
|
||||
|
||||
class SimpleGiBackend(SimplePsBackend):
|
||||
"""
|
||||
The :class:`SimpleGiBackend` class is the default (or simple) implementation
|
||||
used by :class:`~pathspec.gitignore.GitIgnoreSpec` for matching files.
|
||||
"""
|
||||
|
||||
# Change type hint.
|
||||
_patterns: list[tuple[int, RegexPattern]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
patterns: Sequence[RegexPattern],
|
||||
*,
|
||||
no_filter: Optional[bool] = None,
|
||||
no_reverse: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the :class:`SimpleGiBackend` instance.
|
||||
|
||||
*patterns* (:class:`Sequence` of :class:`.RegexPattern`) contains the
|
||||
compiled patterns.
|
||||
|
||||
*no_filter* (:class:`bool`) is whether to keep no-op patterns (:data:`True`),
|
||||
or remove them (:data:`False`).
|
||||
|
||||
*no_reverse* (:class:`bool`) is whether to keep the pattern order
|
||||
(:data:`True`), or reverse the order (:data:`True`).
|
||||
"""
|
||||
super().__init__(patterns, no_filter=no_filter, no_reverse=no_reverse)
|
||||
|
||||
@override
|
||||
def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to check.
|
||||
|
||||
Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
|
||||
or :data:`None`), and the index of the last matched pattern (:class:`int` or
|
||||
:data:`None`).
|
||||
"""
|
||||
is_reversed = self._is_reversed
|
||||
|
||||
out_include: Optional[bool] = None
|
||||
out_index: Optional[int] = None
|
||||
out_priority = 0
|
||||
for index, pattern in self._patterns:
|
||||
if (
|
||||
(include := pattern.include) is not None
|
||||
and (match := pattern.match_file(file)) is not None
|
||||
):
|
||||
# Pattern matched.
|
||||
|
||||
# Check for directory marker.
|
||||
dir_mark = match.match.groupdict().get(_DIR_MARK)
|
||||
|
||||
if dir_mark:
|
||||
# Pattern matched by a directory pattern.
|
||||
priority = 1
|
||||
else:
|
||||
# Pattern matched by a file pattern.
|
||||
priority = 2
|
||||
|
||||
if is_reversed:
|
||||
if priority > out_priority:
|
||||
out_include = include
|
||||
out_index = index
|
||||
out_priority = priority
|
||||
else:
|
||||
# Forward.
|
||||
if (include and dir_mark) or priority >= out_priority:
|
||||
out_include = include
|
||||
out_index = index
|
||||
out_priority = priority
|
||||
|
||||
if is_reversed and priority == 2:
|
||||
# Patterns are being checked in reverse order. The first pattern that
|
||||
# matches with priority 2 takes precedence.
|
||||
break
|
||||
|
||||
return (out_include, out_index)
|
||||
76
lib/pathspec/_backends/simple/pathspec.py
Normal file
76
lib/pathspec/_backends/simple/pathspec.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
This module provides the simple backend for :class:`~pathspec.pathspec.PathSpec`.
|
||||
|
||||
WARNING: The *pathspec._backends.simple* package is not part of the public API.
|
||||
Its contents and structure are likely to change.
|
||||
"""
|
||||
|
||||
from collections.abc import (
|
||||
Sequence)
|
||||
from typing import (
|
||||
Optional) # Replaced by `X | None` in 3.10.
|
||||
|
||||
from pathspec.backend import (
|
||||
_Backend)
|
||||
from pathspec.pattern import (
|
||||
Pattern)
|
||||
from pathspec._typing import (
|
||||
override) # Added in 3.12.
|
||||
from pathspec.util import (
|
||||
check_match_file)
|
||||
|
||||
from .._utils import (
|
||||
enumerate_patterns)
|
||||
|
||||
|
||||
class SimplePsBackend(_Backend):
|
||||
"""
|
||||
The :class:`SimplePsBackend` class is the default (or simple) implementation
|
||||
used by :class:`~pathspec.pathspec.PathSpec` for matching files.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
patterns: Sequence[Pattern],
|
||||
*,
|
||||
no_filter: Optional[bool] = None,
|
||||
no_reverse: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the :class:`SimplePsBackend` instance.
|
||||
|
||||
*patterns* (:class:`Sequence` of :class:`.Pattern`) contains the compiled
|
||||
patterns.
|
||||
|
||||
*no_filter* (:class:`bool`) is whether to keep no-op patterns (:data:`True`),
|
||||
or remove them (:data:`False`).
|
||||
|
||||
*no_reverse* (:class:`bool`) is whether to keep the pattern order
|
||||
(:data:`True`), or reverse the order (:data:`True`).
|
||||
"""
|
||||
|
||||
self._is_reversed: bool = not no_reverse
|
||||
"""
|
||||
*_is_reversed* (:class:`bool`) is whether to the pattern order was reversed.
|
||||
"""
|
||||
|
||||
self._patterns: list[tuple[int, Pattern]] = enumerate_patterns(
|
||||
patterns, filter=not no_filter, reverse=not no_reverse,
|
||||
)
|
||||
"""
|
||||
*_patterns* (:class:`list` of :class:`tuple`) contains the enumerated
|
||||
patterns.
|
||||
"""
|
||||
|
||||
@override
|
||||
def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to check.
|
||||
|
||||
Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
|
||||
or :data:`None`), and the index of the last matched pattern (:class:`int` or
|
||||
:data:`None`).
|
||||
"""
|
||||
return check_match_file(self._patterns, file, self._is_reversed)
|
||||
67
lib/pathspec/_meta.py
Normal file
67
lib/pathspec/_meta.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
This module contains the project meta-data.
|
||||
"""
|
||||
|
||||
__author__ = "Caleb P. Burns"
|
||||
__copyright__ = "Copyright © 2013-2026 Caleb P. Burns"
|
||||
__credits__ = [
|
||||
"Hong Minhee <https://github.com/dahlia>",
|
||||
"Brandon High <https://github.com/highb>",
|
||||
"029xue <https://github.com/029xue>",
|
||||
"Michael Huynh <https://github.com/mikexstudios>",
|
||||
"Nick Humrich <https://github.com/nhumrich>",
|
||||
"David Fraser <https://github.com/davidfraser>",
|
||||
"Charles Samborski <https://github.com/demurgos>",
|
||||
"George Hickman <https://github.com/ghickman>",
|
||||
"Vincent Driessen <https://github.com/nvie>",
|
||||
"Adrien Vergé <https://github.com/adrienverge>",
|
||||
"Anders Blomdell <https://github.com/AndersBlomdell>",
|
||||
"Xavier Thomas <https://github.com/thmxv>",
|
||||
"Wim Jeantine-Glenn <https://github.com/wimglenn>",
|
||||
"Hugo van Kemenade <https://github.com/hugovk>",
|
||||
"Dan Cecile <https://github.com/dcecile>",
|
||||
"MrOutis <https://github.com/mroutis>",
|
||||
"Jon Dufresne <https://github.com/jdufresne>",
|
||||
"Greg Roodt <https://github.com/groodt>",
|
||||
"Florin T. <https://github.com/ftrofin>",
|
||||
"Ben Felder <https://github.com/pykong>",
|
||||
"Nicholas Hollander <https://github.com/nhhollander>",
|
||||
"KOLANICH <https://github.com/KOLANICH>",
|
||||
"Jon Hays <https://github.com/JonjonHays>",
|
||||
"Isaac0616 <https://github.com/Isaac0616>",
|
||||
"Sebastiaan Zeeff <https://github.com/SebastiaanZ>",
|
||||
"Roel Adriaans <https://github.com/RoelAdriaans>",
|
||||
"Ravi Selker <https://github.com/raviselker>",
|
||||
"Johan Vergeer <https://github.com/johanvergeer>",
|
||||
"danjer <https://github.com/danjer>",
|
||||
"Jan-Hein Bührman <https://github.com/jhbuhrman>",
|
||||
"Wim-Peter Dirks <https://github.com/WPDOrdina>",
|
||||
"Karthikeyan Singaravelan <https://github.com/tirkarthi>",
|
||||
"John Vandenberg <https://github.com/jayvdb>",
|
||||
"John T. Wodder II <https://github.com/jwodder>",
|
||||
"Tomasz Kłoczko <https://github.com/kloczek>",
|
||||
"Oren <https://github.com/orens>",
|
||||
"SP Mohanty <https://github.com/spMohanty>",
|
||||
"Richard Si <https://github.com/ichard26>",
|
||||
"Jakub Kuczys <https://github.com/jack1142>",
|
||||
"Michał Górny <https://github.com/mgorny>",
|
||||
"Bartłomiej Żak <https://github.com/bzakdd>",
|
||||
"Matthias <https://github.com/haimat>",
|
||||
"Avasam <https://github.com/Avasam>",
|
||||
"Anıl Karagenç <https://github.com/karagenc>",
|
||||
"Yannic Schröder <https://github.com/yschroeder>",
|
||||
"axesider <https://github.com/axesider>",
|
||||
"TomRuk <https://github.com/tomruk>",
|
||||
"Oleh Prypin <https://github.com/oprypin>",
|
||||
"Lumina <https://github.com/lumina37>",
|
||||
"Kurt McKee <https://github.com/kurtmckee>",
|
||||
"Dobatymo <https://github.com/Dobatymo>",
|
||||
"Tomoki Nakamaru <https://github.com/tomokinakamaru>",
|
||||
"Sebastien Eskenazi <https://github.com/SebastienEske>",
|
||||
"Bar Vered <https://github.com/barv-jfrog>",
|
||||
"Tzach Shabtay <https://github.com/tzachshabtay>",
|
||||
"Adam Dangoor <https://github.com/adamtheturtle>",
|
||||
"Marcel Telka <https://github.com/mtelka>",
|
||||
"Dmytro Kostochko <https://github.com/Alerion>",
|
||||
]
|
||||
__license__ = "MPL 2.0"
|
||||
64
lib/pathspec/_typing.py
Normal file
64
lib/pathspec/_typing.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
This module provides stubs for type hints not supported by all relevant Python
|
||||
versions.
|
||||
|
||||
NOTICE: This project should have zero required dependencies which means it
|
||||
cannot simply require :module:`typing_extensions`, and I do not want to maintain
|
||||
a vendored copy of :module:`typing_extensions`.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import warnings
|
||||
from typing import (
|
||||
Any,
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
TypeVar)
|
||||
try:
|
||||
from typing import AnyStr # Removed in 3.18.
|
||||
except ImportError:
|
||||
AnyStr = TypeVar('AnyStr', str, bytes)
|
||||
try:
|
||||
from typing import Never # Added in 3.11.
|
||||
except ImportError:
|
||||
from typing import NoReturn as Never
|
||||
|
||||
F = TypeVar('F', bound=Callable[..., Any])
|
||||
|
||||
try:
|
||||
from warnings import deprecated # Added in 3.13.
|
||||
except ImportError:
|
||||
try:
|
||||
from typing_extensions import deprecated
|
||||
except ImportError:
|
||||
def deprecated(
|
||||
message: str,
|
||||
/, *,
|
||||
category: Optional[type[Warning]] = DeprecationWarning,
|
||||
stacklevel: int = 1,
|
||||
) -> Callable[[F], F]:
|
||||
def decorator(f: F) -> F:
|
||||
@functools.wraps(f)
|
||||
def wrapper(*a, **k):
|
||||
warnings.warn(message, category=category, stacklevel=stacklevel+1)
|
||||
return f(*a, **k)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
try:
|
||||
from typing import override # Added in 3.12.
|
||||
except ImportError:
|
||||
try:
|
||||
from typing_extensions import override
|
||||
except ImportError:
|
||||
def override(f: F) -> F:
|
||||
return f
|
||||
|
||||
|
||||
def assert_unreachable(message: str) -> Never:
|
||||
"""
|
||||
The code path is unreachable. Raises an :class:`AssertionError`.
|
||||
|
||||
*message* (:class:`str`) is the error message.
|
||||
"""
|
||||
raise AssertionError(message)
|
||||
5
lib/pathspec/_version.py
Normal file
5
lib/pathspec/_version.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
This module defines the version.
|
||||
"""
|
||||
|
||||
__version__ = "1.0.4"
|
||||
40
lib/pathspec/backend.py
Normal file
40
lib/pathspec/backend.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
This module defines the necessary classes and type hints for exposing the bare
|
||||
minimum of the internal implementations for the pattern (regular expression)
|
||||
matching backends. The exact structure of the backends is not solidified and is
|
||||
subject to change.
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
Literal,
|
||||
Optional)
|
||||
|
||||
BackendNamesHint = Literal['best', 'hyperscan', 're2', 'simple']
|
||||
"""
|
||||
The supported backend values.
|
||||
"""
|
||||
|
||||
|
||||
class _Backend(object):
|
||||
"""
|
||||
.. warning:: This class is not part of the public API. It is subject to
|
||||
change.
|
||||
|
||||
The :class:`_Backend` class is the abstract base class defining how to match
|
||||
files against patterns.
|
||||
"""
|
||||
|
||||
def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to check.
|
||||
|
||||
Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
|
||||
or :data:`None`), and the index of the last matched pattern (:class:`int` or
|
||||
:data:`None`).
|
||||
"""
|
||||
raise NotImplementedError((
|
||||
f"{self.__class__.__module__}.{self.__class__.__qualname__}.match_file() "
|
||||
f"must be implemented."
|
||||
)) # NotImplementedError
|
||||
165
lib/pathspec/gitignore.py
Normal file
165
lib/pathspec/gitignore.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
This module provides :class:`.GitIgnoreSpec` which replicates *.gitignore*
|
||||
behavior, and handles edge-cases where Git's behavior differs from what's
|
||||
documented. Git allows including files from excluded directories which directly
|
||||
contradicts the documentation. This uses :class:`.GitIgnoreSpecPattern` to fully
|
||||
replicate Git's handling.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import (
|
||||
Iterable,
|
||||
Sequence)
|
||||
from typing import (
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
TypeVar,
|
||||
Union, # Replaced by `X | Y` in 3.10.
|
||||
cast,
|
||||
overload)
|
||||
|
||||
from pathspec.backend import (
|
||||
BackendNamesHint,
|
||||
_Backend)
|
||||
from pathspec._backends.agg import (
|
||||
make_gitignore_backend)
|
||||
from pathspec.pathspec import (
|
||||
PathSpec)
|
||||
from pathspec.pattern import (
|
||||
Pattern)
|
||||
from pathspec.patterns.gitignore.basic import (
|
||||
GitIgnoreBasicPattern)
|
||||
from pathspec.patterns.gitignore.spec import (
|
||||
GitIgnoreSpecPattern)
|
||||
from pathspec._typing import (
|
||||
AnyStr, # Removed in 3.18.
|
||||
override) # Added in 3.12.
|
||||
from pathspec.util import (
|
||||
_is_iterable,
|
||||
lookup_pattern)
|
||||
|
||||
Self = TypeVar("Self", bound='GitIgnoreSpec')
|
||||
"""
|
||||
:class:`.GitIgnoreSpec` self type hint to support Python v<3.11 using PEP 673
|
||||
recommendation.
|
||||
"""
|
||||
|
||||
|
||||
class GitIgnoreSpec(PathSpec):
|
||||
"""
|
||||
The :class:`GitIgnoreSpec` class extends :class:`.PathSpec` to replicate
|
||||
*gitignore* behavior. This is uses :class:`.GitIgnoreSpecPattern` to fully
|
||||
replicate Git's handling.
|
||||
"""
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""
|
||||
Tests the equality of this gitignore-spec with *other* (:class:`.GitIgnoreSpec`)
|
||||
by comparing their :attr:`self.patterns <.PathSpec.patterns>` attributes. A
|
||||
non-:class:`GitIgnoreSpec` will not compare equal.
|
||||
"""
|
||||
if isinstance(other, GitIgnoreSpec):
|
||||
return super().__eq__(other)
|
||||
elif isinstance(other, PathSpec):
|
||||
return False
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
# Support reversed order of arguments from PathSpec.
|
||||
@overload
|
||||
@classmethod
|
||||
def from_lines(
|
||||
cls: type[Self],
|
||||
pattern_factory: Union[str, Callable[[AnyStr], Pattern], None],
|
||||
lines: Iterable[AnyStr],
|
||||
*,
|
||||
backend: Union[BackendNamesHint, str, None] = None,
|
||||
_test_backend_factory: Optional[Callable[[Sequence[Pattern]], _Backend]] = None,
|
||||
) -> Self:
|
||||
...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def from_lines(
|
||||
cls: type[Self],
|
||||
lines: Iterable[AnyStr],
|
||||
pattern_factory: Union[str, Callable[[AnyStr], Pattern], None] = None,
|
||||
*,
|
||||
backend: Union[BackendNamesHint, str, None] = None,
|
||||
_test_backend_factory: Optional[Callable[[Sequence[Pattern]], _Backend]] = None,
|
||||
) -> Self:
|
||||
...
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def from_lines(
|
||||
cls: type[Self],
|
||||
lines: Iterable[AnyStr],
|
||||
pattern_factory: Union[str, Callable[[AnyStr], Pattern], None] = None,
|
||||
*,
|
||||
backend: Union[BackendNamesHint, str, None] = None,
|
||||
_test_backend_factory: Optional[Callable[[Sequence[Pattern]], _Backend]] = None,
|
||||
) -> Self:
|
||||
"""
|
||||
Compiles the pattern lines.
|
||||
|
||||
*lines* (:class:`~collections.abc.Iterable`) yields each uncompiled pattern
|
||||
(:class:`str`). This simply has to yield each line, so it can be a
|
||||
:class:`io.TextIOBase` (e.g., from :func:`open` or :class:`io.StringIO`) or
|
||||
the result from :meth:`str.splitlines`.
|
||||
|
||||
*pattern_factory* does not need to be set for :class:`GitIgnoreSpec`. If
|
||||
set, it should be either ``"gitignore"`` or :class:`.GitIgnoreSpecPattern`.
|
||||
There is no guarantee it will work with any other pattern class. Default is
|
||||
:data:`None` for :class:`.GitIgnoreSpecPattern`.
|
||||
|
||||
*backend* (:class:`str` or :data:`None`) is the pattern (regular expression)
|
||||
matching backend to use. Default is :data:`None` for "best" to use the best
|
||||
available backend. Priority of backends is: "re2", "hyperscan", "simple".
|
||||
The "simple" backend is always available.
|
||||
|
||||
Returns the :class:`GitIgnoreSpec` instance.
|
||||
"""
|
||||
if (isinstance(lines, (str, bytes)) or callable(lines)) and _is_iterable(pattern_factory):
|
||||
# Support reversed order of arguments from PathSpec.
|
||||
pattern_factory, lines = lines, pattern_factory
|
||||
|
||||
if pattern_factory is None:
|
||||
pattern_factory = GitIgnoreSpecPattern
|
||||
elif pattern_factory == 'gitignore':
|
||||
# Force use of GitIgnoreSpecPattern for "gitignore" to handle edge-cases.
|
||||
# This makes usage easier.
|
||||
pattern_factory = GitIgnoreSpecPattern
|
||||
|
||||
if isinstance(pattern_factory, str):
|
||||
pattern_factory = lookup_pattern(pattern_factory)
|
||||
|
||||
if issubclass(pattern_factory, GitIgnoreBasicPattern):
|
||||
raise TypeError((
|
||||
f"{pattern_factory=!r} cannot be {GitIgnoreBasicPattern} because it "
|
||||
f"will give unexpected results."
|
||||
)) # TypeError
|
||||
|
||||
self = super().from_lines(pattern_factory, lines, backend=backend, _test_backend_factory=_test_backend_factory)
|
||||
return cast(Self, self)
|
||||
|
||||
@override
|
||||
@staticmethod
|
||||
def _make_backend(
|
||||
name: BackendNamesHint,
|
||||
patterns: Sequence[Pattern],
|
||||
) -> _Backend:
|
||||
"""
|
||||
.. warning:: This method is not part of the public API. It is subject to
|
||||
change.
|
||||
|
||||
Create the backend for the patterns.
|
||||
|
||||
*name* (:class:`str`) is the name of the backend.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
|
||||
contains the compiled patterns.
|
||||
|
||||
Returns the backend (:class:`._Backend`).
|
||||
"""
|
||||
return make_gitignore_backend(name, patterns)
|
||||
460
lib/pathspec/pathspec.py
Normal file
460
lib/pathspec/pathspec.py
Normal file
@@ -0,0 +1,460 @@
|
||||
"""
|
||||
This module provides :class:`.PathSpec` which is an object-oriented interface
|
||||
for pattern matching of files.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import (
|
||||
Collection,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Sequence)
|
||||
from itertools import (
|
||||
zip_longest)
|
||||
from typing import (
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
TypeVar,
|
||||
Union, # Replaced by `X | Y` in 3.10.
|
||||
cast)
|
||||
|
||||
Self = TypeVar("Self", bound='PathSpec')
|
||||
"""
|
||||
:class:`.PathSpec` self type hint to support Python v<3.11 using PEP 673
|
||||
recommendation.
|
||||
"""
|
||||
|
||||
from pathspec import util
|
||||
from pathspec.backend import (
|
||||
_Backend,
|
||||
BackendNamesHint)
|
||||
from pathspec._backends.agg import (
|
||||
make_pathspec_backend)
|
||||
from pathspec.pattern import (
|
||||
Pattern)
|
||||
from pathspec._typing import (
|
||||
AnyStr, # Removed in 3.18.
|
||||
deprecated) # Added in 3.13.
|
||||
from pathspec.util import (
|
||||
CheckResult,
|
||||
StrPath,
|
||||
TStrPath,
|
||||
TreeEntry,
|
||||
_is_iterable,
|
||||
normalize_file)
|
||||
|
||||
|
||||
class PathSpec(object):
|
||||
"""
|
||||
The :class:`PathSpec` class is a wrapper around a list of compiled
|
||||
:class:`.Pattern` instances.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
patterns: Union[Sequence[Pattern], Iterable[Pattern]],
|
||||
*,
|
||||
backend: Union[BackendNamesHint, str, None] = None,
|
||||
_test_backend_factory: Optional[Callable[[Sequence[Pattern]], _Backend]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initializes the :class:`.PathSpec` instance.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Sequence` or :class:`~collections.abc.Iterable`)
|
||||
contains each compiled pattern (:class:`.Pattern`). If not a sequence, it
|
||||
will be converted to a :class:`list`.
|
||||
|
||||
*backend* (:class:`str` or :data:`None`) is the pattern (regular expression)
|
||||
matching backend to use. Default is :data:`None` for "best" to use the best
|
||||
available backend. Priority of backends is: "re2", "hyperscan", "simple".
|
||||
The "simple" backend is always available.
|
||||
"""
|
||||
if not isinstance(patterns, Sequence):
|
||||
patterns = list(patterns)
|
||||
|
||||
if backend is None:
|
||||
backend = 'best'
|
||||
|
||||
backend = cast(BackendNamesHint, backend)
|
||||
if _test_backend_factory is not None:
|
||||
use_backend = _test_backend_factory(patterns)
|
||||
else:
|
||||
use_backend = self._make_backend(backend, patterns)
|
||||
|
||||
self._backend: _Backend = use_backend
|
||||
"""
|
||||
*_backend* (:class:`._Backend`) is the pattern (regular expression) matching
|
||||
backend.
|
||||
"""
|
||||
|
||||
self._backend_name: BackendNamesHint = backend
|
||||
"""
|
||||
*_backend_name* (:class:`str`) is the name of backend to use.
|
||||
"""
|
||||
|
||||
self.patterns: Sequence[Pattern] = patterns
|
||||
"""
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
|
||||
contains the compiled patterns.
|
||||
"""
|
||||
|
||||
def __add__(self: Self, other: PathSpec) -> Self:
|
||||
"""
|
||||
Combines the :attr:`self.patterns <.PathSpec.patterns>` patterns from two
|
||||
:class:`PathSpec` instances.
|
||||
"""
|
||||
if isinstance(other, PathSpec):
|
||||
return self.__class__(self.patterns + other.patterns, backend=self._backend_name)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""
|
||||
Tests the equality of this path-spec with *other* (:class:`PathSpec`) by
|
||||
comparing their :attr:`self.patterns <.PathSpec.patterns>` attributes.
|
||||
"""
|
||||
if isinstance(other, PathSpec):
|
||||
paired_patterns = zip_longest(self.patterns, other.patterns)
|
||||
return all(a == b for a, b in paired_patterns)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __iadd__(self: Self, other: PathSpec) -> Self:
|
||||
"""
|
||||
Adds the :attr:`self.patterns <.PathSpec.patterns>` from *other*
|
||||
(:class:`PathSpec`) to this instance.
|
||||
"""
|
||||
if isinstance(other, PathSpec):
|
||||
self.patterns += other.patterns
|
||||
self._backend = self._make_backend(self._backend_name, self.patterns)
|
||||
return self
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Returns the number of :attr:`self.patterns <.PathSpec.patterns>` this
|
||||
path-spec contains (:class:`int`).
|
||||
"""
|
||||
return len(self.patterns)
|
||||
|
||||
def check_file(
|
||||
self,
|
||||
file: TStrPath,
|
||||
separators: Optional[Collection[str]] = None,
|
||||
) -> CheckResult[TStrPath]:
|
||||
"""
|
||||
Check the files against this path-spec.
|
||||
|
||||
*file* (:class:`str` or :class:`os.PathLike`) is the file path to be matched
|
||||
against :attr:`self.patterns <.PathSpec.patterns>`.
|
||||
|
||||
*separators* (:class:`~collections.abc.Collection` of :class:`str`; or
|
||||
:data:`None`) optionally contains the path separators to normalize. See
|
||||
:func:`.normalize_file` for more information.
|
||||
|
||||
Returns the file check result (:class:`.CheckResult`).
|
||||
"""
|
||||
norm_file = normalize_file(file, separators)
|
||||
include, index = self._backend.match_file(norm_file)
|
||||
return CheckResult(file, include, index)
|
||||
|
||||
def check_files(
|
||||
self,
|
||||
files: Iterable[TStrPath],
|
||||
separators: Optional[Collection[str]] = None,
|
||||
) -> Iterator[CheckResult[TStrPath]]:
|
||||
"""
|
||||
Check the files against this path-spec.
|
||||
|
||||
*files* (:class:`~collections.abc.Iterable` of :class:`str` or
|
||||
:class:`os.PathLike`) contains the file paths to be checked against
|
||||
:attr:`self.patterns <.PathSpec.patterns>`.
|
||||
|
||||
*separators* (:class:`~collections.abc.Collection` of :class:`str`; or
|
||||
:data:`None`) optionally contains the path separators to normalize. See
|
||||
:func:`.normalize_file` for more information.
|
||||
|
||||
Returns an :class:`~collections.abc.Iterator` yielding each file check
|
||||
result (:class:`.CheckResult`).
|
||||
"""
|
||||
if not _is_iterable(files):
|
||||
raise TypeError(f"files:{files!r} is not an iterable.")
|
||||
|
||||
for orig_file in files:
|
||||
norm_file = normalize_file(orig_file, separators)
|
||||
include, index = self._backend.match_file(norm_file)
|
||||
yield CheckResult(orig_file, include, index)
|
||||
|
||||
def check_tree_files(
|
||||
self,
|
||||
root: StrPath,
|
||||
on_error: Optional[Callable[[OSError], None]] = None,
|
||||
follow_links: Optional[bool] = None,
|
||||
) -> Iterator[CheckResult[str]]:
|
||||
"""
|
||||
Walks the specified root path for all files and checks them against this
|
||||
path-spec.
|
||||
|
||||
*root* (:class:`str` or :class:`os.PathLike`) is the root directory to
|
||||
search for files.
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally
|
||||
is the error handler for file-system exceptions. It will be called with the
|
||||
exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
|
||||
is :data:`None` to ignore file-system exceptions.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
|
||||
symbolic links that resolve to directories. Default is :data:`None` for
|
||||
:data:`True`.
|
||||
|
||||
*negate* (:class:`bool` or :data:`None`) is whether to negate the match
|
||||
results of the patterns. If :data:`True`, a pattern matching a file will
|
||||
exclude the file rather than include it. Default is :data:`None` for
|
||||
:data:`False`.
|
||||
|
||||
Returns an :class:`~collections.abc.Iterator` yielding each file check
|
||||
result (:class:`.CheckResult`).
|
||||
"""
|
||||
files = util.iter_tree_files(root, on_error=on_error, follow_links=follow_links)
|
||||
yield from self.check_files(files)
|
||||
|
||||
@classmethod
|
||||
def from_lines(
|
||||
cls: type[Self],
|
||||
pattern_factory: Union[str, Callable[[AnyStr], Pattern]],
|
||||
lines: Iterable[AnyStr],
|
||||
*,
|
||||
backend: Union[BackendNamesHint, str, None] = None,
|
||||
_test_backend_factory: Optional[Callable[[Sequence[Pattern]], _Backend]] = None,
|
||||
) -> Self:
|
||||
"""
|
||||
Compiles the pattern lines.
|
||||
|
||||
*pattern_factory* can be either the name of a registered pattern factory
|
||||
(:class:`str`), or a :class:`~collections.abc.Callable` used to compile
|
||||
patterns. It must accept an uncompiled pattern (:class:`str`) and return the
|
||||
compiled pattern (:class:`.Pattern`).
|
||||
|
||||
*lines* (:class:`~collections.abc.Iterable`) yields each uncompiled pattern
|
||||
(:class:`str`). This simply has to yield each line so that it can be a
|
||||
:class:`io.TextIOBase` (e.g., from :func:`open` or :class:`io.StringIO`) or
|
||||
the result from :meth:`str.splitlines`.
|
||||
|
||||
*backend* (:class:`str` or :data:`None`) is the pattern (or regular
|
||||
expression) matching backend to use. Default is :data:`None` for "best" to
|
||||
use the best available backend. Priority of backends is: "re2", "hyperscan",
|
||||
"simple". The "simple" backend is always available.
|
||||
|
||||
Returns the :class:`PathSpec` instance.
|
||||
"""
|
||||
if isinstance(pattern_factory, str):
|
||||
pattern_factory = util.lookup_pattern(pattern_factory)
|
||||
|
||||
if not callable(pattern_factory):
|
||||
raise TypeError(f"pattern_factory:{pattern_factory!r} is not callable.")
|
||||
|
||||
if not _is_iterable(lines):
|
||||
raise TypeError(f"lines:{lines!r} is not an iterable.")
|
||||
|
||||
patterns = [pattern_factory(line) for line in lines if line]
|
||||
return cls(patterns, backend=backend, _test_backend_factory=_test_backend_factory)
|
||||
|
||||
@staticmethod
|
||||
def _make_backend(
|
||||
name: BackendNamesHint,
|
||||
patterns: Sequence[Pattern],
|
||||
) -> _Backend:
|
||||
"""
|
||||
.. warning:: This method is not part of the public API. It is subject to
|
||||
change.
|
||||
|
||||
Create the backend for the patterns.
|
||||
|
||||
*name* (:class:`str`) is the name of the backend.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
|
||||
contains the compiled patterns.
|
||||
|
||||
Returns the matcher (:class:`._Backend`).
|
||||
"""
|
||||
return make_pathspec_backend(name, patterns)
|
||||
|
||||
def match_entries(
|
||||
self,
|
||||
entries: Iterable[TreeEntry],
|
||||
separators: Optional[Collection[str]] = None,
|
||||
*,
|
||||
negate: Optional[bool] = None,
|
||||
) -> Iterator[TreeEntry]:
|
||||
"""
|
||||
Matches the entries to this path-spec.
|
||||
|
||||
*entries* (:class:`~collections.abc.Iterable` of :class:`.TreeEntry`)
|
||||
contains the entries to be matched against :attr:`self.patterns <.PathSpec.patterns>`.
|
||||
|
||||
*separators* (:class:`~collections.abc.Collection` of :class:`str`; or
|
||||
:data:`None`) optionally contains the path separators to normalize. See
|
||||
:func:`.normalize_file` for more information.
|
||||
|
||||
*negate* (:class:`bool` or :data:`None`) is whether to negate the match
|
||||
results of the patterns. If :data:`True`, a pattern matching a file will
|
||||
exclude the file rather than include it. Default is :data:`None` for
|
||||
:data:`False`.
|
||||
|
||||
Returns the matched entries (:class:`~collections.abc.Iterator` of
|
||||
:class:`.TreeEntry`).
|
||||
"""
|
||||
if not _is_iterable(entries):
|
||||
raise TypeError(f"entries:{entries!r} is not an iterable.")
|
||||
|
||||
for entry in entries:
|
||||
norm_file = normalize_file(entry.path, separators)
|
||||
include, _index = self._backend.match_file(norm_file)
|
||||
|
||||
if negate:
|
||||
include = not include
|
||||
|
||||
if include:
|
||||
yield entry
|
||||
|
||||
def match_file(
|
||||
self,
|
||||
file: StrPath,
|
||||
separators: Optional[Collection[str]] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Matches the file to this path-spec.
|
||||
|
||||
*file* (:class:`str` or :class:`os.PathLike`) is the file path to be matched
|
||||
against :attr:`self.patterns <.PathSpec.patterns>`.
|
||||
|
||||
*separators* (:class:`~collections.abc.Collection` of :class:`str`)
|
||||
optionally contains the path separators to normalize. See
|
||||
:func:`.normalize_file` for more information.
|
||||
|
||||
Returns :data:`True` if *file* matched; otherwise, :data:`False`.
|
||||
"""
|
||||
norm_file = normalize_file(file, separators)
|
||||
include, _index = self._backend.match_file(norm_file)
|
||||
return bool(include)
|
||||
|
||||
def match_files(
|
||||
self,
|
||||
files: Iterable[StrPath],
|
||||
separators: Optional[Collection[str]] = None,
|
||||
*,
|
||||
negate: Optional[bool] = None,
|
||||
) -> Iterator[StrPath]:
|
||||
"""
|
||||
Matches the files to this path-spec.
|
||||
|
||||
*files* (:class:`~collections.abc.Iterable` of :class:`str` or
|
||||
:class:`os.PathLike`) contains the file paths to be matched against
|
||||
:attr:`self.patterns <.PathSpec.patterns>`.
|
||||
|
||||
*separators* (:class:`~collections.abc.Collection` of :class:`str`; or
|
||||
:data:`None`) optionally contains the path separators to normalize. See
|
||||
:func:`.normalize_file` for more information.
|
||||
|
||||
*negate* (:class:`bool` or :data:`None`) is whether to negate the match
|
||||
results of the patterns. If :data:`True`, a pattern matching a file will
|
||||
exclude the file rather than include it. Default is :data:`None` for
|
||||
:data:`False`.
|
||||
|
||||
Returns the matched files (:class:`~collections.abc.Iterator` of
|
||||
:class:`str` or :class:`os.PathLike`).
|
||||
"""
|
||||
if not _is_iterable(files):
|
||||
raise TypeError(f"files:{files!r} is not an iterable.")
|
||||
|
||||
for orig_file in files:
|
||||
norm_file = normalize_file(orig_file, separators)
|
||||
include, _index = self._backend.match_file(norm_file)
|
||||
|
||||
if negate:
|
||||
include = not include
|
||||
|
||||
if include:
|
||||
yield orig_file
|
||||
|
||||
def match_tree_entries(
|
||||
self,
|
||||
root: StrPath,
|
||||
on_error: Optional[Callable[[OSError], None]] = None,
|
||||
follow_links: Optional[bool] = None,
|
||||
*,
|
||||
negate: Optional[bool] = None,
|
||||
) -> Iterator[TreeEntry]:
|
||||
"""
|
||||
Walks the specified root path for all files and matches them to this
|
||||
path-spec.
|
||||
|
||||
*root* (:class:`str` or :class:`os.PathLike`) is the root directory to
|
||||
search.
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally
|
||||
is the error handler for file-system exceptions. It will be called with the
|
||||
exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
|
||||
is :data:`None` to ignore file-system exceptions.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
|
||||
symbolic links that resolve to directories. Default is :data:`None` for
|
||||
:data:`True`.
|
||||
|
||||
*negate* (:class:`bool` or :data:`None`) is whether to negate the match
|
||||
results of the patterns. If :data:`True`, a pattern matching a file will
|
||||
exclude the file rather than include it. Default is :data:`None` for
|
||||
:data:`False`.
|
||||
|
||||
Returns the matched files (:class:`~collections.abc.Iterator` of
|
||||
:class:`.TreeEntry`).
|
||||
"""
|
||||
entries = util.iter_tree_entries(root, on_error=on_error, follow_links=follow_links)
|
||||
yield from self.match_entries(entries, negate=negate)
|
||||
|
||||
# NOTICE: The deprecation warning was only added in 1.0.0 (from 2026-01-05).
|
||||
@deprecated((
|
||||
"PathSpec.match_tree() is deprecated. Use .match_tree_files() instead."
|
||||
))
|
||||
def match_tree(self, *args, **kw) -> Iterator[str]:
|
||||
"""
|
||||
.. version-deprecated:: 0.3.2
|
||||
This is an alias for the :meth:`self.match_tree_files <.PathSpec.match_tree_files>`
|
||||
method.
|
||||
"""
|
||||
return self.match_tree_files(*args, **kw)
|
||||
|
||||
def match_tree_files(
|
||||
self,
|
||||
root: StrPath,
|
||||
on_error: Optional[Callable[[OSError], None]] = None,
|
||||
follow_links: Optional[bool] = None,
|
||||
*,
|
||||
negate: Optional[bool] = None,
|
||||
) -> Iterator[str]:
|
||||
"""
|
||||
Walks the specified root path for all files and matches them to this
|
||||
path-spec.
|
||||
|
||||
*root* (:class:`str` or :class:`os.PathLike`) is the root directory to
|
||||
search for files.
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally
|
||||
is the error handler for file-system exceptions. It will be called with the
|
||||
exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
|
||||
is :data:`None` to ignore file-system exceptions.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
|
||||
symbolic links that resolve to directories. Default is :data:`None` for
|
||||
:data:`True`.
|
||||
|
||||
*negate* (:class:`bool` or :data:`None`) is whether to negate the match
|
||||
results of the patterns. If :data:`True`, a pattern matching a file will
|
||||
exclude the file rather than include it. Default is :data:`None` for
|
||||
:data:`False`.
|
||||
|
||||
Returns the matched files (:class:`~collections.abc.Iterable` of :class:`str`).
|
||||
"""
|
||||
files = util.iter_tree_files(root, on_error=on_error, follow_links=follow_links)
|
||||
yield from self.match_files(files, negate=negate)
|
||||
241
lib/pathspec/pattern.py
Normal file
241
lib/pathspec/pattern.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
This module provides the base definition for patterns.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import (
|
||||
Iterable,
|
||||
Iterator)
|
||||
from dataclasses import (
|
||||
dataclass)
|
||||
from typing import (
|
||||
Any,
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
TypeVar,
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
from ._typing import (
|
||||
AnyStr, # Removed in 3.18.
|
||||
deprecated, # Added in 3.13.
|
||||
override) # Added in 3.12.
|
||||
|
||||
RegexPatternSelf = TypeVar("RegexPatternSelf", bound='RegexPattern')
|
||||
"""
|
||||
:class:`.RegexPattern` self type hint to support Python v<3.11 using PEP 673
|
||||
recommendation.
|
||||
"""
|
||||
|
||||
class Pattern(object):
|
||||
"""
|
||||
The :class:`Pattern` class is the abstract definition of a pattern.
|
||||
"""
|
||||
|
||||
# Make the class dict-less.
|
||||
__slots__ = (
|
||||
'include',
|
||||
)
|
||||
|
||||
def __init__(self, include: Optional[bool]) -> None:
|
||||
"""
|
||||
Initializes the :class:`Pattern` instance.
|
||||
|
||||
*include* (:class:`bool` or :data:`None`) is whether the matched files
|
||||
should be included (:data:`True`), excluded (:data:`False`), or is a
|
||||
null-operation (:data:`None`).
|
||||
"""
|
||||
|
||||
self.include = include
|
||||
"""
|
||||
*include* (:class:`bool` or :data:`None`) is whether the matched files
|
||||
should be included (:data:`True`), excluded (:data:`False`), or is a
|
||||
null-operation (:data:`None`).
|
||||
"""
|
||||
|
||||
@deprecated((
|
||||
"Pattern.match() is deprecated. Use Pattern.match_file() with a loop for "
|
||||
"similar results."
|
||||
))
|
||||
def match(self, files: Iterable[str]) -> Iterator[str]:
|
||||
"""
|
||||
.. version-deprecated:: 0.10.0
|
||||
This method is no longer used. Use the :meth:`self.match_file <.Pattern.match_file>`
|
||||
method with a loop for similar results.
|
||||
|
||||
Matches this pattern against the specified files.
|
||||
|
||||
*files* (:class:`~collections.abc.Iterable` of :class:`str`) contains each
|
||||
file relative to the root directory.
|
||||
|
||||
Returns an :class:`~collections.abc.Iterable` yielding each matched file
|
||||
path (:class:`str`).
|
||||
"""
|
||||
for file in files:
|
||||
if self.match_file(file) is not None:
|
||||
yield file
|
||||
|
||||
def match_file(self, file: str) -> Optional[Any]:
|
||||
"""
|
||||
Matches this pattern against the specified file.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to match against.
|
||||
|
||||
Returns the match result if *file* matched; otherwise, :data:`None`.
|
||||
"""
|
||||
raise NotImplementedError((
|
||||
"{cls.__module__}.{cls.__qualname__} must override match_file()."
|
||||
).format(cls=self.__class__))
|
||||
|
||||
|
||||
class RegexPattern(Pattern):
|
||||
"""
|
||||
The :class:`RegexPattern` class is an implementation of a pattern using
|
||||
regular expressions.
|
||||
"""
|
||||
|
||||
# Keep the class dict-less.
|
||||
__slots__ = (
|
||||
'pattern',
|
||||
'regex',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pattern: Union[AnyStr, re.Pattern, None],
|
||||
include: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initializes the :class:`RegexPattern` instance.
|
||||
|
||||
*pattern* (:class:`str`, :class:`bytes`, :class:`re.Pattern`, or
|
||||
:data:`None`) is the pattern to compile into a regular expression.
|
||||
|
||||
*include* (:class:`bool` or :data:`None`) must be :data:`None` unless
|
||||
*pattern* is a precompiled regular expression (:class:`re.Pattern`) in which
|
||||
case it is whether matched files should be included (:data:`True`), excluded
|
||||
(:data:`False`), or is a null operation (:data:`None`).
|
||||
|
||||
.. note:: Subclasses do not need to support the *include* parameter.
|
||||
"""
|
||||
|
||||
if isinstance(pattern, (str, bytes)):
|
||||
assert include is None, (
|
||||
f"include:{include!r} must be null when pattern:{pattern!r} is a string."
|
||||
)
|
||||
regex, include = self.pattern_to_regex(pattern)
|
||||
# NOTE: Make sure to allow a null regular expression to be
|
||||
# returned for a null-operation.
|
||||
if include is not None:
|
||||
regex = re.compile(regex)
|
||||
|
||||
elif pattern is not None and hasattr(pattern, 'match'):
|
||||
# Assume pattern is a precompiled regular expression.
|
||||
# - NOTE: Used specified *include*.
|
||||
regex = pattern
|
||||
|
||||
elif pattern is None:
|
||||
# NOTE: Make sure to allow a null pattern to be passed for a
|
||||
# null-operation.
|
||||
assert include is None, (
|
||||
f"include:{include!r} must be null when pattern:{pattern!r} is null."
|
||||
)
|
||||
regex = None
|
||||
|
||||
else:
|
||||
raise TypeError(f"pattern:{pattern!r} is not a string, re.Pattern, or None.")
|
||||
|
||||
super(RegexPattern, self).__init__(include)
|
||||
|
||||
self.pattern: Union[AnyStr, re.Pattern, None] = pattern
|
||||
"""
|
||||
*pattern* (:class:`str`, :class:`bytes`, :class:`re.Pattern`, or
|
||||
:data:`None`) is the uncompiled, input pattern. This is for reference.
|
||||
"""
|
||||
|
||||
self.regex: Optional[re.Pattern] = regex
|
||||
"""
|
||||
*regex* (:class:`re.Pattern` or :data:`None`) is the compiled regular
|
||||
expression for the pattern.
|
||||
"""
|
||||
|
||||
def __copy__(self: RegexPatternSelf) -> RegexPatternSelf:
|
||||
"""
|
||||
Performa a shallow copy of the pattern.
|
||||
|
||||
Returns the copy (:class:`RegexPattern`).
|
||||
"""
|
||||
other = self.__class__(self.regex, self.include)
|
||||
other.pattern = self.pattern
|
||||
return other
|
||||
|
||||
def __eq__(self, other: RegexPattern) -> bool:
|
||||
"""
|
||||
Tests the equality of this regex pattern with *other* (:class:`RegexPattern`)
|
||||
by comparing their :attr:`~Pattern.include` and :attr:`~RegexPattern.regex`
|
||||
attributes.
|
||||
"""
|
||||
if isinstance(other, RegexPattern):
|
||||
return self.include == other.include and self.regex == other.regex
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
@override
|
||||
def match_file(self, file: AnyStr) -> Optional[RegexMatchResult]:
|
||||
"""
|
||||
Matches this pattern against the specified file.
|
||||
|
||||
*file* (:class:`str` or :class:`bytes`) is the file path relative to the
|
||||
root directory (e.g., "relative/path/to/file").
|
||||
|
||||
Returns the match result (:class:`.RegexMatchResult`) if *file* matched;
|
||||
otherwise, :data:`None`.
|
||||
"""
|
||||
if self.include is not None:
|
||||
match = self.regex.search(file)
|
||||
if match is not None:
|
||||
return RegexMatchResult(match)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def pattern_to_regex(
|
||||
cls,
|
||||
pattern: AnyStr,
|
||||
) -> tuple[Optional[AnyStr], Optional[bool]]:
|
||||
"""
|
||||
Convert the pattern into an uncompiled regular expression.
|
||||
|
||||
*pattern* (:class:`str` or :class:`bytes`) is the pattern to convert into a
|
||||
regular expression.
|
||||
|
||||
Returns a :class:`tuple` containing:
|
||||
|
||||
- *pattern* (:class:`str`, :class:`bytes` or :data:`None`) is the
|
||||
uncompiled regular expression .
|
||||
|
||||
- *include* (:class:`bool` or :data:`None`) is whether matched files
|
||||
should be included (:data:`True`), excluded (:data:`False`), or is a
|
||||
null-operation (:data:`None`).
|
||||
|
||||
.. note:: The default implementation simply returns *pattern* and
|
||||
:data:`True`.
|
||||
"""
|
||||
return pattern, True
|
||||
|
||||
|
||||
@dataclass()
|
||||
class RegexMatchResult(object):
|
||||
"""
|
||||
The :class:`RegexMatchResult` data class is used to return information about
|
||||
the matched regular expression.
|
||||
"""
|
||||
|
||||
# Keep the class dict-less.
|
||||
__slots__ = (
|
||||
'match',
|
||||
)
|
||||
|
||||
match: re.Match
|
||||
"""
|
||||
*match* (:class:`re.Match`) is the regex match result.
|
||||
"""
|
||||
12
lib/pathspec/patterns/__init__.py
Normal file
12
lib/pathspec/patterns/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
The *pathspec.patterns* package contains the pattern matching implementations.
|
||||
"""
|
||||
|
||||
# Load pattern implementations.
|
||||
from .gitignore import basic as _
|
||||
from .gitignore import spec as _
|
||||
|
||||
# DEPRECATED: Deprecated since 0.11.0 (from 2023-01-24). Expose the
|
||||
# GitWildMatchPattern class in this module for backward compatibility with
|
||||
# 0.5.0 (from 2016-08-22).
|
||||
from .gitwildmatch import GitWildMatchPattern
|
||||
BIN
lib/pathspec/patterns/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
lib/pathspec/patterns/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
lib/pathspec/patterns/__pycache__/gitwildmatch.cpython-314.pyc
Normal file
BIN
lib/pathspec/patterns/__pycache__/gitwildmatch.cpython-314.pyc
Normal file
Binary file not shown.
17
lib/pathspec/patterns/gitignore/__init__.py
Normal file
17
lib/pathspec/patterns/gitignore/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
The *pathspec.patterns.gitignore* package provides the *gitignore*
|
||||
implementations.
|
||||
|
||||
The following classes are imported and made available from this package:
|
||||
|
||||
- :class:`pathspec.patterns.gitignore.base.GitIgnorePatternError`
|
||||
"""
|
||||
|
||||
# Expose the GitIgnorePatternError for convenience.
|
||||
from .base import (
|
||||
GitIgnorePatternError)
|
||||
|
||||
# Declare imports as part of the public interface.
|
||||
__all__ = [
|
||||
'GitIgnorePatternError',
|
||||
]
|
||||
Binary file not shown.
BIN
lib/pathspec/patterns/gitignore/__pycache__/base.cpython-314.pyc
Normal file
BIN
lib/pathspec/patterns/gitignore/__pycache__/base.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
lib/pathspec/patterns/gitignore/__pycache__/spec.cpython-314.pyc
Normal file
BIN
lib/pathspec/patterns/gitignore/__pycache__/spec.cpython-314.pyc
Normal file
Binary file not shown.
176
lib/pathspec/patterns/gitignore/base.py
Normal file
176
lib/pathspec/patterns/gitignore/base.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
This module provides common classes for the gitignore patterns.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from pathspec.pattern import (
|
||||
RegexPattern)
|
||||
from pathspec._typing import (
|
||||
AnyStr) # Removed in 3.18.
|
||||
|
||||
_BYTES_ENCODING = 'latin1'
|
||||
"""
|
||||
The encoding to use when parsing a byte string pattern.
|
||||
"""
|
||||
|
||||
|
||||
class _GitIgnoreBasePattern(RegexPattern):
|
||||
"""
|
||||
.. warning:: This class is not part of the public API. It is subject to
|
||||
change.
|
||||
|
||||
The :class:`_GitIgnoreBasePattern` class is the base implementation for a
|
||||
compiled gitignore pattern.
|
||||
"""
|
||||
|
||||
# Keep the dict-less class hierarchy.
|
||||
__slots__ = ()
|
||||
|
||||
@staticmethod
|
||||
def escape(s: AnyStr) -> AnyStr:
|
||||
"""
|
||||
Escape special characters in the given string.
|
||||
|
||||
*s* (:class:`str` or :class:`bytes`) a filename or a string that you want to
|
||||
escape, usually before adding it to a ".gitignore".
|
||||
|
||||
Returns the escaped string (:class:`str` or :class:`bytes`).
|
||||
"""
|
||||
if isinstance(s, str):
|
||||
return_type = str
|
||||
string = s
|
||||
elif isinstance(s, bytes):
|
||||
return_type = bytes
|
||||
string = s.decode(_BYTES_ENCODING)
|
||||
else:
|
||||
raise TypeError(f"s:{s!r} is not a unicode or byte string.")
|
||||
|
||||
# Reference: https://git-scm.com/docs/gitignore#_pattern_format
|
||||
out_string = ''.join((f"\\{x}" if x in '[]!*#?' else x) for x in string)
|
||||
|
||||
if return_type is bytes:
|
||||
return out_string.encode(_BYTES_ENCODING)
|
||||
else:
|
||||
return out_string
|
||||
|
||||
@staticmethod
|
||||
def _translate_segment_glob(pattern: str) -> str:
|
||||
"""
|
||||
Translates the glob pattern to a regular expression. This is used in the
|
||||
constructor to translate a path segment glob pattern to its corresponding
|
||||
regular expression.
|
||||
|
||||
*pattern* (:class:`str`) is the glob pattern.
|
||||
|
||||
Returns the regular expression (:class:`str`).
|
||||
"""
|
||||
# NOTE: This is derived from `fnmatch.translate()` and is similar to the
|
||||
# POSIX function `fnmatch()` with the `FNM_PATHNAME` flag set.
|
||||
|
||||
escape = False
|
||||
regex = ''
|
||||
i, end = 0, len(pattern)
|
||||
while i < end:
|
||||
# Get next character.
|
||||
char = pattern[i]
|
||||
i += 1
|
||||
|
||||
if escape:
|
||||
# Escape the character.
|
||||
escape = False
|
||||
regex += re.escape(char)
|
||||
|
||||
elif char == '\\':
|
||||
# Escape character, escape next character.
|
||||
escape = True
|
||||
|
||||
elif char == '*':
|
||||
# Multi-character wildcard. Match any string (except slashes), including
|
||||
# an empty string.
|
||||
regex += '[^/]*'
|
||||
|
||||
elif char == '?':
|
||||
# Single-character wildcard. Match any single character (except a
|
||||
# slash).
|
||||
regex += '[^/]'
|
||||
|
||||
elif char == '[':
|
||||
# Bracket expression wildcard. Except for the beginning exclamation
|
||||
# mark, the whole bracket expression can be used directly as regex, but
|
||||
# we have to find where the expression ends.
|
||||
# - "[][!]" matches ']', '[' and '!'.
|
||||
# - "[]-]" matches ']' and '-'.
|
||||
# - "[!]a-]" matches any character except ']', 'a' and '-'.
|
||||
j = i
|
||||
|
||||
# Pass bracket expression negation.
|
||||
if j < end and (pattern[j] == '!' or pattern[j] == '^'):
|
||||
j += 1
|
||||
|
||||
# Pass first closing bracket if it is at the beginning of the
|
||||
# expression.
|
||||
if j < end and pattern[j] == ']':
|
||||
j += 1
|
||||
|
||||
# Find closing bracket. Stop once we reach the end or find it.
|
||||
while j < end and pattern[j] != ']':
|
||||
j += 1
|
||||
|
||||
if j < end:
|
||||
# Found end of bracket expression. Increment j to be one past the
|
||||
# closing bracket:
|
||||
#
|
||||
# [...]
|
||||
# ^ ^
|
||||
# i j
|
||||
#
|
||||
j += 1
|
||||
expr = '['
|
||||
|
||||
if pattern[i] == '!':
|
||||
# Bracket expression needs to be negated.
|
||||
expr += '^'
|
||||
i += 1
|
||||
elif pattern[i] == '^':
|
||||
# POSIX declares that the regex bracket expression negation "[^...]"
|
||||
# is undefined in a glob pattern. Python's `fnmatch.translate()`
|
||||
# escapes the caret ('^') as a literal. Git supports the using a
|
||||
# caret for negation. Maintain consistency with Git because that is
|
||||
# the expected behavior.
|
||||
expr += '^'
|
||||
i += 1
|
||||
|
||||
# Build regex bracket expression. Escape slashes so they are treated
|
||||
# as literal slashes by regex as defined by POSIX.
|
||||
expr += pattern[i:j].replace('\\', '\\\\')
|
||||
|
||||
# Add regex bracket expression to regex result.
|
||||
regex += expr
|
||||
|
||||
# Set i to one past the closing bracket.
|
||||
i = j
|
||||
|
||||
else:
|
||||
# Failed to find closing bracket, treat opening bracket as a bracket
|
||||
# literal instead of as an expression.
|
||||
regex += '\\['
|
||||
|
||||
else:
|
||||
# Regular character, escape it for regex.
|
||||
regex += re.escape(char)
|
||||
|
||||
if escape:
|
||||
raise ValueError((
|
||||
f"Escape character found with no next character to escape: {pattern!r}"
|
||||
)) # ValueError
|
||||
|
||||
return regex
|
||||
|
||||
|
||||
class GitIgnorePatternError(ValueError):
|
||||
"""
|
||||
The :class:`GitIgnorePatternError` class indicates an invalid gitignore
|
||||
pattern.
|
||||
"""
|
||||
pass
|
||||
317
lib/pathspec/patterns/gitignore/basic.py
Normal file
317
lib/pathspec/patterns/gitignore/basic.py
Normal file
@@ -0,0 +1,317 @@
|
||||
"""
|
||||
This module provides :class:`GitIgnoreBasicPattern` which implements Git's
|
||||
`gitignore`_ patterns as documented. This differs from how Git actually behaves
|
||||
when including files in excluded directories.
|
||||
|
||||
.. _`gitignore`: https://git-scm.com/docs/gitignore
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
Optional) # Replaced by `X | None` in 3.10.
|
||||
|
||||
from pathspec import util
|
||||
from pathspec._typing import (
|
||||
AnyStr, # Removed in 3.18.
|
||||
assert_unreachable,
|
||||
override) # Added in 3.12.
|
||||
|
||||
from .base import (
|
||||
GitIgnorePatternError,
|
||||
_BYTES_ENCODING,
|
||||
_GitIgnoreBasePattern)
|
||||
|
||||
|
||||
class GitIgnoreBasicPattern(_GitIgnoreBasePattern):
|
||||
"""
|
||||
The :class:`GitIgnoreBasicPattern` class represents a compiled gitignore
|
||||
pattern as documented. This is registered as "gitignore".
|
||||
"""
|
||||
|
||||
# Keep the dict-less class hierarchy.
|
||||
__slots__ = ()
|
||||
|
||||
@staticmethod
|
||||
def __normalize_segments(
|
||||
is_dir_pattern: bool,
|
||||
pattern_segs: list[str],
|
||||
) -> tuple[Optional[list[str]], Optional[str]]:
|
||||
"""
|
||||
Normalize the pattern segments to make processing easier.
|
||||
|
||||
*is_dir_pattern* (:class:`bool`) is whether the pattern is a directory
|
||||
pattern (i.e., ends with a slash '/').
|
||||
|
||||
*pattern_segs* (:class:`list` of :class:`str`) contains the pattern
|
||||
segments. This may be modified in place.
|
||||
|
||||
Returns a :class:`tuple` containing either:
|
||||
|
||||
- The normalized segments (:class:`list` of :class:`str`; or :data:`None`).
|
||||
|
||||
- The regular expression override (:class:`str` or :data:`None`).
|
||||
"""
|
||||
if not pattern_segs[0]:
|
||||
# A pattern beginning with a slash ('/') should match relative to the root
|
||||
# directory. Remove the empty first segment to make the pattern relative
|
||||
# to root.
|
||||
del pattern_segs[0]
|
||||
|
||||
elif len(pattern_segs) == 1 or (len(pattern_segs) == 2 and not pattern_segs[1]):
|
||||
# A single segment pattern with or without a trailing slash ('/') will
|
||||
# match any descendant path. This is equivalent to "**/{pattern}". Prepend
|
||||
# double-asterisk segment to make pattern relative to root.
|
||||
if pattern_segs[0] != '**':
|
||||
pattern_segs.insert(0, '**')
|
||||
|
||||
else:
|
||||
# A pattern without a beginning slash ('/') but contains at least one
|
||||
# prepended directory (e.g., "dir/{pattern}") should match relative to the
|
||||
# root directory. No segment modification is needed.
|
||||
pass
|
||||
|
||||
if not pattern_segs:
|
||||
# After normalization, we end up with no pattern at all. This must be
|
||||
# because the pattern is invalid.
|
||||
raise ValueError("Pattern normalized to nothing.")
|
||||
|
||||
if not pattern_segs[-1]:
|
||||
# A pattern ending with a slash ('/') will match all descendant paths if
|
||||
# it is a directory but not if it is a regular file. This is equivalent to
|
||||
# "{pattern}/**". Set empty last segment to a double-asterisk to include
|
||||
# all descendants.
|
||||
pattern_segs[-1] = '**'
|
||||
|
||||
# EDGE CASE: Collapse duplicate double-asterisk sequences (i.e., '**/**').
|
||||
# Iterate over the segments in reverse order and remove the duplicate double
|
||||
# asterisks as we go.
|
||||
for i in range(len(pattern_segs) - 1, 0, -1):
|
||||
prev = pattern_segs[i-1]
|
||||
seg = pattern_segs[i]
|
||||
if prev == '**' and seg == '**':
|
||||
del pattern_segs[i]
|
||||
|
||||
seg_count = len(pattern_segs)
|
||||
if seg_count == 1 and pattern_segs[0] == '**':
|
||||
if is_dir_pattern:
|
||||
# The pattern "**/" will be normalized to "**", but it should match
|
||||
# everything except for files in the root. Special case this pattern.
|
||||
return (None, '/')
|
||||
else:
|
||||
# The pattern "**" will match every path. Special case this pattern.
|
||||
return (None, '.')
|
||||
|
||||
elif (
|
||||
seg_count == 2
|
||||
and pattern_segs[0] == '**'
|
||||
and pattern_segs[1] == '*'
|
||||
):
|
||||
# The pattern "*" will be normalized to "**/*" and will match every
|
||||
# path. Special case this pattern for efficiency.
|
||||
return (None, '.')
|
||||
|
||||
elif (
|
||||
seg_count == 3
|
||||
and pattern_segs[0] == '**'
|
||||
and pattern_segs[1] == '*'
|
||||
and pattern_segs[2] == '**'
|
||||
):
|
||||
# The pattern "*/" will be normalized to "**/*/**" which will match every
|
||||
# file not in the root directory. Special case this pattern for
|
||||
# efficiency.
|
||||
return (None, '/')
|
||||
|
||||
# No regular expression override, return modified pattern segments.
|
||||
return (pattern_segs, None)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def pattern_to_regex(
|
||||
cls,
|
||||
pattern: AnyStr,
|
||||
) -> tuple[Optional[AnyStr], Optional[bool]]:
|
||||
"""
|
||||
Convert the pattern into a regular expression.
|
||||
|
||||
*pattern* (:class:`str` or :class:`bytes`) is the pattern to convert into a
|
||||
regular expression.
|
||||
|
||||
Returns a :class:`tuple` containing:
|
||||
|
||||
- *pattern* (:class:`str`, :class:`bytes` or :data:`None`) is the
|
||||
uncompiled regular expression.
|
||||
|
||||
- *include* (:class:`bool` or :data:`None`) is whether matched files
|
||||
should be included (:data:`True`), excluded (:data:`False`), or is a
|
||||
null-operation (:data:`None`).
|
||||
"""
|
||||
if isinstance(pattern, str):
|
||||
pattern_str = pattern
|
||||
return_type = str
|
||||
elif isinstance(pattern, bytes):
|
||||
pattern_str = pattern.decode(_BYTES_ENCODING)
|
||||
return_type = bytes
|
||||
else:
|
||||
raise TypeError(f"{pattern=!r} is not a unicode or byte string.")
|
||||
|
||||
original_pattern = pattern_str
|
||||
del pattern
|
||||
|
||||
if pattern_str.endswith('\\ '):
|
||||
# EDGE CASE: Spaces can be escaped with backslash. If a pattern that ends
|
||||
# with a backslash is followed by a space, do not strip from the left.
|
||||
pass
|
||||
else:
|
||||
# EDGE CASE: Leading spaces should be kept (only trailing spaces should be
|
||||
# removed).
|
||||
pattern_str = pattern_str.rstrip()
|
||||
|
||||
regex: Optional[str]
|
||||
include: Optional[bool]
|
||||
|
||||
if not pattern_str:
|
||||
# A blank pattern is a null-operation (neither includes nor excludes
|
||||
# files).
|
||||
return (None, None)
|
||||
|
||||
elif pattern_str.startswith('#'):
|
||||
# A pattern starting with a hash ('#') serves as a comment (neither
|
||||
# includes nor excludes files). Escape the hash with a backslash to match
|
||||
# a literal hash (i.e., '\#').
|
||||
return (None, None)
|
||||
|
||||
if pattern_str.startswith('!'):
|
||||
# A pattern starting with an exclamation mark ('!') negates the pattern
|
||||
# (exclude instead of include). Escape the exclamation mark with a back
|
||||
# slash to match a literal exclamation mark (i.e., '\!').
|
||||
include = False
|
||||
# Remove leading exclamation mark.
|
||||
pattern_str = pattern_str[1:]
|
||||
else:
|
||||
include = True
|
||||
|
||||
# Split pattern into segments.
|
||||
pattern_segs = pattern_str.split('/')
|
||||
|
||||
# Check whether the pattern is specifically a directory pattern before
|
||||
# normalization.
|
||||
is_dir_pattern = not pattern_segs[-1]
|
||||
|
||||
if pattern_str == '/':
|
||||
# EDGE CASE: A single slash ('/') is not addressed by the gitignore
|
||||
# documentation. Git treats it as a no-op (does not match any files). The
|
||||
# straight forward interpretation is to treat it as a directory and match
|
||||
# every descendant path (equivalent to '**'). Remove the directory pattern
|
||||
# flag so that it is treated as '**' instead of '**/'.
|
||||
is_dir_pattern = False
|
||||
|
||||
# Normalize pattern to make processing easier.
|
||||
try:
|
||||
pattern_segs, override_regex = cls.__normalize_segments(
|
||||
is_dir_pattern, pattern_segs,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise GitIgnorePatternError((
|
||||
f"Invalid git pattern: {original_pattern!r}"
|
||||
)) from e # GitIgnorePatternError
|
||||
|
||||
if override_regex is not None:
|
||||
# Use regex override.
|
||||
regex = override_regex
|
||||
|
||||
elif pattern_segs is not None:
|
||||
# Build regular expression from pattern.
|
||||
try:
|
||||
regex_parts = cls.__translate_segments(pattern_segs)
|
||||
except ValueError as e:
|
||||
raise GitIgnorePatternError((
|
||||
f"Invalid git pattern: {original_pattern!r}"
|
||||
)) from e # GitIgnorePatternError
|
||||
|
||||
regex = ''.join(regex_parts)
|
||||
|
||||
else:
|
||||
assert_unreachable((
|
||||
f"{override_regex=} and {pattern_segs=} cannot both be null."
|
||||
)) # assert_unreachable
|
||||
|
||||
# Encode regex if needed.
|
||||
out_regex: AnyStr
|
||||
if regex is not None and return_type is bytes:
|
||||
out_regex = regex.encode(_BYTES_ENCODING)
|
||||
else:
|
||||
out_regex = regex
|
||||
|
||||
return (out_regex, include)
|
||||
|
||||
@classmethod
|
||||
def __translate_segments(cls, pattern_segs: list[str]) -> list[str]:
|
||||
"""
|
||||
Translate the pattern segments to regular expressions.
|
||||
|
||||
*pattern_segs* (:class:`list` of :class:`str`) contains the pattern
|
||||
segments.
|
||||
|
||||
Returns the regular expression parts (:class:`list` of :class:`str`).
|
||||
"""
|
||||
# Build regular expression from pattern.
|
||||
out_parts = []
|
||||
need_slash = False
|
||||
end = len(pattern_segs) - 1
|
||||
for i, seg in enumerate(pattern_segs):
|
||||
if seg == '**':
|
||||
if i == 0:
|
||||
# A normalized pattern beginning with double-asterisks ('**') will
|
||||
# match any leading path segments.
|
||||
# - NOTICE: '(?:^|/)' benchmarks slower using p15 (sm=0.9382,
|
||||
# hs=0.9966, re2=0.9337).
|
||||
out_parts.append('^(?:.+/)?')
|
||||
|
||||
elif i < end:
|
||||
# A pattern with inner double-asterisks ('**') will match multiple (or
|
||||
# zero) inner path segments.
|
||||
out_parts.append('(?:/.+)?')
|
||||
need_slash = True
|
||||
|
||||
else:
|
||||
assert i == end, (i, end)
|
||||
# A normalized pattern ending with double-asterisks ('**') will match
|
||||
# any trailing path segments.
|
||||
out_parts.append('/')
|
||||
|
||||
else:
|
||||
# Match path segment.
|
||||
if i == 0:
|
||||
# Anchor to root directory.
|
||||
out_parts.append('^')
|
||||
|
||||
if need_slash:
|
||||
out_parts.append('/')
|
||||
|
||||
if seg == '*':
|
||||
# Match whole path segment.
|
||||
out_parts.append('[^/]+')
|
||||
|
||||
else:
|
||||
# Match segment glob pattern.
|
||||
out_parts.append(cls._translate_segment_glob(seg))
|
||||
|
||||
if i == end:
|
||||
if seg == '*':
|
||||
# A pattern ending with an asterisk ('*') will match a file or
|
||||
# directory (without matching descendant paths). E.g., "foo/*"
|
||||
# matches "foo/test.json", "foo/bar/", but not "foo/bar/hello.c".
|
||||
out_parts.append('/?$')
|
||||
|
||||
else:
|
||||
# A pattern ending without a slash ('/') will match a file or a
|
||||
# directory (with paths underneath it). E.g., "foo" matches "foo",
|
||||
# "foo/bar", "foo/bar/baz", etc.
|
||||
out_parts.append('(?:/|$)')
|
||||
|
||||
need_slash = True
|
||||
|
||||
return out_parts
|
||||
|
||||
|
||||
# Register GitIgnoreBasicPattern as "gitignore".
|
||||
util.register_pattern('gitignore', GitIgnoreBasicPattern)
|
||||
335
lib/pathspec/patterns/gitignore/spec.py
Normal file
335
lib/pathspec/patterns/gitignore/spec.py
Normal file
@@ -0,0 +1,335 @@
|
||||
"""
|
||||
This module provides :class:`GitIgnoreSpecPattern` which implements Git's
|
||||
`gitignore`_ patterns, and handles edge-cases where Git's behavior differs from
|
||||
what's documented. Git allows including files from excluded directories which
|
||||
appears to contradict the documentation. This is used by
|
||||
:class:`~pathspec.gitignore.GitIgnoreSpec` to fully replicate Git's handling.
|
||||
|
||||
.. _`gitignore`: https://git-scm.com/docs/gitignore
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
Optional) # Replaced by `X | None` in 3.10.
|
||||
|
||||
from pathspec._typing import (
|
||||
AnyStr, # Removed in 3.18.
|
||||
assert_unreachable,
|
||||
override) # Added in 3.12.
|
||||
|
||||
from .base import (
|
||||
GitIgnorePatternError,
|
||||
_BYTES_ENCODING,
|
||||
_GitIgnoreBasePattern)
|
||||
|
||||
_DIR_MARK = 'ps_d'
|
||||
"""
|
||||
The regex group name for the directory marker. This is only used by
|
||||
:class:`GitIgnoreSpec`.
|
||||
"""
|
||||
|
||||
_DIR_MARK_CG = f'(?P<{_DIR_MARK}>/)'
|
||||
"""
|
||||
This regular expression matches the directory marker.
|
||||
"""
|
||||
|
||||
_DIR_MARK_OPT = f'(?:{_DIR_MARK_CG}|$)'
|
||||
"""
|
||||
This regular expression matches the optional directory marker and sub-path.
|
||||
"""
|
||||
|
||||
|
||||
class GitIgnoreSpecPattern(_GitIgnoreBasePattern):
|
||||
"""
|
||||
The :class:`GitIgnoreSpecPattern` class represents a compiled gitignore
|
||||
pattern with special handling for edge-cases to replicate Git's behavior.
|
||||
|
||||
This is registered under the deprecated name "gitwildmatch" for backward
|
||||
compatibility with v0.12. The registered name will be removed in a future
|
||||
version.
|
||||
"""
|
||||
|
||||
# Keep the dict-less class hierarchy.
|
||||
__slots__ = ()
|
||||
|
||||
@staticmethod
|
||||
def __normalize_segments(
|
||||
is_dir_pattern: bool,
|
||||
pattern_segs: list[str],
|
||||
) -> tuple[Optional[list[str]], Optional[str]]:
|
||||
"""
|
||||
Normalize the pattern segments to make processing easier.
|
||||
|
||||
*is_dir_pattern* (:class:`bool`) is whether the pattern is a directory
|
||||
pattern (i.e., ends with a slash '/').
|
||||
|
||||
*pattern_segs* (:class:`list` of :class:`str`) contains the pattern
|
||||
segments. This may be modified in place.
|
||||
|
||||
Returns a :class:`tuple` containing either:
|
||||
|
||||
- The normalized segments (:class:`list` of :class:`str`; or :data:`None`).
|
||||
|
||||
- The regular expression override (:class:`str` or :data:`None`).
|
||||
"""
|
||||
if not pattern_segs[0]:
|
||||
# A pattern beginning with a slash ('/') should match relative to the root
|
||||
# directory. Remove the empty first segment to make the pattern relative
|
||||
# to root.
|
||||
del pattern_segs[0]
|
||||
|
||||
elif len(pattern_segs) == 1 or (len(pattern_segs) == 2 and not pattern_segs[1]):
|
||||
# A single segment pattern with or without a trailing slash ('/') will
|
||||
# match any descendant path. This is equivalent to "**/{pattern}". Prepend
|
||||
# double-asterisk segment to make pattern relative to root.
|
||||
if pattern_segs[0] != '**':
|
||||
pattern_segs.insert(0, '**')
|
||||
|
||||
else:
|
||||
# A pattern without a beginning slash ('/') but contains at least one
|
||||
# prepended directory (e.g., "dir/{pattern}") should match relative to the
|
||||
# root directory. No segment modification is needed.
|
||||
pass
|
||||
|
||||
if not pattern_segs:
|
||||
# After normalization, we end up with no pattern at all. This must be
|
||||
# because the pattern is invalid.
|
||||
raise ValueError("Pattern normalized to nothing.")
|
||||
|
||||
if not pattern_segs[-1]:
|
||||
# A pattern ending with a slash ('/') will match all descendant paths if
|
||||
# it is a directory but not if it is a regular file. This is equivalent to
|
||||
# "{pattern}/**". Set empty last segment to a double-asterisk to include
|
||||
# all descendants.
|
||||
pattern_segs[-1] = '**'
|
||||
|
||||
# EDGE CASE: Collapse duplicate double-asterisk sequences (i.e., '**/**').
|
||||
# Iterate over the segments in reverse order and remove the duplicate double
|
||||
# asterisks as we go.
|
||||
for i in range(len(pattern_segs) - 1, 0, -1):
|
||||
prev = pattern_segs[i-1]
|
||||
seg = pattern_segs[i]
|
||||
if prev == '**' and seg == '**':
|
||||
del pattern_segs[i]
|
||||
|
||||
seg_count = len(pattern_segs)
|
||||
if seg_count == 1 and pattern_segs[0] == '**':
|
||||
if is_dir_pattern:
|
||||
# The pattern "**/" will be normalized to "**", but it should match
|
||||
# everything except for files in the root. Special case this pattern.
|
||||
return (None, _DIR_MARK_CG)
|
||||
else:
|
||||
# The pattern "**" will match every path. Special case this pattern.
|
||||
return (None, '.')
|
||||
|
||||
elif (
|
||||
seg_count == 2
|
||||
and pattern_segs[0] == '**'
|
||||
and pattern_segs[1] == '*'
|
||||
):
|
||||
# The pattern "*" will be normalized to "**/*" and will match every
|
||||
# path. Special case this pattern for efficiency.
|
||||
return (None, '.')
|
||||
|
||||
elif (
|
||||
seg_count == 3
|
||||
and pattern_segs[0] == '**'
|
||||
and pattern_segs[1] == '*'
|
||||
and pattern_segs[2] == '**'
|
||||
):
|
||||
# The pattern "*/" will be normalized to "**/*/**" which will match every
|
||||
# file not in the root directory. Special case this pattern for
|
||||
# efficiency.
|
||||
if is_dir_pattern:
|
||||
return (None, _DIR_MARK_CG)
|
||||
else:
|
||||
return (None, '/')
|
||||
|
||||
# No regular expression override, return modified pattern segments.
|
||||
return (pattern_segs, None)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def pattern_to_regex(
|
||||
cls,
|
||||
pattern: AnyStr,
|
||||
) -> tuple[Optional[AnyStr], Optional[bool]]:
|
||||
"""
|
||||
Convert the pattern into a regular expression.
|
||||
|
||||
*pattern* (:class:`str` or :class:`bytes`) is the pattern to convert into a
|
||||
regular expression.
|
||||
|
||||
Returns a :class:`tuple` containing:
|
||||
|
||||
- *pattern* (:class:`str`, :class:`bytes` or :data:`None`) is the
|
||||
uncompiled regular expression.
|
||||
|
||||
- *include* (:class:`bool` or :data:`None`) is whether matched files
|
||||
should be included (:data:`True`), excluded (:data:`False`), or is a
|
||||
null-operation (:data:`None`).
|
||||
"""
|
||||
if isinstance(pattern, str):
|
||||
pattern_str = pattern
|
||||
return_type = str
|
||||
elif isinstance(pattern, bytes):
|
||||
pattern_str = pattern.decode(_BYTES_ENCODING)
|
||||
return_type = bytes
|
||||
else:
|
||||
raise TypeError(f"{pattern=!r} is not a unicode or byte string.")
|
||||
|
||||
original_pattern = pattern_str
|
||||
del pattern
|
||||
|
||||
if pattern_str.endswith('\\ '):
|
||||
# EDGE CASE: Spaces can be escaped with backslash. If a pattern that ends
|
||||
# with a backslash is followed by a space, do not strip from the left.
|
||||
pass
|
||||
else:
|
||||
# EDGE CASE: Leading spaces should be kept (only trailing spaces should be
|
||||
# removed). Git does not remove leading spaces.
|
||||
pattern_str = pattern_str.rstrip()
|
||||
|
||||
regex: Optional[str]
|
||||
include: Optional[bool]
|
||||
|
||||
if not pattern_str:
|
||||
# A blank pattern is a null-operation (neither includes nor excludes
|
||||
# files).
|
||||
return (None, None)
|
||||
|
||||
elif pattern_str.startswith('#'):
|
||||
# A pattern starting with a hash ('#') serves as a comment (neither
|
||||
# includes nor excludes files). Escape the hash with a backslash to match
|
||||
# a literal hash (i.e., '\#').
|
||||
return (None, None)
|
||||
|
||||
elif pattern_str == '/':
|
||||
# EDGE CASE: According to `git check-ignore` (v2.4.1), a single '/' does
|
||||
# not match any file.
|
||||
return (None, None)
|
||||
|
||||
if pattern_str.startswith('!'):
|
||||
# A pattern starting with an exclamation mark ('!') negates the pattern
|
||||
# (exclude instead of include). Escape the exclamation mark with a back
|
||||
# slash to match a literal exclamation mark (i.e., '\!').
|
||||
include = False
|
||||
# Remove leading exclamation mark.
|
||||
pattern_str = pattern_str[1:]
|
||||
else:
|
||||
include = True
|
||||
|
||||
# Split pattern into segments.
|
||||
pattern_segs = pattern_str.split('/')
|
||||
|
||||
# Check whether the pattern is specifically a directory pattern before
|
||||
# normalization.
|
||||
is_dir_pattern = not pattern_segs[-1]
|
||||
|
||||
# Normalize pattern to make processing easier.
|
||||
try:
|
||||
pattern_segs, override_regex = cls.__normalize_segments(
|
||||
is_dir_pattern, pattern_segs,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise GitIgnorePatternError((
|
||||
f"Invalid git pattern: {original_pattern!r}"
|
||||
)) from e # GitIgnorePatternError
|
||||
|
||||
if override_regex is not None:
|
||||
# Use regex override.
|
||||
regex = override_regex
|
||||
|
||||
elif pattern_segs is not None:
|
||||
# Build regular expression from pattern.
|
||||
try:
|
||||
regex_parts = cls.__translate_segments(is_dir_pattern, pattern_segs)
|
||||
except ValueError as e:
|
||||
raise GitIgnorePatternError((
|
||||
f"Invalid git pattern: {original_pattern!r}"
|
||||
)) from e # GitIgnorePatternError
|
||||
|
||||
regex = ''.join(regex_parts)
|
||||
|
||||
else:
|
||||
assert_unreachable((
|
||||
f"{override_regex=} and {pattern_segs=} cannot both be null."
|
||||
)) # assert_unreachable
|
||||
|
||||
# Encode regex if needed.
|
||||
out_regex: AnyStr
|
||||
if regex is not None and return_type is bytes:
|
||||
out_regex = regex.encode(_BYTES_ENCODING)
|
||||
else:
|
||||
out_regex = regex
|
||||
|
||||
return (out_regex, include)
|
||||
|
||||
@classmethod
|
||||
def __translate_segments(
|
||||
cls,
|
||||
is_dir_pattern: bool,
|
||||
pattern_segs: list[str],
|
||||
) -> list[str]:
|
||||
"""
|
||||
Translate the pattern segments to regular expressions.
|
||||
|
||||
*is_dir_pattern* (:class:`bool`) is whether the pattern is a directory
|
||||
pattern (i.e., ends with a slash '/').
|
||||
|
||||
*pattern_segs* (:class:`list` of :class:`str`) contains the pattern
|
||||
segments.
|
||||
|
||||
Returns the regular expression parts (:class:`list` of :class:`str`).
|
||||
"""
|
||||
# Build regular expression from pattern.
|
||||
out_parts = []
|
||||
need_slash = False
|
||||
end = len(pattern_segs) - 1
|
||||
for i, seg in enumerate(pattern_segs):
|
||||
if seg == '**':
|
||||
if i == 0:
|
||||
# A normalized pattern beginning with double-asterisks ('**') will
|
||||
# match any leading path segments.
|
||||
out_parts.append('^(?:.+/)?')
|
||||
|
||||
elif i < end:
|
||||
# A pattern with inner double-asterisks ('**') will match multiple (or
|
||||
# zero) inner path segments.
|
||||
out_parts.append('(?:/.+)?')
|
||||
need_slash = True
|
||||
|
||||
else:
|
||||
assert i == end, (i, end)
|
||||
# A normalized pattern ending with double-asterisks ('**') will match
|
||||
# any trailing path segments.
|
||||
if is_dir_pattern:
|
||||
out_parts.append(_DIR_MARK_CG)
|
||||
else:
|
||||
out_parts.append('/')
|
||||
|
||||
else:
|
||||
# Match path segment.
|
||||
if i == 0:
|
||||
# Anchor to root directory.
|
||||
out_parts.append('^')
|
||||
|
||||
if need_slash:
|
||||
out_parts.append('/')
|
||||
|
||||
if seg == '*':
|
||||
# Match whole path segment.
|
||||
out_parts.append('[^/]+')
|
||||
|
||||
else:
|
||||
# Match segment glob pattern.
|
||||
out_parts.append(cls._translate_segment_glob(seg))
|
||||
|
||||
if i == end:
|
||||
# A pattern ending without a slash ('/') will match a file or a
|
||||
# directory (with paths underneath it). E.g., "foo" matches "foo",
|
||||
# "foo/bar", "foo/bar/baz", etc.
|
||||
out_parts.append(_DIR_MARK_OPT)
|
||||
|
||||
need_slash = True
|
||||
|
||||
return out_parts
|
||||
52
lib/pathspec/patterns/gitwildmatch.py
Normal file
52
lib/pathspec/patterns/gitwildmatch.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
.. version-deprecated: 1.0.0
|
||||
This module is superseded by :module:`pathspec.patterns.gitignore`.
|
||||
"""
|
||||
|
||||
from pathspec import util
|
||||
from pathspec._typing import (
|
||||
deprecated, # Added in 3.13.
|
||||
override) # Added in 3.12.
|
||||
|
||||
from .gitignore.spec import (
|
||||
GitIgnoreSpecPattern)
|
||||
|
||||
# DEPRECATED: Deprecated since version 1.0.0. Expose GitWildMatchPatternError
|
||||
# in this module for backward compatibility.
|
||||
from .gitignore import (
|
||||
GitIgnorePatternError as GitWildMatchPatternError)
|
||||
|
||||
|
||||
class GitWildMatchPattern(GitIgnoreSpecPattern):
|
||||
"""
|
||||
.. version-deprecated:: 1.0.0
|
||||
This class is superseded by :class:`GitIgnoreSpecPattern` and
|
||||
:class:`~pathspec.patterns.gitignore.basic.GitIgnoreBasicPattern`.
|
||||
"""
|
||||
|
||||
@deprecated((
|
||||
"GitWildMatchPattern ('gitwildmatch') is deprecated. Use 'gitignore' for "
|
||||
"GitIgnoreBasicPattern or GitIgnoreSpecPattern instead."
|
||||
))
|
||||
def __init__(self, *args, **kw) -> None:
|
||||
"""
|
||||
Warn about deprecation.
|
||||
"""
|
||||
super().__init__(*args, **kw)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
@deprecated((
|
||||
"GitWildMatchPattern ('gitwildmatch') is deprecated. Use 'gitignore' for "
|
||||
"GitIgnoreBasicPattern or GitIgnoreSpecPattern instead."
|
||||
))
|
||||
def pattern_to_regex(cls, *args, **kw):
|
||||
"""
|
||||
Warn about deprecation.
|
||||
"""
|
||||
return super().pattern_to_regex(*args, **kw)
|
||||
|
||||
|
||||
# DEPRECATED: Deprecated since version 1.0.0. Register GitWildMatchPattern as
|
||||
# "gitwildmatch" for backward compatibility.
|
||||
util.register_pattern('gitwildmatch', GitWildMatchPattern)
|
||||
1
lib/pathspec/py.typed
Normal file
1
lib/pathspec/py.typed
Normal file
@@ -0,0 +1 @@
|
||||
# Marker file for PEP 561. The pathspec package uses inline types.
|
||||
847
lib/pathspec/util.py
Normal file
847
lib/pathspec/util.py
Normal file
@@ -0,0 +1,847 @@
|
||||
"""
|
||||
This module provides utility methods for dealing with path-specs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import posixpath
|
||||
import stat
|
||||
from collections.abc import (
|
||||
Collection,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Sequence)
|
||||
from dataclasses import (
|
||||
dataclass)
|
||||
from typing import (
|
||||
Any,
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
|
||||
Generic,
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
TypeVar,
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
from .pattern import (
|
||||
Pattern)
|
||||
from ._typing import (
|
||||
AnyStr, # Removed in 3.18.
|
||||
deprecated) # Added in 3.13.
|
||||
|
||||
StrPath = Union[str, os.PathLike[str]]
|
||||
|
||||
TStrPath = TypeVar("TStrPath", bound=StrPath)
|
||||
"""
|
||||
Type variable for :class:`str` or :class:`os.PathLike`.
|
||||
"""
|
||||
|
||||
NORMALIZE_PATH_SEPS = [
|
||||
__sep
|
||||
for __sep in [os.sep, os.altsep]
|
||||
if __sep and __sep != posixpath.sep
|
||||
]
|
||||
"""
|
||||
*NORMALIZE_PATH_SEPS* (:class:`list` of :class:`str`) contains the path
|
||||
separators that need to be normalized to the POSIX separator for the current
|
||||
operating system. The separators are determined by examining :data:`os.sep` and
|
||||
:data:`os.altsep`.
|
||||
"""
|
||||
|
||||
_registered_patterns = {}
|
||||
"""
|
||||
*_registered_patterns* (:class:`dict`) maps a name (:class:`str`) to the
|
||||
registered pattern factory (:class:`~collections.abc.Callable`).
|
||||
"""
|
||||
|
||||
|
||||
def append_dir_sep(path: pathlib.Path) -> str:
|
||||
"""
|
||||
Appends the path separator to the path if the path is a directory. This can be
|
||||
used to aid in distinguishing between directories and files on the file-system
|
||||
by relying on the presence of a trailing path separator.
|
||||
|
||||
*path* (:class:`pathlib.Path`) is the path to use.
|
||||
|
||||
Returns the path (:class:`str`).
|
||||
"""
|
||||
str_path = str(path)
|
||||
if path.is_dir():
|
||||
str_path += os.sep
|
||||
|
||||
return str_path
|
||||
|
||||
|
||||
def check_match_file(
|
||||
patterns: Iterable[tuple[int, Pattern]],
|
||||
file: str,
|
||||
is_reversed: Optional[bool] = None,
|
||||
) -> tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Iterable`) yields each indexed pattern
|
||||
(:class:`tuple`) which contains the pattern index (:class:`int`) and actua
|
||||
pattern (:class:`.Pattern`).
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to be matched against
|
||||
*patterns*.
|
||||
|
||||
*is_reversed* (:class:`bool` or :data:`None`) is whether the order of the
|
||||
patterns has been reversed. Default is :data:`None` for :data:`False`.
|
||||
Reversing the order of the patterns is an optimization.
|
||||
|
||||
Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
|
||||
or :data:`None`), and the index of the last matched pattern (:class:`int` or
|
||||
:data:`None`).
|
||||
"""
|
||||
if is_reversed:
|
||||
# Check patterns in reverse order. The first pattern that matches takes
|
||||
# precedence.
|
||||
for index, pattern in patterns:
|
||||
if pattern.include is not None and pattern.match_file(file) is not None:
|
||||
return pattern.include, index
|
||||
|
||||
return None, None
|
||||
|
||||
else:
|
||||
# Check all patterns. The last pattern that matches takes precedence.
|
||||
out_include: Optional[bool] = None
|
||||
out_index: Optional[int] = None
|
||||
for index, pattern in patterns:
|
||||
if pattern.include is not None and pattern.match_file(file) is not None:
|
||||
out_include = pattern.include
|
||||
out_index = index
|
||||
|
||||
return out_include, out_index
|
||||
|
||||
|
||||
def detailed_match_files(
|
||||
patterns: Iterable[Pattern],
|
||||
files: Iterable[str],
|
||||
all_matches: Optional[bool] = None,
|
||||
) -> dict[str, 'MatchDetail']:
|
||||
"""
|
||||
Matches the files to the patterns, and returns which patterns matched the
|
||||
files.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
|
||||
the patterns to use.
|
||||
|
||||
*files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
|
||||
normalized file paths to be matched against *patterns*.
|
||||
|
||||
*all_matches* (:class:`bool` or :data:`None`) is whether to return all matches
|
||||
patterns (:data:`True`), or only the last matched pattern (:data:`False`).
|
||||
Default is :data:`None` for :data:`False`.
|
||||
|
||||
Returns the matched files (:class:`dict`) which maps each matched file
|
||||
(:class:`str`) to the patterns that matched in order (:class:`.MatchDetail`).
|
||||
"""
|
||||
all_files = files if isinstance(files, Collection) else list(files)
|
||||
return_files = {}
|
||||
for pattern in patterns:
|
||||
if pattern.include is not None:
|
||||
result_files = pattern.match(all_files) # TODO: Replace with `.match_file()`.
|
||||
if pattern.include:
|
||||
# Add files and record pattern.
|
||||
for result_file in result_files:
|
||||
if result_file in return_files:
|
||||
if all_matches:
|
||||
return_files[result_file].patterns.append(pattern)
|
||||
else:
|
||||
return_files[result_file].patterns[0] = pattern
|
||||
else:
|
||||
return_files[result_file] = MatchDetail([pattern])
|
||||
|
||||
else:
|
||||
# Remove files.
|
||||
for file in result_files:
|
||||
del return_files[file]
|
||||
|
||||
return return_files
|
||||
|
||||
|
||||
def _filter_check_patterns(
|
||||
patterns: Iterable[Pattern],
|
||||
) -> list[tuple[int, Pattern]]:
|
||||
"""
|
||||
Filters out null-patterns.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
|
||||
the patterns.
|
||||
|
||||
Returns a :class:`list` containing each indexed pattern (:class:`tuple`) which
|
||||
contains the pattern index (:class:`int`) and the actual pattern
|
||||
(:class:`.Pattern`).
|
||||
"""
|
||||
return [
|
||||
(__index, __pat)
|
||||
for __index, __pat in enumerate(patterns)
|
||||
if __pat.include is not None
|
||||
]
|
||||
|
||||
|
||||
def _is_iterable(value: Any) -> bool:
|
||||
"""
|
||||
Check whether the value is an iterable (excludes strings).
|
||||
|
||||
*value* is the value to check,
|
||||
|
||||
Returns whether *value* is an iterable (:class:`bool`).
|
||||
"""
|
||||
return isinstance(value, Iterable) and not isinstance(value, (str, bytes))
|
||||
|
||||
|
||||
@deprecated((
|
||||
"pathspec.util.iter_tree() is deprecated. Use iter_tree_files() instead."
|
||||
))
|
||||
def iter_tree(root, on_error=None, follow_links=None):
|
||||
"""
|
||||
.. version-deprecated:: 0.10.0
|
||||
This is an alias for the :func:`.iter_tree_files` function.
|
||||
"""
|
||||
return iter_tree_files(root, on_error=on_error, follow_links=follow_links)
|
||||
|
||||
|
||||
def iter_tree_entries(
|
||||
root: StrPath,
|
||||
on_error: Optional[Callable[[OSError], None]] = None,
|
||||
follow_links: Optional[bool] = None,
|
||||
) -> Iterator['TreeEntry']:
|
||||
"""
|
||||
Walks the specified directory for all files and directories.
|
||||
|
||||
*root* (:class:`str` or :class:`os.PathLike`) is the root directory to search.
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
|
||||
the error handler for file-system exceptions. It will be called with the
|
||||
exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
|
||||
is :data:`None` to ignore file-system exceptions.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
|
||||
symbolic links that resolve to directories. Default is :data:`None` for
|
||||
:data:`True`.
|
||||
|
||||
Raises :exc:`.RecursionError` if recursion is detected.
|
||||
|
||||
Returns an :class:`~collections.abc.Iterator` yielding each file or directory
|
||||
entry (:class:`.TreeEntry`) relative to *root*.
|
||||
"""
|
||||
if on_error is not None and not callable(on_error):
|
||||
raise TypeError(f"on_error:{on_error!r} is not callable.")
|
||||
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
yield from _iter_tree_entries_next(os.path.abspath(root), '', {}, on_error, follow_links)
|
||||
|
||||
|
||||
def _iter_tree_entries_next(
|
||||
root_full: str,
|
||||
dir_rel: str,
|
||||
memo: dict[str, str],
|
||||
on_error: Callable[[OSError], None],
|
||||
follow_links: bool,
|
||||
) -> Iterator['TreeEntry']:
|
||||
"""
|
||||
Scan the directory for all descendant files.
|
||||
|
||||
*root_full* (:class:`str`) the absolute path to the root directory.
|
||||
|
||||
*dir_rel* (:class:`str`) the path to the directory to scan relative to
|
||||
*root_full*.
|
||||
|
||||
*memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
|
||||
each ancestor real path (:class:`str`) to relative path (:class:`str`).
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
|
||||
the error handler for file-system exceptions.
|
||||
|
||||
*follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
|
||||
to directories.
|
||||
|
||||
Yields each entry (:class:`.TreeEntry`).
|
||||
"""
|
||||
dir_full = os.path.join(root_full, dir_rel)
|
||||
dir_real = os.path.realpath(dir_full)
|
||||
|
||||
# Remember each encountered ancestor directory and its canonical (real) path.
|
||||
# If a canonical path is encountered more than once, recursion has occurred.
|
||||
if dir_real not in memo:
|
||||
memo[dir_real] = dir_rel
|
||||
else:
|
||||
raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
|
||||
|
||||
with os.scandir(dir_full) as scan_iter:
|
||||
node_ent: os.DirEntry
|
||||
for node_ent in scan_iter:
|
||||
node_rel = os.path.join(dir_rel, node_ent.name)
|
||||
|
||||
# Inspect child node.
|
||||
try:
|
||||
node_lstat = node_ent.stat(follow_symlinks=False)
|
||||
except OSError as e:
|
||||
if on_error is not None:
|
||||
on_error(e)
|
||||
continue
|
||||
|
||||
if node_ent.is_symlink():
|
||||
# Child node is a link, inspect the target node.
|
||||
try:
|
||||
node_stat = node_ent.stat()
|
||||
except OSError as e:
|
||||
if on_error is not None:
|
||||
on_error(e)
|
||||
continue
|
||||
else:
|
||||
node_stat = node_lstat
|
||||
|
||||
if node_ent.is_dir(follow_symlinks=follow_links):
|
||||
# Child node is a directory, recurse into it and yield its descendant
|
||||
# files.
|
||||
yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
|
||||
|
||||
yield from _iter_tree_entries_next(root_full, node_rel, memo, on_error, follow_links)
|
||||
|
||||
elif node_ent.is_file() or node_ent.is_symlink():
|
||||
# Child node is either a file or an unfollowed link, yield it.
|
||||
yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
|
||||
|
||||
# NOTE: Make sure to remove the canonical (real) path of the directory from
|
||||
# the ancestors memo once we are done with it. This allows the same directory
|
||||
# to appear multiple times. If this is not done, the second occurrence of the
|
||||
# directory will be incorrectly interpreted as a recursion. See
|
||||
# <https://github.com/cpburnz/python-path-specification/pull/7>.
|
||||
del memo[dir_real]
|
||||
|
||||
|
||||
def iter_tree_files(
|
||||
root: StrPath,
|
||||
on_error: Optional[Callable[[OSError], None]] = None,
|
||||
follow_links: Optional[bool] = None,
|
||||
) -> Iterator[str]:
|
||||
"""
|
||||
Walks the specified directory for all files.
|
||||
|
||||
*root* (:class:`str` or :class:`os.PathLike`) is the root directory to search
|
||||
for files.
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
|
||||
the error handler for file-system exceptions. It will be called with the
|
||||
exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
|
||||
is :data:`None` to ignore file-system exceptions.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
|
||||
symbolic links that resolve to directories. Default is :data:`None` for
|
||||
:data:`True`.
|
||||
|
||||
Raises :exc:`.RecursionError` if recursion is detected.
|
||||
|
||||
Returns an :class:`~collections.abc.Iterator` yielding the path to each file
|
||||
(:class:`str`) relative to *root*.
|
||||
"""
|
||||
if on_error is not None and not callable(on_error):
|
||||
raise TypeError(f"on_error:{on_error!r} is not callable.")
|
||||
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
yield from _iter_tree_files_next(os.path.abspath(root), '', {}, on_error, follow_links)
|
||||
|
||||
|
||||
def _iter_tree_files_next(
|
||||
root_full: str,
|
||||
dir_rel: str,
|
||||
memo: dict[str, str],
|
||||
on_error: Callable[[OSError], None],
|
||||
follow_links: bool,
|
||||
) -> Iterator[str]:
|
||||
"""
|
||||
Scan the directory for all descendant files.
|
||||
|
||||
*root_full* (:class:`str`) the absolute path to the root directory.
|
||||
|
||||
*dir_rel* (:class:`str`) the path to the directory to scan relative to
|
||||
*root_full*.
|
||||
|
||||
*memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
|
||||
each ancestor real path (:class:`str`) to relative path (:class:`str`).
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
|
||||
the error handler for file-system exceptions.
|
||||
|
||||
*follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
|
||||
to directories.
|
||||
|
||||
Yields each file path (:class:`str`).
|
||||
"""
|
||||
dir_full = os.path.join(root_full, dir_rel)
|
||||
dir_real = os.path.realpath(dir_full)
|
||||
|
||||
# Remember each encountered ancestor directory and its canonical (real) path.
|
||||
# If a canonical path is encountered more than once, recursion has occurred.
|
||||
if dir_real not in memo:
|
||||
memo[dir_real] = dir_rel
|
||||
else:
|
||||
raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
|
||||
|
||||
with os.scandir(dir_full) as scan_iter:
|
||||
node_ent: os.DirEntry
|
||||
for node_ent in scan_iter:
|
||||
node_rel = os.path.join(dir_rel, node_ent.name)
|
||||
|
||||
if node_ent.is_dir(follow_symlinks=follow_links):
|
||||
# Child node is a directory, recurse into it and yield its descendant
|
||||
# files.
|
||||
yield from _iter_tree_files_next(root_full, node_rel, memo, on_error, follow_links)
|
||||
|
||||
elif node_ent.is_file():
|
||||
# Child node is a file, yield it.
|
||||
yield node_rel
|
||||
|
||||
elif not follow_links and node_ent.is_symlink():
|
||||
# Child node is an unfollowed link, yield it.
|
||||
yield node_rel
|
||||
|
||||
# NOTE: Make sure to remove the canonical (real) path of the directory from
|
||||
# the ancestors memo once we are done with it. This allows the same directory
|
||||
# to appear multiple times. If this is not done, the second occurrence of the
|
||||
# directory will be incorrectly interpreted as a recursion. See
|
||||
# <https://github.com/cpburnz/python-path-specification/pull/7>.
|
||||
del memo[dir_real]
|
||||
|
||||
|
||||
def lookup_pattern(name: str) -> Callable[[AnyStr], Pattern]:
|
||||
"""
|
||||
Lookups a registered pattern factory by name.
|
||||
|
||||
*name* (:class:`str`) is the name of the pattern factory.
|
||||
|
||||
Returns the registered pattern factory (:class:`~collections.abc.Callable`).
|
||||
If no pattern factory is registered, raises :exc:`KeyError`.
|
||||
"""
|
||||
return _registered_patterns[name]
|
||||
|
||||
|
||||
def match_file(patterns: Iterable[Pattern], file: str) -> bool:
|
||||
"""
|
||||
Matches the file to the patterns.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
|
||||
the patterns to use.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to be matched against
|
||||
*patterns*.
|
||||
|
||||
Returns :data:`True` if *file* matched; otherwise, :data:`False`.
|
||||
"""
|
||||
matched = False
|
||||
for pattern in patterns:
|
||||
if pattern.include is not None and pattern.match_file(file) is not None:
|
||||
matched = pattern.include
|
||||
|
||||
return matched
|
||||
|
||||
|
||||
@deprecated((
|
||||
"pathspec.util.match_files() is deprecated. Use match_file() with a loop for "
|
||||
"better results."
|
||||
))
|
||||
def match_files(
|
||||
patterns: Iterable[Pattern],
|
||||
files: Iterable[str],
|
||||
) -> set[str]:
|
||||
"""
|
||||
.. version-deprecated:: 0.10.0
|
||||
This function is no longer used. Use the :func:`.match_file` function with a
|
||||
loop for better results.
|
||||
|
||||
Matches the files to the patterns.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
|
||||
the patterns to use.
|
||||
|
||||
*files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
|
||||
normalized file paths to be matched against *patterns*.
|
||||
|
||||
Returns the matched files (:class:`set` of :class:`str`).
|
||||
"""
|
||||
use_patterns = [__pat for __pat in patterns if __pat.include is not None]
|
||||
|
||||
return_files = set()
|
||||
for file in files:
|
||||
if match_file(use_patterns, file):
|
||||
return_files.add(file)
|
||||
|
||||
return return_files
|
||||
|
||||
|
||||
def normalize_file(
|
||||
file: StrPath,
|
||||
separators: Optional[Collection[str]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Normalizes the file path to use the POSIX path separator (i.e., ``"/"``), and
|
||||
make the paths relative (remove leading ``"/"``).
|
||||
|
||||
*file* (:class:`str` or :class:`os.PathLike`) is the file path.
|
||||
|
||||
*separators* (:class:`~collections.abc.Collection` of :class:`str`; or
|
||||
:data:`None`) optionally contains the path separators to normalize. This does
|
||||
not need to include the POSIX path separator (``"/"``), but including it will
|
||||
not affect the results. Default is ``None`` for :data:`.NORMALIZE_PATH_SEPS`.
|
||||
To prevent normalization, pass an empty container (e.g., an empty tuple
|
||||
``()``).
|
||||
|
||||
Returns the normalized file path (:class:`str`).
|
||||
"""
|
||||
# Normalize path separators.
|
||||
if separators is None:
|
||||
separators = NORMALIZE_PATH_SEPS
|
||||
|
||||
# Convert path object to string.
|
||||
norm_file: str = os.fspath(file)
|
||||
|
||||
for sep in separators:
|
||||
norm_file = norm_file.replace(sep, posixpath.sep)
|
||||
|
||||
if norm_file.startswith('/'):
|
||||
# Make path relative.
|
||||
norm_file = norm_file[1:]
|
||||
|
||||
elif norm_file.startswith('./'):
|
||||
# Remove current directory prefix.
|
||||
norm_file = norm_file[2:]
|
||||
|
||||
return norm_file
|
||||
|
||||
|
||||
@deprecated((
|
||||
"pathspec.util.normalize_files() is deprecated. Use normalize_file() with a "
|
||||
"loop for better results."
|
||||
))
|
||||
def normalize_files(
|
||||
files: Iterable[StrPath],
|
||||
separators: Optional[Collection[str]] = None,
|
||||
) -> dict[str, list[StrPath]]:
|
||||
"""
|
||||
.. version-deprecated:: 0.10.0
|
||||
This function is no longer used. Use the :func:`.normalize_file` function
|
||||
with a loop for better results.
|
||||
|
||||
Normalizes the file paths to use the POSIX path separator.
|
||||
|
||||
*files* (:class:`~collections.abc.Iterable` of :class:`str` or
|
||||
:class:`os.PathLike`) contains the file paths to be normalized.
|
||||
|
||||
*separators* (:class:`~collections.abc.Collection` of :class:`str`; or
|
||||
:data:`None`) optionally contains the path separators to normalize. See
|
||||
:func:`.normalize_file` for more information.
|
||||
|
||||
Returns a :class:`dict` mapping each normalized file path (:class:`str`) to
|
||||
the original file paths (:class:`list` of :class:`str` or
|
||||
:class:`os.PathLike`).
|
||||
"""
|
||||
norm_files = {}
|
||||
for path in files:
|
||||
norm_file = normalize_file(path, separators=separators)
|
||||
if norm_file in norm_files:
|
||||
norm_files[norm_file].append(path)
|
||||
else:
|
||||
norm_files[norm_file] = [path]
|
||||
|
||||
return norm_files
|
||||
|
||||
|
||||
def register_pattern(
|
||||
name: str,
|
||||
pattern_factory: Callable[[AnyStr], Pattern],
|
||||
override: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Registers the specified pattern factory.
|
||||
|
||||
*name* (:class:`str`) is the name to register the pattern factory under.
|
||||
|
||||
*pattern_factory* (:class:`~collections.abc.Callable`) is used to compile
|
||||
patterns. It must accept an uncompiled pattern (:class:`str`) and return the
|
||||
compiled pattern (:class:`.Pattern`).
|
||||
|
||||
*override* (:class:`bool` or :data:`None`) optionally is whether to allow
|
||||
overriding an already registered pattern under the same name (:data:`True`),
|
||||
instead of raising an :exc:`.AlreadyRegisteredError` (:data:`False`). Default
|
||||
is :data:`None` for :data:`False`.
|
||||
"""
|
||||
if not isinstance(name, str):
|
||||
raise TypeError(f"name:{name!r} is not a string.")
|
||||
|
||||
if not callable(pattern_factory):
|
||||
raise TypeError(f"pattern_factory:{pattern_factory!r} is not callable.")
|
||||
|
||||
if name in _registered_patterns and not override:
|
||||
raise AlreadyRegisteredError(name, _registered_patterns[name])
|
||||
|
||||
_registered_patterns[name] = pattern_factory
|
||||
|
||||
|
||||
class AlreadyRegisteredError(Exception):
|
||||
"""
|
||||
The :exc:`AlreadyRegisteredError` exception is raised when a pattern factory
|
||||
is registered under a name already in use.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
pattern_factory: Callable[[AnyStr], Pattern],
|
||||
) -> None:
|
||||
"""
|
||||
Initializes the :exc:`AlreadyRegisteredError` instance.
|
||||
|
||||
*name* (:class:`str`) is the name of the registered pattern.
|
||||
|
||||
*pattern_factory* (:class:`~collections.abc.Callable`) is the registered
|
||||
pattern factory.
|
||||
"""
|
||||
super().__init__(name, pattern_factory)
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
"""
|
||||
*message* (:class:`str`) is the error message.
|
||||
"""
|
||||
return (
|
||||
f"{self.name!r} is already registered for pattern factory="
|
||||
f"{self.pattern_factory!r}."
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""
|
||||
*name* (:class:`str`) is the name of the registered pattern.
|
||||
"""
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def pattern_factory(self) -> Callable[[AnyStr], Pattern]:
|
||||
"""
|
||||
*pattern_factory* (:class:`~collections.abc.Callable`) is the registered
|
||||
pattern factory.
|
||||
"""
|
||||
return self.args[1]
|
||||
|
||||
|
||||
class RecursionError(Exception):
|
||||
"""
|
||||
The :exc:`RecursionError` exception is raised when recursion is detected.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
real_path: str,
|
||||
first_path: str,
|
||||
second_path: str,
|
||||
) -> None:
|
||||
"""
|
||||
Initializes the :exc:`RecursionError` instance.
|
||||
|
||||
*real_path* (:class:`str`) is the real path that recursion was encountered
|
||||
on.
|
||||
|
||||
*first_path* (:class:`str`) is the first path encountered for *real_path*.
|
||||
|
||||
*second_path* (:class:`str`) is the second path encountered for *real_path*.
|
||||
"""
|
||||
super().__init__(real_path, first_path, second_path)
|
||||
|
||||
@property
|
||||
def first_path(self) -> str:
|
||||
"""
|
||||
*first_path* (:class:`str`) is the first path encountered for
|
||||
:attr:`self.real_path <RecursionError.real_path>`.
|
||||
"""
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
"""
|
||||
*message* (:class:`str`) is the error message.
|
||||
"""
|
||||
return (
|
||||
f"Real path {self.real_path!r} was encountered at {self.first_path!r} "
|
||||
f"and then {self.second_path!r}."
|
||||
)
|
||||
|
||||
@property
|
||||
def real_path(self) -> str:
|
||||
"""
|
||||
*real_path* (:class:`str`) is the real path that recursion was
|
||||
encountered on.
|
||||
"""
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def second_path(self) -> str:
|
||||
"""
|
||||
*second_path* (:class:`str`) is the second path encountered for
|
||||
:attr:`self.real_path <RecursionError.real_path>`.
|
||||
"""
|
||||
return self.args[2]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CheckResult(Generic[TStrPath]):
|
||||
"""
|
||||
The :class:`CheckResult` class contains information about the file and which
|
||||
pattern matched it.
|
||||
"""
|
||||
|
||||
# Make the class dict-less.
|
||||
__slots__ = (
|
||||
'file',
|
||||
'include',
|
||||
'index',
|
||||
)
|
||||
|
||||
file: TStrPath
|
||||
"""
|
||||
*file* (:class:`str` or :class:`os.PathLike`) is the file path.
|
||||
"""
|
||||
|
||||
include: Optional[bool]
|
||||
"""
|
||||
*include* (:class:`bool` or :data:`None`) is whether to include or exclude the
|
||||
file. If :data:`None`, no pattern matched.
|
||||
"""
|
||||
|
||||
index: Optional[int]
|
||||
"""
|
||||
*index* (:class:`int` or :data:`None`) is the index of the last pattern that
|
||||
matched. If :data:`None`, no pattern matched.
|
||||
"""
|
||||
|
||||
|
||||
class MatchDetail(object):
|
||||
"""
|
||||
The :class:`.MatchDetail` class contains information about
|
||||
"""
|
||||
|
||||
# Make the class dict-less.
|
||||
__slots__ = ('patterns',)
|
||||
|
||||
def __init__(self, patterns: Sequence[Pattern]) -> None:
|
||||
"""
|
||||
Initialize the :class:`.MatchDetail` instance.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
|
||||
contains the patterns that matched the file in the order they were encountered.
|
||||
"""
|
||||
|
||||
self.patterns = patterns
|
||||
"""
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
|
||||
contains the patterns that matched the file in the order they were
|
||||
encountered.
|
||||
"""
|
||||
|
||||
|
||||
class TreeEntry(object):
|
||||
"""
|
||||
The :class:`TreeEntry` class contains information about a file-system entry.
|
||||
"""
|
||||
|
||||
# Make the class dict-less.
|
||||
__slots__ = ('_lstat', 'name', 'path', '_stat')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
path: str,
|
||||
lstat: os.stat_result,
|
||||
stat: os.stat_result,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the :class:`TreeEntry` instance.
|
||||
|
||||
*name* (:class:`str`) is the base name of the entry.
|
||||
|
||||
*path* (:class:`str`) is the relative path of the entry.
|
||||
|
||||
*lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
|
||||
|
||||
*stat* (:class:`os.stat_result`) is the stat result of the entry,
|
||||
potentially linked.
|
||||
"""
|
||||
|
||||
self._lstat: os.stat_result = lstat
|
||||
"""
|
||||
*_lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
|
||||
"""
|
||||
|
||||
self.name: str = name
|
||||
"""
|
||||
*name* (:class:`str`) is the base name of the entry.
|
||||
"""
|
||||
|
||||
self.path: str = path
|
||||
"""
|
||||
*path* (:class:`str`) is the path of the entry.
|
||||
"""
|
||||
|
||||
self._stat: os.stat_result = stat
|
||||
"""
|
||||
*_stat* (:class:`os.stat_result`) is the stat result of the linked entry.
|
||||
"""
|
||||
|
||||
def is_dir(self, follow_links: Optional[bool] = None) -> bool:
|
||||
"""
|
||||
Get whether the entry is a directory.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
|
||||
links. If this is :data:`True`, a symlink to a directory will result in
|
||||
:data:`True`. Default is :data:`None` for :data:`True`.
|
||||
|
||||
Returns whether the entry is a directory (:class:`bool`).
|
||||
"""
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
node_stat = self._stat if follow_links else self._lstat
|
||||
return stat.S_ISDIR(node_stat.st_mode)
|
||||
|
||||
def is_file(self, follow_links: Optional[bool] = None) -> bool:
|
||||
"""
|
||||
Get whether the entry is a regular file.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
|
||||
links. If this is :data:`True`, a symlink to a regular file will result in
|
||||
:data:`True`. Default is :data:`None` for :data:`True`.
|
||||
|
||||
Returns whether the entry is a regular file (:class:`bool`).
|
||||
"""
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
node_stat = self._stat if follow_links else self._lstat
|
||||
return stat.S_ISREG(node_stat.st_mode)
|
||||
|
||||
def is_symlink(self) -> bool:
|
||||
"""
|
||||
Returns whether the entry is a symbolic link (:class:`bool`).
|
||||
"""
|
||||
return stat.S_ISLNK(self._lstat.st_mode)
|
||||
|
||||
def stat(self, follow_links: Optional[bool] = None) -> os.stat_result:
|
||||
"""
|
||||
Get the cached stat result for the entry.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
|
||||
links. If this is :data:`True`, the stat result of the linked file will be
|
||||
returned. Default is :data:`None` for :data:`True`.
|
||||
|
||||
Returns that stat result (:class:`os.stat_result`).
|
||||
"""
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
return self._stat if follow_links else self._lstat
|
||||
Reference in New Issue
Block a user