diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/python/pathspec | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/python/pathspec')
13 files changed, 2415 insertions, 0 deletions
diff --git a/third_party/python/pathspec/pathspec-0.9.0.dist-info/LICENSE b/third_party/python/pathspec/pathspec-0.9.0.dist-info/LICENSE new file mode 100644 index 0000000000..14e2f777f6 --- /dev/null +++ b/third_party/python/pathspec/pathspec-0.9.0.dist-info/LICENSE @@ -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. diff --git a/third_party/python/pathspec/pathspec-0.9.0.dist-info/METADATA b/third_party/python/pathspec/pathspec-0.9.0.dist-info/METADATA new file mode 100644 index 0000000000..2d38736204 --- /dev/null +++ b/third_party/python/pathspec/pathspec-0.9.0.dist-info/METADATA @@ -0,0 +1,411 @@ +Metadata-Version: 2.1 +Name: pathspec +Version: 0.9.0 +Summary: Utility library for gitignore style pattern matching of file paths. +Home-page: https://github.com/cpburnz/python-path-specification +Author: Caleb P. Burns +Author-email: cpburnz@gmail.com +License: MPL 2.0 +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +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 :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Utilities +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7 +Description-Content-Type: text/x-rst + + +*pathspec*: Path Specification +============================== + +*pathspec* is a utility library for pattern matching of file paths. So +far this only includes Git's wildmatch pattern matching which itself is +derived from Rsync's wildmatch. Git uses wildmatch for its `gitignore`_ +files. + +.. _`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:: + + >>> import pathspec + >>> # The gitignore-style patterns for files to select, but we're including + >>> # instead of ignoring. + >>> spec = """ + ... + ... # 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 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/ + ... + ... """ + +We want to use the ``GitWildMatchPattern`` class to compile our patterns. The +``PathSpec`` class provides an interface around pattern implementations:: + + >>> spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, spec.splitlines()) + +That may be a mouthful but it allows for additional patterns to be implemented +in the future without them having to deal with anything but matching the paths +sent to them. ``GitWildMatchPattern`` is the implementation of the actual +pattern which internally gets converted into a regular expression. +``PathSpec`` is a simple wrapper around a list of compiled patterns. + +To make things simpler, we can use the registered name for a pattern class +instead of always having to provide a reference to the class itself. The +``GitWildMatchPattern`` class is registered as **gitwildmatch**:: + + >>> spec = pathspec.PathSpec.from_lines('gitwildmatch', spec.splitlines()) + +If we wanted to manually compile the patterns we can just do the following:: + + >>> patterns = map(pathspec.patterns.GitWildMatchPattern, spec.splitlines()) + >>> spec = PathSpec(patterns) + +``PathSpec.from_lines()`` is simply a class method which does just that. + +If you want to load the patterns from file, you can pass the file instance +directly as well:: + + >>> with open('patterns.list', 'r') as fh: + >>> spec = pathspec.PathSpec.from_lines('gitwildmatch', fh) + +You can perform matching on a whole directory tree with:: + + >>> matches = spec.match_tree('path/to/directory') + +Or you can perform matching on a specific set of file paths with:: + + >>> matches = spec.match_files(file_paths) + +Or check to see if an individual file matches:: + + >>> is_matched = spec.match_file(file_path) + + +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-path-specification`_. + +.. _`cpburnz/python-path-specification`: https://github.com/cpburnz/python-path-specification + + +Installation +------------ + +*pathspec* requires the following packages: + +- `setuptools`_ + +*pathspec* can be installed from source with:: + + python setup.py install + +*pathspec* is also available for install through `PyPI`_:: + + pip install pathspec + +.. _`setuptools`: https://pypi.python.org/pypi/setuptools +.. _`PyPI`: http://pypi.python.org/pypi/pathspec + + +Documentation +------------- + +Documentation for *pathspec* is available on `Read the Docs`_. + +.. _`Read the Docs`: http://python-path-specification.readthedocs.io + + +Other Languages +--------------- + +*pathspec* is also available as a `Ruby gem`_. + +.. _`Ruby gem`: https://github.com/highb/pathspec-ruby + + +Change History +============== + +0.9.0 (2021-07-17) +------------------ + +- `Issue #44`_/`Issue #50`_: Raise `GitWildMatchPatternError` for invalid git patterns. +- `Issue #45`_: Fix for duplicate leading double-asterisk, and edge cases. +- `Issue #46`_: Fix matching absolute paths. +- API change: `util.normalize_files()` now returns a `Dict[str, List[pathlike]]` instead of a `Dict[str, pathlike]`. +- Added type hinting. + +.. _`Issue #44`: https://github.com/cpburnz/python-path-specification/issues/44 +.. _`Issue #45`: https://github.com/cpburnz/python-path-specification/pull/45 +.. _`Issue #46`: https://github.com/cpburnz/python-path-specification/issues/46 +.. _`Issue #50`: https://github.com/cpburnz/python-path-specification/pull/50 + + +0.8.1 (2020-11-07) +------------------ + +- `Issue #43`_: Add support for addition operator. + +.. _`Issue #43`: https://github.com/cpburnz/python-path-specification/pull/43 + + +0.8.0 (2020-04-09) +------------------ + +- `Issue #30`_: Expose what patterns matched paths. Added `util.detailed_match_files()`. +- `Issue #31`_: `match_tree()` doesn't return symlinks. +- `Issue #34`_: Support `pathlib.Path`\ s. +- Add `PathSpec.match_tree_entries` and `util.iter_tree_entries()` to support directories and symlinks. +- API change: `match_tree()` has been renamed to `match_tree_files()`. The old name `match_tree()` is still available as an alias. +- API change: `match_tree_files()` now returns symlinks. This is a bug fix but it will change the returned results. + +.. _`Issue #30`: https://github.com/cpburnz/python-path-specification/issues/30 +.. _`Issue #31`: https://github.com/cpburnz/python-path-specification/issues/31 +.. _`Issue #34`: https://github.com/cpburnz/python-path-specification/issues/34 + + +0.7.0 (2019-12-27) +------------------ + +- `Issue #28`_: Add support for Python 3.8, and drop Python 3.4. +- `Issue #29`_: Publish bdist wheel. + +.. _`Issue #28`: https://github.com/cpburnz/python-path-specification/pull/28 +.. _`Issue #29`: https://github.com/cpburnz/python-path-specification/pull/29 + + +0.6.0 (2019-10-03) +------------------ + +- `Issue #24`_: Drop support for Python 2.6, 3.2, and 3.3. +- `Issue #25`_: Update README.rst. +- `Issue #26`_: Method to escape gitwildmatch. + +.. _`Issue #24`: https://github.com/cpburnz/python-path-specification/pull/24 +.. _`Issue #25`: https://github.com/cpburnz/python-path-specification/pull/25 +.. _`Issue #26`: https://github.com/cpburnz/python-path-specification/pull/26 + + +0.5.9 (2018-09-15) +------------------ + +- Fixed file system error handling. + + +0.5.8 (2018-09-15) +------------------ + +- Improved type checking. +- Created scripts to test Python 2.6 because Tox removed support for it. +- Improved byte string handling in Python 3. +- `Issue #22`_: Handle dangling symlinks. + +.. _`Issue #22`: https://github.com/cpburnz/python-path-specification/issues/22 + + +0.5.7 (2018-08-14) +------------------ + +- `Issue #21`_: Fix collections deprecation warning. + +.. _`Issue #21`: https://github.com/cpburnz/python-path-specification/issues/21 + + +0.5.6 (2018-04-06) +------------------ + +- Improved unit tests. +- Improved type checking. +- `Issue #20`_: Support current directory prefix. + +.. _`Issue #20`: https://github.com/cpburnz/python-path-specification/issues/20 + + +0.5.5 (2017-09-09) +------------------ + +- Add documentation link to README. + + +0.5.4 (2017-09-09) +------------------ + +- `Issue #17`_: Add link to Ruby implementation of *pathspec*. +- Add sphinx documentation. + +.. _`Issue #17`: https://github.com/cpburnz/python-path-specification/pull/17 + + +0.5.3 (2017-07-01) +------------------ + +- `Issue #14`_: Fix byte strings for Python 3. +- `Issue #15`_: Include "LICENSE" in source package. +- `Issue #16`_: Support Python 2.6. + +.. _`Issue #14`: https://github.com/cpburnz/python-path-specification/issues/14 +.. _`Issue #15`: https://github.com/cpburnz/python-path-specification/pull/15 +.. _`Issue #16`: https://github.com/cpburnz/python-path-specification/issues/16 + + +0.5.2 (2017-04-04) +------------------ + +- Fixed change log. + + +0.5.1 (2017-04-04) +------------------ + +- `Issue #13`_: Add equality methods to `PathSpec` and `RegexPattern`. + +.. _`Issue #13`: https://github.com/cpburnz/python-path-specification/pull/13 + + +0.5.0 (2016-08-22) +------------------ + +- `Issue #12`_: Add `PathSpec.match_file()`. +- Renamed `gitignore.GitIgnorePattern` to `patterns.gitwildmatch.GitWildMatchPattern`. +- Deprecated `gitignore.GitIgnorePattern`. + +.. _`Issue #12`: https://github.com/cpburnz/python-path-specification/issues/12 + + +0.4.0 (2016-07-15) +------------------ + +- `Issue #11`_: Support converting patterns into regular expressions without compiling them. +- API change: Subclasses of `RegexPattern` should implement `pattern_to_regex()`. + +.. _`Issue #11`: https://github.com/cpburnz/python-path-specification/issues/11 + + +0.3.4 (2015-08-24) +------------------ + +- `Issue #7`_: Fixed non-recursive links. +- `Issue #8`_: Fixed edge cases in gitignore patterns. +- `Issue #9`_: Fixed minor usage documentation. +- Fixed recursion detection. +- Fixed trivial incompatibility with Python 3.2. + +.. _`Issue #7`: https://github.com/cpburnz/python-path-specification/pull/7 +.. _`Issue #8`: https://github.com/cpburnz/python-path-specification/pull/8 +.. _`Issue #9`: https://github.com/cpburnz/python-path-specification/pull/9 + + +0.3.3 (2014-11-21) +------------------ + +- Improved documentation. + + +0.3.2 (2014-11-08) +------------------ + +- `Issue #5`_: Use tox for testing. +- `Issue #6`_: Fixed matching Windows paths. +- Improved documentation. +- API change: `spec.match_tree()` and `spec.match_files()` now return iterators instead of sets. + +.. _`Issue #5`: https://github.com/cpburnz/python-path-specification/pull/5 +.. _`Issue #6`: https://github.com/cpburnz/python-path-specification/issues/6 + + +0.3.1 (2014-09-17) +------------------ + +- Updated README. + + +0.3.0 (2014-09-17) +------------------ + +- `Issue #3`_: Fixed trailing slash in gitignore patterns. +- `Issue #4`_: Fixed test for trailing slash in gitignore patterns. +- Added registered patterns. + +.. _`Issue #3`: https://github.com/cpburnz/python-path-specification/pull/3 +.. _`Issue #4`: https://github.com/cpburnz/python-path-specification/pull/4 + + +0.2.2 (2013-12-17) +------------------ + +- Fixed setup.py. + + +0.2.1 (2013-12-17) +------------------ + +- Added tests. +- Fixed comment gitignore patterns. +- Fixed relative path gitignore patterns. + + +0.2.0 (2013-12-07) +------------------ + +- Initial release. + + diff --git a/third_party/python/pathspec/pathspec-0.9.0.dist-info/RECORD b/third_party/python/pathspec/pathspec-0.9.0.dist-info/RECORD new file mode 100644 index 0000000000..328aa58483 --- /dev/null +++ b/third_party/python/pathspec/pathspec-0.9.0.dist-info/RECORD @@ -0,0 +1,17 @@ +pathspec/__init__.py,sha256=72Wc9H_-xRaisgbnz3pNf8bJJlFu6ZP3MMEYVvlIowk,1085 +pathspec/_meta.py,sha256=MyOTCY28N1FOIxhPQBNxXei9oiQEvzVISrwnyCIAb-0,1617 +pathspec/compat.py,sha256=G0_QT3NRlFTcrV1bhx9G1qIvBdmnaWh-f4VrcpXzfhc,830 +pathspec/pathspec.py,sha256=J2lrhvT1iqkZ5rSrTvMEOjuLBkdzl2QVZkJYWIoMVmw,8048 +pathspec/pattern.py,sha256=sWBuHJNnDKa6tqpKU50ZeCUYXSkKz2dWPLtSHpIyP5U,4811 +pathspec/util.py,sha256=XayuxOvE0LaTZDyffhpel-1ew1_MP_FTCeqXj0M3C8I,19889 +pathspec/patterns/__init__.py,sha256=Falv9rzI0S-Sjc-t-vCS9nUPcKwBptmdNderY9Kok50,184 +pathspec/patterns/gitwildmatch.py,sha256=NzrN7IqFvJJT3x7jcXIMZBmeWF6n0nDjUMoPWpGmvZs,11899 +pathspec/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pathspec/tests/test_gitwildmatch.py,sha256=m9a5-dJ1-MXZ-SVzci3JqtHShfEP3ty-kaYMkVGWS9U,15160 +pathspec/tests/test_pathspec.py,sha256=UB_aETIpTxpuei79zI2_kbjI2XnM2tF_xTQOFD4IYL8,4845 +pathspec/tests/test_util.py,sha256=IjvDbKYyEnQOaa07ebx-qD4bb-YFSm3qm0PE-VhKUa0,7906 +pathspec-0.9.0.dist-info/LICENSE,sha256=-rPda9qyJvHAhjCx3ZF-Efy07F4eAg4sFvg6ChOGPoU,16726 +pathspec-0.9.0.dist-info/METADATA,sha256=nmGOFs7gDO9imfQOjDP2Mhu-NX1xK2DoDv4B7D37D80,12141 +pathspec-0.9.0.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110 +pathspec-0.9.0.dist-info/top_level.txt,sha256=0NA6IbW6iCUZ4p402IRSyZpvx4yqoltHzMoAxVQHI1M,9 +pathspec-0.9.0.dist-info/RECORD,, diff --git a/third_party/python/pathspec/pathspec-0.9.0.dist-info/WHEEL b/third_party/python/pathspec/pathspec-0.9.0.dist-info/WHEEL new file mode 100644 index 0000000000..8b701e93c2 --- /dev/null +++ b/third_party/python/pathspec/pathspec-0.9.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.6) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/third_party/python/pathspec/pathspec-0.9.0.dist-info/top_level.txt b/third_party/python/pathspec/pathspec-0.9.0.dist-info/top_level.txt new file mode 100644 index 0000000000..6486958df0 --- /dev/null +++ b/third_party/python/pathspec/pathspec-0.9.0.dist-info/top_level.txt @@ -0,0 +1 @@ +pathspec diff --git a/third_party/python/pathspec/pathspec/__init__.py b/third_party/python/pathspec/pathspec/__init__.py new file mode 100644 index 0000000000..790d0a33bb --- /dev/null +++ b/third_party/python/pathspec/pathspec/__init__.py @@ -0,0 +1,43 @@ +# encoding: utf-8 +""" +The *pathspec* package provides pattern matching for file paths. So far +this only includes Git's wildmatch pattern matching (the style used for +".gitignore" files). + +The following classes are imported and made available from the root of +the `pathspec` package: + +- :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.iter_tree` +- :func:`pathspec.util.lookup_pattern` +- :func:`pathspec.util.match_files` +""" +from __future__ import unicode_literals + +from .pathspec import PathSpec +from .pattern import Pattern, RegexPattern +from .util import iter_tree, lookup_pattern, match_files, RecursionError + +from ._meta import ( + __author__, + __copyright__, + __credits__, + __license__, + __version__, +) + +# Load pattern implementations. +from . import patterns + +# Expose `GitIgnorePattern` class in the root module for backward +# compatibility with v0.4. +from .patterns.gitwildmatch import GitIgnorePattern diff --git a/third_party/python/pathspec/pathspec/_meta.py b/third_party/python/pathspec/pathspec/_meta.py new file mode 100644 index 0000000000..ff4c3239a9 --- /dev/null +++ b/third_party/python/pathspec/pathspec/_meta.py @@ -0,0 +1,43 @@ +# encoding: utf-8 +""" +This module contains the project meta-data. +""" + +__author__ = "Caleb P. Burns" +__copyright__ = "Copyright © 2013-2021 Caleb P. Burns" +__credits__ = [ + "dahlia <https://github.com/dahlia>", + "highb <https://github.com/highb>", + "029xue <https://github.com/029xue>", + "mikexstudios <https://github.com/mikexstudios>", + "nhumrich <https://github.com/nhumrich>", + "davidfraser <https://github.com/davidfraser>", + "demurgos <https://github.com/demurgos>", + "ghickman <https://github.com/ghickman>", + "nvie <https://github.com/nvie>", + "adrienverge <https://github.com/adrienverge>", + "AndersBlomdell <https://github.com/AndersBlomdell>", + "highb <https://github.com/highb>", + "thmxv <https://github.com/thmxv>", + "wimglenn <https://github.com/wimglenn>", + "hugovk <https://github.com/hugovk>", + "dcecile <https://github.com/dcecile>", + "mroutis <https://github.com/mroutis>", + "jdufresne <https://github.com/jdufresne>", + "groodt <https://github.com/groodt>", + "ftrofin <https://github.com/ftrofin>", + "pykong <https://github.com/pykong>", + "nhhollander <https://github.com/nhhollander>", + "KOLANICH <https://github.com/KOLANICH>", + "JonjonHays <https://github.com/JonjonHays>", + "Isaac0616 <https://github.com/Isaac0616>", + "SebastiaanZ <https://github.com/SebastiaanZ>", + "RoelAdriaans <https://github.com/RoelAdriaans>", + "raviselker <https://github.com/raviselker>", + "johanvergeer <https://github.com/johanvergeer>", + "danjer <https://github.com/danjer>", + "jhbuhrman <https://github.com/jhbuhrman>", + "WPDOrdina <https://github.com/WPDOrdina>", +] +__license__ = "MPL 2.0" +__version__ = "0.9.0" diff --git a/third_party/python/pathspec/pathspec/compat.py b/third_party/python/pathspec/pathspec/compat.py new file mode 100644 index 0000000000..f5d17bf3ce --- /dev/null +++ b/third_party/python/pathspec/pathspec/compat.py @@ -0,0 +1,41 @@ +# encoding: utf-8 +""" +This module provides compatibility between Python 2 and 3. Hardly +anything is used by this project to constitute including `six`_. + +.. _`six`: http://pythonhosted.org/six +""" + +import sys + +if sys.version_info[0] < 3: + # Python 2. + unicode = unicode + string_types = (basestring,) + + from collections import Iterable + from itertools import izip_longest + + def iterkeys(mapping): + return mapping.iterkeys() + +else: + # Python 3. + unicode = str + string_types = (unicode,) + + from collections.abc import Iterable + from itertools import zip_longest as izip_longest + + def iterkeys(mapping): + return mapping.keys() + +try: + # Python 3.6+. + from collections.abc import Collection +except ImportError: + # Python 2.7 - 3.5. + from collections import Container as Collection + +CollectionType = Collection +IterableType = Iterable diff --git a/third_party/python/pathspec/pathspec/pathspec.py b/third_party/python/pathspec/pathspec/pathspec.py new file mode 100644 index 0000000000..ff40089d2c --- /dev/null +++ b/third_party/python/pathspec/pathspec/pathspec.py @@ -0,0 +1,243 @@ +# encoding: utf-8 +""" +This module provides an object oriented interface for pattern matching +of files. +""" + +try: + from typing import ( + Any, + AnyStr, + Callable, + Iterable, + Iterator, + Optional, + Text, + Union) +except ImportError: + pass + +try: + # Python 3.6+ type hints. + from os import PathLike + from typing import Collection +except ImportError: + pass + +from . import util +from .compat import ( + CollectionType, + iterkeys, + izip_longest, + string_types) +from .pattern import Pattern +from .util import TreeEntry + + +class PathSpec(object): + """ + The :class:`PathSpec` class is a wrapper around a list of compiled + :class:`.Pattern` instances. + """ + + def __init__(self, patterns): + # type: (Iterable[Pattern]) -> None + """ + Initializes the :class:`PathSpec` instance. + + *patterns* (:class:`~collections.abc.Collection` or :class:`~collections.abc.Iterable`) + yields each compiled pattern (:class:`.Pattern`). + """ + + self.patterns = patterns if isinstance(patterns, CollectionType) else list(patterns) + """ + *patterns* (:class:`~collections.abc.Collection` of :class:`.Pattern`) + contains the compiled patterns. + """ + + def __eq__(self, other): + # type: (PathSpec) -> bool + """ + Tests the equality of this path-spec with *other* (:class:`PathSpec`) + by comparing their :attr:`~PathSpec.patterns` attributes. + """ + if isinstance(other, PathSpec): + paired_patterns = izip_longest(self.patterns, other.patterns) + return all(a == b for a, b in paired_patterns) + else: + return NotImplemented + + def __len__(self): + """ + Returns the number of compiled patterns this path-spec contains + (:class:`int`). + """ + return len(self.patterns) + + def __add__(self, other): + # type: (PathSpec) -> PathSpec + """ + Combines the :attr:`Pathspec.patterns` patterns from two + :class:`PathSpec` instances. + """ + if isinstance(other, PathSpec): + return PathSpec(self.patterns + other.patterns) + else: + return NotImplemented + + def __iadd__(self, other): + # type: (PathSpec) -> PathSpec + """ + Adds the :attr:`Pathspec.patterns` patterns from one :class:`PathSpec` + instance to this instance. + """ + if isinstance(other, PathSpec): + self.patterns += other.patterns + return self + else: + return NotImplemented + + @classmethod + def from_lines(cls, pattern_factory, lines): + # type: (Union[Text, Callable[[AnyStr], Pattern]], Iterable[AnyStr]) -> PathSpec + """ + 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 it can + be a :class:`file` (e.g., from :func:`open` or :class:`io.StringIO`) + or the result from :meth:`str.splitlines`. + + Returns the :class:`PathSpec` instance. + """ + if isinstance(pattern_factory, string_types): + pattern_factory = util.lookup_pattern(pattern_factory) + if not callable(pattern_factory): + raise TypeError("pattern_factory:{!r} is not callable.".format(pattern_factory)) + + if not util._is_iterable(lines): + raise TypeError("lines:{!r} is not an iterable.".format(lines)) + + patterns = [pattern_factory(line) for line in lines if line] + return cls(patterns) + + def match_file(self, file, separators=None): + # type: (Union[Text, PathLike], Optional[Collection[Text]]) -> bool + """ + Matches the file to this path-spec. + + *file* (:class:`str` or :class:`~pathlib.PurePath`) 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:`~pathspec.util.normalize_file` for more information. + + Returns :data:`True` if *file* matched; otherwise, :data:`False`. + """ + norm_file = util.normalize_file(file, separators=separators) + return util.match_file(self.patterns, norm_file) + + def match_entries(self, entries, separators=None): + # type: (Iterable[TreeEntry], Optional[Collection[Text]]) -> Iterator[TreeEntry] + """ + Matches the entries to this path-spec. + + *entries* (:class:`~collections.abc.Iterable` of :class:`~util.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:`~pathspec.util.normalize_file` for more + information. + + Returns the matched entries (:class:`~collections.abc.Iterator` of + :class:`~util.TreeEntry`). + """ + if not util._is_iterable(entries): + raise TypeError("entries:{!r} is not an iterable.".format(entries)) + + entry_map = util._normalize_entries(entries, separators=separators) + match_paths = util.match_files(self.patterns, iterkeys(entry_map)) + for path in match_paths: + yield entry_map[path] + + def match_files(self, files, separators=None): + # type: (Iterable[Union[Text, PathLike]], Optional[Collection[Text]]) -> Iterator[Union[Text, PathLike]] + """ + Matches the files to this path-spec. + + *files* (:class:`~collections.abc.Iterable` of :class:`str; or + :class:`pathlib.PurePath`) 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:`~pathspec.util.normalize_file` for more + information. + + Returns the matched files (:class:`~collections.abc.Iterator` of + :class:`str` or :class:`pathlib.PurePath`). + """ + if not util._is_iterable(files): + raise TypeError("files:{!r} is not an iterable.".format(files)) + + file_map = util.normalize_files(files, separators=separators) + matched_files = util.match_files(self.patterns, iterkeys(file_map)) + for norm_file in matched_files: + for orig_file in file_map[norm_file]: + yield orig_file + + def match_tree_entries(self, root, on_error=None, follow_links=None): + # type: (Text, Optional[Callable], Optional[bool]) -> Iterator[TreeEntry] + """ + Walks the specified root path for all files and matches them to this + path-spec. + + *root* (:class:`str`; or :class:`pathlib.PurePath`) is the root + directory to search. + + *on_error* (:class:`~collections.abc.Callable` or :data:`None`) + optionally is the error handler for file-system exceptions. See + :func:`~pathspec.util.iter_tree_entries` for more information. + + *follow_links* (:class:`bool` or :data:`None`) optionally is whether + to walk symbolic links that resolve to directories. See + :func:`~pathspec.util.iter_tree_files` for more information. + + 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) + return self.match_entries(entries) + + def match_tree_files(self, root, on_error=None, follow_links=None): + # type: (Text, Optional[Callable], Optional[bool]) -> Iterator[Text] + """ + Walks the specified root path for all files and matches them to this + path-spec. + + *root* (:class:`str`; or :class:`pathlib.PurePath`) 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. See + :func:`~pathspec.util.iter_tree_files` for more information. + + *follow_links* (:class:`bool` or :data:`None`) optionally is whether + to walk symbolic links that resolve to directories. See + :func:`~pathspec.util.iter_tree_files` for more information. + + 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) + return self.match_files(files) + + # Alias `match_tree_files()` as `match_tree()`. + match_tree = match_tree_files diff --git a/third_party/python/pathspec/pathspec/pattern.py b/third_party/python/pathspec/pathspec/pattern.py new file mode 100644 index 0000000000..c354c2632a --- /dev/null +++ b/third_party/python/pathspec/pathspec/pattern.py @@ -0,0 +1,164 @@ +# encoding: utf-8 +""" +This module provides the base definition for patterns. +""" + +import re +try: + from typing import ( + AnyStr, + Iterable, + Iterator, + Optional, + Pattern as RegexHint, + Text, + Tuple, + Union) +except ImportError: + pass + +from .compat import unicode + + +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): + # type: (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`). + """ + + def match(self, files): + # type: (Iterable[Text]) -> Iterator[Text] + """ + Matches this pattern against the specified files. + + *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains + each file relative to the root directory (e.g., ``"relative/path/to/file"``). + + Returns an :class:`~collections.abc.Iterable` yielding each matched + file path (:class:`str`). + """ + raise NotImplementedError("{}.{} must override match().".format(self.__class__.__module__, self.__class__.__name__)) + + +class RegexPattern(Pattern): + """ + The :class:`RegexPattern` class is an implementation of a pattern + using regular expressions. + """ + + # Make the class dict-less. + __slots__ = ('regex',) + + def __init__(self, pattern, include=None): + # type: (Union[AnyStr, RegexHint], Optional[bool]) -> None + """ + Initializes the :class:`RegexPattern` instance. + + *pattern* (:class:`unicode`, :class:`bytes`, :class:`re.RegexObject`, + 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.RegexObject`) + 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. + """ + + self.regex = None + """ + *regex* (:class:`re.RegexObject`) is the regular expression for the + pattern. + """ + + if isinstance(pattern, (unicode, bytes)): + assert include is None, "include:{!r} must be null when pattern:{!r} is a string.".format(include, pattern) + 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, "include:{!r} must be null when pattern:{!r} is null.".format(include, pattern) + + else: + raise TypeError("pattern:{!r} is not a string, RegexObject, or None.".format(pattern)) + + super(RegexPattern, self).__init__(include) + self.regex = regex + + def __eq__(self, other): + # type: (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 + + def match(self, files): + # type: (Iterable[Text]) -> Iterable[Text] + """ + Matches this pattern against the specified files. + + *files* (:class:`~collections.abc.Iterable` of :class:`str`) + contains each file relative to the root directory (e.g., "relative/path/to/file"). + + Returns an :class:`~collections.abc.Iterable` yielding each matched + file path (:class:`str`). + """ + if self.include is not None: + for path in files: + if self.regex.match(path) is not None: + yield path + + @classmethod + def pattern_to_regex(cls, pattern): + # type: (Text) -> Tuple[Text, bool] + """ + Convert the pattern into an uncompiled regular expression. + + *pattern* (:class:`str`) is the pattern to convert into a regular + expression. + + Returns the uncompiled regular expression (:class:`str` or :data:`None`), + and 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 diff --git a/third_party/python/pathspec/pathspec/patterns/__init__.py b/third_party/python/pathspec/pathspec/patterns/__init__.py new file mode 100644 index 0000000000..1a0d55ec74 --- /dev/null +++ b/third_party/python/pathspec/pathspec/patterns/__init__.py @@ -0,0 +1,8 @@ +# encoding: utf-8 +""" +The *pathspec.patterns* package contains the pattern matching +implementations. +""" + +# Load pattern implementations. +from .gitwildmatch import GitWildMatchPattern diff --git a/third_party/python/pathspec/pathspec/patterns/gitwildmatch.py b/third_party/python/pathspec/pathspec/patterns/gitwildmatch.py new file mode 100644 index 0000000000..afd8f6b4cf --- /dev/null +++ b/third_party/python/pathspec/pathspec/patterns/gitwildmatch.py @@ -0,0 +1,400 @@ +# encoding: utf-8 +""" +This module implements Git's wildmatch pattern matching which itself is +derived from Rsync's wildmatch. Git uses wildmatch for its ".gitignore" +files. +""" +from __future__ import unicode_literals + +import re +import warnings +try: + from typing import ( + AnyStr, + Optional, + Text, + Tuple) +except ImportError: + pass + +from .. import util +from ..compat import unicode +from ..pattern import RegexPattern + +#: The encoding to use when parsing a byte string pattern. +_BYTES_ENCODING = 'latin1' + + +class GitWildMatchPatternError(ValueError): + """ + The :class:`GitWildMatchPatternError` indicates an invalid git wild match + pattern. + """ + pass + + +class GitWildMatchPattern(RegexPattern): + """ + The :class:`GitWildMatchPattern` class represents a compiled Git + wildmatch pattern. + """ + + # Keep the dict-less class hierarchy. + __slots__ = () + + @classmethod + def pattern_to_regex(cls, pattern): + # type: (AnyStr) -> Tuple[Optional[AnyStr], Optional[bool]] + """ + Convert the pattern into a regular expression. + + *pattern* (:class:`unicode` or :class:`bytes`) is the pattern to + convert into a regular expression. + + Returns the uncompiled regular expression (:class:`unicode`, :class:`bytes`, + or :data:`None`), and whether matched files should be included + (:data:`True`), excluded (:data:`False`), or if it is a + null-operation (:data:`None`). + """ + if isinstance(pattern, unicode): + return_type = unicode + elif isinstance(pattern, bytes): + return_type = bytes + pattern = pattern.decode(_BYTES_ENCODING) + else: + raise TypeError("pattern:{!r} is not a unicode or byte string.".format(pattern)) + + original_pattern = pattern + pattern = pattern.strip() + + if pattern.startswith('#'): + # A pattern starting with a hash ('#') serves as a comment + # (neither includes nor excludes files). Escape the hash with a + # back-slash to match a literal hash (i.e., '\#'). + regex = None + include = None + + elif pattern == '/': + # EDGE CASE: According to `git check-ignore` (v2.4.1), a single + # '/' does not match any file. + regex = None + include = None + + elif pattern: + if pattern.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 = pattern[1:] + else: + include = True + + if pattern.startswith('\\'): + # Remove leading back-slash escape for escaped hash ('#') or + # exclamation mark ('!'). + pattern = pattern[1:] + + # Allow a regex override for edge cases that cannot be handled + # through normalization. + override_regex = None + + # Split pattern into segments. + pattern_segs = pattern.split('/') + + # Normalize pattern to make processing easier. + + # EDGE CASE: Deal with duplicate double-asterisk sequences. + # Collapse each sequence down to one double-asterisk. Iterate over + # the segments in reverse 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] + + if len(pattern_segs) == 2 and pattern_segs[0] == '**' and not pattern_segs[1]: + # EDGE CASE: The '**/' pattern should match everything except + # individual files in the root directory. This case cannot be + # adequately handled through normalization. Use the override. + override_regex = '^.+/.*$' + + if not pattern_segs[0]: + # A pattern beginning with a slash ('/') will only match paths + # directly on the root directory instead of any descendant + # paths. So, remove empty first segment to make 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 pattern without a beginning slash ('/') will match + # any descendant path. This is equivalent to "**/{pattern}". So, + # prepend with double-asterisks to make pattern relative to + # root. + # EDGE CASE: This also holds for a single pattern with a + # trailing slash (e.g. dir/). + if pattern_segs[0] != '**': + pattern_segs.insert(0, '**') + + else: + # EDGE CASE: A pattern without a beginning slash ('/') but + # contains at least one prepended directory (e.g. + # "dir/{pattern}") should not match "**/dir/{pattern}", + # according to `git check-ignore` (v2.4.1). + pass + + if not pattern_segs: + # After resolving the edge cases, we end up with no + # pattern at all. This must be because the pattern is + # invalid. + raise GitWildMatchPatternError("Invalid git pattern: %r" % (original_pattern,)) + + if not pattern_segs[-1] and len(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}/**". So, set + # last segment to a double-asterisk to include all + # descendants. + pattern_segs[-1] = '**' + + if override_regex is None: + # Build regular expression from pattern. + output = ['^'] + need_slash = False + end = len(pattern_segs) - 1 + for i, seg in enumerate(pattern_segs): + if seg == '**': + if i == 0 and i == end: + # A pattern consisting solely of double-asterisks ('**') + # will match every path. + output.append('.+') + elif i == 0: + # A normalized pattern beginning with double-asterisks + # ('**') will match any leading path segments. + output.append('(?:.+/)?') + need_slash = False + elif i == end: + # A normalized pattern ending with double-asterisks ('**') + # will match any trailing path segments. + output.append('/.*') + else: + # A pattern with inner double-asterisks ('**') will match + # multiple (or zero) inner path segments. + output.append('(?:/.+)?') + need_slash = True + + elif seg == '*': + # Match single path segment. + if need_slash: + output.append('/') + output.append('[^/]+') + need_slash = True + + else: + # Match segment glob pattern. + if need_slash: + output.append('/') + + output.append(cls._translate_segment_glob(seg)) + if i == end and include is True: + # 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. + # EDGE CASE: However, this does not hold for exclusion cases + # according to `git check-ignore` (v2.4.1). + output.append('(?:/.*)?') + + need_slash = True + + output.append('$') + regex = ''.join(output) + + else: + # Use regex override. + regex = override_regex + + else: + # A blank pattern is a null-operation (neither includes nor + # excludes files). + regex = None + include = None + + if regex is not None and return_type is bytes: + regex = regex.encode(_BYTES_ENCODING) + + return regex, include + + @staticmethod + def _translate_segment_glob(pattern): + # type: (Text) -> Text + """ + 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 brack expression negation. + if j < end and 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] == '!': + # Braket 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. To maintain consistency with undefined behavior, + # I am escaping the '^' as well. + 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) + + return regex + + @staticmethod + def escape(s): + # type: (AnyStr) -> AnyStr + """ + Escape special characters in the given string. + + *s* (:class:`unicode` 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:`unicode` or :class:`bytes`) + """ + if isinstance(s, unicode): + return_type = unicode + string = s + elif isinstance(s, bytes): + return_type = bytes + string = s.decode(_BYTES_ENCODING) + else: + raise TypeError("s:{!r} is not a unicode or byte string.".format(s)) + + # Reference: https://git-scm.com/docs/gitignore#_pattern_format + meta_characters = r"[]!*#?" + + out_string = "".join("\\" + x if x in meta_characters else x for x in string) + + if return_type is bytes: + return out_string.encode(_BYTES_ENCODING) + else: + return out_string + +util.register_pattern('gitwildmatch', GitWildMatchPattern) + + +class GitIgnorePattern(GitWildMatchPattern): + """ + The :class:`GitIgnorePattern` class is deprecated by :class:`GitWildMatchPattern`. + This class only exists to maintain compatibility with v0.4. + """ + + def __init__(self, *args, **kw): + """ + Warn about deprecation. + """ + self._deprecated() + super(GitIgnorePattern, self).__init__(*args, **kw) + + @staticmethod + def _deprecated(): + """ + Warn about deprecation. + """ + warnings.warn("GitIgnorePattern ('gitignore') is deprecated. Use GitWildMatchPattern ('gitwildmatch') instead.", DeprecationWarning, stacklevel=3) + + @classmethod + def pattern_to_regex(cls, *args, **kw): + """ + Warn about deprecation. + """ + cls._deprecated() + return super(GitIgnorePattern, cls).pattern_to_regex(*args, **kw) + +# Register `GitIgnorePattern` as "gitignore" for backward compatibility +# with v0.4. +util.register_pattern('gitignore', GitIgnorePattern) diff --git a/third_party/python/pathspec/pathspec/util.py b/third_party/python/pathspec/pathspec/util.py new file mode 100644 index 0000000000..64a5dea9db --- /dev/null +++ b/third_party/python/pathspec/pathspec/util.py @@ -0,0 +1,665 @@ +# encoding: utf-8 +""" +This module provides utility methods for dealing with path-specs. +""" + +import os +import os.path +import posixpath +import stat +try: + from typing import ( + Any, + AnyStr, + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Sequence, + Set, + Text, + Union) +except ImportError: + pass +try: + # Python 3.6+ type hints. + from os import PathLike + from typing import Collection +except ImportError: + pass + +from .compat import ( + CollectionType, + IterableType, + string_types, + unicode) +from .pattern import Pattern + +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 detailed_match_files(patterns, files, all_matches=None): + # type: (Iterable[Pattern], Iterable[Text], Optional[bool]) -> Dict[Text, 'MatchDetail'] + """ + Matches the files to the patterns, and returns which patterns matched + the files. + + *patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.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:`boot` 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, CollectionType) else list(files) + return_files = {} + for pattern in patterns: + if pattern.include is not None: + result_files = pattern.match(all_files) + 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 _is_iterable(value): + # type: (Any) -> bool + """ + Check whether the value is an iterable (excludes strings). + + *value* is the value to check, + + Returns whether *value* is a iterable (:class:`bool`). + """ + return isinstance(value, IterableType) and not isinstance(value, (unicode, bytes)) + + +def iter_tree_entries(root, on_error=None, follow_links=None): + # type: (Text, Optional[Callable], Optional[bool]) -> Iterator['TreeEntry'] + """ + Walks the specified directory for all files and directories. + + *root* (:class:`str`) 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("on_error:{!r} is not callable.".format(on_error)) + + if follow_links is None: + follow_links = True + + for entry in _iter_tree_entries_next(os.path.abspath(root), '', {}, on_error, follow_links): + yield entry + + +def iter_tree_files(root, on_error=None, follow_links=None): + # type: (Text, Optional[Callable], Optional[bool]) -> Iterator[Text] + """ + Walks the specified directory for all files. + + *root* (:class:`str`) 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("on_error:{!r} is not callable.".format(on_error)) + + if follow_links is None: + follow_links = True + + for entry in _iter_tree_entries_next(os.path.abspath(root), '', {}, on_error, follow_links): + if not entry.is_dir(follow_links): + yield entry.path + + +# Alias `iter_tree_files()` as `iter_tree()`. +iter_tree = iter_tree_files + + +def _iter_tree_entries_next(root_full, dir_rel, memo, on_error, follow_links): + # type: (Text, Text, Dict[Text, Text], Callable, 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) + + for node_name in os.listdir(dir_full): + node_rel = os.path.join(dir_rel, node_name) + node_full = os.path.join(root_full, node_rel) + + # Inspect child node. + try: + node_lstat = os.lstat(node_full) + except OSError as e: + if on_error is not None: + on_error(e) + continue + + if stat.S_ISLNK(node_lstat.st_mode): + # Child node is a link, inspect the target node. + is_link = True + try: + node_stat = os.stat(node_full) + except OSError as e: + if on_error is not None: + on_error(e) + continue + else: + is_link = False + node_stat = node_lstat + + if stat.S_ISDIR(node_stat.st_mode) and (follow_links or not is_link): + # Child node is a directory, recurse into it and yield its + # descendant files. + yield TreeEntry(node_name, node_rel, node_lstat, node_stat) + + for entry in _iter_tree_entries_next(root_full, node_rel, memo, on_error, follow_links): + yield entry + + elif stat.S_ISREG(node_stat.st_mode) or is_link: + # Child node is either a file or an unfollowed link, yield it. + yield TreeEntry(node_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 lookup_pattern(name): + # type: (Text) -> 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, file): + # type: (Iterable[Pattern], Text) -> bool + """ + Matches the file to the patterns. + + *patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.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: + if file in pattern.match((file,)): + matched = pattern.include + return matched + + +def match_files(patterns, files): + # type: (Iterable[Pattern], Iterable[Text]) -> Set[Text] + """ + Matches the files to the patterns. + + *patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.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`). + """ + all_files = files if isinstance(files, CollectionType) else list(files) + return_files = set() + for pattern in patterns: + if pattern.include is not None: + result_files = pattern.match(all_files) + if pattern.include: + return_files.update(result_files) + else: + return_files.difference_update(result_files) + return return_files + + +def _normalize_entries(entries, separators=None): + # type: (Iterable['TreeEntry'], Optional[Collection[Text]]) -> Dict[Text, 'TreeEntry'] + """ + Normalizes the entry paths to use the POSIX path separator. + + *entries* (:class:`~collections.abc.Iterable` of :class:`.TreeEntry`) + contains the entries 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 the each normalized file path (:class:`str`) + to the entry (:class:`.TreeEntry`) + """ + norm_files = {} + for entry in entries: + norm_files[normalize_file(entry.path, separators=separators)] = entry + return norm_files + + +def normalize_file(file, separators=None): + # type: (Union[Text, PathLike], Optional[Collection[Text]]) -> Text + """ + Normalizes the file path to use the POSIX path separator (i.e., + ``'/'``), and make the paths relative (remove leading ``'/'``). + + *file* (:class:`str` or :class:`pathlib.PurePath`) 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 :data:`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(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 + + +def normalize_files(files, separators=None): + # type: (Iterable[Union[str, PathLike]], Optional[Collection[Text]]) -> Dict[Text, List[Union[str, PathLike]]] + """ + Normalizes the file paths to use the POSIX path separator. + + *files* (:class:`~collections.abc.Iterable` of :class:`str` or + :class:`pathlib.PurePath`) 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 the each normalized file path + (:class:`str`) to the original file paths (:class:`list` of + :class:`str` or :class:`pathlib.PurePath`). + """ + 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, pattern_factory, override=None): + # type: (Text, Callable[[AnyStr], Pattern], Optional[bool]) -> 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, string_types): + raise TypeError("name:{!r} is not a string.".format(name)) + if not callable(pattern_factory): + raise TypeError("pattern_factory:{!r} is not callable.".format(pattern_factory)) + 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, pattern_factory): + # type: (Text, 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(AlreadyRegisteredError, self).__init__(name, pattern_factory) + + @property + def message(self): + # type: () -> Text + """ + *message* (:class:`str`) is the error message. + """ + return "{name!r} is already registered for pattern factory:{pattern_factory!r}.".format( + name=self.name, + pattern_factory=self.pattern_factory, + ) + + @property + def name(self): + # type: () -> Text + """ + *name* (:class:`str`) is the name of the registered pattern. + """ + return self.args[0] + + @property + def pattern_factory(self): + # type: () -> 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, first_path, second_path): + # type: (Text, Text, Text) -> 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(RecursionError, self).__init__(real_path, first_path, second_path) + + @property + def first_path(self): + # type: () -> Text + """ + *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): + # type: () -> Text + """ + *message* (:class:`str`) is the error message. + """ + return "Real path {real!r} was encountered at {first!r} and then {second!r}.".format( + real=self.real_path, + first=self.first_path, + second=self.second_path, + ) + + @property + def real_path(self): + # type: () -> Text + """ + *real_path* (:class:`str`) is the real path that recursion was + encountered on. + """ + return self.args[0] + + @property + def second_path(self): + # type: () -> Text + """ + *second_path* (:class:`str`) is the second path encountered for + :attr:`self.real_path <RecursionError.real_path>`. + """ + return self.args[2] + + +class MatchDetail(object): + """ + The :class:`.MatchDetail` class contains information about + """ + + #: Make the class dict-less. + __slots__ = ('patterns',) + + def __init__(self, patterns): + # type: (Sequence[Pattern]) -> None + """ + Initialize the :class:`.MatchDetail` instance. + + *patterns* (:class:`~collections.abc.Sequence` of :class:`~pathspec.pattern.Pattern`) + contains the patterns that matched the file in the order they were + encountered. + """ + + self.patterns = patterns + """ + *patterns* (:class:`~collections.abc.Sequence` of :class:`~pathspec.pattern.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, path, lstat, stat): + # type: (Text, Text, os.stat_result, 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 = lstat + """ + *_lstat* (:class:`~os.stat_result`) is the stat result of the direct + entry. + """ + + self.name = name + """ + *name* (:class:`str`) is the base name of the entry. + """ + + self.path = path + """ + *path* (:class:`str`) is the path of the entry. + """ + + self._stat = stat + """ + *_stat* (:class:`~os.stat_result`) is the stat result of the linked + entry. + """ + + def is_dir(self, follow_links=None): + # type: (Optional[bool]) -> 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=None): + # type: (Optional[bool]) -> 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): + # type: () -> bool + """ + Returns whether the entry is a symbolic link (:class:`bool`). + """ + return stat.S_ISLNK(self._lstat.st_mode) + + def stat(self, follow_links=None): + # type: (Optional[bool]) -> 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 |