././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7150722 pathspec-1.0.4/CHANGES.rst0000644000000000000000000004602015136034031012051 0ustar00Change 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 `_. - 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/ 0.12.1 (2023-12-10) ------------------- Bug fixes: - `Issue #84`_: PathSpec.match_file() returns None since 0.12.0. .. _`Issue #84`: https://github.com/cpburnz/python-pathspec/issues/84 0.12.0 (2023-12-09) ------------------- Major changes: - Dropped support of EoL Python 3.7. See `Pull #82`_. API changes: - Signature of protected method `pathspec.pathspec.PathSpec._match_file()` (with a leading underscore) has been changed from `def _match_file(patterns: Iterable[Pattern], file: str) -> bool` to `def _match_file(patterns: Iterable[Tuple[int, Pattern]], file: str) -> Tuple[Optional[bool], Optional[int]]`. New features: - Added `pathspec.pathspec.PathSpec.check_*()` methods. These methods behave similarly to `.match_*()` but return additional information in the `pathspec.util.CheckResult` objects (e.g., `CheckResult.index` indicates the index of the last pattern that matched the file). - Added `pathspec.pattern.RegexPattern.pattern` attribute which stores the original, uncompiled pattern. Bug fixes: - `Issue #81`_: GitIgnoreSpec behaviors differ from git. - `Pull #83`_: Fix ReadTheDocs builds. Improvements: - Mark Python 3.12 as supported. See `Pull #82`_. - Improve test debugging. - Improve type hint on *on_error* parameter on `pathspec.pathspec.PathSpec.match_tree_entries()`. - Improve type hint on *on_error* parameter on `pathspec.util.iter_tree_entries()`. .. _`Issue #81`: https://github.com/cpburnz/python-pathspec/issues/81 .. _`Pull #82`: https://github.com/cpburnz/python-pathspec/pull/82 .. _`Pull #83`: https://github.com/cpburnz/python-pathspec/pull/83 0.11.2 (2023-07-28) ------------------- New features: - `Issue #80`_: match_files with negated path spec. `pathspec.PathSpec.match_*()` now have a `negate` parameter to make using *.gitignore* logic easier and more efficient. Bug fixes: - `Pull #76`_: Add edge case: patterns that end with an escaped space - `Issue #77`_/`Pull #78`_: Negate with caret symbol as with the exclamation mark. .. _`Pull #76`: https://github.com/cpburnz/python-pathspec/pull/76 .. _`Issue #77`: https://github.com/cpburnz/python-pathspec/issues/77 .. _`Pull #78`: https://github.com/cpburnz/python-pathspec/pull/78/ .. _`Issue #80`: https://github.com/cpburnz/python-pathspec/issues/80 0.11.1 (2023-03-14) ------------------- Bug fixes: - `Issue #74`_: Include directory should override exclude file. Improvements: - `Pull #75`_: Fix partially unknown PathLike type. - Convert `os.PathLike` to a string properly using `os.fspath`. .. _`Issue #74`: https://github.com/cpburnz/python-pathspec/issues/74 .. _`Pull #75`: https://github.com/cpburnz/python-pathspec/pull/75 0.11.0 (2023-01-24) ------------------- Major changes: - Changed build backend to `flit_core.buildapi`_ from `setuptools.build_meta`_. Building with `setuptools` through `setup.py` is still supported for distributions that need it. See `Issue #72`_. Improvements: - `Issue #72`_/`Pull #73`_: Please consider switching the build-system to flit_core to ease setuptools bootstrap. .. _`flit_core.buildapi`: https://flit.pypa.io/en/latest/index.html .. _`Issue #72`: https://github.com/cpburnz/python-pathspec/issues/72 .. _`Pull #73`: https://github.com/cpburnz/python-pathspec/pull/73 0.10.3 (2022-12-09) ------------------- New features: - Added utility function `pathspec.util.append_dir_sep()` to aid in distinguishing between directories and files on the file-system. See `Issue #65`_. Bug fixes: - `Issue #66`_/`Pull #67`_: Package not marked as py.typed. - `Issue #68`_: Exports are considered private. - `Issue #70`_/`Pull #71`_: 'Self' string literal type is Unknown in pyright. Improvements: - `Issue #65`_: Checking directories via match_file() does not work on Path objects. .. _`Issue #65`: https://github.com/cpburnz/python-pathspec/issues/65 .. _`Issue #66`: https://github.com/cpburnz/python-pathspec/issues/66 .. _`Pull #67`: https://github.com/cpburnz/python-pathspec/pull/67 .. _`Issue #68`: https://github.com/cpburnz/python-pathspec/issues/68 .. _`Issue #70`: https://github.com/cpburnz/python-pathspec/issues/70 .. _`Pull #71`: https://github.com/cpburnz/python-pathspec/pull/71 0.10.2 (2022-11-12) ------------------- Bug fixes: - Fix failing tests on Windows. - Type hint on *root* parameter on `pathspec.pathspec.PathSpec.match_tree_entries()`. - Type hint on *root* parameter on `pathspec.pathspec.PathSpec.match_tree_files()`. - Type hint on *root* parameter on `pathspec.util.iter_tree_entries()`. - Type hint on *root* parameter on `pathspec.util.iter_tree_files()`. - `Issue #64`_: IndexError with my .gitignore file when trying to build a Python package. Improvements: - `Pull #58`_: CI: add GitHub Actions test workflow. .. _`Pull #58`: https://github.com/cpburnz/python-pathspec/pull/58 .. _`Issue #64`: https://github.com/cpburnz/python-pathspec/issues/64 0.10.1 (2022-09-02) ------------------- Bug fixes: - Fix documentation on `pathspec.pattern.RegexPattern.match_file()`. - `Pull #60`_: Remove redundant wheel dep from pyproject.toml. - `Issue #61`_: Dist failure for Fedora, CentOS, EPEL. - `Issue #62`_: Since version 0.10.0 pure wildcard does not work in some cases. Improvements: - Restore support for legacy installations using `setup.py`. See `Issue #61`_. .. _`Pull #60`: https://github.com/cpburnz/python-pathspec/pull/60 .. _`Issue #61`: https://github.com/cpburnz/python-pathspec/issues/61 .. _`Issue #62`: https://github.com/cpburnz/python-pathspec/issues/62 0.10.0 (2022-08-30) ------------------- Major changes: - Dropped support of EoL Python 2.7, 3.5, 3.6. See `Issue #47`_. - The *gitwildmatch* pattern `dir/*` is now handled the same as `dir/`. This means `dir/*` will now match all descendants rather than only direct children. See `Issue #19`_. - Added `pathspec.GitIgnoreSpec` class (see new features). - Changed build system to `pyproject.toml`_ and build backend to `setuptools.build_meta`_ which may have unforeseen consequences. - Renamed GitHub project from `python-path-specification`_ to `python-pathspec`_. See `Issue #35`_. API changes: - Deprecated: `pathspec.util.match_files()` is an old function no longer used. - Deprecated: `pathspec.match_files()` is an old function no longer used. - Deprecated: `pathspec.util.normalize_files()` is no longer used. - Deprecated: `pathspec.util.iter_tree()` is an alias for `pathspec.util.iter_tree_files()`. - Deprecated: `pathspec.iter_tree()` is an alias for `pathspec.util.iter_tree_files()`. - Deprecated: `pathspec.pattern.Pattern.match()` is no longer used. Use or implement `pathspec.pattern.Pattern.match_file()`. New features: - Added class `pathspec.gitignore.GitIgnoreSpec` (with alias `pathspec.GitIgnoreSpec`) to implement *gitignore* behavior not possible with standard `PathSpec` class. The particular *gitignore* behavior implemented is prioritizing patterns matching the file directly over matching an ancestor directory. Bug fixes: - `Issue #19`_: Files inside an ignored sub-directory are not matched. - `Issue #41`_: Incorrectly (?) matches files inside directories that do match. - `Pull #51`_: Refactor deprecated unittest aliases for Python 3.11 compatibility. - `Issue #53`_: Symlink pathspec_meta.py breaks Windows. - `Issue #54`_: test_util.py uses os.symlink which can fail on Windows. - `Issue #55`_: Backslashes at start of pattern not handled correctly. - `Pull #56`_: pyproject.toml: include subpackages in setuptools config - `Issue #57`_: `!` doesn't exclude files in directories if the pattern doesn't have a trailing slash. Improvements: - Support Python 3.10, 3.11. - Modernize code to Python 3.7. - `Issue #52`_: match_files() is not a pure generator function, and it impacts tree_*() gravely. .. _`python-path-specification`: https://github.com/cpburnz/python-path-specification .. _`python-pathspec`: https://github.com/cpburnz/python-pathspec .. _`pyproject.toml`: https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/ .. _`setuptools.build_meta`: https://setuptools.pypa.io/en/latest/build_meta.html .. _`Issue #19`: https://github.com/cpburnz/python-pathspec/issues/19 .. _`Issue #35`: https://github.com/cpburnz/python-pathspec/issues/35 .. _`Issue #41`: https://github.com/cpburnz/python-pathspec/issues/41 .. _`Issue #47`: https://github.com/cpburnz/python-pathspec/issues/47 .. _`Pull #51`: https://github.com/cpburnz/python-pathspec/pull/51 .. _`Issue #52`: https://github.com/cpburnz/python-pathspec/issues/52 .. _`Issue #53`: https://github.com/cpburnz/python-pathspec/issues/53 .. _`Issue #54`: https://github.com/cpburnz/python-pathspec/issues/54 .. _`Issue #55`: https://github.com/cpburnz/python-pathspec/issues/55 .. _`Pull #56`: https://github.com/cpburnz/python-pathspec/pull/56 .. _`Issue #57`: https://github.com/cpburnz/python-pathspec/issues/57 0.9.0 (2021-07-17) ------------------ - `Issue #44`_/`Pull #50`_: Raise `GitWildMatchPatternError` for invalid git patterns. - `Pull #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-pathspec/issues/44 .. _`Pull #45`: https://github.com/cpburnz/python-pathspec/pull/45 .. _`Issue #46`: https://github.com/cpburnz/python-pathspec/issues/46 .. _`Pull #50`: https://github.com/cpburnz/python-pathspec/pull/50 0.8.1 (2020-11-07) ------------------ - `Pull #43`_: Add support for addition operator. .. _`Pull #43`: https://github.com/cpburnz/python-pathspec/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-pathspec/issues/30 .. _`Issue #31`: https://github.com/cpburnz/python-pathspec/issues/31 .. _`Issue #34`: https://github.com/cpburnz/python-pathspec/issues/34 0.7.0 (2019-12-27) ------------------ - `Pull #28`_: Add support for Python 3.8, and drop Python 3.4. - `Pull #29`_: Publish bdist wheel. .. _`Pull #28`: https://github.com/cpburnz/python-pathspec/pull/28 .. _`Pull #29`: https://github.com/cpburnz/python-pathspec/pull/29 0.6.0 (2019-10-03) ------------------ - `Pull #24`_: Drop support for Python 2.6, 3.2, and 3.3. - `Pull #25`_: Update README.rst. - `Pull #26`_: Method to escape gitwildmatch. .. _`Pull #24`: https://github.com/cpburnz/python-pathspec/pull/24 .. _`Pull #25`: https://github.com/cpburnz/python-pathspec/pull/25 .. _`Pull #26`: https://github.com/cpburnz/python-pathspec/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-pathspec/issues/22 0.5.7 (2018-08-14) ------------------ - `Issue #21`_: Fix collections deprecation warning. .. _`Issue #21`: https://github.com/cpburnz/python-pathspec/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-pathspec/issues/20 0.5.5 (2017-09-09) ------------------ - Add documentation link to README. 0.5.4 (2017-09-09) ------------------ - `Pull #17`_: Add link to Ruby implementation of *pathspec*. - Add sphinx documentation. .. _`Pull #17`: https://github.com/cpburnz/python-pathspec/pull/17 0.5.3 (2017-07-01) ------------------ - `Issue #14`_: Fix byte strings for Python 3. - `Pull #15`_: Include "LICENSE" in source package. - `Issue #16`_: Support Python 2.6. .. _`Issue #14`: https://github.com/cpburnz/python-pathspec/issues/14 .. _`Pull #15`: https://github.com/cpburnz/python-pathspec/pull/15 .. _`Issue #16`: https://github.com/cpburnz/python-pathspec/issues/16 0.5.2 (2017-04-04) ------------------ - Fixed change log. 0.5.1 (2017-04-04) ------------------ - `Pull #13`_: Add equality methods to `PathSpec` and `RegexPattern`. .. _`Pull #13`: https://github.com/cpburnz/python-pathspec/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-pathspec/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-pathspec/issues/11 0.3.4 (2015-08-24) ------------------ - `Pull #7`_: Fixed non-recursive links. - `Pull #8`_: Fixed edge cases in gitignore patterns. - `Pull #9`_: Fixed minor usage documentation. - Fixed recursion detection. - Fixed trivial incompatibility with Python 3.2. .. _`Pull #7`: https://github.com/cpburnz/python-pathspec/pull/7 .. _`Pull #8`: https://github.com/cpburnz/python-pathspec/pull/8 .. _`Pull #9`: https://github.com/cpburnz/python-pathspec/pull/9 0.3.3 (2014-11-21) ------------------ - Improved documentation. 0.3.2 (2014-11-08) ------------------ - `Pull #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. .. _`Pull #5`: https://github.com/cpburnz/python-pathspec/pull/5 .. _`Issue #6`: https://github.com/cpburnz/python-pathspec/issues/6 0.3.1 (2014-09-17) ------------------ - Updated README. 0.3.0 (2014-09-17) ------------------ - `Pull #3`_: Fixed trailing slash in gitignore patterns. - `Pull #4`_: Fixed test for trailing slash in gitignore patterns. - Added registered patterns. .. _`Pull #3`: https://github.com/cpburnz/python-pathspec/pull/3 .. _`Pull #4`: https://github.com/cpburnz/python-pathspec/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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7150722 pathspec-1.0.4/CHANGES_0.in.rst0000644000000000000000000003503415136034031012700 0ustar00 0.12.1 (2023-12-10) ------------------- Bug fixes: - `Issue #84`_: PathSpec.match_file() returns None since 0.12.0. .. _`Issue #84`: https://github.com/cpburnz/python-pathspec/issues/84 0.12.0 (2023-12-09) ------------------- Major changes: - Dropped support of EoL Python 3.7. See `Pull #82`_. API changes: - Signature of protected method `pathspec.pathspec.PathSpec._match_file()` (with a leading underscore) has been changed from `def _match_file(patterns: Iterable[Pattern], file: str) -> bool` to `def _match_file(patterns: Iterable[Tuple[int, Pattern]], file: str) -> Tuple[Optional[bool], Optional[int]]`. New features: - Added `pathspec.pathspec.PathSpec.check_*()` methods. These methods behave similarly to `.match_*()` but return additional information in the `pathspec.util.CheckResult` objects (e.g., `CheckResult.index` indicates the index of the last pattern that matched the file). - Added `pathspec.pattern.RegexPattern.pattern` attribute which stores the original, uncompiled pattern. Bug fixes: - `Issue #81`_: GitIgnoreSpec behaviors differ from git. - `Pull #83`_: Fix ReadTheDocs builds. Improvements: - Mark Python 3.12 as supported. See `Pull #82`_. - Improve test debugging. - Improve type hint on *on_error* parameter on `pathspec.pathspec.PathSpec.match_tree_entries()`. - Improve type hint on *on_error* parameter on `pathspec.util.iter_tree_entries()`. .. _`Issue #81`: https://github.com/cpburnz/python-pathspec/issues/81 .. _`Pull #82`: https://github.com/cpburnz/python-pathspec/pull/82 .. _`Pull #83`: https://github.com/cpburnz/python-pathspec/pull/83 0.11.2 (2023-07-28) ------------------- New features: - `Issue #80`_: match_files with negated path spec. `pathspec.PathSpec.match_*()` now have a `negate` parameter to make using *.gitignore* logic easier and more efficient. Bug fixes: - `Pull #76`_: Add edge case: patterns that end with an escaped space - `Issue #77`_/`Pull #78`_: Negate with caret symbol as with the exclamation mark. .. _`Pull #76`: https://github.com/cpburnz/python-pathspec/pull/76 .. _`Issue #77`: https://github.com/cpburnz/python-pathspec/issues/77 .. _`Pull #78`: https://github.com/cpburnz/python-pathspec/pull/78/ .. _`Issue #80`: https://github.com/cpburnz/python-pathspec/issues/80 0.11.1 (2023-03-14) ------------------- Bug fixes: - `Issue #74`_: Include directory should override exclude file. Improvements: - `Pull #75`_: Fix partially unknown PathLike type. - Convert `os.PathLike` to a string properly using `os.fspath`. .. _`Issue #74`: https://github.com/cpburnz/python-pathspec/issues/74 .. _`Pull #75`: https://github.com/cpburnz/python-pathspec/pull/75 0.11.0 (2023-01-24) ------------------- Major changes: - Changed build backend to `flit_core.buildapi`_ from `setuptools.build_meta`_. Building with `setuptools` through `setup.py` is still supported for distributions that need it. See `Issue #72`_. Improvements: - `Issue #72`_/`Pull #73`_: Please consider switching the build-system to flit_core to ease setuptools bootstrap. .. _`flit_core.buildapi`: https://flit.pypa.io/en/latest/index.html .. _`Issue #72`: https://github.com/cpburnz/python-pathspec/issues/72 .. _`Pull #73`: https://github.com/cpburnz/python-pathspec/pull/73 0.10.3 (2022-12-09) ------------------- New features: - Added utility function `pathspec.util.append_dir_sep()` to aid in distinguishing between directories and files on the file-system. See `Issue #65`_. Bug fixes: - `Issue #66`_/`Pull #67`_: Package not marked as py.typed. - `Issue #68`_: Exports are considered private. - `Issue #70`_/`Pull #71`_: 'Self' string literal type is Unknown in pyright. Improvements: - `Issue #65`_: Checking directories via match_file() does not work on Path objects. .. _`Issue #65`: https://github.com/cpburnz/python-pathspec/issues/65 .. _`Issue #66`: https://github.com/cpburnz/python-pathspec/issues/66 .. _`Pull #67`: https://github.com/cpburnz/python-pathspec/pull/67 .. _`Issue #68`: https://github.com/cpburnz/python-pathspec/issues/68 .. _`Issue #70`: https://github.com/cpburnz/python-pathspec/issues/70 .. _`Pull #71`: https://github.com/cpburnz/python-pathspec/pull/71 0.10.2 (2022-11-12) ------------------- Bug fixes: - Fix failing tests on Windows. - Type hint on *root* parameter on `pathspec.pathspec.PathSpec.match_tree_entries()`. - Type hint on *root* parameter on `pathspec.pathspec.PathSpec.match_tree_files()`. - Type hint on *root* parameter on `pathspec.util.iter_tree_entries()`. - Type hint on *root* parameter on `pathspec.util.iter_tree_files()`. - `Issue #64`_: IndexError with my .gitignore file when trying to build a Python package. Improvements: - `Pull #58`_: CI: add GitHub Actions test workflow. .. _`Pull #58`: https://github.com/cpburnz/python-pathspec/pull/58 .. _`Issue #64`: https://github.com/cpburnz/python-pathspec/issues/64 0.10.1 (2022-09-02) ------------------- Bug fixes: - Fix documentation on `pathspec.pattern.RegexPattern.match_file()`. - `Pull #60`_: Remove redundant wheel dep from pyproject.toml. - `Issue #61`_: Dist failure for Fedora, CentOS, EPEL. - `Issue #62`_: Since version 0.10.0 pure wildcard does not work in some cases. Improvements: - Restore support for legacy installations using `setup.py`. See `Issue #61`_. .. _`Pull #60`: https://github.com/cpburnz/python-pathspec/pull/60 .. _`Issue #61`: https://github.com/cpburnz/python-pathspec/issues/61 .. _`Issue #62`: https://github.com/cpburnz/python-pathspec/issues/62 0.10.0 (2022-08-30) ------------------- Major changes: - Dropped support of EoL Python 2.7, 3.5, 3.6. See `Issue #47`_. - The *gitwildmatch* pattern `dir/*` is now handled the same as `dir/`. This means `dir/*` will now match all descendants rather than only direct children. See `Issue #19`_. - Added `pathspec.GitIgnoreSpec` class (see new features). - Changed build system to `pyproject.toml`_ and build backend to `setuptools.build_meta`_ which may have unforeseen consequences. - Renamed GitHub project from `python-path-specification`_ to `python-pathspec`_. See `Issue #35`_. API changes: - Deprecated: `pathspec.util.match_files()` is an old function no longer used. - Deprecated: `pathspec.match_files()` is an old function no longer used. - Deprecated: `pathspec.util.normalize_files()` is no longer used. - Deprecated: `pathspec.util.iter_tree()` is an alias for `pathspec.util.iter_tree_files()`. - Deprecated: `pathspec.iter_tree()` is an alias for `pathspec.util.iter_tree_files()`. - Deprecated: `pathspec.pattern.Pattern.match()` is no longer used. Use or implement `pathspec.pattern.Pattern.match_file()`. New features: - Added class `pathspec.gitignore.GitIgnoreSpec` (with alias `pathspec.GitIgnoreSpec`) to implement *gitignore* behavior not possible with standard `PathSpec` class. The particular *gitignore* behavior implemented is prioritizing patterns matching the file directly over matching an ancestor directory. Bug fixes: - `Issue #19`_: Files inside an ignored sub-directory are not matched. - `Issue #41`_: Incorrectly (?) matches files inside directories that do match. - `Pull #51`_: Refactor deprecated unittest aliases for Python 3.11 compatibility. - `Issue #53`_: Symlink pathspec_meta.py breaks Windows. - `Issue #54`_: test_util.py uses os.symlink which can fail on Windows. - `Issue #55`_: Backslashes at start of pattern not handled correctly. - `Pull #56`_: pyproject.toml: include subpackages in setuptools config - `Issue #57`_: `!` doesn't exclude files in directories if the pattern doesn't have a trailing slash. Improvements: - Support Python 3.10, 3.11. - Modernize code to Python 3.7. - `Issue #52`_: match_files() is not a pure generator function, and it impacts tree_*() gravely. .. _`python-path-specification`: https://github.com/cpburnz/python-path-specification .. _`python-pathspec`: https://github.com/cpburnz/python-pathspec .. _`pyproject.toml`: https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/ .. _`setuptools.build_meta`: https://setuptools.pypa.io/en/latest/build_meta.html .. _`Issue #19`: https://github.com/cpburnz/python-pathspec/issues/19 .. _`Issue #35`: https://github.com/cpburnz/python-pathspec/issues/35 .. _`Issue #41`: https://github.com/cpburnz/python-pathspec/issues/41 .. _`Issue #47`: https://github.com/cpburnz/python-pathspec/issues/47 .. _`Pull #51`: https://github.com/cpburnz/python-pathspec/pull/51 .. _`Issue #52`: https://github.com/cpburnz/python-pathspec/issues/52 .. _`Issue #53`: https://github.com/cpburnz/python-pathspec/issues/53 .. _`Issue #54`: https://github.com/cpburnz/python-pathspec/issues/54 .. _`Issue #55`: https://github.com/cpburnz/python-pathspec/issues/55 .. _`Pull #56`: https://github.com/cpburnz/python-pathspec/pull/56 .. _`Issue #57`: https://github.com/cpburnz/python-pathspec/issues/57 0.9.0 (2021-07-17) ------------------ - `Issue #44`_/`Pull #50`_: Raise `GitWildMatchPatternError` for invalid git patterns. - `Pull #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-pathspec/issues/44 .. _`Pull #45`: https://github.com/cpburnz/python-pathspec/pull/45 .. _`Issue #46`: https://github.com/cpburnz/python-pathspec/issues/46 .. _`Pull #50`: https://github.com/cpburnz/python-pathspec/pull/50 0.8.1 (2020-11-07) ------------------ - `Pull #43`_: Add support for addition operator. .. _`Pull #43`: https://github.com/cpburnz/python-pathspec/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-pathspec/issues/30 .. _`Issue #31`: https://github.com/cpburnz/python-pathspec/issues/31 .. _`Issue #34`: https://github.com/cpburnz/python-pathspec/issues/34 0.7.0 (2019-12-27) ------------------ - `Pull #28`_: Add support for Python 3.8, and drop Python 3.4. - `Pull #29`_: Publish bdist wheel. .. _`Pull #28`: https://github.com/cpburnz/python-pathspec/pull/28 .. _`Pull #29`: https://github.com/cpburnz/python-pathspec/pull/29 0.6.0 (2019-10-03) ------------------ - `Pull #24`_: Drop support for Python 2.6, 3.2, and 3.3. - `Pull #25`_: Update README.rst. - `Pull #26`_: Method to escape gitwildmatch. .. _`Pull #24`: https://github.com/cpburnz/python-pathspec/pull/24 .. _`Pull #25`: https://github.com/cpburnz/python-pathspec/pull/25 .. _`Pull #26`: https://github.com/cpburnz/python-pathspec/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-pathspec/issues/22 0.5.7 (2018-08-14) ------------------ - `Issue #21`_: Fix collections deprecation warning. .. _`Issue #21`: https://github.com/cpburnz/python-pathspec/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-pathspec/issues/20 0.5.5 (2017-09-09) ------------------ - Add documentation link to README. 0.5.4 (2017-09-09) ------------------ - `Pull #17`_: Add link to Ruby implementation of *pathspec*. - Add sphinx documentation. .. _`Pull #17`: https://github.com/cpburnz/python-pathspec/pull/17 0.5.3 (2017-07-01) ------------------ - `Issue #14`_: Fix byte strings for Python 3. - `Pull #15`_: Include "LICENSE" in source package. - `Issue #16`_: Support Python 2.6. .. _`Issue #14`: https://github.com/cpburnz/python-pathspec/issues/14 .. _`Pull #15`: https://github.com/cpburnz/python-pathspec/pull/15 .. _`Issue #16`: https://github.com/cpburnz/python-pathspec/issues/16 0.5.2 (2017-04-04) ------------------ - Fixed change log. 0.5.1 (2017-04-04) ------------------ - `Pull #13`_: Add equality methods to `PathSpec` and `RegexPattern`. .. _`Pull #13`: https://github.com/cpburnz/python-pathspec/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-pathspec/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-pathspec/issues/11 0.3.4 (2015-08-24) ------------------ - `Pull #7`_: Fixed non-recursive links. - `Pull #8`_: Fixed edge cases in gitignore patterns. - `Pull #9`_: Fixed minor usage documentation. - Fixed recursion detection. - Fixed trivial incompatibility with Python 3.2. .. _`Pull #7`: https://github.com/cpburnz/python-pathspec/pull/7 .. _`Pull #8`: https://github.com/cpburnz/python-pathspec/pull/8 .. _`Pull #9`: https://github.com/cpburnz/python-pathspec/pull/9 0.3.3 (2014-11-21) ------------------ - Improved documentation. 0.3.2 (2014-11-08) ------------------ - `Pull #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. .. _`Pull #5`: https://github.com/cpburnz/python-pathspec/pull/5 .. _`Issue #6`: https://github.com/cpburnz/python-pathspec/issues/6 0.3.1 (2014-09-17) ------------------ - Updated README. 0.3.0 (2014-09-17) ------------------ - `Pull #3`_: Fixed trailing slash in gitignore patterns. - `Pull #4`_: Fixed test for trailing slash in gitignore patterns. - Added registered patterns. .. _`Pull #3`: https://github.com/cpburnz/python-pathspec/pull/3 .. _`Pull #4`: https://github.com/cpburnz/python-pathspec/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. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/CHANGES_1.in.rst0000644000000000000000000001072415136034031012700 0ustar00 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 `_. - 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/ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/DEV.md0000644000000000000000000001045115136034031011206 0ustar00 Development Notes ================= TODO ---- - Release v1.0.0. Python Versions --------------- These are notes to myself for things to review before decommissioning EoL versions of Python. ### Python **Python 3.9:** - Becomes EoL in 2025-10. - Cannot remove support until RHEL 9 ends support in 2027-05-31. - Cannot remove support until all major dependents stop supporting Python 3.9. **Python 3.10:** - Becomes EoL in 2026-10. **Python 3.11:** - Becomes EoL in 2027-10. **Python 3.12:** - Becomes EoL in 2028-10. **Python 3.13:** - Becomes EoL in 2029-10. **Python 3.14** - Becomes EoL in 2030-10. References: - [Status of Python Versions](https://devguide.python.org/versions/) ### Linux Review the following Linux distributions. **Debian:** - Goal: - Support stable release. - Debian 12 "Bookworm": - Current stable release as of 2025-06-26. - Becomes EoL on 2028-06-30. - Uses Python 3.11. - References: - [Debian Releases](https://wiki.debian.org/DebianReleases) - Package: [python3](https://packages.debian.org/stable/python3) - Package: [python3-pathspec](https://packages.debian.org/stable/python3-pathspec) **Fedora:** - Goal: - Support oldest supported release. - Fedora 41: - Oldest supported release as of 2025-06-26. - Becomes EoL on 2025-11-19. - Uses Python 3.13. - References: - [End of Life Releases ](https://docs.fedoraproject.org/en-US/releases/eol/) - [Fedora Linux 41 Schedule: Key ](https://fedorapeople.org/groups/schedule/f-41/f-41-key-tasks.html) - [Multiple Pythons](https://developer.fedoraproject.org/tech/languages/python/multiple-pythons.html) - Package: [python-pathspec](https://src.fedoraproject.org/rpms/python-pathspec) **Gentoo:** - Uses Python 3.11+ (as of 2025-06-26). - References: - Package: [pathspec](https://packages.gentoo.org/packages/dev-python/pathspec) **RHEL via Fedora EPEL:** - Goal: - Support oldest release with recent version of *python-pathspec* package. - RHEL 9: - Oldest release with recent version of *python-pathspec* package (v0.12.1/latest from 2023-12-01; as of 2025-06-26). - Ends full support on 2027-05-31. - Uses Python 3.9. - References: - [Chapter 1. Introduction to Python](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/installing_and_using_dynamic_programming_languages/assembly_introduction-to-python_installing-and-using-dynamic-programming-languages#con_python-versions_assembly_introduction-to-python) - Package: [python-pathspec](https://src.fedoraproject.org/rpms/python-pathspec) **Ubuntu:** - Goal: - Support oldest LTS release in standard support. - Ubuntu 22.04 "Jammy Jellyfish": - Active LTS release as of 2025-06-26. - Ends standard support in 2027-04. - Package is outdated (v0.9.0 from 2021-07-17; as of 2025-06-26). - Uses Python 3.10. - Ubuntu 24.04 "Noble Numbat": - Latest LTS release as of 2025-06-26. - Ends standard support in 2029-04. - Package is update-to-date (v0.12.1 from 2023-12-10; as of 2025-06-26). - Uses Python 3.12. - References: - [Releases](https://wiki.ubuntu.com/Releases) - Package: [python3](https://packages.ubuntu.com/jammy/python3) (jammy) - Package: [python3](https://packages.ubuntu.com/noble/python3) (noble) - Package: [python3-pathspec](https://packages.ubuntu.com/jammy/python3-pathspec) (jammy) - Package: [python3-pathspec](https://packages.ubuntu.com/noble/python3-pathspec) (noble) ### PyPI Review the following PyPI packages. [ansible-lint](https://pypi.org/project/ansible-lint/) - v25.9.2 (latest as of 2025-10-20) requires Python 3.10+. - [ansible-lint on Wheelodex](https://www.wheelodex.org/projects/ansible-lint/). [black](https://pypi.org/project/black/) - v25.9.0 (latest as of 2025-10-20) requires Python 3.9+. - [black on Wheelodex](https://www.wheelodex.org/projects/black/). [dvc](https://pypi.org/project/dvc/) - v3.63.0 (latest as of 2025-10-20) requires Python 3.9+. - [dvc on Wheelodex](https://www.wheelodex.org/projects/dvc/). [hatchling](https://pypi.org/project/hatchling/) - v1.27.0 (latest as of 2025-10-20) requires Python 3.8+, but next release will require Python 3.9+. - [hatchling on Wheelodex](https://www.wheelodex.org/projects/hatchling/). [yamllint](https://pypi.org/project/yamllint/) - v1.37.1 (latest as of 2025-10-20) requires Python 3.9+. - [yamllint on Wheelodex](https://www.wheelodex.org/projects/yamllint/). ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/LICENSE0000644000000000000000000004052615136034031011261 0ustar00Mozilla 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. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/MANIFEST.in0000644000000000000000000000041215136034031012000 0ustar00include *.cfg include *.in include *.ini include *.md include *.py include *.rst include *.toml include pathspec/py.typed include LICENSE recursive-include benchmarks * recursive-include doc * recursive-include tests * prune dev prune doc/build global-exclude *.pyc ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/README-dist.rst0000644000000000000000000002736715136034031012714 0ustar00 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 `_. - 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/ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/README.rst0000644000000000000000000001640215136034031011737 0ustar00 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 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/UPGRADING.rst0000644000000000000000000000335115136034031012321 0ustar00 Upgrade Guide ============= From 0.12.1 to 1.0.0 -------------------- `PathSpec`: - The "gitwildmatch" pattern has been replaced by "gitignore". Use ``PathSpec.from_lines('gitignore', ...)`` instead of ``PathSpec.from_lines('gitwildmatch', ...)``. `PathSpec.from_lines('gitignore', ...)`: - Patterns of the form "``foo/*``" will no longer match files in subdirectories. "``foo/test.json``" will match, but "``foo/bar/hello.c``" will no longer match. `GitIgnoreSpec` will continue to match those files. See `Issue #95`_. - The exact pattern "``/``" will now match every file (equivalent to "``**``"). `GitIgnoreSpec` will continue to discard this pattern. `PathSpec.from_lines('gitwildmatch', ...)`: - "gitwildmatch" patterns are deprecated and will be removed in a future version. - Its behavior is unchanged and does not have the changes documented above for "gitignore" patterns. - To maintain this exact behavior, `GitIgnoreSpecPattern` can be used, though this use is discouraged because it's a hybrid between Git's behavior and the `gitignore`_ docs. `GitIgnoreSpec`: - No changes. `GitWildMatchPattern`: - This class is deprecated and will be removed in a future version. - This has been split into `pathspec.patterns.gitignore.basic.GitIgnoreBasicPattern` and `pathspec.patterns.gitignore.spec.GitIgnoreSpecPattern`. `GitIgnoreSpecPattern` is the unchanged implementation. `GitIgnoreBasicPattern` has the changes documented above for "gitignore". `GitWildMatchPatternError`: - This class is deprecated and will be removed in a future version. - This has been renamed to `pathspec.patterns.gitignore.GitIgnorePatternError`. .. _`Issue #95`: https://github.com/cpburnz/python-pathspec/issues/95 .. _`gitignore`: https://git-scm.com/docs/gitignore ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/__init__.py0000644000000000000000000000000015136034031014461 0ustar00././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_fs.py0000644000000000000000000000177015136034031014510 0ustar00 from pathlib import ( Path) import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec.util import ( iter_tree_entries, iter_tree_files) @pytest.mark.benchmark(group="iter_tree_entries", warmup=True) def bench_iter_tree_entries(benchmark: BenchmarkFixture, cpython_dir: Path): benchmark(run_iter_tree_entries, cpython_dir) @pytest.mark.benchmark(group="iter_tree_files", warmup=True) def bench_iter_tree_files_v0(benchmark: BenchmarkFixture, cpython_dir: Path): benchmark(run_iter_tree_files_v0, cpython_dir) @pytest.mark.benchmark(group="iter_tree_files", warmup=True) def bench_iter_tree_files_v1(benchmark: BenchmarkFixture, cpython_dir: Path): benchmark(run_iter_tree_files_v1, cpython_dir) def run_iter_tree_entries(path: Path): for _ in iter_tree_entries(path): pass def run_iter_tree_files_v0(path: Path): for entry in iter_tree_entries(path): if not entry.is_dir(): pass def run_iter_tree_files_v1(path: Path): for _ in iter_tree_files(path): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_150p_to_6500f.py0000644000000000000000000001257315136034031020161 0ustar00""" This module benchmarks :class:`.GitIgnoreSpec` using ~150 patterns against ~6.5k files. """ from functools import ( partial) import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) from pathspec._backends.simple.gitignore import ( SimpleGiBackend) from benchmarks.hyperscan_gitignore_r1 import ( HyperscanGiR1BlockClosureBackend, HyperscanGiR1BlockStateBackend, HyperscanGiR1StreamClosureBackend, HyperscanGiR1StreamStateBackend) from benchmarks.hyperscan_gitignore_r2 import ( HyperscanGiR2BlockClosureBackend, HyperscanGiR2BlockStateBackend, HyperscanGiR2StreamClosureBackend) GROUP = "GitIgnore.match_files(): 150 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_block_closure( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR1BlockClosureBackend, ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_block_state( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR1BlockStateBackend, ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_stream_closure( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR1StreamClosureBackend, ) benchmark(run_match, spec, cpython_files) # WARNING: This segfaults. # @pytest.mark.benchmark(group=GROUP) # def bench_hs_r1_stream_state( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = GitIgnoreSpec.from_lines( # cpython_gi_lines_all, # backend='hyperscan', # _test_backend_factory=GiHyperscanStreamStateBackend, # ) # benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r2_block_closure( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR2BlockClosureBackend, ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r2_block_state( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR2BlockStateBackend, ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r2_stream_closure( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR2StreamClosureBackend, ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_filtered( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', _test_backend_factory=partial(SimpleGiBackend, no_reverse=True) ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_filtered_reversed( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_unfiltered( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', _test_backend_factory=partial(SimpleGiBackend, no_filter=True, no_reverse=True) ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_unfiltered_reversed( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', _test_backend_factory=partial(SimpleGiBackend, no_filter=True) ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_15p_to_400f.py0000644000000000000000000001145715136034031020012 0ustar00""" This module benchmarks :class:`.GitIgnoreSpec` using ~15 patterns against ~400 files. """ from functools import ( partial) import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) from pathspec._backends.simple.gitignore import ( SimpleGiBackend) from benchmarks.hyperscan_gitignore_r1 import ( HyperscanGiR1BlockClosureBackend, HyperscanGiR1BlockStateBackend, HyperscanGiR1StreamClosureBackend) from benchmarks.hyperscan_gitignore_r2 import ( HyperscanGiR2BlockClosureBackend, HyperscanGiR2BlockStateBackend, HyperscanGiR2StreamClosureBackend) GROUP = "GitIgnore.match_files(): 15 lines, 400 files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_block_closure( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR1BlockClosureBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_block_state( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR1BlockStateBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_stream_closure( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR1StreamClosureBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r2_block_closure( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR2BlockClosureBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r2_block_state( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR2BlockStateBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r2_stream_closure( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanGiR2StreamClosureBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_filtered( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', _test_backend_factory=partial(SimpleGiBackend, no_reverse=True) ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_filtered_reversed( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_unfiltered( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', _test_backend_factory=partial(SimpleGiBackend, no_filter=True, no_reverse=True) ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_unfiltered_reversed( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', _test_backend_factory=partial(SimpleGiBackend, no_filter=True) ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_file_p1.py0000644000000000000000000000400315136034031020632 0ustar00""" This module benchmarks `GitIgnoreSpec.match_file()` using 1 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_file(): 1 line, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_1, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_1, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_1, backend='re2', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_1, backend='re2', ) benchmark(run_match, spec, flit_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_1, backend='simple', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_1, backend='simple', ) benchmark(run_match, spec, flit_file_match_start) def run_match(spec: GitIgnoreSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_file_p100.py0000644000000000000000000000751515136034031021005 0ustar00""" This module benchmarks `GitIgnoreSpec.match_file()` using ~100 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_file(): 100 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_file_match_start) def run_match(spec: GitIgnoreSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_file_p15.py0000644000000000000000000000727315136034031020733 0ustar00""" This module benchmarks `GitIgnoreSpec.match_file()` using ~15 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_file(): 15 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_file_match_start) def run_match(spec: GitIgnoreSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_file_p150.py0000644000000000000000000000751515136034031021012 0ustar00""" This module benchmarks `GitIgnoreSpec.match_file()` using ~150 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_file(): 150 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_file_match_start) def run_match(spec: GitIgnoreSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_file_p2.py0000644000000000000000000000547115136034031020645 0ustar00""" This module benchmarks `GitIgnoreSpec.match_file()` using 2 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_file(): 2 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='re2', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='re2', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='re2', ) benchmark(run_match, spec, flit_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='simple', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='simple', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_2: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_2, backend='simple', ) benchmark(run_match, spec, flit_file_match_start) def run_match(spec: GitIgnoreSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_file_p25.py0000644000000000000000000000746315136034031020735 0ustar00""" This module benchmarks `GitIgnoreSpec.match_file()` using ~25 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_file(): 25 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_file_match_start) def run_match(spec: GitIgnoreSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_file_p5.py0000644000000000000000000000721015136034031020641 0ustar00""" This module benchmarks `GitIgnoreSpec.match_file()` using 5 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_file(): 5 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='re2', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='re2', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='re2', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='re2', ) benchmark(run_match, spec, flit_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='simple', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='simple', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='simple', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( flit_gi_lines_5, backend='simple', ) benchmark(run_match, spec, flit_file_match_start) def run_match(spec: GitIgnoreSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_file_p50.py0000644000000000000000000000746315136034031020733 0ustar00""" This module benchmarks `GitIgnoreSpec.match_file()` using ~50 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_file(): 50 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_file_match_start) def run_match(spec: GitIgnoreSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_files_p1.py0000644000000000000000000000225215136034031021021 0ustar00""" This module benchmarks `GitIgnoreSpec.match_files()` using 1 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_files(): 1 line, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_1, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_1, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_1: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_1, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_files_p100.py0000644000000000000000000000227415136034031021165 0ustar00""" This module benchmarks `GitIgnoreSpec.match_files()` using 100 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_files(): 100 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_100: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_files_p15.py0000644000000000000000000000226415136034031021111 0ustar00""" This module benchmarks `GitIgnoreSpec.match_files()` using 15 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_files(): 15 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_15: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_15, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_15: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_15, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_15: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_15, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_files_p150.py0000644000000000000000000000227415136034031021172 0ustar00""" This module benchmarks `GitIgnoreSpec.match_files()` using 150 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_files(): 150 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_files_p25.py0000644000000000000000000000226415136034031021112 0ustar00""" This module benchmarks `GitIgnoreSpec.match_files()` using 25 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_files(): 25 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_25: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_files_p5.py0000644000000000000000000000225415136034031021027 0ustar00""" This module benchmarks `GitIgnoreSpec.match_files()` using 5 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_files(): 5 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_5, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_5: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_5, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitignore_match_files_p50.py0000644000000000000000000000226415136034031021110 0ustar00""" This module benchmarks `GitIgnoreSpec.match_files()` using 50 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec) GROUP = "GitIgnoreSpec.match_files(): 50 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: str, cpython_gi_lines_50: list[str], ): spec = GitIgnoreSpec.from_lines( cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitwildmatch_150p_to_6500f.py0000644000000000000000000001115515136034031020645 0ustar00""" This module benchmarks :class:`.GitWildMatchPattern` using ~150 patterns against ~6.5k files. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec, PathSpec) from benchmarks.gitwildmatch_v1 import ( GitWildMatchV1Pattern) GROUP_GITIGNORE = "GitWildMatchPattern: GitIgnore.match_files(): 150 lines, 6.5k files" GROUP_PATHSPEC = "GitWildMatchPattern: PathSpec.match_files(): 150 lines, 6.5k files" # # Hyperscan backend. # # @pytest.mark.benchmark(group=GROUP_GITIGNORE) # def bench_hs_gitignore_v1( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = GitIgnoreSpec.from_lines( # lines=cpython_gi_lines_all, # pattern_factory=GitWildMatchV1Pattern, # backend='hyperscan', # ) # benchmark(run_match, spec, cpython_files) # # # @pytest.mark.benchmark(group=GROUP_GITIGNORE) # def bench_hs_gitignore_v2( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = GitIgnoreSpec.from_lines( # lines=cpython_gi_lines_all, # backend='hyperscan', # ) # benchmark(run_match, spec, cpython_files) # # # @pytest.mark.benchmark(group=GROUP_PATHSPEC) # def bench_hs_pathspec_v1( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = PathSpec.from_lines( # lines=cpython_gi_lines_all, # pattern_factory=GitWildMatchV1Pattern, # backend='hyperscan', # ) # benchmark(run_match, spec, cpython_files) # # # @pytest.mark.benchmark(group=GROUP_PATHSPEC) # def bench_hs_pathspec_v2( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = PathSpec.from_lines( # lines=cpython_gi_lines_all, # pattern_factory='gitwildmatch', # backend='hyperscan', # ) # benchmark(run_match, spec, cpython_files) # # # # Re2 backend. # # @pytest.mark.benchmark(group=GROUP_GITIGNORE) # def bench_re2_gitignore_v1( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = GitIgnoreSpec.from_lines( # lines=cpython_gi_lines_all, # pattern_factory=GitWildMatchV1Pattern, # backend='re2', # ) # benchmark(run_match, spec, cpython_files) # # # @pytest.mark.benchmark(group=GROUP_GITIGNORE) # def bench_re2_gitignore_v2( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = GitIgnoreSpec.from_lines( # lines=cpython_gi_lines_all, # backend='re2', # ) # benchmark(run_match, spec, cpython_files) # # # @pytest.mark.benchmark(group=GROUP_PATHSPEC) # def bench_re2_pathspec_v1( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = PathSpec.from_lines( # lines=cpython_gi_lines_all, # pattern_factory=GitWildMatchV1Pattern, # backend='re2', # ) # benchmark(run_match, spec, cpython_files) # # # @pytest.mark.benchmark(group=GROUP_PATHSPEC) # def bench_re2_pathspec_v2( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = PathSpec.from_lines( # lines=cpython_gi_lines_all, # pattern_factory='gitwildmatch', # backend='re2', # ) # benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP_GITIGNORE) def bench_sm_gitignore_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( lines=cpython_gi_lines_all, pattern_factory=GitWildMatchV1Pattern, backend='simple', ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP_GITIGNORE) def bench_sm_gitignore_v2( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( lines=cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP_PATHSPEC) def bench_sm_pathspec_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( lines=cpython_gi_lines_all, pattern_factory=GitWildMatchV1Pattern, backend='simple', ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP_PATHSPEC) def bench_sm_pathspec_v2( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( lines=cpython_gi_lines_all, pattern_factory='gitwildmatch', backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_gitwildmatch_15p_to_400f.py0000644000000000000000000001040715136034031020475 0ustar00""" This module benchmarks :class:`.GitWildMatchPattern` using ~15 patterns against ~400 files. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( GitIgnoreSpec, PathSpec) from benchmarks.gitwildmatch_v1 import ( GitWildMatchV1Pattern) GROUP_GITIGNORE = "GitWildMatchPattern: GitIgnore.match_files(): 15 lines, 400 files" GROUP_PATHSPEC = "GitWildMatchPattern: PathSpec.match_files(): 15 lines, 400 files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP_GITIGNORE) def bench_hs_gitignore_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( lines=flit_gi_lines_all, pattern_factory=GitWildMatchV1Pattern, backend='hyperscan', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_GITIGNORE) def bench_hs_gitignore_v2( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( lines=flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_PATHSPEC) def bench_hs_pathspec_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( lines=flit_gi_lines_all, pattern_factory=GitWildMatchV1Pattern, backend='hyperscan', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_PATHSPEC) def bench_hs_pathspec_v2( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( lines=flit_gi_lines_all, pattern_factory='gitwildmatch', backend='hyperscan', ) benchmark(run_match, spec, flit_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP_GITIGNORE) def bench_re2_gitignore_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( lines=flit_gi_lines_all, pattern_factory=GitWildMatchV1Pattern, backend='re2', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_GITIGNORE) def bench_re2_gitignore_v2( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( lines=flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_PATHSPEC) def bench_re2_pathspec_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( lines=flit_gi_lines_all, pattern_factory=GitWildMatchV1Pattern, backend='re2', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_PATHSPEC) def bench_re2_pathspec_v2( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( lines=flit_gi_lines_all, pattern_factory='gitwildmatch', backend='re2', ) benchmark(run_match, spec, flit_files) # Simple backend. @pytest.mark.benchmark(group=GROUP_GITIGNORE) def bench_sm_gitignore_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( lines=flit_gi_lines_all, pattern_factory=GitWildMatchV1Pattern, backend='simple', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_GITIGNORE) def bench_sm_gitignore_v2( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = GitIgnoreSpec.from_lines( lines=flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_PATHSPEC) def bench_sm_pathspec_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( lines=flit_gi_lines_all, pattern_factory=GitWildMatchV1Pattern, backend='simple', ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP_PATHSPEC) def bench_sm_pathspec_v2( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( lines=flit_gi_lines_all, pattern_factory='gitwildmatch', backend='simple', ) benchmark(run_match, spec, flit_files) def run_match(spec: GitIgnoreSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.716072 pathspec-1.0.4/benchmarks/bench_match_files_cpython313_i7-165G7.json0000644000000000000000000016623015136034031022003 0ustar00{ "machine_info": { "node": "galp5", "processor": "", "machine": "x86_64", "python_compiler": "GCC 15.2.1 20251112", "python_implementation": "CPython", "python_implementation_version": "3.13.11", "python_version": "3.13.11", "python_build": [ "main", "Dec 7 2025 13:01:45" ], "release": "6.12.62-1-MANJARO", "system": "Linux", "cpu": { "python_version": "3.13.11.final.0 (64 bit)", "cpuinfo_version": [ 9, 0, 0 ], "cpuinfo_version_string": "9.0.0", "arch": "X86_64", "bits": 64, "count": 8, "arch_string_raw": "x86_64", "vendor_id_raw": "GenuineIntel", "brand_raw": "11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz", "hz_advertised_friendly": "2.8000 GHz", "hz_actual_friendly": "456.6970 MHz", "hz_advertised": [ 2800000000, 0 ], "hz_actual": [ 456697000, 0 ], "stepping": 1, "model": 140, "family": 6, "flags": [ "3dnowprefetch", "abm", "acpi", "adx", "aes", "aperfmperf", "apic", "arat", "arch_capabilities", "arch_perfmon", "art", "avx", "avx2", "avx512_bitalg", "avx512_vbmi2", "avx512_vnni", "avx512_vp2intersect", "avx512_vpopcntdq", "avx512bitalg", "avx512bw", "avx512cd", "avx512dq", "avx512f", "avx512ifma", "avx512vbmi", "avx512vbmi2", "avx512vl", "avx512vnni", "avx512vpopcntdq", "bmi1", "bmi2", "bts", "cat_l2", "cdp_l2", "clflush", "clflushopt", "clwb", "cmov", "constant_tsc", "cpuid", "cpuid_fault", "cx16", "cx8", "de", "ds_cpl", "dtes64", "dtherm", "dts", "epb", "ept", "ept_ad", "erms", "est", "f16c", "flexpriority", "flush_l1d", "fma", "fpu", "fsgsbase", "fsrm", "fxsr", "gfni", "ht", "hwp", "hwp_act_window", "hwp_epp", "hwp_notify", "hwp_pkg_req", "ibpb", "ibrs", "ibrs_enhanced", "ibt", "ida", "intel_pt", "invpcid", "lahf_lm", "lm", "mca", "mce", "md_clear", "mmx", "monitor", "movbe", "movdir64b", "movdiri", "msr", "mtrr", "nonstop_tsc", "nopl", "nx", "ospke", "osxsave", "pae", "pat", "pbe", "pcid", "pclmulqdq", "pdcm", "pdpe1gb", "pebs", "pge", "pku", "pln", "pni", "popcnt", "pqe", "pse", "pse36", "pts", "rdpid", "rdrand", "rdrnd", "rdseed", "rdt_a", "rdtscp", "rep_good", "sdbg", "sep", "sha", "sha_ni", "smap", "smep", "split_lock_detect", "ss", "ssbd", "sse", "sse2", "sse4_1", "sse4_2", "ssse3", "stibp", "syscall", "tm", "tm2", "tpr_shadow", "tsc", "tsc_adjust", "tsc_deadline_timer", "tsc_known_freq", "tscdeadline", "umip", "user_shstk", "vaes", "vme", "vmx", "vnmi", "vpclmulqdq", "vpid", "x2apic", "xgetbv1", "xsave", "xsavec", "xsaveopt", "xsaves", "xtopology", "xtpr" ], "l3_cache_size": 12582912, "l2_cache_size": 5242880, "l1_data_cache_size": 196608, "l1_instruction_cache_size": 131072, "l2_cache_line_size": 256, "l2_cache_associativity": 7 } }, "commit_info": { "id": "765e0bbc85ef9c92ed4a90b85ba78ad723057da3", "time": "2025-12-26T23:45:09-05:00", "author_time": "2025-12-26T23:45:09-05:00", "dirty": false, "project": "python-pathspec", "branch": "master" }, "benchmarks": [ { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.01461109000047145, "max": 0.016519138000148814, "mean": 0.014940808640716341, "stddev": 0.000371952221560657, "rounds": 64, "median": 0.01479980699969019, "iqr": 0.00029899499986640876, "q1": 0.014708668500134081, "q3": 0.01500766350000049, "iqr_outliers": 5, "stddev_outliers": 9, "outliers": "9;5", "ld15iqr": 0.01461109000047145, "hd15iqr": 0.01554243800092081, "ops": 66.93078159603915, "total": 0.9562117530058458, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005128275999595644, "max": 0.005847990998518071, "mean": 0.005239293749941441, "stddev": 0.0001312400943102171, "rounds": 76, "median": 0.005208518999097578, "iqr": 8.864900064509129e-05, "q1": 0.005163024499779567, "q3": 0.005251673500424658, "iqr_outliers": 5, "stddev_outliers": 6, "outliers": "6;5", "ld15iqr": 0.005128275999595644, "hd15iqr": 0.00542464799946174, "ops": 190.86541960186463, "total": 0.3981863249955495, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.11699812099868723, "max": 0.11882632800006832, "mean": 0.11771544277730249, "stddev": 0.0005816110516735742, "rounds": 9, "median": 0.11772227199980989, "iqr": 0.0008537472490388609, "q1": 0.11724349850010185, "q3": 0.11809724574914071, "iqr_outliers": 0, "stddev_outliers": 3, "outliers": "3;0", "ld15iqr": 0.11699812099868723, "hd15iqr": 0.11882632800006832, "ops": 8.495062129544287, "total": 1.0594389849957224, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.015964588999850093, "max": 0.022772140000597574, "mean": 0.016534497316630828, "stddev": 0.0011627081695163707, "rounds": 60, "median": 0.0161932995006282, "iqr": 0.00038189800034160726, "q1": 0.016082409000773623, "q3": 0.01646430700111523, "iqr_outliers": 5, "stddev_outliers": 3, "outliers": "3;5", "ld15iqr": 0.015964588999850093, "hd15iqr": 0.017341505999866058, "ops": 60.479613068984804, "total": 0.9920698389978497, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005664874999638414, "max": 0.006396688000677386, "mean": 0.005781540059833787, "stddev": 0.0001262330659504193, "rounds": 50, "median": 0.005755362500167394, "iqr": 6.897399907757062e-05, "q1": 0.005714905999411712, "q3": 0.005783879998489283, "iqr_outliers": 5, "stddev_outliers": 4, "outliers": "4;5", "ld15iqr": 0.005664874999638414, "hd15iqr": 0.005906034999497933, "ops": 172.96429491984682, "total": 0.28907700299168937, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.17212004199973308, "max": 0.17380983800103422, "mean": 0.17291908250020546, "stddev": 0.0006596853749524823, "rounds": 6, "median": 0.17304644550040393, "iqr": 0.0011179640005138936, "q1": 0.17218687999957183, "q3": 0.17330484400008572, "iqr_outliers": 0, "stddev_outliers": 3, "outliers": "3;0", "ld15iqr": 0.17212004199973308, "hd15iqr": 0.17380983800103422, "ops": 5.783051734610099, "total": 1.0375144950012327, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.006642042000748916, "max": 0.007803892998708761, "mean": 0.006825518864682916, "stddev": 0.00014223974041206942, "rounds": 133, "median": 0.0067909640001744265, "iqr": 0.0001060099989445007, "q1": 0.006751520500984043, "q3": 0.006857530499928544, "iqr_outliers": 9, "stddev_outliers": 17, "outliers": "17;9", "ld15iqr": 0.006642042000748916, "hd15iqr": 0.007073726001181058, "ops": 146.50900830034635, "total": 0.9077940090028278, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005041239999627578, "max": 0.005814206000650302, "mean": 0.005173290565767924, "stddev": 0.00011352037812269578, "rounds": 175, "median": 0.005139092001627432, "iqr": 7.647449956493801e-05, "q1": 0.005115116000524722, "q3": 0.00519159050008966, "iqr_outliers": 12, "stddev_outliers": 15, "outliers": "15;12", "ld15iqr": 0.005041239999627578, "hd15iqr": 0.005322252998666954, "ops": 193.30056707370733, "total": 0.9053258490093867, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.02111750599942752, "max": 0.023634527000467642, "mean": 0.021688659130434287, "stddev": 0.0004982624324089485, "rounds": 46, "median": 0.0215373594992343, "iqr": 0.0004526650009211153, "q1": 0.02135991499926604, "q3": 0.021812580000187154, "iqr_outliers": 3, "stddev_outliers": 6, "outliers": "6;3", "ld15iqr": 0.02111750599942752, "hd15iqr": 0.022674892999930307, "ops": 46.107045806108175, "total": 0.9976783199999772, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005022429000746342, "max": 0.00614746099927288, "mean": 0.005341720781359527, "stddev": 0.00019665408000321742, "rounds": 192, "median": 0.005320474500877026, "iqr": 0.000304351000522729, "q1": 0.005174748000172258, "q3": 0.005479099000694987, "iqr_outliers": 4, "stddev_outliers": 51, "outliers": "51;4", "ld15iqr": 0.005022429000746342, "hd15iqr": 0.0060151559991936665, "ops": 187.20559178038673, "total": 1.0256103900210292, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.004950434999045683, "max": 0.005715805998988799, "mean": 0.005093646989917033, "stddev": 0.00010730555665901586, "rounds": 200, "median": 0.005074001000139106, "iqr": 7.214349989226321e-05, "q1": 0.005039461000706069, "q3": 0.005111604500598332, "iqr_outliers": 10, "stddev_outliers": 20, "outliers": "20;10", "ld15iqr": 0.004950434999045683, "hd15iqr": 0.005328252000253997, "ops": 196.32298861297576, "total": 1.0187293979834067, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0035663790004036855, "max": 0.004285777000404778, "mean": 0.0036776690643624274, "stddev": 8.646376513339311e-05, "rounds": 280, "median": 0.0036578920007741544, "iqr": 8.693350082467077e-05, "q1": 0.0036221850004949374, "q3": 0.003709118501319608, "iqr_outliers": 9, "stddev_outliers": 44, "outliers": "44;9", "ld15iqr": 0.0035663790004036855, "hd15iqr": 0.0038435829992522486, "ops": 271.91136083729253, "total": 1.0297473380214797, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.017627958000957733, "max": 0.019985216000350192, "mean": 0.018038770222254377, "stddev": 0.00039642534565129453, "rounds": 54, "median": 0.01787906200024736, "iqr": 0.0003371879993210314, "q1": 0.01781915100036713, "q3": 0.01815633899968816, "iqr_outliers": 3, "stddev_outliers": 8, "outliers": "8;3", "ld15iqr": 0.017627958000957733, "hd15iqr": 0.018765254999379977, "ops": 55.436151560171375, "total": 0.9740935920017364, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005058554001152515, "max": 0.006185751999510103, "mean": 0.0052132267078672774, "stddev": 0.00012227088513213364, "rounds": 154, "median": 0.005182976499781944, "iqr": 8.133799929055385e-05, "q1": 0.005154520000360208, "q3": 0.0052358579996507615, "iqr_outliers": 10, "stddev_outliers": 13, "outliers": "13;10", "ld15iqr": 0.005058554001152515, "hd15iqr": 0.0053859550007473445, "ops": 191.81977996293554, "total": 0.8028369130115607, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.03578309799922863, "max": 0.04189880500052823, "mean": 0.03674541892589197, "stddev": 0.0014386114485027861, "rounds": 27, "median": 0.036174689999825205, "iqr": 0.001028976751058508, "q1": 0.0359316717499496, "q3": 0.03696064850100811, "iqr_outliers": 3, "stddev_outliers": 3, "outliers": "3;3", "ld15iqr": 0.03578309799922863, "hd15iqr": 0.038615756999206496, "ops": 27.2142767515264, "total": 0.9921263109990832, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.025664275999588426, "max": 0.02989515199988091, "mean": 0.026489024111301492, "stddev": 0.0009990466373196298, "rounds": 27, "median": 0.026157584001339274, "iqr": 0.0010799255014717346, "q1": 0.02584570274939324, "q3": 0.026925628250864975, "iqr_outliers": 2, "stddev_outliers": 3, "outliers": "3;2", "ld15iqr": 0.025664275999588426, "hd15iqr": 0.028817046999392915, "ops": 37.75148513581336, "total": 0.7152036510051403, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0051103509995300556, "max": 0.005689727999197203, "mean": 0.00524435037815016, "stddev": 7.76222330666811e-05, "rounds": 119, "median": 0.00522203000036825, "iqr": 6.776474992875592e-05, "q1": 0.005199414001253899, "q3": 0.005267178751182655, "iqr_outliers": 4, "stddev_outliers": 16, "outliers": "16;4", "ld15iqr": 0.0051103509995300556, "hd15iqr": 0.00546347700037586, "ops": 190.68138623352814, "total": 0.624077694999869, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.06312226099908003, "max": 0.06404174200179114, "mean": 0.06353556325018417, "stddev": 0.0003280618955276863, "rounds": 16, "median": 0.0633808874999886, "iqr": 0.0006054349978512619, "q1": 0.06329543750143785, "q3": 0.06390087249928911, "iqr_outliers": 0, "stddev_outliers": 6, "outliers": "6;0", "ld15iqr": 0.06312226099908003, "hd15iqr": 0.06404174200179114, "ops": 15.739216729098587, "total": 1.0165690120029467, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.006015130000378122, "max": 0.006882773001052556, "mean": 0.006209233070619984, "stddev": 0.00011953025592651626, "rounds": 156, "median": 0.006185715999890817, "iqr": 0.00013981550091557438, "q1": 0.0061242454994498985, "q3": 0.006264061000365473, "iqr_outliers": 3, "stddev_outliers": 37, "outliers": "37;3", "ld15iqr": 0.006015130000378122, "hd15iqr": 0.006550125999638112, "ops": 161.05048540240276, "total": 0.9686403590167174, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.004894450999927358, "max": 0.005880695998712326, "mean": 0.005030227653210501, "stddev": 0.0001421544796584626, "rounds": 199, "median": 0.0049959619991568616, "iqr": 8.120749998852261e-05, "q1": 0.00496356424946498, "q3": 0.005044771749453503, "iqr_outliers": 12, "stddev_outliers": 12, "outliers": "12;12", "ld15iqr": 0.004894450999927358, "hd15iqr": 0.005197103999307728, "ops": 198.79815963433748, "total": 1.0010153029888897, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.009143662000496988, "max": 0.010138706000361708, "mean": 0.009437858231413647, "stddev": 0.0001866699778208236, "rounds": 108, "median": 0.00938916950053681, "iqr": 0.00012262349900993286, "q1": 0.009335348000604426, "q3": 0.00945797149961436, "iqr_outliers": 12, "stddev_outliers": 16, "outliers": "16;12", "ld15iqr": 0.00923107800008438, "hd15iqr": 0.009679091999714728, "ops": 105.95624298228256, "total": 1.019288688992674, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.018491720999008976, "max": 0.0195031849998486, "mean": 0.018724567980650766, "stddev": 0.00022031835958576158, "rounds": 52, "median": 0.018650493999302853, "iqr": 0.0002002515002459404, "q1": 0.018585022499792103, "q3": 0.018785274000038044, "iqr_outliers": 3, "stddev_outliers": 10, "outliers": "10;3", "ld15iqr": 0.018491720999008976, "hd15iqr": 0.019297496999570285, "ops": 53.40577155282625, "total": 0.9736775349938398, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005431045001387247, "max": 0.006319890000668238, "mean": 0.005564072845408725, "stddev": 0.00011232914113057161, "rounds": 97, "median": 0.00553523700000369, "iqr": 7.362974974967074e-05, "q1": 0.005507805749857653, "q3": 0.0055814354996073234, "iqr_outliers": 6, "stddev_outliers": 8, "outliers": "8;6", "ld15iqr": 0.005431045001387247, "hd15iqr": 0.005740079999668524, "ops": 179.72446223905288, "total": 0.5397150660046464, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.1144444849996944, "max": 0.11571898799957125, "mean": 0.11511518188874309, "stddev": 0.00040107879745630983, "rounds": 9, "median": 0.11514317100045446, "iqr": 0.0004961869985891099, "q1": 0.11486171875048967, "q3": 0.11535790574907878, "iqr_outliers": 0, "stddev_outliers": 4, "outliers": "4;0", "ld15iqr": 0.1144444849996944, "hd15iqr": 0.11571898799957125, "ops": 8.686951482789503, "total": 1.0360366369986878, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.01741726999898674, "max": 0.020907465999698616, "mean": 0.01775757885456683, "stddev": 0.0005029363623362412, "rounds": 55, "median": 0.017617876001168042, "iqr": 0.00014712949996464886, "q1": 0.017563352499564644, "q3": 0.017710481999529293, "iqr_outliers": 7, "stddev_outliers": 3, "outliers": "3;7", "ld15iqr": 0.01741726999898674, "hd15iqr": 0.018023689000983723, "ops": 56.313983352681184, "total": 0.9766668370011757, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005513109001185512, "max": 0.006237686999156722, "mean": 0.005657154542807672, "stddev": 0.00013076269463484756, "rounds": 70, "median": 0.005615932999717188, "iqr": 6.27350000286242e-05, "q1": 0.005592118999629747, "q3": 0.005654853999658371, "iqr_outliers": 8, "stddev_outliers": 8, "outliers": "8;8", "ld15iqr": 0.005513109001185512, "hd15iqr": 0.0057517569985066075, "ops": 176.7673116286647, "total": 0.3960008179965371, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.1610070930000802, "max": 0.1711392369998066, "mean": 0.16317367271429767, "stddev": 0.003705053906713775, "rounds": 7, "median": 0.16157711899904825, "iqr": 0.002662559999862424, "q1": 0.16108926574997895, "q3": 0.16375182574984137, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.1610070930000802, "hd15iqr": 0.1711392369998066, "ops": 6.128439615077546, "total": 1.1422157090000837, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.006299245998889091, "max": 0.00702432500111172, "mean": 0.006469204805524795, "stddev": 0.00012089137484460649, "rounds": 144, "median": 0.006432826000491332, "iqr": 0.0001025179999487591, "q1": 0.006397221500265005, "q3": 0.006499739500213764, "iqr_outliers": 11, "stddev_outliers": 23, "outliers": "23;11", "ld15iqr": 0.006299245998889091, "hd15iqr": 0.006722225998601061, "ops": 154.57850385969934, "total": 0.9315654919955705, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005280425000819378, "max": 0.006190658999912557, "mean": 0.005420151174098209, "stddev": 0.00012890825216954992, "rounds": 178, "median": 0.005388026500440901, "iqr": 8.293900100397877e-05, "q1": 0.0053542299992841436, "q3": 0.005437169000288122, "iqr_outliers": 13, "stddev_outliers": 16, "outliers": "16;13", "ld15iqr": 0.005280425000819378, "hd15iqr": 0.005567198000790086, "ops": 184.4966990549627, "total": 0.9647869089894812, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.020886501000859425, "max": 0.021711376999519416, "mean": 0.02118120419153785, "stddev": 0.0002044376828762903, "rounds": 47, "median": 0.021109568999236217, "iqr": 0.0002977277504214726, "q1": 0.021021394499712187, "q3": 0.02131912225013366, "iqr_outliers": 0, "stddev_outliers": 12, "outliers": "12;0", "ld15iqr": 0.020886501000859425, "hd15iqr": 0.021711376999519416, "ops": 47.21166893804424, "total": 0.995516597002279, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.004730550001113443, "max": 0.0054262789999484085, "mean": 0.004887357320158878, "stddev": 9.676186033942789e-05, "rounds": 203, "median": 0.004861522000283003, "iqr": 7.467749946954427e-05, "q1": 0.004834725750697544, "q3": 0.004909403250167088, "iqr_outliers": 13, "stddev_outliers": 18, "outliers": "18;13", "ld15iqr": 0.004730550001113443, "hd15iqr": 0.005032508999647689, "ops": 204.6095536897417, "total": 0.9921335359922523, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.004977787999450811, "max": 0.005573204000029364, "mean": 0.005131941308401784, "stddev": 9.743529570732675e-05, "rounds": 201, "median": 0.005102803999761818, "iqr": 9.648150080465712e-05, "q1": 0.0050708582493825816, "q3": 0.005167339750187239, "iqr_outliers": 9, "stddev_outliers": 31, "outliers": "31;9", "ld15iqr": 0.004977787999450811, "hd15iqr": 0.005332491999070044, "ops": 194.85803517722326, "total": 1.0315202029887587, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0034736529996735044, "max": 0.00423766899984912, "mean": 0.003620568959215436, "stddev": 0.00010703971435255818, "rounds": 270, "median": 0.003586719999475463, "iqr": 0.00010416999975859653, "q1": 0.003550497000105679, "q3": 0.0036546669998642756, "iqr_outliers": 16, "stddev_outliers": 45, "outliers": "45;16", "ld15iqr": 0.0034736529996735044, "hd15iqr": 0.0038134919996082317, "ops": 276.19968332731224, "total": 0.9775536189881677, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.01732148100018094, "max": 0.018504756999391248, "mean": 0.017611760351866432, "stddev": 0.00022894766958509442, "rounds": 54, "median": 0.017550306499288126, "iqr": 0.00012767199950758368, "q1": 0.01749838800060388, "q3": 0.017626060000111465, "iqr_outliers": 6, "stddev_outliers": 8, "outliers": "8;6", "ld15iqr": 0.01732148100018094, "hd15iqr": 0.017918014000315452, "ops": 56.780241158233984, "total": 0.9510350590007874, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005302264999045292, "max": 0.00582003099952999, "mean": 0.00543998086666559, "stddev": 8.100635101985533e-05, "rounds": 135, "median": 0.005421521998869139, "iqr": 7.572050162707455e-05, "q1": 0.0053913069991722296, "q3": 0.005467027500799304, "iqr_outliers": 7, "stddev_outliers": 18, "outliers": "18;7", "ld15iqr": 0.005302264999045292, "hd15iqr": 0.005595636001089588, "ops": 183.82417595026308, "total": 0.7343974169998546, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.03293946199846687, "max": 0.034785279000061564, "mean": 0.03350922090330935, "stddev": 0.00040832861542253423, "rounds": 31, "median": 0.03333443999872543, "iqr": 0.000548970998806908, "q1": 0.033211814750757185, "q3": 0.03376078574956409, "iqr_outliers": 1, "stddev_outliers": 7, "outliers": "7;1", "ld15iqr": 0.03293946199846687, "hd15iqr": 0.034785279000061564, "ops": 29.842532086481327, "total": 1.0387858480025898, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.02759230499941623, "max": 0.03304075400046713, "mean": 0.028445163888793305, "stddev": 0.001082249734257857, "rounds": 36, "median": 0.028071039499991457, "iqr": 0.0009260204997190158, "q1": 0.027777610999692115, "q3": 0.02870363149941113, "iqr_outliers": 2, "stddev_outliers": 2, "outliers": "2;2", "ld15iqr": 0.02759230499941623, "hd15iqr": 0.03123476499968092, "ops": 35.1553608166756, "total": 1.024025899996559, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.00536283600013121, "max": 0.005924953000430833, "mean": 0.0054995138194600356, "stddev": 8.026113167039015e-05, "rounds": 144, "median": 0.005481877001329849, "iqr": 6.214750010258285e-05, "q1": 0.00545578299988847, "q3": 0.005517930499991053, "iqr_outliers": 9, "stddev_outliers": 17, "outliers": "17;9", "ld15iqr": 0.00536283600013121, "hd15iqr": 0.005615830999886384, "ops": 181.83425532298853, "total": 0.7919299900022452, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.061938525999721605, "max": 0.06483284499881847, "mean": 0.06264050923527975, "stddev": 0.0007176160613975393, "rounds": 17, "median": 0.06245879100060847, "iqr": 0.0005189337507545133, "q1": 0.06224038299933454, "q3": 0.06275931675008906, "iqr_outliers": 2, "stddev_outliers": 2, "outliers": "2;2", "ld15iqr": 0.061938525999721605, "hd15iqr": 0.06375684200065734, "ops": 15.964110321069839, "total": 1.0648886569997558, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005644366001433809, "max": 0.006572120999408071, "mean": 0.005812702136977134, "stddev": 0.0001136249388353219, "rounds": 168, "median": 0.005787640499875124, "iqr": 7.603850008308655e-05, "q1": 0.005755126999247295, "q3": 0.005831165499330382, "iqr_outliers": 10, "stddev_outliers": 17, "outliers": "17;10", "ld15iqr": 0.005644366001433809, "hd15iqr": 0.006006939998769667, "ops": 172.03702794928435, "total": 0.9765339590121584, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005002385998523096, "max": 0.006816102999437135, "mean": 0.005151768510330612, "stddev": 0.00018016091634565288, "rounds": 192, "median": 0.005110796499138814, "iqr": 7.699399975535925e-05, "q1": 0.0050815089998650365, "q3": 0.005158502999620396, "iqr_outliers": 16, "stddev_outliers": 12, "outliers": "12;16", "ld15iqr": 0.005002385998523096, "hd15iqr": 0.005280691999359988, "ops": 194.10810054736436, "total": 0.9891395539834775, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.009122446999754175, "max": 0.010732804001236218, "mean": 0.009548831000051267, "stddev": 0.00023607596895901812, "rounds": 107, "median": 0.00945739899907494, "iqr": 0.00017931849924934795, "q1": 0.009417942000254698, "q3": 0.009597260499504046, "iqr_outliers": 11, "stddev_outliers": 15, "outliers": "15;11", "ld15iqr": 0.009350364000056288, "hd15iqr": 0.009910750000926782, "ops": 104.72486108452763, "total": 1.0217249170054856, "iterations": 1 } } ], "datetime": "2025-12-27T04:55:39.177796+00:00", "version": "5.2.3" }././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_match_files_cpython313_ryzen_ai_max_395.json0000644000000000000000000016752715136034031024054 0ustar00{ "machine_info": { "node": "fwd1", "processor": "", "machine": "x86_64", "python_compiler": "GCC 15.2.1 20251112", "python_implementation": "CPython", "python_implementation_version": "3.13.11", "python_version": "3.13.11", "python_build": [ "main", "Dec 15 2025 10:06:58" ], "release": "6.18.2-3-cachyos", "system": "Linux", "cpu": { "python_version": "3.13.11.final.0 (64 bit)", "cpuinfo_version": [ 9, 0, 0 ], "cpuinfo_version_string": "9.0.0", "arch": "X86_64", "bits": 64, "count": 32, "arch_string_raw": "x86_64", "vendor_id_raw": "AuthenticAMD", "brand_raw": "AMD RYZEN AI MAX+ 395 w/ Radeon 8060S", "hz_advertised_friendly": "2.0000 GHz", "hz_actual_friendly": "2.0000 GHz", "hz_advertised": [ 2000000000, 0 ], "hz_actual": [ 2000000000, 0 ], "model": 112, "family": 26, "flags": [ "3dnowprefetch", "abm", "adx", "aes", "amd_lbr_pmc_freeze", "amd_lbr_v2", "aperfmperf", "apic", "arat", "avic", "avx", "avx2", "avx512_bf16", "avx512_bitalg", "avx512_vbmi2", "avx512_vnni", "avx512_vp2intersect", "avx512_vpopcntdq", "avx512bitalg", "avx512bw", "avx512cd", "avx512dq", "avx512f", "avx512ifma", "avx512vbmi", "avx512vbmi2", "avx512vl", "avx512vnni", "avx512vpopcntdq", "avx_vnni", "bmi1", "bmi2", "bpext", "bus_lock_detect", "cat_l3", "cdp_l3", "clflush", "clflushopt", "clwb", "clzero", "cmov", "cmp_legacy", "constant_tsc", "cpb", "cppc", "cpuid", "cpuid_fault", "cqm", "cqm_llc", "cqm_mbm_local", "cqm_mbm_total", "cqm_occup_llc", "cr8_legacy", "cx16", "cx8", "dbx", "de", "decodeassists", "erms", "extapic", "extd_apicid", "f16c", "flush_l1d", "flushbyasid", "fma", "fpu", "fsgsbase", "fsrm", "fxsr", "fxsr_opt", "gfni", "ht", "hw_pstate", "ibpb", "ibrs", "ibrs_enhanced", "ibs", "invpcid", "irperf", "lahf_lm", "lbrv", "lm", "mba", "mca", "mce", "misalignsse", "mmx", "mmxext", "monitor", "movbe", "movdir64b", "movdiri", "msr", "mtrr", "mwaitx", "nonstop_tsc", "nopl", "npt", "nrip_save", "nx", "ospke", "osvw", "osxsave", "overflow_recov", "pae", "pat", "pausefilter", "pci_l2i", "pclmulqdq", "pdpe1gb", "perfctr_core", "perfctr_llc", "perfctr_nb", "perfmon_v2", "pfthreshold", "pge", "pku", "pni", "popcnt", "pqe", "pqm", "pse", "pse36", "rapl", "rdpid", "rdpru", "rdrand", "rdrnd", "rdseed", "rdt_a", "rdtscp", "rep_good", "sep", "sha", "sha_ni", "skinit", "smap", "smca", "smep", "ssbd", "sse", "sse2", "sse4_1", "sse4_2", "sse4a", "ssse3", "stibp", "succor", "svm", "svm_lock", "syscall", "tce", "topoext", "tsc", "tsc_adjust", "tsc_scale", "umip", "user_shstk", "v_spec_ctrl", "v_vmsave_vmload", "vaes", "vgif", "vmcb_clean", "vme", "vmmcall", "vnmi", "vpclmulqdq", "wbnoinvd", "wdt", "x2avic", "xgetbv1", "xsave", "xsavec", "xsaveerptr", "xsaveopt", "xsaves", "xtopology" ], "l3_cache_size": 1048576, "l2_cache_size": 16777216, "l1_data_cache_size": 786432, "l1_instruction_cache_size": 524288, "l2_cache_line_size": 1024, "l2_cache_associativity": 8 } }, "commit_info": { "id": "4f4794c8fb04dc28f7c234a1e901ddc297143b6b", "time": "2025-12-27T08:18:48-05:00", "author_time": "2025-12-27T08:18:48-05:00", "dirty": true, "project": "python-pathspec", "branch": "master" }, "benchmarks": [ { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.010779888019897044, "max": 0.010846732999198139, "mean": 0.01081863061343837, "stddev": 1.3979093032151941e-05, "rounds": 90, "median": 0.01081723248353228, "iqr": 1.966708805412054e-05, "q1": 0.010809602914378047, "q3": 0.010829270002432168, "iqr_outliers": 1, "stddev_outliers": 28, "outliers": "28;1", "ld15iqr": 0.010780237964354455, "hd15iqr": 0.010846732999198139, "ops": 92.43314017560125, "total": 0.9736767552094534, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.003674971987493336, "max": 0.0037101589841768146, "mean": 0.0036864562030962627, "stddev": 5.876074001626008e-06, "rounds": 115, "median": 0.0036862140987068415, "iqr": 6.140995537862182e-06, "q1": 0.0036830705066677183, "q3": 0.0036892115022055805, "iqr_outliers": 4, "stddev_outliers": 32, "outliers": "32;4", "ld15iqr": 0.003674971987493336, "hd15iqr": 0.0036988970823585987, "ops": 271.26322541417903, "total": 0.4239424633560702, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0744934439426288, "max": 0.07580390898510814, "mean": 0.0748051172288667, "stddev": 0.0005026005077239803, "rounds": 14, "median": 0.07454888249048963, "iqr": 0.000117730931378901, "q1": 0.07453113503288478, "q3": 0.07464886596426368, "iqr_outliers": 3, "stddev_outliers": 3, "outliers": "3;3", "ld15iqr": 0.0744934439426288, "hd15iqr": 0.07567189005203545, "ops": 13.368069418840616, "total": 1.0472716412041336, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.011797390994615853, "max": 0.011832566931843758, "mean": 0.011816175772331744, "stddev": 8.201608142925637e-06, "rounds": 84, "median": 0.01181645697215572, "iqr": 1.2668955605477095e-05, "q1": 0.01180990954162553, "q3": 0.011822578497231007, "iqr_outliers": 0, "stddev_outliers": 29, "outliers": "29;0", "ld15iqr": 0.011797390994615853, "hd15iqr": 0.011832566931843758, "ops": 84.62974986725888, "total": 0.9925587648758665, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.004014470963738859, "max": 0.004229555954225361, "mean": 0.004056953432527968, "stddev": 7.490855401348111e-05, "rounds": 78, "median": 0.004024039022624493, "iqr": 1.1350959539413452e-05, "q1": 0.004019681015051901, "q3": 0.004031031974591315, "iqr_outliers": 14, "stddev_outliers": 13, "outliers": "13;14", "ld15iqr": 0.004014470963738859, "hd15iqr": 0.0040903240442276, "ops": 246.4903816696955, "total": 0.3164423677371815, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.1144953080220148, "max": 0.1147723289905116, "mean": 0.1146369712272038, "stddev": 7.752787794355918e-05, "rounds": 9, "median": 0.1146618010243401, "iqr": 6.922343163751066e-05, "q1": 0.11460019001970068, "q3": 0.11466941345133819, "iqr_outliers": 1, "stddev_outliers": 2, "outliers": "2;1", "ld15iqr": 0.11456642195116729, "hd15iqr": 0.1147723289905116, "ops": 8.723189293077695, "total": 1.0317327410448343, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.004821488982997835, "max": 0.006635471945628524, "mean": 0.004843518510690243, "stddev": 0.00012687643718230956, "rounds": 203, "median": 0.004831467987969518, "iqr": 1.1239026207476854e-05, "q1": 0.004827650962397456, "q3": 0.004838889988604933, "iqr_outliers": 11, "stddev_outliers": 1, "outliers": "1;11", "ld15iqr": 0.004821488982997835, "hd15iqr": 0.004856636049225926, "ops": 206.46147997429483, "total": 0.9832342576701194, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.003562231082469225, "max": 0.003695962019264698, "mean": 0.003580543046871967, "stddev": 1.0885331540952632e-05, "rounds": 255, "median": 0.003579352982342243, "iqr": 1.1251919204369187e-05, "q1": 0.0035745255299843848, "q3": 0.003585777449188754, "iqr_outliers": 3, "stddev_outliers": 44, "outliers": "44;3", "ld15iqr": 0.003562231082469225, "hd15iqr": 0.0036066139582544565, "ops": 279.287244116118, "total": 0.9130384769523516, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.014303195057436824, "max": 0.014510113978758454, "mean": 0.014405254888281758, "stddev": 7.330434991213536e-05, "rounds": 70, "median": 0.01439084397861734, "iqr": 0.00014655583072453737, "q1": 0.014334313105791807, "q3": 0.014480868936516345, "iqr_outliers": 0, "stddev_outliers": 33, "outliers": "33;0", "ld15iqr": 0.014303195057436824, "hd15iqr": 0.014510113978758454, "ops": 69.41911182796703, "total": 1.008367842179723, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0038102269172668457, "max": 0.005018079071305692, "mean": 0.0038271718663019094, "stddev": 7.47707025296624e-05, "rounds": 258, "median": 0.00382132304366678, "iqr": 6.122048944234848e-06, "q1": 0.003818562952801585, "q3": 0.00382468500174582, "iqr_outliers": 15, "stddev_outliers": 1, "outliers": "1;15", "ld15iqr": 0.0038102269172668457, "hd15iqr": 0.003835253999568522, "ops": 261.2895461541612, "total": 0.9874103415058926, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0033810699824243784, "max": 0.0035224459134042263, "mean": 0.003430130163303994, "stddev": 2.458372176260498e-05, "rounds": 290, "median": 0.003417539002839476, "iqr": 4.382303450256586e-05, "q1": 0.0034099839394912124, "q3": 0.0034538069739937782, "iqr_outliers": 1, "stddev_outliers": 101, "outliers": "101;1", "ld15iqr": 0.0033810699824243784, "hd15iqr": 0.0035224459134042263, "ops": 291.53412622591935, "total": 0.9947377473581582, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0023219980066642165, "max": 0.0024134500417858362, "mean": 0.0023467831539675664, "stddev": 1.1960474705719224e-05, "rounds": 413, "median": 0.002343617961741984, "iqr": 1.58541661221534e-05, "q1": 0.002338709746254608, "q3": 0.0023545639123767614, "iqr_outliers": 4, "stddev_outliers": 126, "outliers": "126;4", "ld15iqr": 0.0023219980066642165, "hd15iqr": 0.002381739905104041, "ops": 426.1152115010539, "total": 0.969221442588605, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.013028386980295181, "max": 0.013400065945461392, "mean": 0.013076491416493235, "stddev": 8.526860855543999e-05, "rounds": 76, "median": 0.013051966030616313, "iqr": 2.214638516306877e-05, "q1": 0.013041566533502191, "q3": 0.01306371291866526, "iqr_outliers": 7, "stddev_outliers": 6, "outliers": "6;7", "ld15iqr": 0.013028386980295181, "hd15iqr": 0.013100581942126155, "ops": 76.47311256127244, "total": 0.9938133476534858, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.003598117968067527, "max": 0.003754102042876184, "mean": 0.0036245474405371134, "stddev": 2.4452566474102796e-05, "rounds": 216, "median": 0.003614659537561238, "iqr": 2.9855931643396616e-05, "q1": 0.00360880303196609, "q3": 0.003638658963609487, "iqr_outliers": 8, "stddev_outliers": 32, "outliers": "32;8", "ld15iqr": 0.003598117968067527, "hd15iqr": 0.0036845810245722532, "ops": 275.8965129869599, "total": 0.7829022471560165, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.021649884060025215, "max": 0.021960207959637046, "mean": 0.021821517299147362, "stddev": 7.795729927487794e-05, "rounds": 47, "median": 0.021819792920723557, "iqr": 0.00011765619274228811, "q1": 0.021755870518973097, "q3": 0.021873526711715385, "iqr_outliers": 0, "stddev_outliers": 19, "outliers": "19;0", "ld15iqr": 0.021649884060025215, "hd15iqr": 0.021960207959637046, "ops": 45.826327578012794, "total": 1.025611313059926, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.01853408303577453, "max": 0.0185726749477908, "mean": 0.018546481123107864, "stddev": 7.555700069111123e-06, "rounds": 54, "median": 0.018544878461398184, "iqr": 1.03700440376997e-05, "q1": 0.018540584947913885, "q3": 0.018550954991951585, "iqr_outliers": 1, "stddev_outliers": 16, "outliers": "16;1", "ld15iqr": 0.01853408303577453, "hd15iqr": 0.0185726749477908, "ops": 53.91858398162963, "total": 1.0015099806478247, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.003617445006966591, "max": 0.0037003910401836038, "mean": 0.003634793716587461, "stddev": 7.463809280775071e-06, "rounds": 178, "median": 0.003634426451753825, "iqr": 6.37187622487545e-06, "q1": 0.0036311900475993752, "q3": 0.0036375619238242507, "iqr_outliers": 8, "stddev_outliers": 31, "outliers": "31;8", "ld15iqr": 0.0036238960456103086, "hd15iqr": 0.0036491439677774906, "ops": 275.1187764621904, "total": 0.6469932815525681, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.04204195202328265, "max": 0.04268756601959467, "mean": 0.042392158449122995, "stddev": 0.00016326173970461884, "rounds": 24, "median": 0.04239687946392223, "iqr": 0.00013503391528502107, "q1": 0.04232603101991117, "q3": 0.04246106493519619, "iqr_outliers": 4, "stddev_outliers": 7, "outliers": "7;4", "ld15iqr": 0.042160796001553535, "hd15iqr": 0.042672398034483194, "ops": 23.58926831244395, "total": 1.0174118027789518, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.004337189020588994, "max": 0.004456953029148281, "mean": 0.0043449484489742765, "stddev": 9.314151728482413e-06, "rounds": 226, "median": 0.004343489999882877, "iqr": 3.6669662222266197e-06, "q1": 0.004341655992902815, "q3": 0.004345322959125042, "iqr_outliers": 17, "stddev_outliers": 12, "outliers": "12;17", "ld15iqr": 0.004337189020588994, "hd15iqr": 0.00435133499559015, "ops": 230.1523278684866, "total": 0.9819583494681865, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0034430669620633125, "max": 0.0034835420083254576, "mean": 0.003466273284288541, "stddev": 7.4298818923052586e-06, "rounds": 281, "median": 0.0034668409498408437, "iqr": 1.074248575605452e-05, "q1": 0.0034612210001796484, "q3": 0.003471963485935703, "iqr_outliers": 1, "stddev_outliers": 96, "outliers": "96;1", "ld15iqr": 0.0034459619782865047, "hd15iqr": 0.0034835420083254576, "ops": 288.49427554736263, "total": 0.97402279288508, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.006230600061826408, "max": 0.006358549930155277, "mean": 0.00626451201493846, "stddev": 1.6120443069398314e-05, "rounds": 159, "median": 0.006263010902330279, "iqr": 1.77412002813071e-05, "q1": 0.006253988278331235, "q3": 0.006271729478612542, "iqr_outliers": 5, "stddev_outliers": 33, "outliers": "33;5", "ld15iqr": 0.006230600061826408, "hd15iqr": 0.006298398016951978, "ops": 159.62935303107142, "total": 0.9960574103752151, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.012880147900432348, "max": 0.012971649994142354, "mean": 0.012945901023453802, "stddev": 1.3124760204012475e-05, "rounds": 77, "median": 0.012947955052368343, "iqr": 1.34672736749053e-05, "q1": 0.012940165470354259, "q3": 0.012953632744029164, "iqr_outliers": 2, "stddev_outliers": 13, "outliers": "13;2", "ld15iqr": 0.012925393064506352, "hd15iqr": 0.012971649994142354, "ops": 77.24452691151602, "total": 0.9968343788059428, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0037574280286207795, "max": 0.0038096560165286064, "mean": 0.0037882141028107567, "stddev": 1.4406006409640285e-05, "rounds": 154, "median": 0.003784112981520593, "iqr": 2.7150963433086872e-05, "q1": 0.003775321994908154, "q3": 0.003802472958341241, "iqr_outliers": 0, "stddev_outliers": 67, "outliers": "67;0", "ld15iqr": 0.0037574280286207795, "hd15iqr": 0.0038096560165286064, "ops": 263.9766319591139, "total": 0.5833849718328565, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0760109779657796, "max": 0.07634038804098964, "mean": 0.07615697778861172, "stddev": 8.913164109583734e-05, "rounds": 14, "median": 0.07614080206258222, "iqr": 4.923297092318535e-05, "q1": 0.07611592498142272, "q3": 0.07616515795234591, "iqr_outliers": 3, "stddev_outliers": 4, "outliers": "4;3", "ld15iqr": 0.0760570450220257, "hd15iqr": 0.07631262601353228, "ops": 13.130773161399492, "total": 1.066197689040564, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.012497156974859536, "max": 0.012540698982775211, "mean": 0.012515014984819573, "stddev": 1.0998086320032523e-05, "rounds": 80, "median": 0.012519524549134076, "iqr": 2.036342630162835e-05, "q1": 0.012503870064392686, "q3": 0.012524233490694314, "iqr_outliers": 0, "stddev_outliers": 33, "outliers": "33;0", "ld15iqr": 0.012497156974859536, "hd15iqr": 0.012540698982775211, "ops": 79.90401938894817, "total": 1.0012011987855658, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0037774050142616034, "max": 0.0038014899473637342, "mean": 0.003792046567208932, "stddev": 4.197417941978289e-06, "rounds": 112, "median": 0.0037925190408714116, "iqr": 4.443456418812275e-06, "q1": 0.0037901345058344305, "q3": 0.0037945779622532427, "iqr_outliers": 6, "stddev_outliers": 27, "outliers": "27;6", "ld15iqr": 0.0037839380092918873, "hd15iqr": 0.0038014899473637342, "ops": 263.709841711156, "total": 0.4247092155274004, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.10169318702537566, "max": 0.1020699260989204, "mean": 0.10189328142441809, "stddev": 0.00015456423482287742, "rounds": 10, "median": 0.10194681899156421, "iqr": 0.00029648805502802134, "q1": 0.10174621699843556, "q3": 0.10204270505346358, "iqr_outliers": 0, "stddev_outliers": 3, "outliers": "3;0", "ld15iqr": 0.10169318702537566, "hd15iqr": 0.1020699260989204, "ops": 9.814189768162244, "total": 1.018932814244181, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.00457188009750098, "max": 0.00468607502989471, "mean": 0.004653845798948082, "stddev": 2.2155339284187537e-05, "rounds": 211, "median": 0.004659906029701233, "iqr": 1.751311356201768e-05, "q1": 0.004653172945836559, "q3": 0.004670686059398577, "iqr_outliers": 47, "stddev_outliers": 60, "outliers": "60;47", "ld15iqr": 0.0046430240618065, "hd15iqr": 0.00468607502989471, "ops": 214.8760494441032, "total": 0.9819614635780454, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0036346670240163803, "max": 0.0037164509994909167, "mean": 0.0036648387897536736, "stddev": 2.517334660913734e-05, "rounds": 259, "median": 0.0036490140482783318, "iqr": 4.52957465313375e-05, "q1": 0.003643748234026134, "q3": 0.0036890439805574715, "iqr_outliers": 0, "stddev_outliers": 70, "outliers": "70;0", "ld15iqr": 0.0036346670240163803, "hd15iqr": 0.0037164509994909167, "ops": 272.86329832456653, "total": 0.9491932465462014, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.013014049036428332, "max": 0.013122032978571951, "mean": 0.013069713132927653, "stddev": 2.8335543289228786e-05, "rounds": 77, "median": 0.013059666031040251, "iqr": 5.05605130456388e-05, "q1": 0.013047757995082065, "q3": 0.013098318508127704, "iqr_outliers": 0, "stddev_outliers": 31, "outliers": "31;0", "ld15iqr": 0.013014049036428332, "hd15iqr": 0.013122032978571951, "ops": 76.51277345029203, "total": 1.0063679112354293, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0035323339980095625, "max": 0.0035797730088233948, "mean": 0.003540743053789534, "stddev": 4.5178544529646375e-06, "rounds": 276, "median": 0.00354013399919495, "iqr": 4.7089415602386e-06, "q1": 0.0035379499895498157, "q3": 0.0035426589311100543, "iqr_outliers": 7, "stddev_outliers": 48, "outliers": "48;7", "ld15iqr": 0.0035323339980095625, "hd15iqr": 0.003549778019078076, "ops": 282.4265937427272, "total": 0.9772450828459114, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.003428047988563776, "max": 0.0034756569657474756, "mean": 0.0034517013484650512, "stddev": 7.4809212470290595e-06, "rounds": 291, "median": 0.0034523740177974105, "iqr": 1.0865682270377874e-05, "q1": 0.0034461895120330155, "q3": 0.0034570551943033934, "iqr_outliers": 2, "stddev_outliers": 93, "outliers": "93;2", "ld15iqr": 0.0034326870227232575, "hd15iqr": 0.0034756569657474756, "ops": 289.71220248956166, "total": 1.00444509240333, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0022288329200819135, "max": 0.0023147250758484006, "mean": 0.0022775510953133046, "stddev": 1.8255852820006245e-05, "rounds": 424, "median": 0.0022850179811939597, "iqr": 3.0457449611276388e-05, "q1": 0.002260321518406272, "q3": 0.0022907789680175483, "iqr_outliers": 0, "stddev_outliers": 140, "outliers": "140;0", "ld15iqr": 0.0022288329200819135, "hd15iqr": 0.0023147250758484006, "ops": 439.0680859181506, "total": 0.9656816644128412, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.012762416037730873, "max": 0.012799807009287179, "mean": 0.012771152034521295, "stddev": 6.94832684183827e-06, "rounds": 78, "median": 0.012769779947120696, "iqr": 4.949048161506653e-06, "q1": 0.012767495936714113, "q3": 0.01277244498487562, "iqr_outliers": 5, "stddev_outliers": 13, "outliers": "13;5", "ld15iqr": 0.012762416037730873, "hd15iqr": 0.01278474903665483, "ops": 78.30147173073595, "total": 0.9961498586926609, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0036530420184135437, "max": 0.0037008210783824325, "mean": 0.003679086951623586, "stddev": 7.446980692134558e-06, "rounds": 242, "median": 0.003676299995277077, "iqr": 8.406117558479309e-06, "q1": 0.0036741408985108137, "q3": 0.003682547016069293, "iqr_outliers": 12, "stddev_outliers": 66, "outliers": "66;12", "ld15iqr": 0.0036685910308733582, "hd15iqr": 0.0036952811060473323, "ops": 271.80656862668025, "total": 0.8903390422929078, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.020706749986857176, "max": 0.020899662980809808, "mean": 0.020826680927712005, "stddev": 5.3146809056548925e-05, "rounds": 48, "median": 0.020838437019847333, "iqr": 4.224455915391445e-05, "q1": 0.020822356978897005, "q3": 0.02086460153805092, "iqr_outliers": 9, "stddev_outliers": 12, "outliers": "12;9", "ld15iqr": 0.02080942306201905, "hd15iqr": 0.020899662980809808, "ops": 48.01533203830856, "total": 0.9996806845301762, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.019837324041873217, "max": 0.01992406800854951, "mean": 0.019867819503415377, "stddev": 1.4113597442715087e-05, "rounds": 50, "median": 0.019867866532877088, "iqr": 9.236973710358143e-06, "q1": 0.019863292924128473, "q3": 0.01987252989783883, "iqr_outliers": 7, "stddev_outliers": 13, "outliers": "13;7", "ld15iqr": 0.019849608070217073, "hd15iqr": 0.019892968935891986, "ops": 50.332649731798455, "total": 0.9933909751707688, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0036902320571243763, "max": 0.003753990982659161, "mean": 0.0037271535434272316, "stddev": 5.6652104993920505e-06, "rounds": 210, "median": 0.003727651492226869, "iqr": 6.031012162566185e-06, "q1": 0.003724665963090956, "q3": 0.0037306969752535224, "iqr_outliers": 5, "stddev_outliers": 38, "outliers": "38;5", "ld15iqr": 0.0037158799823373556, "hd15iqr": 0.003753990982659161, "ops": 268.3012621692181, "total": 0.7827022441197187, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.040844819974154234, "max": 0.04273787105921656, "mean": 0.04227426475845277, "stddev": 0.0005768013016036951, "rounds": 25, "median": 0.04244976898189634, "iqr": 0.00022094498854130507, "q1": 0.042400413716677576, "q3": 0.04262135870521888, "iqr_outliers": 5, "stddev_outliers": 4, "outliers": "4;5", "ld15iqr": 0.04238141106907278, "hd15iqr": 0.04273787105921656, "ops": 23.655053629289892, "total": 1.0568566189613193, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.004083040985278785, "max": 0.004204057971946895, "mean": 0.004088415941320515, "stddev": 9.186935543186665e-06, "rounds": 240, "median": 0.004086898057721555, "iqr": 2.8649228624999523e-06, "q1": 0.004085791006218642, "q3": 0.004088655929081142, "iqr_outliers": 13, "stddev_outliers": 8, "outliers": "8;13", "ld15iqr": 0.004083040985278785, "hd15iqr": 0.004093039082363248, "ops": 244.59350867245925, "total": 0.9812198259169236, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0033473160583525896, "max": 0.0046043910551816225, "mean": 0.0034273019073609735, "stddev": 8.056767826116815e-05, "rounds": 284, "median": 0.0034507809905335307, "iqr": 7.816147990524769e-05, "q1": 0.003382783499546349, "q3": 0.0034609449794515967, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.0033473160583525896, "hd15iqr": 0.0046043910551816225, "ops": 291.77470413454216, "total": 0.9733537416905165, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.005970209953375161, "max": 0.00851319299545139, "mean": 0.006123909512592227, "stddev": 0.00020055822645414107, "rounds": 162, "median": 0.006088291993364692, "iqr": 0.00010586890857666731, "q1": 0.006064648041501641, "q3": 0.006170516950078309, "iqr_outliers": 2, "stddev_outliers": 2, "outliers": "2;2", "ld15iqr": 0.005970209953375161, "hd15iqr": 0.006592541001737118, "ops": 163.29437885124855, "total": 0.9920733410399407, "iterations": 1 } } ], "datetime": "2025-12-27T14:03:25.796368+00:00", "version": "5.2.3" }././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_match_files_cpython313_xeon_e5-2690.json0000644000000000000000000016226515136034031022721 0ustar00{ "machine_info": { "node": "cmd1", "processor": "x86_64", "machine": "x86_64", "python_compiler": "GCC 11.4.0", "python_implementation": "CPython", "python_implementation_version": "3.13.11", "python_version": "3.13.11", "python_build": [ "main", "Dec 6 2025 08:52:51" ], "release": "5.15.0-164-generic", "system": "Linux", "cpu": { "python_version": "3.13.11.final.0 (64 bit)", "cpuinfo_version": [ 9, 0, 0 ], "cpuinfo_version_string": "9.0.0", "arch": "X86_64", "bits": 64, "count": 32, "arch_string_raw": "x86_64", "vendor_id_raw": "GenuineIntel", "brand_raw": "Intel(R) Xeon(R) CPU E5-2690 0 @ 2.90GHz", "hz_advertised_friendly": "2.9000 GHz", "hz_actual_friendly": "1.2000 GHz", "hz_advertised": [ 2900000000, 0 ], "hz_actual": [ 1200000000, 0 ], "stepping": 7, "model": 45, "family": 6, "flags": [ "acpi", "aes", "aperfmperf", "apic", "arat", "arch_perfmon", "avx", "bts", "clflush", "cmov", "constant_tsc", "cpuid", "cx16", "cx8", "dca", "de", "ds_cpl", "dtes64", "dtherm", "dts", "epb", "ept", "est", "flexpriority", "flush_l1d", "fpu", "fxsr", "ht", "ibpb", "ibpb_exit_to_user", "ibrs", "ida", "lahf_lm", "lm", "mca", "mce", "md_clear", "mmx", "monitor", "msr", "mtrr", "nonstop_tsc", "nopl", "nx", "osxsave", "pae", "pat", "pbe", "pcid", "pclmulqdq", "pdcm", "pdpe1gb", "pebs", "pge", "pln", "pni", "popcnt", "pse", "pse36", "pti", "pts", "rdtscp", "rep_good", "sep", "smx", "ss", "ssbd", "sse", "sse2", "sse4_1", "sse4_2", "ssse3", "stibp", "syscall", "tm", "tm2", "tpr_shadow", "tsc", "tsc_deadline_timer", "tscdeadline", "vme", "vmx", "vnmi", "vpid", "x2apic", "xsave", "xsaveopt", "xtopology", "xtpr" ], "l3_cache_size": 20971520, "l2_cache_size": 4194304, "l1_data_cache_size": 524288, "l1_instruction_cache_size": 524288, "l2_cache_line_size": 256, "l2_cache_associativity": 6 } }, "commit_info": { "id": "4f4794c8fb04dc28f7c234a1e901ddc297143b6b", "time": "2025-12-27T08:18:48-05:00", "author_time": "2025-12-27T08:18:48-05:00", "dirty": false, "project": "python-pathspec", "branch": "master" }, "benchmarks": [ { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.010850705002667382, "max": 0.011527555994689465, "mean": 0.010935894280586953, "stddev": 0.00010497522082372589, "rounds": 93, "median": 0.01091062999330461, "iqr": 5.046250589657575e-05, "q1": 0.010888769007578958, "q3": 0.010939231513475534, "iqr_outliers": 7, "stddev_outliers": 6, "outliers": "6;7", "ld15iqr": 0.010850705002667382, "hd15iqr": 0.011016024014679715, "ops": 91.44199590290187, "total": 1.0170381680945866, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.011356090020854026, "max": 0.011588599008973688, "mean": 0.011464598918154344, "stddev": 5.3703059309526465e-05, "rounds": 87, "median": 0.011461134999990463, "iqr": 7.471450953744352e-05, "q1": 0.01142881498526549, "q3": 0.011503529494802933, "iqr_outliers": 0, "stddev_outliers": 29, "outliers": "29;0", "ld15iqr": 0.011356090020854026, "hd15iqr": 0.011588599008973688, "ops": 87.22503134553507, "total": 0.9974201058794279, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.006052754004485905, "max": 0.006286729010753334, "mean": 0.006121326334197543, "stddev": 3.711670193328939e-05, "rounds": 159, "median": 0.006118268007412553, "iqr": 3.396099782548845e-05, "q1": 0.006100300241087098, "q3": 0.006134261238912586, "iqr_outliers": 11, "stddev_outliers": 39, "outliers": "39;11", "ld15iqr": 0.006052754004485905, "hd15iqr": 0.006187621998833492, "ops": 163.36328851042902, "total": 0.9732908871374093, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.030532947013853118, "max": 0.030761055008042604, "mean": 0.030603311243325923, "stddev": 4.729944504572654e-05, "rounds": 33, "median": 0.0306020220159553, "iqr": 6.661248335149139e-05, "q1": 0.030561915002181195, "q3": 0.030628527485532686, "iqr_outliers": 1, "stddev_outliers": 9, "outliers": "9;1", "ld15iqr": 0.030532947013853118, "hd15iqr": 0.030761055008042604, "ops": 32.67620265170108, "total": 1.0099092710297555, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.012618087988812476, "max": 0.014754731993889436, "mean": 0.012882713787428871, "stddev": 0.000513622446823909, "rounds": 34, "median": 0.012713948002783582, "iqr": 0.00013996500638313591, "q1": 0.012661739980103448, "q3": 0.012801704986486584, "iqr_outliers": 4, "stddev_outliers": 3, "outliers": "3;4", "ld15iqr": 0.012618087988812476, "hd15iqr": 0.013046367006609216, "ops": 77.6233964753462, "total": 0.4380122687725816, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.20230469698435627, "max": 0.2033356530009769, "mean": 0.2026293283968698, "stddev": 0.00041476625097594256, "rounds": 5, "median": 0.2025295229977928, "iqr": 0.0004537537388387136, "q1": 0.20234445525420597, "q3": 0.20279820899304468, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.20230469698435627, "hd15iqr": 0.2033356530009769, "ops": 4.93511974752934, "total": 1.0131466419843491, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.014788886008318514, "max": 0.015323047002311796, "mean": 0.014861822409918258, "stddev": 8.252063350608861e-05, "rounds": 68, "median": 0.014842279997537844, "iqr": 3.296550130471587e-05, "q1": 0.014830756495939568, "q3": 0.014863721997244284, "iqr_outliers": 6, "stddev_outliers": 5, "outliers": "5;6", "ld15iqr": 0.014788886008318514, "hd15iqr": 0.01492263498948887, "ops": 67.28649908591528, "total": 1.0106039238744415, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.011782134009990841, "max": 0.012020230002235621, "mean": 0.011855760222763485, "stddev": 4.7981561488407696e-05, "rounds": 76, "median": 0.011847193498397246, "iqr": 4.717950650956482e-05, "q1": 0.011825855995994061, "q3": 0.011873035502503626, "iqr_outliers": 4, "stddev_outliers": 17, "outliers": "17;4", "ld15iqr": 0.011782134009990841, "hd15iqr": 0.011985029006609693, "ops": 84.34718493040742, "total": 0.9010377769300248, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.035435872996458784, "max": 0.04935939999995753, "mean": 0.04008456337835704, "stddev": 0.005454035291906278, "rounds": 29, "median": 0.03557304499554448, "iqr": 0.010777806011901703, "q1": 0.03550936525425641, "q3": 0.04628717126615811, "iqr_outliers": 0, "stddev_outliers": 11, "outliers": "11;0", "ld15iqr": 0.035435872996458784, "hd15iqr": 0.04935939999995753, "ops": 24.947259386637914, "total": 1.1624523379723541, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.032936881005298346, "max": 0.03314564301399514, "mean": 0.03301088606765974, "stddev": 4.813993498003078e-05, "rounds": 31, "median": 0.03298891501617618, "iqr": 5.114273517392576e-05, "q1": 0.03297721601120429, "q3": 0.03302835874637822, "iqr_outliers": 2, "stddev_outliers": 6, "outliers": "6;2", "ld15iqr": 0.032936881005298346, "hd15iqr": 0.033130555995739996, "ops": 30.293037210524457, "total": 1.023337468097452, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.013563974993303418, "max": 0.013784749986371025, "mean": 0.013703709998441374, "stddev": 6.67221994608883e-05, "rounds": 23, "median": 0.013728037010878325, "iqr": 5.315049202181399e-05, "q1": 0.01368874975014478, "q3": 0.013741900242166594, "iqr_outliers": 4, "stddev_outliers": 6, "outliers": "6;4", "ld15iqr": 0.013669578009285033, "hd15iqr": 0.013784749986371025, "ops": 72.97293945316542, "total": 0.3151853299641516, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.2770618910144549, "max": 0.2787187820067629, "mean": 0.27752402960904876, "stddev": 0.0006929738140450661, "rounds": 5, "median": 0.27719186901231296, "iqr": 0.0007312402303796262, "q1": 0.27709981626685476, "q3": 0.2778310564972344, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.2770618910144549, "hd15iqr": 0.2787187820067629, "ops": 3.603291583106196, "total": 1.3876201480452437, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.03169234498636797, "max": 0.031874147010967135, "mean": 0.03174334378218191, "stddev": 3.3465183686157124e-05, "rounds": 32, "median": 0.031738197518279776, "iqr": 3.0276496545411646e-05, "q1": 0.03172597500088159, "q3": 0.031756251497427, "iqr_outliers": 2, "stddev_outliers": 6, "outliers": "6;2", "ld15iqr": 0.03169234498636797, "hd15iqr": 0.031805770995561033, "ops": 31.502667357977497, "total": 1.0157870010298211, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.012052078993292525, "max": 0.01224114399519749, "mean": 0.012117966136429459, "stddev": 3.8447305742530346e-05, "rounds": 66, "median": 0.012106522000976838, "iqr": 5.629501538351178e-05, "q1": 0.012090840988093987, "q3": 0.012147136003477499, "iqr_outliers": 1, "stddev_outliers": 20, "outliers": "20;1", "ld15iqr": 0.012052078993292525, "hd15iqr": 0.01224114399519749, "ops": 82.52209890187468, "total": 0.7997857650043443, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.05711905300267972, "max": 0.05848232301650569, "mean": 0.05728606394970686, "stddev": 0.0003088893426624332, "rounds": 18, "median": 0.05719050300831441, "iqr": 0.00012948899529874325, "q1": 0.05716640700120479, "q3": 0.05729589599650353, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.05711905300267972, "hd15iqr": 0.05848232301650569, "ops": 17.45625255171886, "total": 1.0311491510947235, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.013382872013607994, "max": 0.013662454002769664, "mean": 0.013454930837552465, "stddev": 4.468136648603026e-05, "rounds": 74, "median": 0.013446988014038652, "iqr": 3.918199217878282e-05, "q1": 0.0134295969910454, "q3": 0.013468778983224183, "iqr_outliers": 3, "stddev_outliers": 18, "outliers": "18;3", "ld15iqr": 0.013382872013607994, "hd15iqr": 0.013538622006308287, "ops": 74.32219548903353, "total": 0.9956648819788825, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.011248950992012396, "max": 0.011512819008203223, "mean": 0.011379217800876015, "stddev": 4.631146741971366e-05, "rounds": 85, "median": 0.011375506990589201, "iqr": 5.7288489188067615e-05, "q1": 0.01135031100420747, "q3": 0.011407599493395537, "iqr_outliers": 2, "stddev_outliers": 25, "outliers": "25;2", "ld15iqr": 0.011284501990303397, "hd15iqr": 0.011512819008203223, "ops": 87.87950257204992, "total": 0.9672335130744614, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.015423690987518057, "max": 0.015783661976456642, "mean": 0.015525927124599548, "stddev": 7.389707986328474e-05, "rounds": 64, "median": 0.015517908992478624, "iqr": 0.00011695250577758998, "q1": 0.015463850504602306, "q3": 0.015580803010379896, "iqr_outliers": 1, "stddev_outliers": 24, "outliers": "24;1", "ld15iqr": 0.015423690987518057, "hd15iqr": 0.015783661976456642, "ops": 64.40839197393775, "total": 0.9936593359743711, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.047263683984056115, "max": 0.04756945799454115, "mean": 0.047369173043989576, "stddev": 7.134098231636061e-05, "rounds": 22, "median": 0.04735402650840115, "iqr": 9.854801464825869e-05, "q1": 0.047313413000665605, "q3": 0.047411961015313864, "iqr_outliers": 1, "stddev_outliers": 6, "outliers": "6;1", "ld15iqr": 0.047263683984056115, "hd15iqr": 0.04756945799454115, "ops": 21.11077596966588, "total": 1.0421218069677707, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.012032033002469689, "max": 0.012332918005995452, "mean": 0.012129753765846402, "stddev": 5.3149318715664594e-05, "rounds": 51, "median": 0.012124157015932724, "iqr": 6.004774331813678e-05, "q1": 0.012091556745872367, "q3": 0.012151604489190504, "iqr_outliers": 1, "stddev_outliers": 13, "outliers": "13;1", "ld15iqr": 0.012032033002469689, "hd15iqr": 0.012332918005995452, "ops": 82.44190437036633, "total": 0.6186174420581665, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.10923160400125198, "max": 0.10946732497541234, "mean": 0.10930644090403803, "stddev": 7.259569139490577e-05, "rounds": 10, "median": 0.109292227512924, "iqr": 0.00010420696344226599, "q1": 0.10923984501278028, "q3": 0.10934405197622254, "iqr_outliers": 0, "stddev_outliers": 3, "outliers": "3;0", "ld15iqr": 0.10923160400125198, "hd15iqr": 0.10946732497541234, "ops": 9.148591718194512, "total": 1.0930644090403803, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.009732374979648739, "max": 0.009886169980745763, "mean": 0.009785233007843416, "stddev": 2.691128905087308e-05, "rounds": 103, "median": 0.009782731998711824, "iqr": 3.635326720541343e-05, "q1": 0.00976457298384048, "q3": 0.009800926251045894, "iqr_outliers": 1, "stddev_outliers": 34, "outliers": "34;1", "ld15iqr": 0.009732374979648739, "hd15iqr": 0.009886169980745763, "ops": 102.19480713422395, "total": 1.0078789998078719, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.011273256997810677, "max": 0.01204195199534297, "mean": 0.011390500893910593, "stddev": 0.00012709185711241744, "rounds": 84, "median": 0.01135647451155819, "iqr": 7.263651059474796e-05, "q1": 0.011324032995617017, "q3": 0.011396669506211765, "iqr_outliers": 10, "stddev_outliers": 9, "outliers": "9;10", "ld15iqr": 0.011273256997810677, "hd15iqr": 0.011507371003972366, "ops": 87.7924517379744, "total": 0.9568020750884898, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.0060053300112485886, "max": 0.00628533199778758, "mean": 0.006040605830882366, "stddev": 3.1329794135401825e-05, "rounds": 165, "median": 0.006036760023562238, "iqr": 3.088824450969696e-05, "q1": 0.006019421743985731, "q3": 0.006050309988495428, "iqr_outliers": 6, "stddev_outliers": 24, "outliers": "24;6", "ld15iqr": 0.0060053300112485886, "hd15iqr": 0.00609759398503229, "ops": 165.54630909494844, "total": 0.9966999620955903, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.039475814992329106, "max": 0.03971824000473134, "mean": 0.0395791609245442, "stddev": 5.5416857262759565e-05, "rounds": 26, "median": 0.03956716999527998, "iqr": 6.104397471062839e-05, "q1": 0.03954321300261654, "q3": 0.03960425697732717, "iqr_outliers": 1, "stddev_outliers": 6, "outliers": "6;1", "ld15iqr": 0.039475814992329106, "hd15iqr": 0.03971824000473134, "ops": 25.265821120019517, "total": 1.0290581840381492, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.013358636002521962, "max": 0.013523814996005967, "mean": 0.013441182417811306, "stddev": 3.8832270310980586e-05, "rounds": 43, "median": 0.013445451011648402, "iqr": 5.7254015700891614e-05, "q1": 0.013412467487796675, "q3": 0.013469721503497567, "iqr_outliers": 0, "stddev_outliers": 16, "outliers": "16;0", "ld15iqr": 0.013358636002521962, "hd15iqr": 0.013523814996005967, "ops": 74.39821653449704, "total": 0.5779708439658862, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.1906764270097483, "max": 0.19160673799342476, "mean": 0.19097418000455946, "stddev": 0.0003318965027037465, "rounds": 6, "median": 0.19090868999774102, "iqr": 0.00025548701523803174, "q1": 0.1907445240067318, "q3": 0.19100001102196984, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.1906764270097483, "hd15iqr": 0.19160673799342476, "ops": 5.236309955493069, "total": 1.1458450800273567, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.013724754011491314, "max": 0.013984151999466121, "mean": 0.013806448670898322, "stddev": 4.369572903582326e-05, "rounds": 73, "median": 0.01380220998544246, "iqr": 4.28140047006309e-05, "q1": 0.013781152250885498, "q3": 0.013823966255586129, "iqr_outliers": 4, "stddev_outliers": 18, "outliers": "18;4", "ld15iqr": 0.013724754011491314, "hd15iqr": 0.013891608978156, "ops": 72.42992197608588, "total": 1.0078707529755775, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.01258547100587748, "max": 0.012907377997180447, "mean": 0.01273479989401027, "stddev": 6.576274780392889e-05, "rounds": 74, "median": 0.012721351507934742, "iqr": 6.250999285839498e-05, "q1": 0.012698197999270633, "q3": 0.012760707992129028, "iqr_outliers": 8, "stddev_outliers": 17, "outliers": "17;8", "ld15iqr": 0.012612850026926026, "hd15iqr": 0.01286715001333505, "ops": 78.52498730430334, "total": 0.94237519215676, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.034192941995570436, "max": 0.034403170022415, "mean": 0.034241646095567076, "stddev": 4.515865441951126e-05, "rounds": 30, "median": 0.03422929499356542, "iqr": 5.0915987230837345e-05, "q1": 0.03421089198673144, "q3": 0.03426180797396228, "iqr_outliers": 1, "stddev_outliers": 6, "outliers": "6;1", "ld15iqr": 0.034192941995570436, "hd15iqr": 0.034403170022415, "ops": 29.20420347809915, "total": 1.0272493828670122, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.034956747986143455, "max": 0.03516956002567895, "mean": 0.03500502375917959, "stddev": 4.694002906490681e-05, "rounds": 29, "median": 0.034989016014151275, "iqr": 6.02047293796204e-05, "q1": 0.03497263751341961, "q3": 0.035032842242799234, "iqr_outliers": 1, "stddev_outliers": 7, "outliers": "7;1", "ld15iqr": 0.034956747986143455, "hd15iqr": 0.03516956002567895, "ops": 28.567328132087432, "total": 1.015145689016208, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.013548330025514588, "max": 0.014421650004805997, "mean": 0.013736223151103, "stddev": 0.0001400334277837983, "rounds": 33, "median": 0.01371455698972568, "iqr": 7.502925291191787e-05, "q1": 0.013682690747373272, "q3": 0.01375772000028519, "iqr_outliers": 3, "stddev_outliers": 3, "outliers": "3;3", "ld15iqr": 0.013609303015982732, "hd15iqr": 0.013948881009127945, "ops": 72.8002150954938, "total": 0.453295363986399, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.2675187900022138, "max": 0.27289203999680467, "mean": 0.2689707751909737, "stddev": 0.0022920536616428096, "rounds": 5, "median": 0.2677605869830586, "iqr": 0.002539674263971392, "q1": 0.2675374387326883, "q3": 0.2700771129966597, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.2675187900022138, "hd15iqr": 0.27289203999680467, "ops": 3.7178760379821325, "total": 1.3448538759548683, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.03036637100740336, "max": 0.03083306399639696, "mean": 0.03045957163858199, "stddev": 7.511975948302726e-05, "rounds": 33, "median": 0.030448995996266603, "iqr": 4.370451642898843e-05, "q1": 0.030426837751292624, "q3": 0.030470542267721612, "iqr_outliers": 1, "stddev_outliers": 2, "outliers": "2;1", "ld15iqr": 0.03036637100740336, "hd15iqr": 0.03083306399639696, "ops": 32.83040260268591, "total": 1.0051658640732057, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.012561794021166861, "max": 0.012815186026273295, "mean": 0.012624033753746662, "stddev": 4.3919104788148276e-05, "rounds": 69, "median": 0.012617109983693808, "iqr": 4.106801497982815e-05, "q1": 0.012598688990692608, "q3": 0.012639757005672436, "iqr_outliers": 4, "stddev_outliers": 15, "outliers": "15;4", "ld15iqr": 0.012561794021166861, "hd15iqr": 0.012707277986919507, "ops": 79.2139833833391, "total": 0.8710583290085196, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.05570186401018873, "max": 0.07361819001380354, "mean": 0.06263498099967062, "stddev": 0.008827788653478514, "rounds": 18, "median": 0.05583400800242089, "iqr": 0.017583383014425635, "q1": 0.05575403798138723, "q3": 0.07333742099581286, "iqr_outliers": 0, "stddev_outliers": 7, "outliers": "7;0", "ld15iqr": 0.05570186401018873, "hd15iqr": 0.07361819001380354, "ops": 15.965519331845233, "total": 1.127429657994071, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.011818242986919358, "max": 0.012152793002314866, "mean": 0.011881892191359796, "stddev": 3.991005209683374e-05, "rounds": 84, "median": 0.011879949990543537, "iqr": 3.5724486224353313e-05, "q1": 0.01185938149865251, "q3": 0.011895105984876864, "iqr_outliers": 1, "stddev_outliers": 13, "outliers": "13;1", "ld15iqr": 0.011818242986919358, "hd15iqr": 0.012152793002314866, "ops": 84.16167929272865, "total": 0.9980789440742228, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.01125565599068068, "max": 0.012101386993890628, "mean": 0.011367760506677038, "stddev": 0.000114553706301067, "rounds": 81, "median": 0.011333531991112977, "iqr": 8.206524216802791e-05, "q1": 0.011306205262371805, "q3": 0.011388270504539832, "iqr_outliers": 7, "stddev_outliers": 10, "outliers": "10;7", "ld15iqr": 0.01125565599068068, "hd15iqr": 0.011527067021233961, "ops": 87.96807422294248, "total": 0.9207886010408401, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.015152489009778947, "max": 0.01570313301635906, "mean": 0.015220353762353105, "stddev": 7.558926340912413e-05, "rounds": 63, "median": 0.015203964023385197, "iqr": 3.064426709897816e-05, "q1": 0.01518707874492975, "q3": 0.015217723012028728, "iqr_outliers": 9, "stddev_outliers": 8, "outliers": "8;9", "ld15iqr": 0.015152489009778947, "hd15iqr": 0.015277299011358991, "ops": 65.70149522236844, "total": 0.9588822870282456, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.04871747098513879, "max": 0.05040977298631333, "mean": 0.04899229418896582, "stddev": 0.0004830547111586764, "rounds": 21, "median": 0.048796952003613114, "iqr": 0.00011831449955934659, "q1": 0.04875083874503616, "q3": 0.04886915324459551, "iqr_outliers": 4, "stddev_outliers": 2, "outliers": "2;4", "ld15iqr": 0.04871747098513879, "hd15iqr": 0.04938698900514282, "ops": 20.41137318744348, "total": 1.0288381779682823, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_re2_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_re2_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.012791927991202101, "max": 0.013664270023582503, "mean": 0.012989135980024002, "stddev": 0.00018548207878058971, "rounds": 60, "median": 0.012967408983968198, "iqr": 0.00013067701365798712, "q1": 0.01287277098163031, "q3": 0.013003447995288298, "iqr_outliers": 5, "stddev_outliers": 10, "outliers": "10;5", "ld15iqr": 0.012791927991202101, "hd15iqr": 0.013285230990732089, "ops": 76.98741483174096, "total": 0.7793481588014401, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": false }, "stats": { "min": 0.10703447597916238, "max": 0.10878181300358847, "mean": 0.10773133639595471, "stddev": 0.0007509912168768946, "rounds": 10, "median": 0.10723447149212006, "iqr": 0.0014179570134729147, "q1": 0.10714839000138454, "q3": 0.10856634701485746, "iqr_outliers": 0, "stddev_outliers": 3, "outliers": "3;0", "ld15iqr": 0.10703447597916238, "hd15iqr": 0.10878181300358847, "ops": 9.28235027480407, "total": 1.0773133639595471, "iterations": 1 } } ], "datetime": "2025-12-27T14:02:06.149177+00:00", "version": "5.2.3" }././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_match_files_pypy310_i7-165G7.json0000644000000000000000000012226715136034031021317 0ustar00{ "machine_info": { "node": "galp5", "processor": "", "machine": "x86_64", "python_compiler": "", "python_implementation": "PyPy", "python_implementation_version": "7.3.19", "python_version": "3.10.16", "python_build": [ "64367dfeb263", "Feb 24 2025" ], "release": "6.12.62-1-MANJARO", "system": "Linux", "cpu": { "python_version": "3.10.16.final.0 (64 bit)", "cpuinfo_version": [ 9, 0, 0 ], "cpuinfo_version_string": "9.0.0", "arch": "X86_64", "bits": 64, "count": 8, "arch_string_raw": "x86_64", "vendor_id_raw": "GenuineIntel", "brand_raw": "11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz", "hz_advertised_friendly": "2.8000 GHz", "hz_actual_friendly": "400.0000 MHz", "hz_advertised": [ 2800000000, 0 ], "hz_actual": [ 400000000, 0 ], "stepping": 1, "model": 140, "family": 6, "flags": [ "3dnowprefetch", "abm", "acpi", "adx", "aes", "aperfmperf", "apic", "arat", "arch_capabilities", "arch_perfmon", "art", "avx", "avx2", "avx512_bitalg", "avx512_vbmi2", "avx512_vnni", "avx512_vp2intersect", "avx512_vpopcntdq", "avx512bitalg", "avx512bw", "avx512cd", "avx512dq", "avx512f", "avx512ifma", "avx512vbmi", "avx512vbmi2", "avx512vl", "avx512vnni", "avx512vpopcntdq", "bmi1", "bmi2", "bts", "cat_l2", "cdp_l2", "clflush", "clflushopt", "clwb", "cmov", "constant_tsc", "cpuid", "cpuid_fault", "cx16", "cx8", "de", "ds_cpl", "dtes64", "dtherm", "dts", "epb", "ept", "ept_ad", "erms", "est", "f16c", "flexpriority", "flush_l1d", "fma", "fpu", "fsgsbase", "fsrm", "fxsr", "gfni", "ht", "hwp", "hwp_act_window", "hwp_epp", "hwp_notify", "hwp_pkg_req", "ibpb", "ibrs", "ibrs_enhanced", "ibt", "ida", "intel_pt", "invpcid", "lahf_lm", "lm", "mca", "mce", "md_clear", "mmx", "monitor", "movbe", "movdir64b", "movdiri", "msr", "mtrr", "nonstop_tsc", "nopl", "nx", "ospke", "osxsave", "pae", "pat", "pbe", "pcid", "pclmulqdq", "pdcm", "pdpe1gb", "pebs", "pge", "pku", "pln", "pni", "popcnt", "pqe", "pse", "pse36", "pts", "rdpid", "rdrand", "rdrnd", "rdseed", "rdt_a", "rdtscp", "rep_good", "sdbg", "sep", "sha", "sha_ni", "smap", "smep", "split_lock_detect", "ss", "ssbd", "sse", "sse2", "sse4_1", "sse4_2", "ssse3", "stibp", "syscall", "tm", "tm2", "tpr_shadow", "tsc", "tsc_adjust", "tsc_deadline_timer", "tsc_known_freq", "tscdeadline", "umip", "user_shstk", "vaes", "vme", "vmx", "vnmi", "vpclmulqdq", "vpid", "x2apic", "xgetbv1", "xsave", "xsavec", "xsaveopt", "xsaves", "xtopology", "xtpr" ], "l3_cache_size": 12582912, "l2_cache_size": 5242880, "l1_data_cache_size": 196608, "l1_instruction_cache_size": 131072, "l2_cache_line_size": 256, "l2_cache_associativity": 7 } }, "commit_info": { "id": "6cd482937e004878985404ca3e3de1540b6f962f", "time": "2025-12-27T09:15:56-05:00", "author_time": "2025-12-27T09:15:56-05:00", "dirty": true, "project": "python-pathspec", "branch": "master" }, "benchmarks": [ { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01792401499915286, "max": 0.03534226999909151, "mean": 0.019307546267815008, "stddev": 0.002853626884644418, "rounds": 56, "median": 0.01853147800102306, "iqr": 0.0005009444994357182, "q1": 0.018324392000067746, "q3": 0.018825336499503464, "iqr_outliers": 9, "stddev_outliers": 3, "outliers": "3;9", "ld15iqr": 0.01792401499915286, "hd15iqr": 0.019670164998387918, "ops": 51.79322043976993, "total": 1.0812225909976405, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.16628985900024418, "max": 0.19180684400271275, "mean": 0.18064106400051969, "stddev": 0.009595032450765689, "rounds": 5, "median": 0.1831658230003086, "iqr": 0.012288010250813386, "q1": 0.17434663049971277, "q3": 0.18663464075052616, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 0.16628985900024418, "hd15iqr": 0.19180684400271275, "ops": 5.5358398464544205, "total": 0.9032053200025985, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01935519299877342, "max": 0.038899734001461184, "mean": 0.02124468096168969, "stddev": 0.004162027375921229, "rounds": 52, "median": 0.020039957998960745, "iqr": 0.0006951560008019442, "q1": 0.019660804000523058, "q3": 0.020355960001325002, "iqr_outliers": 7, "stddev_outliers": 4, "outliers": "4;7", "ld15iqr": 0.01935519299877342, "hd15iqr": 0.0215858939991449, "ops": 47.070605663756, "total": 1.104723410007864, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.32398700900012045, "max": 0.3250267440016614, "mean": 0.3246174454005086, "stddev": 0.00039324563226757995, "rounds": 5, "median": 0.32474869699944975, "iqr": 0.00045063550169288646, "q1": 0.32439819274986803, "q3": 0.3248488282515609, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 0.32398700900012045, "hd15iqr": 0.3250267440016614, "ops": 3.0805491638510483, "total": 1.623087227002543, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01093219199901796, "max": 0.02323984400209156, "mean": 0.012294989293363671, "stddev": 0.0027487522041622905, "rounds": 92, "median": 0.011372813498383039, "iqr": 0.0006155785013106652, "q1": 0.011077934999775607, "q3": 0.011693513501086272, "iqr_outliers": 16, "stddev_outliers": 8, "outliers": "8;16", "ld15iqr": 0.01093219199901796, "hd15iqr": 0.012636301002203254, "ops": 81.33394638576536, "total": 1.1311390149894578, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.02193738599817152, "max": 0.029432233997795265, "mean": 0.02245222884749123, "stddev": 0.0011907785748831452, "rounds": 46, "median": 0.022124290499050403, "iqr": 0.0002724720034166239, "q1": 0.02201760499883676, "q3": 0.022290077002253383, "iqr_outliers": 6, "stddev_outliers": 3, "outliers": "3;6", "ld15iqr": 0.02193738599817152, "hd15iqr": 0.02294928699848242, "ops": 44.5390079885872, "total": 1.0328025269845966, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.008421955000812886, "max": 0.026539873997535324, "mean": 0.009383311453760854, "stddev": 0.0026379980192670275, "rounds": 119, "median": 0.008631666001747362, "iqr": 0.0004058577487739967, "q1": 0.008501248500579095, "q3": 0.008907106249353092, "iqr_outliers": 15, "stddev_outliers": 7, "outliers": "7;15", "ld15iqr": 0.008421955000812886, "hd15iqr": 0.009633829002268612, "ops": 106.5721845563591, "total": 1.1166140629975416, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.001636799999687355, "max": 0.0020735209982376546, "mean": 0.0016951913990654723, "stddev": 3.720085348382077e-05, "rounds": 619, "median": 0.0016894309992494527, "iqr": 2.5490247026027646e-05, "q1": 0.0016784765020929626, "q3": 0.0017039667491189903, "iqr_outliers": 26, "stddev_outliers": 65, "outliers": "65;26", "ld15iqr": 0.001640768998186104, "hd15iqr": 0.0017436130001442507, "ops": 589.9038896441319, "total": 1.0493234760215273, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.021466399000928504, "max": 0.03381714200077113, "mean": 0.02272831351078918, "stddev": 0.002240888605600322, "rounds": 47, "median": 0.022038385999621823, "iqr": 0.0006729775013809558, "q1": 0.02187983650037495, "q3": 0.022552814001755905, "iqr_outliers": 6, "stddev_outliers": 3, "outliers": "3;6", "ld15iqr": 0.021466399000928504, "hd15iqr": 0.02360266400137334, "ops": 43.99798513538182, "total": 1.0682307350070914, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.035371670997847104, "max": 0.058148328000243055, "mean": 0.0364467608275751, "stddev": 0.004182210934110145, "rounds": 29, "median": 0.03561164200073108, "iqr": 0.0003051670009881491, "q1": 0.03549909249886696, "q3": 0.03580425949985511, "iqr_outliers": 3, "stddev_outliers": 1, "outliers": "1;3", "ld15iqr": 0.035371670997847104, "hd15iqr": 0.036267659001168795, "ops": 27.437280496087713, "total": 1.056956063999678, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.029256046000227798, "max": 0.05128654500003904, "mean": 0.031131593285889332, "stddev": 0.004051055840093934, "rounds": 35, "median": 0.02992006400017999, "iqr": 0.0010489152518857736, "q1": 0.0296093765000478, "q3": 0.030658291751933575, "iqr_outliers": 4, "stddev_outliers": 2, "outliers": "2;4", "ld15iqr": 0.029256046000227798, "hd15iqr": 0.03329604499958805, "ops": 32.1217096348634, "total": 1.0896057650061266, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0768136049991881, "max": 0.07753719999891473, "mean": 0.07713785878565561, "stddev": 0.00021781188846780697, "rounds": 14, "median": 0.07712773549974372, "iqr": 0.0003630380015238188, "q1": 0.07695163799871807, "q3": 0.07731467600024189, "iqr_outliers": 0, "stddev_outliers": 5, "outliers": "5;0", "ld15iqr": 0.0768136049991881, "hd15iqr": 0.07753719999891473, "ops": 12.963802933378256, "total": 1.0799300229991786, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.009797126997000305, "max": 0.028590199999598553, "mean": 0.010879773126256164, "stddev": 0.0029439183863983753, "rounds": 103, "median": 0.010087299000588246, "iqr": 0.000364915747923078, "q1": 0.009869806252027047, "q3": 0.010234721999950125, "iqr_outliers": 16, "stddev_outliers": 7, "outliers": "7;16", "ld15iqr": 0.009797126997000305, "hd15iqr": 0.010893537997617386, "ops": 91.91368132362055, "total": 1.1206166320043849, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.008378555998206139, "max": 0.009883681999781402, "mean": 0.008532170991717674, "stddev": 0.0001549459781073793, "rounds": 120, "median": 0.008496733000356471, "iqr": 7.95620017015608e-05, "q1": 0.00846750150049047, "q3": 0.008547063502192032, "iqr_outliers": 10, "stddev_outliers": 8, "outliers": "8;10", "ld15iqr": 0.008378555998206139, "hd15iqr": 0.00867384699813556, "ops": 117.20346450753478, "total": 1.0238605190061207, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.021782043000712292, "max": 0.03417820300091989, "mean": 0.023720604869278916, "stddev": 0.0031721959651491802, "rounds": 46, "median": 0.02254077799989318, "iqr": 0.0008876520005287603, "q1": 0.022238286001083907, "q3": 0.023125938001612667, "iqr_outliers": 7, "stddev_outliers": 5, "outliers": "5;7", "ld15iqr": 0.021782043000712292, "hd15iqr": 0.025483200999588007, "ops": 42.1574409889995, "total": 1.0911478239868302, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.1575040820025606, "max": 0.15965602999858675, "mean": 0.15861716540093768, "stddev": 0.0007838523969879781, "rounds": 5, "median": 0.15877481500137947, "iqr": 0.0008916345004763571, "q1": 0.1581307842507158, "q3": 0.15902241875119216, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 0.1575040820025606, "hd15iqr": 0.15965602999858675, "ops": 6.304487899984175, "total": 0.7930858270046883, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.020882474000245566, "max": 0.043196443002671, "mean": 0.02264594337490659, "stddev": 0.0037888102039482685, "rounds": 48, "median": 0.021604039500743966, "iqr": 0.0005951209986960748, "q1": 0.021251899499475257, "q3": 0.021847020498171332, "iqr_outliers": 8, "stddev_outliers": 3, "outliers": "3;8", "ld15iqr": 0.020882474000245566, "hd15iqr": 0.02310033599860617, "ops": 44.15801909617399, "total": 1.0870052819955163, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.30480189499940025, "max": 0.30804601599811576, "mean": 0.3059477221991983, "stddev": 0.0013168303751952596, "rounds": 5, "median": 0.30551285999899847, "iqr": 0.0018157782515118015, "q1": 0.30496478974873753, "q3": 0.30678056800024933, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.30480189499940025, "hd15iqr": 0.30804601599811576, "ops": 3.2685322603870013, "total": 1.5297386109959916, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.011018478999176295, "max": 0.024064216999249766, "mean": 0.012245959630292971, "stddev": 0.0028241849056098267, "rounds": 92, "median": 0.011426659999415278, "iqr": 0.00044136350152257364, "q1": 0.011145535498144454, "q3": 0.011586898999667028, "iqr_outliers": 15, "stddev_outliers": 6, "outliers": "6;15", "ld15iqr": 0.011018478999176295, "hd15iqr": 0.012432849001925206, "ops": 81.65958652405553, "total": 1.1266282859869534, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.018066557997372, "max": 0.018820764002157375, "mean": 0.01823426562493426, "stddev": 0.00014183191315676383, "rounds": 56, "median": 0.018191044000559486, "iqr": 0.00016416800281149335, "q1": 0.018139210498702596, "q3": 0.01830337850151409, "iqr_outliers": 2, "stddev_outliers": 13, "outliers": "13;2", "ld15iqr": 0.018066557997372, "hd15iqr": 0.01855447499838192, "ops": 54.8418028216371, "total": 1.0211188749963185, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.008498850002069958, "max": 0.020370775000628782, "mean": 0.009375286245705279, "stddev": 0.0022618305913423052, "rounds": 118, "median": 0.008708966501217219, "iqr": 0.0002839330045389943, "q1": 0.008613465997768799, "q3": 0.008897399002307793, "iqr_outliers": 18, "stddev_outliers": 7, "outliers": "7;18", "ld15iqr": 0.008498850002069958, "hd15iqr": 0.009675453999079764, "ops": 106.66340992607981, "total": 1.106283776993223, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0016429570023319684, "max": 0.002811244998156326, "mean": 0.0017201125040407473, "stddev": 7.850407031541426e-05, "rounds": 613, "median": 0.001709172000118997, "iqr": 3.260175071773119e-05, "q1": 0.0016940972500378848, "q3": 0.001726699000755616, "iqr_outliers": 27, "stddev_outliers": 23, "outliers": "23;27", "ld15iqr": 0.0016466119996039197, "hd15iqr": 0.0017772649989638012, "ops": 581.3573226465606, "total": 1.054428964976978, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.021501183000509627, "max": 0.03477439299967955, "mean": 0.02325712689351813, "stddev": 0.0035168357164402854, "rounds": 47, "median": 0.021949064001091756, "iqr": 0.000763677498071047, "q1": 0.0217399942503107, "q3": 0.02250367174838175, "iqr_outliers": 8, "stddev_outliers": 4, "outliers": "4;8", "ld15iqr": 0.021501183000509627, "hd15iqr": 0.023735124999802792, "ops": 42.99757251093232, "total": 1.0930849639953522, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.03139255200221669, "max": 0.03230680799970287, "mean": 0.03171959087489995, "stddev": 0.00019287913544765905, "rounds": 32, "median": 0.031699467999715125, "iqr": 0.0002648155004862929, "q1": 0.0315706549990864, "q3": 0.03183547049957269, "iqr_outliers": 1, "stddev_outliers": 8, "outliers": "8;1", "ld15iqr": 0.03139255200221669, "hd15iqr": 0.03230680799970287, "ops": 31.526257824192513, "total": 1.0150269079967984, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0314203109992377, "max": 0.04703544999938458, "mean": 0.03307164737509538, "stddev": 0.003465815741414019, "rounds": 32, "median": 0.03208165249998274, "iqr": 0.0006193695007823408, "q1": 0.03177969600073993, "q3": 0.03239906550152227, "iqr_outliers": 5, "stddev_outliers": 2, "outliers": "2;5", "ld15iqr": 0.0314203109992377, "hd15iqr": 0.0333963759985636, "ops": 30.237380940178095, "total": 1.0582927160030522, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.07722921200183919, "max": 0.10381576200234122, "mean": 0.0797763634621403, "stddev": 0.007237969695814087, "rounds": 13, "median": 0.07770329100094386, "iqr": 0.00017689775177132105, "q1": 0.07761982224837993, "q3": 0.07779672000015125, "iqr_outliers": 3, "stddev_outliers": 1, "outliers": "1;3", "ld15iqr": 0.07747875200220733, "hd15iqr": 0.07923138200203539, "ops": 12.53504116510115, "total": 1.037092725007824, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.00983785800053738, "max": 0.03182684599960339, "mean": 0.011021786921571452, "stddev": 0.0033435473872207872, "rounds": 102, "median": 0.010125502998562297, "iqr": 0.00042382899846415967, "q1": 0.009966732999600936, "q3": 0.010390561998065095, "iqr_outliers": 16, "stddev_outliers": 5, "outliers": "5;16", "ld15iqr": 0.00983785800053738, "hd15iqr": 0.011058216998208081, "ops": 90.72938962763246, "total": 1.124222266000288, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "monotonic", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.00721929399878718, "max": 0.00829820400031167, "mean": 0.007371218949128229, "stddev": 0.0001329922258912909, "rounds": 138, "median": 0.0073341224997420795, "iqr": 6.603099973290227e-05, "q1": 0.007310387001780327, "q3": 0.00737641800151323, "iqr_outliers": 12, "stddev_outliers": 12, "outliers": "12;12", "ld15iqr": 0.00721929399878718, "hd15iqr": 0.007495950001612073, "ops": 135.66277258909355, "total": 1.0172282149796956, "iterations": 1 } } ], "datetime": "2025-12-27T14:30:02.452490+00:00", "version": "5.2.3" }././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_match_files_pypy311_i7-165G7.json0000644000000000000000000012242715136034031021316 0ustar00{ "machine_info": { "node": "galp5", "processor": "", "machine": "x86_64", "python_compiler": "", "python_implementation": "PyPy", "python_implementation_version": "7.3.20", "python_version": "3.11.13", "python_build": [ "413c9b7f57f5", "Jul 08 2025" ], "release": "6.12.62-1-MANJARO", "system": "Linux", "cpu": { "python_version": "3.11.13.final.0 (64 bit)", "cpuinfo_version": [ 9, 0, 0 ], "cpuinfo_version_string": "9.0.0", "arch": "X86_64", "bits": 64, "count": 8, "arch_string_raw": "x86_64", "vendor_id_raw": "GenuineIntel", "brand_raw": "11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz", "hz_advertised_friendly": "2.8000 GHz", "hz_actual_friendly": "4.1349 GHz", "hz_advertised": [ 2800000000, 0 ], "hz_actual": [ 4134950000, 0 ], "stepping": 1, "model": 140, "family": 6, "flags": [ "3dnowprefetch", "abm", "acpi", "adx", "aes", "aperfmperf", "apic", "arat", "arch_capabilities", "arch_perfmon", "art", "avx", "avx2", "avx512_bitalg", "avx512_vbmi2", "avx512_vnni", "avx512_vp2intersect", "avx512_vpopcntdq", "avx512bitalg", "avx512bw", "avx512cd", "avx512dq", "avx512f", "avx512ifma", "avx512vbmi", "avx512vbmi2", "avx512vl", "avx512vnni", "avx512vpopcntdq", "bmi1", "bmi2", "bts", "cat_l2", "cdp_l2", "clflush", "clflushopt", "clwb", "cmov", "constant_tsc", "cpuid", "cpuid_fault", "cx16", "cx8", "de", "ds_cpl", "dtes64", "dtherm", "dts", "epb", "ept", "ept_ad", "erms", "est", "f16c", "flexpriority", "flush_l1d", "fma", "fpu", "fsgsbase", "fsrm", "fxsr", "gfni", "ht", "hwp", "hwp_act_window", "hwp_epp", "hwp_notify", "hwp_pkg_req", "ibpb", "ibrs", "ibrs_enhanced", "ibt", "ida", "intel_pt", "invpcid", "lahf_lm", "lm", "mca", "mce", "md_clear", "mmx", "monitor", "movbe", "movdir64b", "movdiri", "msr", "mtrr", "nonstop_tsc", "nopl", "nx", "ospke", "osxsave", "pae", "pat", "pbe", "pcid", "pclmulqdq", "pdcm", "pdpe1gb", "pebs", "pge", "pku", "pln", "pni", "popcnt", "pqe", "pse", "pse36", "pts", "rdpid", "rdrand", "rdrnd", "rdseed", "rdt_a", "rdtscp", "rep_good", "sdbg", "sep", "sha", "sha_ni", "smap", "smep", "split_lock_detect", "ss", "ssbd", "sse", "sse2", "sse4_1", "sse4_2", "ssse3", "stibp", "syscall", "tm", "tm2", "tpr_shadow", "tsc", "tsc_adjust", "tsc_deadline_timer", "tsc_known_freq", "tscdeadline", "umip", "user_shstk", "vaes", "vme", "vmx", "vnmi", "vpclmulqdq", "vpid", "x2apic", "xgetbv1", "xsave", "xsavec", "xsaveopt", "xsaves", "xtopology", "xtpr" ], "l3_cache_size": 12582912, "l2_cache_size": 5242880, "l1_data_cache_size": 196608, "l1_instruction_cache_size": 131072, "l2_cache_line_size": 256, "l2_cache_associativity": 7 } }, "commit_info": { "id": "054d7a200856136c51c2b7537330bd7319fbfb18", "time": "2025-12-27T09:43:07-05:00", "author_time": "2025-12-27T09:43:07-05:00", "dirty": true, "project": "python-pathspec", "branch": "master" }, "benchmarks": [ { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.015122870998311555, "max": 0.0315288670026348, "mean": 0.01661476680612341, "stddev": 0.0033834198134327586, "rounds": 67, "median": 0.015682190001825802, "iqr": 0.0006128127497504465, "q1": 0.015346715500527353, "q3": 0.0159595282502778, "iqr_outliers": 8, "stddev_outliers": 4, "outliers": "4;8", "ld15iqr": 0.015122870998311555, "hd15iqr": 0.01857162300075288, "ops": 60.18742313202059, "total": 1.1131893760102685, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.17311422799684806, "max": 0.17429024299781304, "mean": 0.17358497959939995, "stddev": 0.00045769631118108763, "rounds": 5, "median": 0.17350565000015195, "iqr": 0.0006304290027401294, "q1": 0.1732408857487826, "q3": 0.17387131475152273, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 0.17311422799684806, "hd15iqr": 0.17429024299781304, "ops": 5.7608671113583885, "total": 0.8679248979969998, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01645151200136752, "max": 0.034749841997836484, "mean": 0.01846027206550791, "stddev": 0.003630870470511447, "rounds": 61, "median": 0.017179749000206357, "iqr": 0.0013195562469263677, "q1": 0.01680923750063812, "q3": 0.018128793747564487, "iqr_outliers": 7, "stddev_outliers": 6, "outliers": "6;7", "ld15iqr": 0.01645151200136752, "hd15iqr": 0.020212683000863763, "ops": 54.170382562695245, "total": 1.1260765959959826, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.33741560199996457, "max": 0.3389677209997899, "mean": 0.33830703840067144, "stddev": 0.0005716149164574685, "rounds": 5, "median": 0.3383202959994378, "iqr": 0.0006298650014286977, "q1": 0.3380448227508168, "q3": 0.3386746877522455, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 0.33741560199996457, "hd15iqr": 0.3389677209997899, "ops": 2.9558947538527334, "total": 1.6915351920033572, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.011174466999364085, "max": 0.029840495997632388, "mean": 0.012505189233383538, "stddev": 0.0032357224004791905, "rounds": 90, "median": 0.011597291499128914, "iqr": 0.0005652139989251737, "q1": 0.011313539998809574, "q3": 0.011878753997734748, "iqr_outliers": 13, "stddev_outliers": 6, "outliers": "6;13", "ld15iqr": 0.011174466999364085, "hd15iqr": 0.012839700000768062, "ops": 79.9668026878334, "total": 1.1254670310045185, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.02292865499839536, "max": 0.024228201000369154, "mean": 0.02321376593177278, "stddev": 0.00021101154640999114, "rounds": 44, "median": 0.02315257949885563, "iqr": 0.0001820070010580821, "q1": 0.023095754999303608, "q3": 0.02327776200036169, "iqr_outliers": 2, "stddev_outliers": 6, "outliers": "6;2", "ld15iqr": 0.02292865499839536, "hd15iqr": 0.02357633000065107, "ops": 43.07788761802305, "total": 1.0214057009980024, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.007301356999960262, "max": 0.024108650999551173, "mean": 0.00823477512416383, "stddev": 0.002549294739071322, "rounds": 137, "median": 0.007494099001633003, "iqr": 0.00030324850104079815, "q1": 0.007418368748403736, "q3": 0.0077216172494445345, "iqr_outliers": 20, "stddev_outliers": 9, "outliers": "9;20", "ld15iqr": 0.007301356999960262, "hd15iqr": 0.008187605999410152, "ops": 121.4362244168193, "total": 1.1281641920104448, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0016652220001560636, "max": 0.0030776069979765452, "mean": 0.0017294956365180713, "stddev": 6.372825083210582e-05, "rounds": 608, "median": 0.0017239209992112592, "iqr": 3.340199873491656e-05, "q1": 0.0017081434998544864, "q3": 0.001741545498589403, "iqr_outliers": 10, "stddev_outliers": 10, "outliers": "10;10", "ld15iqr": 0.0016652220001560636, "hd15iqr": 0.0017929389978235122, "ops": 578.2032512167898, "total": 1.0515333470029873, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.016272581000521313, "max": 0.03540609600167954, "mean": 0.017769534435677588, "stddev": 0.0034022656033276997, "rounds": 62, "median": 0.016785593499662355, "iqr": 0.0008169430002453737, "q1": 0.016398424999351846, "q3": 0.01721536799959722, "iqr_outliers": 8, "stddev_outliers": 5, "outliers": "5;8", "ld15iqr": 0.016272581000521313, "hd15iqr": 0.019219043999328278, "ops": 56.27609454934311, "total": 1.1017111350120103, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.03548820300056832, "max": 0.0361921009971411, "mean": 0.03580749582748655, "stddev": 0.00019526961533908202, "rounds": 29, "median": 0.035776247998001054, "iqr": 0.00026898449868895113, "q1": 0.03564872125025431, "q3": 0.03591770574894326, "iqr_outliers": 0, "stddev_outliers": 12, "outliers": "12;0", "ld15iqr": 0.03548820300056832, "hd15iqr": 0.0361921009971411, "ops": 27.927113496509296, "total": 1.03841737899711, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.021814038998854812, "max": 0.04169271099817706, "mean": 0.02337238504356798, "stddev": 0.003986484247300672, "rounds": 46, "median": 0.022427412501201616, "iqr": 0.0006719439988955855, "q1": 0.022049790000892244, "q3": 0.02272173399978783, "iqr_outliers": 5, "stddev_outliers": 2, "outliers": "2;5", "ld15iqr": 0.021814038998854812, "hd15iqr": 0.024138459000823786, "ops": 42.7855350720913, "total": 1.0751297120041272, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0744527749993722, "max": 0.07585979899886297, "mean": 0.07507353499931924, "stddev": 0.0003880553896585708, "rounds": 14, "median": 0.07504256850006641, "iqr": 0.00041522799801896326, "q1": 0.07487134999973932, "q3": 0.07528657799775829, "iqr_outliers": 0, "stddev_outliers": 5, "outliers": "5;0", "ld15iqr": 0.0744527749993722, "hd15iqr": 0.07585979899886297, "ops": 13.320273249542172, "total": 1.0510294899904693, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.010209529998974176, "max": 0.028781208999134833, "mean": 0.011295405565687535, "stddev": 0.003102885006344051, "rounds": 99, "median": 0.010388052000052994, "iqr": 0.0004657969993786537, "q1": 0.010296548750375223, "q3": 0.010762345749753877, "iqr_outliers": 13, "stddev_outliers": 6, "outliers": "6;13", "ld15iqr": 0.010209529998974176, "hd15iqr": 0.011515546000737231, "ops": 88.53157101660311, "total": 1.118245151003066, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.008721872000023723, "max": 0.009210794996761251, "mean": 0.008829315182463134, "stddev": 6.49099596240615e-05, "rounds": 115, "median": 0.008820306000416167, "iqr": 6.30192480457481e-05, "q1": 0.008790763002252788, "q3": 0.008853782250298536, "iqr_outliers": 4, "stddev_outliers": 23, "outliers": "23;4", "ld15iqr": 0.008721872000023723, "hd15iqr": 0.008968572998128366, "ops": 113.25906702098584, "total": 1.0153712459832605, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.020430485001270426, "max": 0.04171740599849727, "mean": 0.022156374400292406, "stddev": 0.004112983403343476, "rounds": 50, "median": 0.021201798001129646, "iqr": 0.0006251000049815048, "q1": 0.020749787996464875, "q3": 0.02137488800144638, "iqr_outliers": 10, "stddev_outliers": 2, "outliers": "2;10", "ld15iqr": 0.020430485001270426, "hd15iqr": 0.022319992000120692, "ops": 45.13373812580106, "total": 1.1078187200146203, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.16159194599822513, "max": 0.1650751009983651, "mean": 0.1631549575999088, "stddev": 0.0015205749113184463, "rounds": 5, "median": 0.16333303400097066, "iqr": 0.002690586497919867, "q1": 0.16165360275135754, "q3": 0.1643441892492774, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 0.16159194599822513, "hd15iqr": 0.1650751009983651, "ops": 6.129142593706628, "total": 0.8157747879995441, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01835545900030411, "max": 0.04054672600250342, "mean": 0.019892738000172275, "stddev": 0.0035722746886755986, "rounds": 55, "median": 0.01900434699928155, "iqr": 0.0006018407502779155, "q1": 0.018618907249219774, "q3": 0.01922074799949769, "iqr_outliers": 7, "stddev_outliers": 3, "outliers": "3;7", "ld15iqr": 0.01835545900030411, "hd15iqr": 0.020826494997891132, "ops": 50.26960089613304, "total": 1.0941005900094751, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.3196756799989089, "max": 0.3269716190006875, "mean": 0.32235862820089095, "stddev": 0.002741907361416694, "rounds": 5, "median": 0.3215070510013902, "iqr": 0.0023928005002744612, "q1": 0.3209990587511129, "q3": 0.32339185925138736, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.3196756799989089, "hd15iqr": 0.3269716190006875, "ops": 3.1021350524447855, "total": 1.6117931410044548, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.011079967000114266, "max": 0.03156004299671622, "mean": 0.012301649373502197, "stddev": 0.0029865169370576566, "rounds": 91, "median": 0.011403938999137608, "iqr": 0.0006004485012454097, "q1": 0.011183815748154302, "q3": 0.011784264249399712, "iqr_outliers": 13, "stddev_outliers": 5, "outliers": "5;13", "ld15iqr": 0.011079967000114266, "hd15iqr": 0.012934992999362294, "ops": 81.28991240426703, "total": 1.1194500929887, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.023219008999149082, "max": 0.027686159002769273, "mean": 0.023580944022466032, "stddev": 0.0007084474093747961, "rounds": 44, "median": 0.023433614998793928, "iqr": 0.00016861249969224446, "q1": 0.02333708149853919, "q3": 0.023505693998231436, "iqr_outliers": 3, "stddev_outliers": 3, "outliers": "3;3", "ld15iqr": 0.023219008999149082, "hd15iqr": 0.02462729900071281, "ops": 42.407123270691805, "total": 1.0375615369885054, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.007257245000801049, "max": 0.028073354998923605, "mean": 0.008508550834409866, "stddev": 0.0028971676761146503, "rounds": 139, "median": 0.007560956000816077, "iqr": 0.0006049482472008094, "q1": 0.00740527125162771, "q3": 0.00801021949882852, "iqr_outliers": 21, "stddev_outliers": 9, "outliers": "9;21", "ld15iqr": 0.007257245000801049, "hd15iqr": 0.008957505000580568, "ops": 117.52882711305536, "total": 1.1826885659829713, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0016498100012540817, "max": 0.00222103299893206, "mean": 0.0017322091306047418, "stddev": 3.944871672376067e-05, "rounds": 605, "median": 0.0017289520001213532, "iqr": 4.2351251977379434e-05, "q1": 0.0017081084988603834, "q3": 0.0017504597508377628, "iqr_outliers": 15, "stddev_outliers": 118, "outliers": "118;15", "ld15iqr": 0.0016498100012540817, "hd15iqr": 0.0018201589991804212, "ops": 577.297499667886, "total": 1.0479865240158688, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01603115699981572, "max": 0.028766993000317598, "mean": 0.017567936516159885, "stddev": 0.0027284897456744065, "rounds": 62, "median": 0.01661207749930327, "iqr": 0.0009217670012731105, "q1": 0.016313796000758884, "q3": 0.017235563002031995, "iqr_outliers": 8, "stddev_outliers": 6, "outliers": "6;8", "ld15iqr": 0.01603115699981572, "hd15iqr": 0.019068129997322103, "ops": 56.92188146741929, "total": 1.089212064001913, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.03566857099940535, "max": 0.03710646599938627, "mean": 0.03606084289652966, "stddev": 0.00032565941700188995, "rounds": 29, "median": 0.03594625299956533, "iqr": 0.0004135590006626444, "q1": 0.035844956000801176, "q3": 0.03625851500146382, "iqr_outliers": 1, "stddev_outliers": 7, "outliers": "7;1", "ld15iqr": 0.03566857099940535, "hd15iqr": 0.03710646599938627, "ops": 27.73091030815133, "total": 1.04576444399936, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.023730135999358026, "max": 0.03900648099806858, "mean": 0.027810544999998195, "stddev": 0.003967842792747093, "rounds": 42, "median": 0.026714553499914473, "iqr": 0.0066758719985955395, "q1": 0.02443095600028755, "q3": 0.03110682799888309, "iqr_outliers": 0, "stddev_outliers": 7, "outliers": "7;0", "ld15iqr": 0.023730135999358026, "hd15iqr": 0.03900648099806858, "ops": 35.95758371510033, "total": 1.1680428899999242, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0781765979991178, "max": 0.08012265800061869, "mean": 0.07896179538455236, "stddev": 0.0006356598073929737, "rounds": 13, "median": 0.07867367200014996, "iqr": 0.0010752822463473422, "q1": 0.07853165925098438, "q3": 0.07960694149733172, "iqr_outliers": 0, "stddev_outliers": 5, "outliers": "5;0", "ld15iqr": 0.0781765979991178, "hd15iqr": 0.08012265800061869, "ops": 12.664352363442262, "total": 1.0265033399991808, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.009888943000987638, "max": 0.03130084800068289, "mean": 0.01090147729415798, "stddev": 0.002985124075303298, "rounds": 102, "median": 0.010097443500853842, "iqr": 0.0003301639990240801, "q1": 0.01000688999920385, "q3": 0.01033705399822793, "iqr_outliers": 14, "stddev_outliers": 5, "outliers": "5;14", "ld15iqr": 0.009888943000987638, "hd15iqr": 0.010860464000870707, "ops": 91.73068686166897, "total": 1.111950684004114, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.009243125001376029, "max": 0.01047200099856127, "mean": 0.009381798963308647, "stddev": 0.00019597564902588344, "rounds": 109, "median": 0.00933218099817168, "iqr": 6.789374856452923e-05, "q1": 0.009298633000071277, "q3": 0.009366526748635806, "iqr_outliers": 12, "stddev_outliers": 9, "outliers": "9;12", "ld15iqr": 0.009243125001376029, "hd15iqr": 0.009472723999351729, "ops": 106.5893656334897, "total": 1.0226160870006424, "iterations": 1 } } ], "datetime": "2025-12-30T01:01:39.182946+00:00", "version": "5.2.3" }././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_match_files_pypy311_ryzen_ai_max_395.json0000644000000000000000000012370215136034031023352 0ustar00{ "machine_info": { "node": "fwd1", "processor": "", "machine": "x86_64", "python_compiler": "", "python_implementation": "PyPy", "python_implementation_version": "7.3.20", "python_version": "3.11.13", "python_build": [ "413c9b7f57f5", "Jul 08 2025" ], "release": "6.18.2-3-cachyos", "system": "Linux", "cpu": { "python_version": "3.11.13.final.0 (64 bit)", "cpuinfo_version": [ 9, 0, 0 ], "cpuinfo_version_string": "9.0.0", "arch": "X86_64", "bits": 64, "count": 32, "arch_string_raw": "x86_64", "vendor_id_raw": "AuthenticAMD", "brand_raw": "AMD RYZEN AI MAX+ 395 w/ Radeon 8060S", "hz_advertised_friendly": "2.0000 GHz", "hz_actual_friendly": "2.0000 GHz", "hz_advertised": [ 2000000000, 0 ], "hz_actual": [ 2000000000, 0 ], "model": 112, "family": 26, "flags": [ "3dnowprefetch", "abm", "adx", "aes", "amd_lbr_pmc_freeze", "amd_lbr_v2", "aperfmperf", "apic", "arat", "avic", "avx", "avx2", "avx512_bf16", "avx512_bitalg", "avx512_vbmi2", "avx512_vnni", "avx512_vp2intersect", "avx512_vpopcntdq", "avx512bitalg", "avx512bw", "avx512cd", "avx512dq", "avx512f", "avx512ifma", "avx512vbmi", "avx512vbmi2", "avx512vl", "avx512vnni", "avx512vpopcntdq", "avx_vnni", "bmi1", "bmi2", "bpext", "bus_lock_detect", "cat_l3", "cdp_l3", "clflush", "clflushopt", "clwb", "clzero", "cmov", "cmp_legacy", "constant_tsc", "cpb", "cppc", "cpuid", "cpuid_fault", "cqm", "cqm_llc", "cqm_mbm_local", "cqm_mbm_total", "cqm_occup_llc", "cr8_legacy", "cx16", "cx8", "dbx", "de", "decodeassists", "erms", "extapic", "extd_apicid", "f16c", "flush_l1d", "flushbyasid", "fma", "fpu", "fsgsbase", "fsrm", "fxsr", "fxsr_opt", "gfni", "ht", "hw_pstate", "ibpb", "ibrs", "ibrs_enhanced", "ibs", "invpcid", "irperf", "lahf_lm", "lbrv", "lm", "mba", "mca", "mce", "misalignsse", "mmx", "mmxext", "monitor", "movbe", "movdir64b", "movdiri", "msr", "mtrr", "mwaitx", "nonstop_tsc", "nopl", "npt", "nrip_save", "nx", "ospke", "osvw", "osxsave", "overflow_recov", "pae", "pat", "pausefilter", "pci_l2i", "pclmulqdq", "pdpe1gb", "perfctr_core", "perfctr_llc", "perfctr_nb", "perfmon_v2", "pfthreshold", "pge", "pku", "pni", "popcnt", "pqe", "pqm", "pse", "pse36", "rapl", "rdpid", "rdpru", "rdrand", "rdrnd", "rdseed", "rdt_a", "rdtscp", "rep_good", "sep", "sha", "sha_ni", "skinit", "smap", "smca", "smep", "ssbd", "sse", "sse2", "sse4_1", "sse4_2", "sse4a", "ssse3", "stibp", "succor", "svm", "svm_lock", "syscall", "tce", "topoext", "tsc", "tsc_adjust", "tsc_scale", "umip", "user_shstk", "v_spec_ctrl", "v_vmsave_vmload", "vaes", "vgif", "vmcb_clean", "vme", "vmmcall", "vnmi", "vpclmulqdq", "wbnoinvd", "wdt", "x2avic", "xgetbv1", "xsave", "xsavec", "xsaveerptr", "xsaveopt", "xsaves", "xtopology" ], "l3_cache_size": 1048576, "l2_cache_size": 16777216, "l1_data_cache_size": 786432, "l1_instruction_cache_size": 524288, "l2_cache_line_size": 1024, "l2_cache_associativity": 8 } }, "commit_info": { "id": "054d7a200856136c51c2b7537330bd7319fbfb18", "time": "2025-12-27T09:43:07-05:00", "author_time": "2025-12-27T09:43:07-05:00", "dirty": true, "project": "python-pathspec", "branch": "master" }, "benchmarks": [ { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.009375893998367246, "max": 0.029353986001297017, "mean": 0.010926296999901055, "stddev": 0.0036638763437906283, "rounds": 107, "median": 0.009674265000285232, "iqr": 0.00040461724984197645, "q1": 0.009520368500034238, "q3": 0.009924985749876214, "iqr_outliers": 22, "stddev_outliers": 9, "outliers": "9;22", "ld15iqr": 0.009375893998367246, "hd15iqr": 0.01083071599896357, "ops": 91.52231538361585, "total": 1.1691137789894128, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.09821355000167387, "max": 0.1260378850001871, "mean": 0.10096347354539664, "stddev": 0.008317166542403373, "rounds": 11, "median": 0.09843713000009302, "iqr": 0.00017289474953940953, "q1": 0.098414459999276, "q3": 0.09858735474881541, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.09821355000167387, "hd15iqr": 0.1260378850001871, "ops": 9.90457206833683, "total": 1.1105982089993631, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.010363388999394374, "max": 0.0326324530014972, "mean": 0.012116896072107058, "stddev": 0.004090684917364351, "rounds": 97, "median": 0.010832318999746349, "iqr": 0.000834265999856143, "q1": 0.010529248499096866, "q3": 0.011363514498953009, "iqr_outliers": 15, "stddev_outliers": 7, "outliers": "7;15", "ld15iqr": 0.010363388999394374, "hd15iqr": 0.012722167000902118, "ops": 82.52938657301745, "total": 1.1753389189943846, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.1911070780006412, "max": 0.19164190199990117, "mean": 0.1913617165003719, "stddev": 0.00021626983975472854, "rounds": 6, "median": 0.19134360299995024, "iqr": 0.0003734210004040506, "q1": 0.19118034600069223, "q3": 0.19155376700109628, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 0.1911070780006412, "hd15iqr": 0.19164190199990117, "ops": 5.2257056337496675, "total": 1.1481702990022313, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0070163139989745105, "max": 0.027765916000134894, "mean": 0.008328775506899748, "stddev": 0.0036173191725044408, "rounds": 144, "median": 0.007130895000045712, "iqr": 0.00024864700117177563, "q1": 0.007085569499395206, "q3": 0.007334216500566981, "iqr_outliers": 27, "stddev_outliers": 11, "outliers": "11;27", "ld15iqr": 0.0070163139989745105, "hd15iqr": 0.008022494001124869, "ops": 120.06566861738284, "total": 1.1993436729935638, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01588640999943891, "max": 0.01597886400122661, "mean": 0.015925737656516503, "stddev": 2.3105002275835095e-05, "rounds": 64, "median": 0.01592320450072293, "iqr": 3.12490001306287e-05, "q1": 0.015910359999907087, "q3": 0.015941609000037715, "iqr_outliers": 0, "stddev_outliers": 24, "outliers": "24;0", "ld15iqr": 0.01588640999943891, "hd15iqr": 0.01597886400122661, "ops": 62.7914399676689, "total": 1.0192472100170562, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.004614515000866959, "max": 0.024053936000200338, "mean": 0.005586026292164027, "stddev": 0.002987802246756745, "rounds": 219, "median": 0.004756110000016633, "iqr": 2.5681249553599628e-05, "q1": 0.004746780749428581, "q3": 0.00477246199898218, "iqr_outliers": 53, "stddev_outliers": 13, "outliers": "13;53", "ld15iqr": 0.004725363000034122, "hd15iqr": 0.005252552999081672, "ops": 179.01813340957258, "total": 1.223339757983922, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0011014879983122228, "max": 0.001159999001174583, "mean": 0.0011128743214073372, "stddev": 8.998094624146158e-06, "rounds": 924, "median": 0.0011106259998996393, "iqr": 5.4704978538211435e-06, "q1": 0.001108206000935752, "q3": 0.001113676498789573, "iqr_outliers": 75, "stddev_outliers": 92, "outliers": "92;75", "ld15iqr": 0.0011014879983122228, "hd15iqr": 0.0011219770003663143, "ops": 898.574062465026, "total": 1.0282958729803795, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.010276826998961042, "max": 0.032300622999173356, "mean": 0.01167177722449456, "stddev": 0.0036311353921806996, "rounds": 98, "median": 0.010483605999070278, "iqr": 0.00041745400085346773, "q1": 0.010412581999844406, "q3": 0.010830036000697874, "iqr_outliers": 17, "stddev_outliers": 7, "outliers": "7;17", "ld15iqr": 0.010276826998961042, "hd15iqr": 0.011566008000954753, "ops": 85.67675519897566, "total": 1.1438341680004669, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.02569262500037439, "max": 0.0258306249997986, "mean": 0.025750685050070388, "stddev": 3.6831535495501295e-05, "rounds": 40, "median": 0.025745996000296145, "iqr": 6.180050058901543e-05, "q1": 0.02571934549996513, "q3": 0.025781146000554145, "iqr_outliers": 0, "stddev_outliers": 14, "outliers": "14;0", "ld15iqr": 0.02569262500037439, "hd15iqr": 0.0258306249997986, "ops": 38.8339183231658, "total": 1.0300274020028155, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.014097773000685265, "max": 0.04013511599987396, "mean": 0.01582476205642796, "stddev": 0.004951584850943108, "rounds": 71, "median": 0.014312485998743796, "iqr": 0.00042122199965888285, "q1": 0.014245950750137126, "q3": 0.01466717274979601, "iqr_outliers": 13, "stddev_outliers": 3, "outliers": "3;13", "ld15iqr": 0.014097773000685265, "hd15iqr": 0.016041142000176478, "ops": 63.19210339050904, "total": 1.123558106006385, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.04793615500057058, "max": 0.04813703299987537, "mean": 0.04801334561887502, "stddev": 5.230601570983959e-05, "rounds": 21, "median": 0.04799868299960508, "iqr": 6.104925068939338e-05, "q1": 0.04797718249983518, "q3": 0.048038231750524574, "iqr_outliers": 1, "stddev_outliers": 6, "outliers": "6;1", "ld15iqr": 0.04793615500057058, "hd15iqr": 0.04813703299987537, "ops": 20.82754257405632, "total": 1.0082802579963754, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.006449100001191255, "max": 0.02811149899935117, "mean": 0.007576993070555405, "stddev": 0.0035408907131135557, "rounds": 156, "median": 0.006589894999706303, "iqr": 4.907750007987488e-05, "q1": 0.006575848000466067, "q3": 0.006624925500545942, "iqr_outliers": 38, "stddev_outliers": 9, "outliers": "9;38", "ld15iqr": 0.006555900999956066, "hd15iqr": 0.007148844000766985, "ops": 131.9784762488503, "total": 1.1820109190066432, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.006154988001071615, "max": 0.0062356799990084255, "mean": 0.006184331079300425, "stddev": 1.3763936739687426e-05, "rounds": 164, "median": 0.006182775000524998, "iqr": 1.1496999832161237e-05, "q1": 0.006176839000545442, "q3": 0.006188336000377603, "iqr_outliers": 16, "stddev_outliers": 37, "outliers": "37;16", "ld15iqr": 0.0061619910011359025, "hd15iqr": 0.006206074000147055, "ops": 161.6989755524409, "total": 1.0142302970052697, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.012795878999895649, "max": 0.03721335199952591, "mean": 0.014403763705115872, "stddev": 0.00470372321657852, "rounds": 78, "median": 0.013099289000820136, "iqr": 0.00044577799963008147, "q1": 0.012963513001523097, "q3": 0.013409291001153179, "iqr_outliers": 12, "stddev_outliers": 3, "outliers": "3;12", "ld15iqr": 0.012795878999895649, "hd15iqr": 0.014286788999015698, "ops": 69.42629860310913, "total": 1.123493568999038, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.09266847100116138, "max": 0.09322486499877414, "mean": 0.09294476599941183, "stddev": 0.00020446068729603636, "rounds": 11, "median": 0.09294488999876194, "iqr": 0.00040212949988926994, "q1": 0.09272631224939687, "q3": 0.09312844174928614, "iqr_outliers": 0, "stddev_outliers": 5, "outliers": "5;0", "ld15iqr": 0.09266847100116138, "hd15iqr": 0.09322486499877414, "ops": 10.75907813901353, "total": 1.0223924259935302, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01168158600012248, "max": 0.036788655999771436, "mean": 0.013492817046595561, "stddev": 0.004677130979139805, "rounds": 86, "median": 0.011961758499637654, "iqr": 0.0004273930007911986, "q1": 0.011822221000329591, "q3": 0.01224961400112079, "iqr_outliers": 16, "stddev_outliers": 6, "outliers": "6;16", "ld15iqr": 0.01168158600012248, "hd15iqr": 0.013413339000180713, "ops": 74.11350769425239, "total": 1.1603822660072183, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.17600111700085108, "max": 0.17689156800042838, "mean": 0.1763556536664813, "stddev": 0.0003570311434298343, "rounds": 6, "median": 0.17625115049850137, "iqr": 0.0006359540002449648, "q1": 0.17605149100018025, "q3": 0.17668744500042521, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.17600111700085108, "hd15iqr": 0.17689156800042838, "ops": 5.67035974866545, "total": 1.0581339219988877, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.007045161000860389, "max": 0.030361617000380647, "mean": 0.008250852000047517, "stddev": 0.0038177210395003544, "rounds": 144, "median": 0.007155317000069772, "iqr": 0.0001170999994428712, "q1": 0.007111144000191416, "q3": 0.007228243999634287, "iqr_outliers": 26, "stddev_outliers": 8, "outliers": "8;26", "ld15iqr": 0.007045161000860389, "hd15iqr": 0.007452585999999428, "ops": 121.19960459771195, "total": 1.1881226880068425, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.014183655999659095, "max": 0.01670582299993839, "mean": 0.01435469671846925, "stddev": 0.00029085836801027586, "rounds": 71, "median": 0.014305475000583101, "iqr": 0.00011461024905656814, "q1": 0.014279163000992412, "q3": 0.01439377325004898, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.014183655999659095, "hd15iqr": 0.01670582299993839, "ops": 69.6636104274753, "total": 1.0191834670113167, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.004564352000670624, "max": 0.025965392000216525, "mean": 0.00565224895459654, "stddev": 0.003396175163864211, "rounds": 220, "median": 0.00472366099984356, "iqr": 4.5280000449565705e-05, "q1": 0.0047032229995238595, "q3": 0.004748502999973425, "iqr_outliers": 54, "stddev_outliers": 11, "outliers": "11;54", "ld15iqr": 0.004665962000217405, "hd15iqr": 0.00510545799988904, "ops": 176.92072802044163, "total": 1.2434947700112389, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0010697390007408103, "max": 0.0011890730002050987, "mean": 0.0010952997814817162, "stddev": 9.220741861523074e-06, "rounds": 938, "median": 0.0010960180006804876, "iqr": 8.667000656714663e-06, "q1": 0.0010916999999608379, "q3": 0.0011003670006175525, "iqr_outliers": 73, "stddev_outliers": 219, "outliers": "219;73", "ld15iqr": 0.0010788360013975762, "hd15iqr": 0.001113471000280697, "ops": 912.9920565191795, "total": 1.0273911950298498, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.010257924001052743, "max": 0.035344156000064686, "mean": 0.011951888816467396, "stddev": 0.00474460152751937, "rounds": 98, "median": 0.010435282500111498, "iqr": 0.0003920760009350488, "q1": 0.010386916999777895, "q3": 0.010778993000712944, "iqr_outliers": 16, "stddev_outliers": 6, "outliers": "6;16", "ld15iqr": 0.010257924001052743, "hd15iqr": 0.01189694299864641, "ops": 83.6687836839808, "total": 1.1712851040138048, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0235151010001573, "max": 0.02364967399989837, "mean": 0.023572741069877994, "stddev": 3.511179399134953e-05, "rounds": 43, "median": 0.02357278000090446, "iqr": 5.06972505718295e-05, "q1": 0.023543808249087306, "q3": 0.023594505499659135, "iqr_outliers": 0, "stddev_outliers": 14, "outliers": "14;0", "ld15iqr": 0.0235151010001573, "hd15iqr": 0.02364967399989837, "ops": 42.42188029960725, "total": 1.0136278660047537, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.015489460000026156, "max": 0.042114963000130956, "mean": 0.01692051730768491, "stddev": 0.004400980009738201, "rounds": 65, "median": 0.01566105199890444, "iqr": 0.00043814274977194145, "q1": 0.015598663000673696, "q3": 0.016036805750445637, "iqr_outliers": 10, "stddev_outliers": 4, "outliers": "4;10", "ld15iqr": 0.015489460000026156, "hd15iqr": 0.01708069800042722, "ops": 59.0998479429363, "total": 1.099833624999519, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.04741638099949341, "max": 0.047709820999443764, "mean": 0.04758609440903621, "stddev": 8.324285724543332e-05, "rounds": 22, "median": 0.047589520499968785, "iqr": 0.00010748099884949625, "q1": 0.04753282899946498, "q3": 0.04764030999831448, "iqr_outliers": 0, "stddev_outliers": 8, "outliers": "8;0", "ld15iqr": 0.04741638099949341, "hd15iqr": 0.047709820999443764, "ops": 21.014542429229245, "total": 1.0468940769987967, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.006402785000318545, "max": 0.029729613999734283, "mean": 0.007537659879779289, "stddev": 0.003748770437474907, "rounds": 158, "median": 0.006554776000484708, "iqr": 3.625799945439212e-05, "q1": 0.006538839999848278, "q3": 0.00657509799930267, "iqr_outliers": 33, "stddev_outliers": 9, "outliers": "9;33", "ld15iqr": 0.006512220001241076, "hd15iqr": 0.007243574000312947, "ops": 132.66716938006508, "total": 1.1909502610051277, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.00583972700042068, "max": 0.005938772999797948, "mean": 0.005878408202242272, "stddev": 1.7983960132695317e-05, "rounds": 173, "median": 0.005878760000996408, "iqr": 1.8089250261255074e-05, "q1": 0.005868584249583364, "q3": 0.005886673499844619, "iqr_outliers": 11, "stddev_outliers": 48, "outliers": "48;11", "ld15iqr": 0.005846409998412128, "hd15iqr": 0.005915019000894972, "ops": 170.11407945752353, "total": 1.0169646189879131, "iterations": 1 } } ], "datetime": "2025-12-30T01:17:34.965394+00:00", "version": "5.2.3" }././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_match_files_pypy311_xeon_e5-2690.json0000644000000000000000000011657315136034031022235 0ustar00{ "machine_info": { "node": "cmd1", "processor": "x86_64", "machine": "x86_64", "python_compiler": "", "python_implementation": "PyPy", "python_implementation_version": "7.3.20", "python_version": "3.11.13", "python_build": [ "413c9b7f57f5", "Jul 03 2025" ], "release": "5.15.0-164-generic", "system": "Linux", "cpu": { "python_version": "3.11.13.final.0 (64 bit)", "cpuinfo_version": [ 9, 0, 0 ], "cpuinfo_version_string": "9.0.0", "arch": "X86_64", "bits": 64, "count": 32, "arch_string_raw": "x86_64", "vendor_id_raw": "GenuineIntel", "brand_raw": "Intel(R) Xeon(R) CPU E5-2690 0 @ 2.90GHz", "hz_advertised_friendly": "2.9000 GHz", "hz_actual_friendly": "1.2000 GHz", "hz_advertised": [ 2900000000, 0 ], "hz_actual": [ 1200000000, 0 ], "stepping": 7, "model": 45, "family": 6, "flags": [ "acpi", "aes", "aperfmperf", "apic", "arat", "arch_perfmon", "avx", "bts", "clflush", "cmov", "constant_tsc", "cpuid", "cx16", "cx8", "dca", "de", "ds_cpl", "dtes64", "dtherm", "dts", "epb", "ept", "est", "flexpriority", "flush_l1d", "fpu", "fxsr", "ht", "ibpb", "ibpb_exit_to_user", "ibrs", "ida", "lahf_lm", "lm", "mca", "mce", "md_clear", "mmx", "monitor", "msr", "mtrr", "nonstop_tsc", "nopl", "nx", "osxsave", "pae", "pat", "pbe", "pcid", "pclmulqdq", "pdcm", "pdpe1gb", "pebs", "pge", "pln", "pni", "popcnt", "pse", "pse36", "pti", "pts", "rdtscp", "rep_good", "sep", "smx", "ss", "ssbd", "sse", "sse2", "sse4_1", "sse4_2", "ssse3", "stibp", "syscall", "tm", "tm2", "tpr_shadow", "tsc", "tsc_deadline_timer", "tscdeadline", "vme", "vmx", "vnmi", "vpid", "x2apic", "xsave", "xsaveopt", "xtopology", "xtpr" ], "l3_cache_size": 20971520, "l2_cache_size": 4194304, "l1_data_cache_size": 524288, "l1_instruction_cache_size": 524288, "l2_cache_line_size": 256, "l2_cache_associativity": 6 } }, "commit_info": { "id": "054d7a200856136c51c2b7537330bd7319fbfb18", "time": "2025-12-27T09:43:07-05:00", "author_time": "2025-12-27T09:43:07-05:00", "dirty": false, "project": "python-pathspec", "branch": "master" }, "benchmarks": [ { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.04787205599131994, "max": 0.06889702001353726, "mean": 0.053953568574451355, "stddev": 0.007061698463360231, "rounds": 21, "median": 0.050409738993039355, "iqr": 0.013244205780210905, "q1": 0.04800500323472079, "q3": 0.061249209014931694, "iqr_outliers": 0, "stddev_outliers": 5, "outliers": "5;0", "ld15iqr": 0.04787205599131994, "hd15iqr": 0.06889702001353726, "ops": 18.534455206981995, "total": 1.1330249400634784, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.5329382909985725, "max": 0.5391972890065517, "mean": 0.5351401678053662, "stddev": 0.0027505647369471943, "rounds": 5, "median": 0.5337410689971875, "iqr": 0.004349345013906714, "q1": 0.5330263462528819, "q3": 0.5373756912667886, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.5329382909985725, "hd15iqr": 0.5391972890065517, "ops": 1.8686692948149357, "total": 2.675700839026831, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.04999396202038042, "max": 0.07448559300974011, "mean": 0.05144877134880517, "stddev": 0.005434126930503131, "rounds": 20, "median": 0.0501591409847606, "iqr": 0.00012501952005550265, "q1": 0.05009551398688927, "q3": 0.050220533506944776, "iqr_outliers": 3, "stddev_outliers": 1, "outliers": "1;3", "ld15iqr": 0.04999396202038042, "hd15iqr": 0.05049417898408137, "ops": 19.436810127502174, "total": 1.0289754269761033, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 1.0752721680037212, "max": 1.0780090439948253, "mean": 1.0767224484006874, "stddev": 0.0012306660659700752, "rounds": 5, "median": 1.07657041400671, "iqr": 0.0022669004974886775, "q1": 1.0756869322503917, "q3": 1.0779538327478804, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 1.0752721680037212, "hd15iqr": 1.0780090439948253, "ops": 0.9287444517250966, "total": 5.383612242003437, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.02862432098481804, "max": 0.055457516020396724, "mean": 0.03150877174588718, "stddev": 0.004443254340994187, "rounds": 35, "median": 0.03027206301339902, "iqr": 0.0011672370092128403, "q1": 0.030123069511319045, "q3": 0.031290306520531885, "iqr_outliers": 5, "stddev_outliers": 1, "outliers": "1;5", "ld15iqr": 0.02862432098481804, "hd15iqr": 0.03360463000717573, "ops": 31.737193949190647, "total": 1.1028070111060515, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0614209360210225, "max": 0.062456640996970236, "mean": 0.06163675147142973, "stddev": 0.0002687768949260745, "rounds": 17, "median": 0.06155294002383016, "iqr": 0.00014385950635187328, "q1": 0.061482956996769644, "q3": 0.06162681650312152, "iqr_outliers": 3, "stddev_outliers": 2, "outliers": "2;3", "ld15iqr": 0.0614209360210225, "hd15iqr": 0.0618587129865773, "ops": 16.224086703588306, "total": 1.0478247750143055, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.021762438002042472, "max": 0.047934495989466086, "mean": 0.02353986276331001, "stddev": 0.0037676853755710454, "rounds": 47, "median": 0.022990491008386016, "iqr": 0.00016814799892017618, "q1": 0.022888188490469474, "q3": 0.02305633648938965, "iqr_outliers": 14, "stddev_outliers": 2, "outliers": "2;14", "ld15iqr": 0.022858277981868014, "hd15iqr": 0.02344775400706567, "ops": 42.481131264649186, "total": 1.1063735498755705, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.003203147993190214, "max": 0.004596240003593266, "mean": 0.0032526266796233517, "stddev": 8.66302227469673e-05, "rounds": 315, "median": 0.0032370919943787158, "iqr": 3.3733987947925925e-05, "q1": 0.0032234730024356395, "q3": 0.0032572069903835654, "iqr_outliers": 20, "stddev_outliers": 16, "outliers": "16;20", "ld15iqr": 0.003203147993190214, "hd15iqr": 0.003317063004942611, "ops": 307.44382878756875, "total": 1.0245774040813558, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.04091476698522456, "max": 0.06795560399768874, "mean": 0.04467040974729267, "stddev": 0.005472085541092099, "rounds": 24, "median": 0.042666818510042503, "iqr": 0.0015449655038537458, "q1": 0.04255451099015772, "q3": 0.04409947649401147, "iqr_outliers": 4, "stddev_outliers": 2, "outliers": "2;4", "ld15iqr": 0.04091476698522456, "hd15iqr": 0.04689334399881773, "ops": 22.386183732299582, "total": 1.0720898339350242, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.1254917219921481, "max": 0.138484930008417, "mean": 0.12785466575223836, "stddev": 0.00432784678443429, "rounds": 8, "median": 0.12634695250017103, "iqr": 0.0009969064849428833, "q1": 0.1260432390117785, "q3": 0.12704014549672138, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.1254917219921481, "hd15iqr": 0.138484930008417, "ops": 7.821380581744573, "total": 1.022837326017907, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.05914544500410557, "max": 0.0630826450069435, "mean": 0.06114234388223849, "stddev": 0.0007335816179196855, "rounds": 17, "median": 0.06107395500293933, "iqr": 0.00010328049393137917, "q1": 0.06104083175159758, "q3": 0.06114411224552896, "iqr_outliers": 3, "stddev_outliers": 3, "outliers": "3;3", "ld15iqr": 0.060909405001439154, "hd15iqr": 0.06198310400941409, "ops": 16.355277480464636, "total": 1.0394198459980544, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.27652391101582907, "max": 0.2794690580049064, "mean": 0.27810662740375847, "stddev": 0.0012466067444437691, "rounds": 5, "median": 0.27808113500941545, "iqr": 0.0021687874832423404, "q1": 0.2770868647567113, "q3": 0.27925565223995363, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 0.27652391101582907, "hd15iqr": 0.2794690580049064, "ops": 3.5957431483579434, "total": 1.3905331370187923, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.025730230001499876, "max": 0.03040692998911254, "mean": 0.026967867282786384, "stddev": 0.0008203408550675759, "rounds": 39, "median": 0.02701792898005806, "iqr": 0.00018730299052549526, "q1": 0.026912500012258533, "q3": 0.02709980300278403, "iqr_outliers": 11, "stddev_outliers": 10, "outliers": "10;11", "ld15iqr": 0.026850794005440548, "hd15iqr": 0.02750075500807725, "ops": 37.08116735795051, "total": 1.051746824028669, "iterations": 1 } }, { "group": "GitIgnoreSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_gitignore_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.01868814998306334, "max": 0.01969703601207584, "mean": 0.018772259284302872, "stddev": 0.00014226108042624373, "rounds": 53, "median": 0.01875156900496222, "iqr": 5.53505087736994e-05, "q1": 0.01871952799410792, "q3": 0.01877487850288162, "iqr_outliers": 3, "stddev_outliers": 2, "outliers": "2;3", "ld15iqr": 0.01868814998306334, "hd15iqr": 0.018909834005171433, "ops": 53.27009311213741, "total": 0.9949297420680523, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.061643037013709545, "max": 0.06551563198445365, "mean": 0.06332280093738518, "stddev": 0.0008866396644512391, "rounds": 16, "median": 0.06314822798594832, "iqr": 0.00023117999080568552, "q1": 0.06302506000793073, "q3": 0.06325623999873642, "iqr_outliers": 3, "stddev_outliers": 3, "outliers": "3;3", "ld15iqr": 0.06299673800822347, "hd15iqr": 0.06520748499315232, "ops": 15.792099926041166, "total": 1.013164814998163, "iterations": 1 } }, { "group": "PathSpec.match_files(): 100 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p100.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.47030844099936076, "max": 0.47667527600424364, "mean": 0.47233389340690335, "stddev": 0.0026260468589597284, "rounds": 5, "median": 0.47096811002120376, "iqr": 0.0032064482584246434, "q1": 0.470663855499879, "q3": 0.47387030375830363, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.47030844099936076, "hd15iqr": 0.47667527600424364, "ops": 2.1171463957140295, "total": 2.361669467034517, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.05347704197629355, "max": 0.08448790598777123, "mean": 0.05710093647021016, "stddev": 0.007260841719130678, "rounds": 19, "median": 0.05392292098258622, "iqr": 0.004298812498745974, "q1": 0.05370879950351082, "q3": 0.05800761200225679, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.05347704197629355, "hd15iqr": 0.08448790598777123, "ops": 17.5128476311716, "total": 1.084917792933993, "iterations": 1 } }, { "group": "PathSpec.match_files(): 150 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p150.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 1.034147664991906, "max": 1.0388089290063363, "mean": 1.035944702004781, "stddev": 0.0017669515339980373, "rounds": 5, "median": 1.0353449170070235, "iqr": 0.0019775052642216906, "q1": 1.0349140712496592, "q3": 1.0368915765138809, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", "ld15iqr": 1.034147664991906, "hd15iqr": 1.0388089290063363, "ops": 0.9653024896645351, "total": 5.179723510023905, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.028884208004456013, "max": 0.06038872300996445, "mean": 0.03181386854349902, "stddev": 0.005229019426173259, "rounds": 35, "median": 0.030556743004126474, "iqr": 0.00037699597305618227, "q1": 0.030425682765780948, "q3": 0.03080267873883713, "iqr_outliers": 9, "stddev_outliers": 1, "outliers": "1;9", "ld15iqr": 0.030026633001398295, "hd15iqr": 0.032350316003430635, "ops": 31.432832465272266, "total": 1.1134853990224656, "iterations": 1 } }, { "group": "PathSpec.match_files(): 15 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p15.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.05270765000022948, "max": 0.05372784900828265, "mean": 0.052880838527160356, "stddev": 0.00023226788205862322, "rounds": 19, "median": 0.05283385599614121, "iqr": 9.811202471610159e-05, "q1": 0.0527666672351188, "q3": 0.052864779259834904, "iqr_outliers": 2, "stddev_outliers": 2, "outliers": "2;2", "ld15iqr": 0.05270765000022948, "hd15iqr": 0.05322365101892501, "ops": 18.910441434970547, "total": 1.0047359320160467, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.021922728017671034, "max": 0.05317636701511219, "mean": 0.023936837480109913, "stddev": 0.004534284472515122, "rounds": 46, "median": 0.023199776012916118, "iqr": 0.00021008899784646928, "q1": 0.023076468001818284, "q3": 0.023286556999664754, "iqr_outliers": 16, "stddev_outliers": 1, "outliers": "1;16", "ld15iqr": 0.023010745993815362, "hd15iqr": 0.023621873027877882, "ops": 41.77661317335427, "total": 1.101094524085056, "iterations": 1 } }, { "group": "PathSpec.match_files(): 1 line, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p1.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.0031351209909189492, "max": 0.004144912993069738, "mean": 0.0031792864946997364, "stddev": 6.262859974147339e-05, "rounds": 323, "median": 0.0031703220156487077, "iqr": 2.9596492822747678e-05, "q1": 0.003157261002343148, "q3": 0.0031868574951658957, "iqr_outliers": 11, "stddev_outliers": 10, "outliers": "10;11", "ld15iqr": 0.0031351209909189492, "hd15iqr": 0.00323492698953487, "ops": 314.53598210388515, "total": 1.0269095377880149, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.04143998702056706, "max": 0.0741977000143379, "mean": 0.0451533028317499, "stddev": 0.006491084795991654, "rounds": 24, "median": 0.04323247850697953, "iqr": 0.0007457854953827336, "q1": 0.04313885350711644, "q3": 0.04388463900249917, "iqr_outliers": 6, "stddev_outliers": 1, "outliers": "1;6", "ld15iqr": 0.043043168989242986, "hd15iqr": 0.046636809973279014, "ops": 22.146774151299564, "total": 1.0836792679619975, "iterations": 1 } }, { "group": "PathSpec.match_files(): 25 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p25.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.10550875001354143, "max": 0.10650890300166793, "mean": 0.10582886150514241, "stddev": 0.00031073642537006967, "rounds": 10, "median": 0.10572756751207635, "iqr": 0.00028482000925578177, "q1": 0.10562063800171018, "q3": 0.10590545801096596, "iqr_outliers": 1, "stddev_outliers": 3, "outliers": "3;1", "ld15iqr": 0.10550875001354143, "hd15iqr": 0.10650890300166793, "ops": 9.449218160127407, "total": 1.0582886150514241, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.05953412200324237, "max": 0.09244772398960777, "mean": 0.06436598893967183, "stddev": 0.007698136718223811, "rounds": 17, "median": 0.061470244982047006, "iqr": 0.004288581003493164, "q1": 0.06080614175880328, "q3": 0.06509472276229644, "iqr_outliers": 1, "stddev_outliers": 1, "outliers": "1;1", "ld15iqr": 0.05953412200324237, "hd15iqr": 0.09244772398960777, "ops": 15.536155296817824, "total": 1.0942218119744211, "iterations": 1 } }, { "group": "PathSpec.match_files(): 50 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p50.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.2431023399985861, "max": 0.24536749499384314, "mean": 0.2439051878056489, "stddev": 0.0009293956205117304, "rounds": 5, "median": 0.24344932101666927, "iqr": 0.0012727700086543337, "q1": 0.24327488800190622, "q3": 0.24454765801056055, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", "ld15iqr": 0.2431023399985861, "hd15iqr": 0.24536749499384314, "ops": 4.099953793507789, "total": 1.2195259390282445, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_hs_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_hs_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.025104505009949207, "max": 0.05887012300081551, "mean": 0.027600897525553593, "stddev": 0.0052711286808218094, "rounds": 40, "median": 0.026563214501948096, "iqr": 0.0005281550111249089, "q1": 0.026482860004762188, "q3": 0.027011015015887097, "iqr_outliers": 14, "stddev_outliers": 1, "outliers": "1;14", "ld15iqr": 0.026463023998076096, "hd15iqr": 0.028092884982470423, "ops": 36.230705870132496, "total": 1.1040359010221437, "iterations": 1 } }, { "group": "PathSpec.match_files(): 5 lines, 6.5k files", "name": "bench_sm_v1", "fullname": "bench_pathspec_match_files_p5.py::bench_sm_v1", "params": null, "param": null, "extra_info": {}, "options": { "disable_gc": false, "timer": "perf_counter", "min_rounds": 5, "max_time": 1.0, "min_time": 5e-06, "warmup": 100000 }, "stats": { "min": 0.017490219994215295, "max": 0.018503086001146585, "mean": 0.017595369947749923, "stddev": 0.0001280943249942655, "rounds": 56, "median": 0.01758422849525232, "iqr": 4.2045998270623386e-05, "q1": 0.017559818501467817, "q3": 0.01760186449973844, "iqr_outliers": 3, "stddev_outliers": 1, "outliers": "1;3", "ld15iqr": 0.01750593399628997, "hd15iqr": 0.01767048498732038, "ops": 56.83313297586442, "total": 0.9853407170739956, "iterations": 1 } } ], "datetime": "2025-12-30T01:27:45.860957+00:00", "version": "5.2.3" }././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_150p_to_6500f.py0000644000000000000000000001057615136034031020002 0ustar00""" This module benchmarks :class:`.PathSpec` using ~150 patterns against ~6.5k files. """ from functools import ( partial) import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) from pathspec._backends.simple.pathspec import ( SimplePsBackend) from benchmarks.hyperscan_pathspec_r1 import ( HyperscanPsR1BlockClosureBackend, HyperscanPsR1BlockStateBackend, HyperscanPsR1StreamClosureBackend, HyperscanPsR1StreamStateBackend) GROUP = "PathSpec.match_files(): 150 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_block_closure( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanPsR1BlockClosureBackend, ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_block_state( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanPsR1BlockStateBackend, ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_stream_closure( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanPsR1StreamClosureBackend, ) benchmark(run_match, spec, cpython_files) # WARNING: This segfaults. # @pytest.mark.benchmark(group=GROUP) # def bench_hs_r1_stream_state( # benchmark: BenchmarkFixture, # cpython_files: set[str], # cpython_gi_lines_all: list[str], # ): # spec = PathSpec.from_lines( # 'gitwildmatch', # cpython_gi_lines_all, # backend='hyperscan', # _test_backend_factory=HyperscanPsR1StreamStateBackend, # ) # benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_filtered( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='simple', _test_backend_factory=partial(SimplePsBackend, no_reverse=True) ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_filtered_reversed( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='simple', _test_backend_factory=SimplePsBackend, ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_unfiltered( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='simple', _test_backend_factory=partial(SimplePsBackend, no_filter=True, no_reverse=True) ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_unfiltered_reversed( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='simple', _test_backend_factory=partial(SimplePsBackend, no_filter=True) ) benchmark(run_match, spec, cpython_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_15p_to_400f.py0000644000000000000000000000750515136034031017631 0ustar00""" This module benchmarks :class:`.PathSpec` using ~15 patterns against ~400 files. """ from functools import ( partial) import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) from pathspec._backends.simple.pathspec import ( SimplePsBackend) from benchmarks.hyperscan_pathspec_r1 import ( HyperscanPsR1BlockClosureBackend, HyperscanPsR1BlockStateBackend, HyperscanPsR1StreamClosureBackend) GROUP = "PathSpec.match_files(): 15 lines, 400 files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_block_closure( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanPsR1BlockClosureBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_block_state( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanPsR1BlockStateBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_r1_stream_closure( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='hyperscan', _test_backend_factory=HyperscanPsR1StreamClosureBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_filtered( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='simple', _test_backend_factory=partial(SimplePsBackend, no_reverse=True) ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_filtered_reversed( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='simple', _test_backend_factory=SimplePsBackend, ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_unfiltered( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='simple', _test_backend_factory=partial(SimplePsBackend, no_filter=True, no_reverse=True) ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_unfiltered_reversed( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='simple', _test_backend_factory=partial(SimplePsBackend, no_filter=True) ) benchmark(run_match, spec, flit_files) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, flit_files: set[str], flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitwildmatch', flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_match_file_p1.py0000644000000000000000000000405315136034031020457 0ustar00""" This module benchmarks `PathSpec.match_file()` using 1 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_file(): 1 line, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_1, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_1, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_1, backend='re2', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_1, backend='re2', ) benchmark(run_match, spec, flit_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_1, backend='simple', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_1, backend='simple', ) benchmark(run_match, spec, flit_file_match_start) def run_match(spec: PathSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_match_file_p100.py0000644000000000000000000000766115136034031020627 0ustar00""" This module benchmarks `PathSpec.match_file()` using ~100 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_file(): 100 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_file_match_start) def run_match(spec: PathSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_match_file_p15.py0000644000000000000000000000743715136034031020555 0ustar00""" This module benchmarks `PathSpec.match_file()` using ~15 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_file(): 15 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='re2', ) benchmark(run_match, spec, flit_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_all, backend='simple', ) benchmark(run_match, spec, flit_file_match_start) def run_match(spec: PathSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_match_file_p150.py0000644000000000000000000000766115136034031020634 0ustar00""" This module benchmarks `PathSpec.match_file()` using ~150 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_file(): 150 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_file_match_start) def run_match(spec: PathSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_match_file_p2.py0000644000000000000000000000557715136034031020474 0ustar00""" This module benchmarks `PathSpec.match_file()` using 2 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_file(): 2 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='re2', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='re2', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='re2', ) benchmark(run_match, spec, flit_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='simple', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='simple', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_2: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_2, backend='simple', ) benchmark(run_match, spec, flit_file_match_start) def run_match(spec: PathSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_match_file_p25.py0000644000000000000000000000762715136034031020557 0ustar00""" This module benchmarks `PathSpec.match_file()` using ~25 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_file(): 25 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_file_match_start) def run_match(spec: PathSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.717072 pathspec-1.0.4/benchmarks/bench_pathspec_match_file_p5.py0000644000000000000000000000735415136034031020472 0ustar00""" This module benchmarks `PathSpec.match_file()` using 5 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_file(): 5 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, flit_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='re2', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='re2', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='re2', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='re2', ) benchmark(run_match, spec, flit_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, flit_file_match_end: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='simple', ) benchmark(run_match, spec, flit_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, flit_file_match_middle: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='simple', ) benchmark(run_match, spec, flit_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, flit_file_match_none: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='simple', ) benchmark(run_match, spec, flit_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, flit_file_match_start: str, flit_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', flit_gi_lines_5, backend='simple', ) benchmark(run_match, spec, flit_file_match_start) def run_match(spec: PathSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/bench_pathspec_match_file_p50.py0000644000000000000000000000762715136034031020555 0ustar00""" This module benchmarks `PathSpec.match_file()` using ~50 patterns. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_file(): 50 lines, one file" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_hs_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_file_match_start) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_re2_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_file_match_start) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_end( benchmark: BenchmarkFixture, cpython_file_match_end: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_file_match_end) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_middle( benchmark: BenchmarkFixture, cpython_file_match_middle: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_file_match_middle) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_none( benchmark: BenchmarkFixture, cpython_file_match_none: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_file_match_none) @pytest.mark.benchmark(group=GROUP) def bench_sm_v1_start( benchmark: BenchmarkFixture, cpython_file_match_start: str, cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_file_match_start) def run_match(spec: PathSpec, file: str): _match = spec.match_file(file) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/bench_pathspec_match_files_p1.py0000644000000000000000000000230315136034031020636 0ustar00""" This module benchmarks `PathSpec.match_files()` using 1 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_files(): 1 line, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_1, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_1, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_1: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_1, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/bench_pathspec_match_files_p100.py0000644000000000000000000000232415136034031021001 0ustar00""" This module benchmarks `PathSpec.match_files()` using 100 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_files(): 100 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_100: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_100, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/bench_pathspec_match_files_p15.py0000644000000000000000000000231415136034031020725 0ustar00""" This module benchmarks `PathSpec.match_files()` using 15 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_files(): 15 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_15: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_15, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_15: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_15, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_15: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_15, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/bench_pathspec_match_files_p150.py0000644000000000000000000000232415136034031021006 0ustar00""" This module benchmarks `PathSpec.match_files()` using 150 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_files(): 150 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_all: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_all, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/bench_pathspec_match_files_p25.py0000644000000000000000000000231415136034031020726 0ustar00""" This module benchmarks `PathSpec.match_files()` using 25 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_files(): 25 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_25: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_25, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/bench_pathspec_match_files_p5.py0000644000000000000000000000230415136034031020643 0ustar00""" This module benchmarks `PathSpec.match_files()` using 5 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_files(): 5 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_5, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_5, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_5: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_5, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/bench_pathspec_match_files_p50.py0000644000000000000000000000231415136034031020724 0ustar00""" This module benchmarks `PathSpec.match_files()` using 50 pattern. """ import pytest from pytest_benchmark.fixture import ( BenchmarkFixture) from pathspec import ( PathSpec) GROUP = "PathSpec.match_files(): 50 lines, 6.5k files" # Hyperscan backend. @pytest.mark.benchmark(group=GROUP) def bench_hs_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='hyperscan', ) benchmark(run_match, spec, cpython_files) # Re2 backend. @pytest.mark.benchmark(group=GROUP) def bench_re2_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='re2', ) benchmark(run_match, spec, cpython_files) # Simple backend. @pytest.mark.benchmark(group=GROUP) def bench_sm_v1( benchmark: BenchmarkFixture, cpython_files: set[str], cpython_gi_lines_50: list[str], ): spec = PathSpec.from_lines( 'gitignore', cpython_gi_lines_50, backend='simple', ) benchmark(run_match, spec, cpython_files) def run_match(spec: PathSpec, files: set[str]): for _ in spec.match_files(files): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/conftest.py0000644000000000000000000000764715136034031014577 0ustar00 from pathlib import ( Path) import pytest from pathspec.util import ( iter_tree_files) @pytest.fixture(scope='session') def cpython_dir() -> Path: return Path("~/Downloads/cpython").expanduser() @pytest.fixture(scope='session') def cpython_files(cpython_dir: Path) -> set[str]: return set(iter_tree_files(cpython_dir)) @pytest.fixture(scope='session') def cpython_gi_file(cpython_dir: Path) -> Path: return cpython_dir / ".gitignore" @pytest.fixture(scope='session') def cpython_gi_lines_all(cpython_gi_file: Path) -> list[str]: return [ __line for __line in cpython_gi_file.read_text().splitlines() if __line and not __line.startswith('#') ] @pytest.fixture(scope='session') def cpython_gi_lines_1(cpython_gi_lines_all: list[str]) -> list[str]: return [ "*.cover", ] @pytest.fixture(scope='session') def cpython_gi_lines_5(cpython_gi_lines_all: list[str]) -> list[str]: return [ "*.cover", "*.iml", "Modules/config.c", "CLAUDE.local.md", "Doc/data/python*.abi", ] @pytest.fixture(scope='session') def cpython_gi_lines_15(cpython_gi_lines_all: list[str]) -> list[str]: return ( cpython_gi_lines_all[:5] + cpython_gi_lines_all[82:87] + cpython_gi_lines_all[-5:] ) @pytest.fixture(scope='session') def cpython_gi_lines_25(cpython_gi_lines_all: list[str]) -> list[str]: return ( cpython_gi_lines_all[:10] + cpython_gi_lines_all[82:87] + cpython_gi_lines_all[-10:] ) @pytest.fixture(scope='session') def cpython_gi_lines_50(cpython_gi_lines_all: list[str]) -> list[str]: return ( cpython_gi_lines_all[:20] + cpython_gi_lines_all[80:90] + cpython_gi_lines_all[-20:] ) @pytest.fixture(scope='session') def cpython_gi_lines_100(cpython_gi_lines_all: list[str]) -> list[str]: return ( cpython_gi_lines_all[:40] + cpython_gi_lines_all[80:100] + cpython_gi_lines_all[-40:] ) @pytest.fixture(scope='session') def cpython_file_match_end() -> str: """ File matching pattern near the end of cpython ".gitignore". """ return "CLAUDE.local.md" @pytest.fixture(scope='session') def cpython_file_match_middle() -> str: """ File matching pattern near the middle of cpython ".gitignore". """ return "Modules/config.c" @pytest.fixture(scope='session') def cpython_file_match_none() -> str: """ File not matching any pattern in cpython ".gitignore". """ return "Unladen Swallow" @pytest.fixture(scope='session') def cpython_file_match_start() -> str: """ File matching pattern near the beginning of cpython ".gitignore". """ return "spam.cover" @pytest.fixture(scope='session') def flit_dir() -> Path: return Path("~/Downloads/flit").expanduser() @pytest.fixture(scope='session') def flit_files(flit_dir: Path) -> set[str]: return set(iter_tree_files(flit_dir)) @pytest.fixture(scope='session') def flit_gi_file(flit_dir: Path) -> Path: return flit_dir / ".gitignore" @pytest.fixture(scope='session') def flit_gi_lines_all(flit_gi_file: Path) -> list[str]: return flit_gi_file.read_text().splitlines() @pytest.fixture(scope='session') def flit_gi_lines_1() -> list[str]: return [ "/dist/", ] @pytest.fixture(scope='session') def flit_gi_lines_2() -> list[str]: return [ "/dist/", "*.pyc", ] @pytest.fixture(scope='session') def flit_gi_lines_5() -> list[str]: return [ "/dist/", "__pycache__/", "/htmlcov/", "*.pyc", ".python-version", ] @pytest.fixture(scope='session') def flit_file_match_end() -> str: """ File matching pattern near the end of flit ".gitignore". """ return "green.pyc" @pytest.fixture(scope='session') def flit_file_match_middle() -> str: """ File matching pattern near the middle of flit ".gitignore". """ return "htmlcov/eggs" @pytest.fixture(scope='session') def flit_file_match_none() -> str: """ File not matching any pattern in flit ".gitignore". """ return "Unladen Swallow" @pytest.fixture(scope='session') def flit_file_match_start() -> str: """ File matching pattern near the beginning of flit ".gitignore". """ return "dist/ham" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/gen_md_tables.py0000644000000000000000000000471615136034031015527 0ustar00""" This script generates the markdown tables for the benchmark run. """ import argparse import dataclasses import json import pathlib import re import sys def output_md_tables(in_file: pathlib.Path) -> None: """ Output the markdown tables for the benchmark. """ run_info = json.loads(in_file.read_text()) python_ver = run_info['machine_info']['python_version'] impl_ver = run_info['machine_info']['python_implementation_version'] python = "{python} {version}".format( python=run_info['machine_info']['python_implementation'], version=python_ver, ) if impl_ver != python_ver: python += f" ({impl_ver})" machine = run_info['machine_info']['cpu']['brand_raw'] table_to_times: dict[TableKey, dict[int, dict[str, float]]] = {} for benchmark in run_info['benchmarks']: ops = benchmark['stats']['ops'] method = benchmark['group'].split(':')[0] file_count = re.search('\\b(\\S+) files\\b', benchmark['group']).group(1) line_count = int(re.search('\\b(\\S+) lines?\\b', benchmark['group']).group(1)) backend = re.search('^bench_([a-z0-9]+)_', benchmark['name']).group(1) table_key = TableKey(method=method, file_count=file_count) table_to_times.setdefault(table_key, {}).setdefault(line_count, {})[backend] = ops print() print(f"{python} on {machine}") print("----------") print() for table_key, table_rows in table_to_times.items(): print(f"{table_key.method}: {table_key.file_count} files ") print() print("| Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x |") print("| --: | --: | --: | --: | --: | --: |") for line_count, backend_ops in sorted(table_rows.items()): sm_ops = backend_ops['sm'] hs_ops = backend_ops.get('hs') re2_ops = backend_ops.get('re2') print("| " + " | ".join([ str(line_count), format(sm_ops, '.1f'), format(hs_ops, '.1f') if hs_ops else "-", format(hs_ops / sm_ops, '.2f') if hs_ops else "-", format(re2_ops, '.1f') if re2_ops else "-", format(re2_ops / sm_ops, '.2f') if re2_ops else "-", ]) + " |") print() def main() -> int: """ Run the script. """ # Parse command-line arguments. parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('-i', type=pathlib.Path, required=True, help=( "The saved benchmark run." ), metavar="") args = parser.parse_args() output_md_tables(args.i) return 0 @dataclasses.dataclass(frozen=True) class TableKey(object): file_count: str method: str if __name__ == '__main__': sys.exit(main()) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/gitwildmatch_v1.py0000644000000000000000000002424715136034031016033 0ustar00""" This module implements `.GitWildMatchPattern`, version 1, used in benchmarking, but not included in the released library. """ import re from typing import ( Optional) # Replaced by `X | None` in 3.10. from typing_extensions import ( override) from pathspec.pattern import ( RegexPattern) from pathspec.patterns.gitignore.spec import ( _BYTES_ENCODING, _DIR_MARK) from pathspec._typing import ( AnyStr) # Removed in 3.18. class GitWildMatchV1Pattern(RegexPattern): """ The :class:`GitWildMatchV1Pattern` class represents a compiled Git wildmatch pattern. """ # Keep the dict-less class hierarchy. __slots__ = () @override @classmethod def pattern_to_regex( cls, pattern: AnyStr, ) -> tuple[Optional[AnyStr], Optional[bool]]: if isinstance(pattern, str): return_type = str elif isinstance(pattern, bytes): return_type = bytes pattern = pattern.decode(_BYTES_ENCODING) else: raise TypeError(f"pattern:{pattern!r} is not a unicode or byte string.") original_pattern = pattern if pattern.endswith('\\ '): # EDGE CASE: Spaces can be escaped with backslash. If a pattern that ends # with backslash followed by a space, only strip from left. pattern = pattern.lstrip() else: 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 # Allow a regex override for edge cases that cannot be handled through # normalization. override_regex = None # Split pattern into segments. pattern_segs = pattern.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. # 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 = f'^.+(?P<{_DIR_MARK}>/).*$' 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 Exception(f"Invalid git pattern: {original_pattern!r}") 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(f'[^/]+(?:/.*)?') 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. if is_dir_pattern: output.append(f'(?P<{_DIR_MARK}>/).*') else: output.append(f'/.*') 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('[^/]+') 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. output.append(f'(?:(?P<{_DIR_MARK}>/).*)?') need_slash = True else: # Match segment glob pattern. if need_slash: output.append('/') try: output.append(cls._translate_segment_glob(seg)) except ValueError as e: raise Exception(f"Invalid git pattern: {original_pattern!r}") from e 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. output.append(f'(?:(?P<{_DIR_MARK}>/).*)?') 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: str) -> 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}") return regex @staticmethod def escape(s: AnyStr) -> AnyStr: 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 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 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/hyperscan_gitignore_r1.py0000644000000000000000000001743215136034031017410 0ustar00""" This module defines the hyperscan backends for `.GitIgnoreSpec`, revision 1, used in benchmarking, but not included in the released library. """ from __future__ import annotations from collections.abc import ( Sequence) from typing import ( Any, Optional) # Replaced by `X | None` in 3.10. from typing_extensions import ( override) # Added in 3.12. try: import hyperscan except ModuleNotFoundError: hyperscan = None from pathspec.pattern import ( RegexPattern) from pathspec.patterns.gitignore.spec import ( _DIR_MARK) from benchmarks.hyperscan_pathspec_r1 import ( HyperscanPsR1BaseBackend) class _HyperscanGiR1BaseBackend(HyperscanPsR1BaseBackend): """ The :class:`_HyperscanGiR1BaseBackend` base class uses a hyperscan database in block mode for matching files. """ class _HyperscanGiR1BlockBaseBackend(_HyperscanGiR1BaseBackend): """ The :class:`_HyperscanGiR1BlockBaseBackend` base class uses a hyperscan database in block mode for matching files. """ @override @staticmethod def _make_db() -> hyperscan.Database: return hyperscan.Database(mode=hyperscan.HS_MODE_BLOCK) class HyperscanGiR1BlockClosureBackend(_HyperscanGiR1BlockBaseBackend): """ The :class:`HyperscanGiR1BlockClosureBackend` class uses a hyperscan database in block mode for matching files, and uses a closure to capture state. """ # Prevent accidental usage. _out: tuple[()] @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: out_include: Optional[bool] = None out_index: Optional[int] = -1 out_priority = 0 def on_match( expr_id: int, _from: int, _to: int, _flags: int, _context: Any, ) -> Optional[bool]: nonlocal out_include, out_index, out_priority expr_dat = self._expr_data[expr_id] index = expr_dat.index # Rematch pattern because Hyperscan does not support capture groups. pattern = self._patterns[index] match = pattern.match_file(file) # 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 # WARNING: Hyperscan does not guarantee matches will be produced in order! include = expr_dat.include if ( (include and dir_mark 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 self._db.scan(file.encode('utf8'), match_event_handler=on_match) if out_index == -1: out_index = None return (out_include, out_index) class HyperscanGiR1BlockStateBackend(_HyperscanGiR1BlockBaseBackend): """ The :class:`HyperscanGiR1BlockStateBackend` class uses a hyperscan database in block mode for matching files, and stores state in variables. """ # Change type hint. _out: tuple[Optional[bool], int, int] def __init__(self, patterns: Sequence[RegexPattern]) -> None: super().__init__(patterns) self._out = (None, -1, 0) @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: self._out = (None, -1, 0) self._db.scan( file.encode('utf8'), match_event_handler=self.__on_match, context=file, ) 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]: file: str = context expr_dat = self._expr_data[expr_id] index = expr_dat.index # Rematch pattern because Hyperscan does not support capture groups. pattern = self._patterns[index] match = pattern.match_file(file) # 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 # WARNING: Hyperscan does not guarantee matches will be produced in order! include = expr_dat.include prev_index = self._out[1] prev_priority = self._out[2] if ( (include and dir_mark and index > prev_index) or (priority == prev_priority and index > prev_index) or priority > prev_priority ): self._out = (include, index, priority) class _HyperscanGiR1StreamBaseBackend(_HyperscanGiR1BaseBackend): """ The :class:`_HyperscanGiR1StreamBaseBackend` base class uses a hyperscan database in streaming mode for matching files. """ @override @staticmethod def _make_db() -> hyperscan.Database: return hyperscan.Database(mode=hyperscan.HS_MODE_STREAM) class HyperscanGiR1StreamClosureBackend(_HyperscanGiR1StreamBaseBackend): """ The :class:`HyperscanGiR1StreamClosureBackend` class uses a hyperscan database in streaming mode for matching files, and uses a closure to capture state. """ # Prevent accidental usage. _out: tuple[()] @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: out_include: Optional[bool] = None out_index: Optional[int] = -1 out_priority = 0 def on_match( expr_id: int, _from: int, _to: int, _flags: int, _context: Any, ) -> Optional[bool]: nonlocal out_include, out_index, out_priority expr_dat = self._expr_data[expr_id] index = expr_dat.index # Rematch pattern because Hyperscan does not support capture groups. pattern = self._patterns[index] match = pattern.match_file(file) # 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 # WARNING: Hyperscan does not guarantee matches will be produced in order! include = expr_dat.include if ( (include and dir_mark 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 with self._db.stream(match_event_handler=on_match) as stream: stream.scan(file.encode('utf8')) if out_index == -1: out_index = None return (out_include, out_index) # WARNING: This segfaults. class HyperscanGiR1StreamStateBackend(_HyperscanGiR1StreamBaseBackend): """ The :class:`HyperscanGiR1StreamStateBackend` class uses a hyperscan database in streaming mode for matching files, and stores state in variables. """ # Change type hint. _out: tuple[Optional[bool], int, int] def __init__(self, patterns: Sequence[RegexPattern]) -> None: super().__init__(patterns) self._out = (None, -1, 0) @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: self._out = (None, -1, 0) with self._db.stream(match_event_handler=self.__on_match, context=file) as stream: stream.scan(file.encode('utf8')) 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]: file: str = context expr_dat = self._expr_data[expr_id] index = expr_dat.index # Rematch pattern because Hyperscan does not support capture groups. pattern = self._patterns[index] match = pattern.match_file(file) # 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 # WARNING: Hyperscan does not guarantee matches will be produced in order! include = expr_dat.include prev_index = self._out[1] prev_priority = self._out[2] if ( (include and dir_mark and index > prev_index) or (priority == prev_priority and index > prev_index) or priority > prev_priority ): self._out = (include, index, priority) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/hyperscan_gitignore_r2.py0000644000000000000000000001767015136034031017415 0ustar00""" This module defines the hyperscan backends for `.GitIgnoreSpec`, revision 2, used in benchmarking, but not included in the released library. """ 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. from typing_extensions import ( override) # Added in 3.12. try: import hyperscan except ModuleNotFoundError: hyperscan = None from pathspec._backends.hyperscan._base import ( HS_FLAGS, HyperscanExprDat) from pathspec._backends.hyperscan.gitignore import ( _DIR_MARK_CG, _DIR_MARK_OPT) from pathspec._backends.hyperscan.pathspec import ( HyperscanPsBackend) from pathspec.pattern import ( RegexPattern) from pathspec.patterns.gitignore.spec import ( GitIgnoreSpecPattern, _BYTES_ENCODING) class _HyperscanGiR2BaseBackend(HyperscanPsBackend): """ The :class:`_HyperscanGiR2BaseBackend` base class uses a hyperscan database in block mode for matching files. """ @override @staticmethod def _init_db( db: hyperscan.Database, debug: bool, patterns: list[tuple[int, RegexPattern]], sort_ids: Optional[Callable[[list[int]], None]], ) -> list[HyperscanExprDat]: # NOTICE: This is the current implementation. # Prepare patterns. expr_data: list[HyperscanExprDat] = [] exprs: list[bytes] = [] for pattern_index, pattern in patterns: if pattern.include is None: continue # Encode regex. assert isinstance(pattern, RegexPattern), pattern regex = pattern.regex.pattern use_regexes: list[tuple[Union[str, bytes], bool]] = [] if isinstance(pattern, GitIgnoreSpecPattern): # GitWildMatch uses capture groups for its directory marker but # Hyperscan does not support capture groups. Check for this scenario. 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 isinstance(regex, bytes): regex_bytes = regex else: assert isinstance(regex, str), regex regex_bytes = regex.encode('utf8') expr_data.append(HyperscanExprDat( include=pattern.include, index=pattern_index, is_dir_pattern=is_dir_pattern, )) exprs.append(regex_bytes) # Compile patterns. db.compile( expressions=exprs, ids=list(range(len(exprs))), elements=len(exprs), flags=HS_FLAGS, ) return expr_data @override @staticmethod def _make_db() -> hyperscan.Database: raise NotImplementedError() class _HyperscanGiR2BlockBaseBackend(_HyperscanGiR2BaseBackend): """ The :class:`_HyperscanGiR2BlockBaseBackend` base class uses a hyperscan database in block mode for matching files. """ @override @staticmethod def _make_db() -> hyperscan.Database: return hyperscan.Database(mode=hyperscan.HS_MODE_BLOCK) class HyperscanGiR2BlockClosureBackend(_HyperscanGiR2BlockBaseBackend): """ The :class:`HyperscanGiR2BlockClosureBackend` class uses a hyperscan database in block mode for matching files, and uses a closure to capture state. """ # Prevent accidental usage. _out: tuple[()] @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: out_include: Optional[bool] = None out_index: Optional[int] = -1 out_priority = 0 def on_match( expr_id: int, _from: int, _to: int, _flags: int, _context: Any, ) -> Optional[bool]: nonlocal out_include, out_index, out_priority 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 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 self._db.scan(file.encode('utf8'), match_event_handler=on_match) if out_index == -1: out_index = None return (out_include, out_index) class HyperscanGiR2BlockStateBackend(_HyperscanGiR2BlockBaseBackend): """ The :class:`HyperscanGiR2BlockStateBackend` class uses a hyperscan database in block mode for matching files, and stores state in variables. """ # Change type hint. _out: tuple[Optional[bool], int, int] def __init__(self, patterns: Sequence[RegexPattern]) -> None: super().__init__(patterns) self._out = (None, -1, 0) @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: self._out = (None, -1, 0) self._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]: 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, index, priority) class _HyperscanGiR2StreamBaseBackend(_HyperscanGiR2BaseBackend): """ The :class:`_HyperscanGiR2StreamBaseBackend` base class uses a hyperscan database in streaming mode for matching files. """ @override @staticmethod def _make_db() -> hyperscan.Database: return hyperscan.Database(mode=hyperscan.HS_MODE_STREAM) class HyperscanGiR2StreamClosureBackend(_HyperscanGiR2StreamBaseBackend): """ The :class:`HyperscanGiR2StreamClosureBackend` class uses a hyperscan database in streaming mode for matching files, and uses a closure to capture state. """ # Prevent accidental usage. _out: tuple[()] @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: out_include: Optional[bool] = None out_index: Optional[int] = -1 out_priority = 0 def on_match( expr_id: int, _from: int, _to: int, _flags: int, _context: Any, ) -> Optional[bool]: nonlocal out_include, out_index, out_priority 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 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 with self._db.stream(match_event_handler=on_match) as stream: stream.scan(file.encode('utf8')) if out_index == -1: out_index = None return (out_include, out_index) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/hyperscan_pathspec_r1.py0000644000000000000000000001462115136034031017225 0ustar00""" This module defines the hyperscan backends for `.PathSpec`, revision 1, used in benchmarking, but not included in the released library. """ 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. from typing_extensions import ( override) # Added in 3.12. try: import hyperscan except ModuleNotFoundError: hyperscan = None from pathspec._backends.hyperscan._base import ( HS_FLAGS, HyperscanExprDat) from pathspec._backends.hyperscan.pathspec import ( HyperscanPsBackend) from pathspec.pattern import ( RegexPattern) class HyperscanPsR1BaseBackend(HyperscanPsBackend): """ The :class:`HyperscanPsR1BaseBackend` base class uses a hyperscan database in block mode for matching files. """ @override @staticmethod def _init_db( db: hyperscan.Database, debug: bool, patterns: list[tuple[int, RegexPattern]], sort_ids: Optional[Callable[[list[int]], None]], ) -> list[HyperscanExprDat]: # NOTICE: This is the current implementation. # Prepare patterns. expr_data: list[HyperscanExprDat] = [] exprs: list[bytes] = [] for pattern_index, pattern in patterns: if pattern.include is None: continue # 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') expr_data.append(HyperscanExprDat( include=pattern.include, index=pattern_index, is_dir_pattern=False, )) exprs.append(regex_bytes) # Compile patterns. db.compile( expressions=exprs, ids=list(range(len(exprs))), elements=len(exprs), flags=HS_FLAGS, ) return expr_data @override @staticmethod def _make_db() -> hyperscan.Database: raise NotImplementedError() class _HyperscanPsR1BlockBaseBackend(HyperscanPsR1BaseBackend): """ The :class:`_HyperscanPsR1BlockBaseBackend` base class uses a hyperscan database in block mode for matching files. """ @override @staticmethod def _make_db() -> hyperscan.Database: return hyperscan.Database(mode=hyperscan.HS_MODE_BLOCK) class HyperscanPsR1BlockClosureBackend(_HyperscanPsR1BlockBaseBackend): """ The :class:`HyperscanPsR1BlockClosureBackend` class uses a hyperscan database in block mode for matching files, and uses a closure to capture state. """ # Prevent accidental usage. _out: tuple[()] @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: out_include = False out_index: Optional[int] = -1 def on_match( expr_id: int, _from: int, _to: int, _flags: int, _context: Any, ) -> Optional[bool]: # WARNING: Hyperscan does not guarantee matches will be produced in order! nonlocal out_include, out_index expr_dat = self._expr_data[expr_id] index = expr_dat.index if index > out_index: out_include = expr_dat.include out_index = index self._db.scan(file.encode('utf8'), match_event_handler=on_match) if out_index == -1: out_index = None return (out_include, out_index) class HyperscanPsR1BlockStateBackend(_HyperscanPsR1BlockBaseBackend): """ The :class:`HyperscanPsR1BlockStateBackend` class uses a hyperscan database in block mode for matching files, and stores state in variables. """ def __init__(self, patterns: Sequence[RegexPattern]) -> None: super().__init__(patterns) self._out = (None, -1) @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: self._out = (None, -1) self._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) @override def __on_match( self, expr_id: int, _from: int, _to: int, _flags: int, _context: Any, ) -> Optional[bool]: # WARNING: Hyperscan does not guarantee matches will be produced in order! 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) class _HyperscanPsR1StreamBaseBackend(HyperscanPsR1BaseBackend): """ The :class:`_HyperscanPsR1StreamBaseBackend` base class uses a hyperscan database in streaming mode for matching files. """ @override @staticmethod def _make_db() -> hyperscan.Database: return hyperscan.Database(mode=hyperscan.HS_MODE_STREAM) class HyperscanPsR1StreamClosureBackend(_HyperscanPsR1StreamBaseBackend): """ The :class:`HyperscanPsR1StreamClosureBackend` class uses a hyperscan database in streaming mode for matching files, and uses a closure to capture state. """ @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: out_include = False out_index: Optional[int] = -1 def on_match( expr_id: int, _from: int, _to: int, _flags: int, _context: Any, ) -> Optional[bool]: # WARNING: Hyperscan does not guarantee matches will be produced in order! nonlocal out_include, out_index expr_dat = self._expr_data[expr_id] index = expr_dat.index if index > out_index: out_include = expr_dat.include out_index = index with self._db.stream(match_event_handler=on_match) as stream: stream.scan(file.encode('utf8')) if out_index == -1: out_index = None return (out_include, out_index) # WARNING: This segfaults. class HyperscanPsR1StreamStateBackend(_HyperscanPsR1StreamBaseBackend): """ The :class:`HyperscanPsR1StreamStateBackend` class uses a hyperscan database in streaming mode for matching files, and stores state in variables. """ def __init__(self, patterns: Sequence[RegexPattern]) -> None: super().__init__(patterns) self._out = (None, -1) @override def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: self._out = (None, -1) with self._db.stream(match_event_handler=self.__on_match) as stream: stream.scan(file.encode('utf8')) out_include, out_index = self._out 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]: # WARNING: Hyperscan does not guarantee matches will be produced in order! 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) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks/pytest.ini0000644000000000000000000000015515136034031014414 0ustar00[pytest] python_files = bench_*.py python_functions = bench_* python_classes = *Bench testpaths = benchmarks ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/benchmarks_backends.md0000644000000000000000000001543115136034031014542 0ustar00 These benchmarks compare the native Python `re` module ("simple" backend) with the optional backends. They were run against the CPython source main branch from 2025-12-27 (commit 00e24b80e092e7d36dc189fd260b2a4e730a6e7f), configured and compiled. `PathSpec` and `GitIgnoreSpec` are tested using preloaded `.gitignore` patterns and file paths. File-system speed is not tested. CPython 3.13.11 on Intel(R) Xeon(R) CPU E5-2690 0 @ 2.90GHz ---------- GitIgnoreSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 163.4 | 91.4 | 0.56 | 87.2 | 0.53 | | 5 | 64.4 | 74.3 | 1.15 | 87.9 | 1.36 | | 15 | 24.9 | 67.3 | 2.70 | 84.3 | 3.38 | | 25 | 17.5 | 31.5 | 1.80 | 82.5 | 4.73 | | 50 | 9.1 | 21.1 | 2.31 | 82.4 | 9.01 | | 100 | 4.9 | 32.7 | 6.62 | 77.6 | 15.73 | | 150 | 3.6 | 30.3 | 8.41 | 73.0 | 20.25 | PathSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 165.5 | 102.2 | 0.62 | 87.8 | 0.53 | | 5 | 65.7 | 84.2 | 1.28 | 88.0 | 1.34 | | 15 | 29.2 | 72.4 | 2.48 | 78.5 | 2.69 | | 25 | 16.0 | 32.8 | 2.06 | 79.2 | 4.96 | | 50 | 9.3 | 20.4 | 2.20 | 77.0 | 8.29 | | 100 | 5.2 | 25.3 | 4.83 | 74.4 | 14.21 | | 150 | 3.7 | 28.6 | 7.68 | 72.8 | 19.58 | PyPy 3.11.13 (7.3.20) on Intel(R) Xeon(R) CPU E5-2690 0 @ 2.90GHz ---------- GitIgnoreSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 307.4 | 42.5 | 0.14 | - | - | | 5 | 53.3 | 37.1 | 0.70 | - | - | | 15 | 16.2 | 31.7 | 1.96 | - | - | | 25 | 7.8 | 22.4 | 2.86 | - | - | | 50 | 3.6 | 16.4 | 4.55 | - | - | | 100 | 1.9 | 18.5 | 9.92 | - | - | | 150 | 0.9 | 19.4 | 20.93 | - | - | PathSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 314.5 | 41.8 | 0.13 | - | - | | 5 | 56.8 | 36.2 | 0.64 | - | - | | 15 | 18.9 | 31.4 | 1.66 | - | - | | 25 | 9.4 | 22.1 | 2.34 | - | - | | 50 | 4.1 | 15.5 | 3.79 | - | - | | 100 | 2.1 | 15.8 | 7.46 | - | - | | 150 | 1.0 | 17.5 | 18.14 | - | - | CPython 3.13.11 on 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz ---------- GitIgnoreSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 271.9 | 187.2 | 0.69 | 196.3 | 0.72 | | 5 | 106.0 | 161.1 | 1.52 | 198.8 | 1.88 | | 15 | 46.1 | 146.5 | 3.18 | 193.3 | 4.19 | | 25 | 27.2 | 55.4 | 2.04 | 191.8 | 7.05 | | 50 | 15.7 | 37.8 | 2.40 | 190.7 | 12.12 | | 100 | 8.5 | 66.9 | 7.88 | 190.9 | 22.47 | | 150 | 5.8 | 60.5 | 10.46 | 173.0 | 29.91 | PathSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 276.2 | 204.6 | 0.74 | 194.9 | 0.71 | | 5 | 104.7 | 172.0 | 1.64 | 194.1 | 1.85 | | 15 | 47.2 | 154.6 | 3.27 | 184.5 | 3.91 | | 25 | 29.8 | 56.8 | 1.90 | 183.8 | 6.16 | | 50 | 16.0 | 35.2 | 2.20 | 181.8 | 11.39 | | 100 | 8.7 | 53.4 | 6.15 | 179.7 | 20.69 | | 150 | 6.1 | 56.3 | 9.19 | 176.8 | 28.84 | PyPy 3.11.13 (7.3.20) on 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz ---------- GitIgnoreSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 578.2 | 121.4 | 0.21 | - | - | | 5 | 113.3 | 88.5 | 0.78 | - | - | | 15 | 43.1 | 80.0 | 1.86 | - | - | | 25 | 27.9 | 56.3 | 2.02 | - | - | | 50 | 13.3 | 42.8 | 3.21 | - | - | | 100 | 5.8 | 60.2 | 10.45 | - | - | | 150 | 3.0 | 54.2 | 18.33 | - | - | PathSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 577.3 | 117.5 | 0.20 | - | - | | 5 | 106.6 | 91.7 | 0.86 | - | - | | 15 | 42.4 | 81.3 | 1.92 | - | - | | 25 | 27.7 | 56.9 | 2.05 | - | - | | 50 | 12.7 | 36.0 | 2.84 | - | - | | 100 | 6.1 | 45.1 | 7.36 | - | - | | 150 | 3.1 | 50.3 | 16.20 | - | - | PyPy 3.10.16 (7.3.19) on 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz ---------- GitIgnoreSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 589.9 | 106.6 | 0.18 | - | - | | 5 | 117.2 | 91.9 | 0.78 | - | - | | 15 | 44.5 | 81.3 | 1.83 | - | - | | 25 | 27.4 | 44.0 | 1.60 | - | - | | 50 | 13.0 | 32.1 | 2.48 | - | - | | 100 | 5.5 | 51.8 | 9.36 | - | - | | 150 | 3.1 | 47.1 | 15.28 | - | - | PathSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 581.4 | 106.7 | 0.18 | - | - | | 5 | 135.7 | 90.7 | 0.67 | - | - | | 15 | 54.8 | 81.7 | 1.49 | - | - | | 25 | 31.5 | 43.0 | 1.36 | - | - | | 50 | 12.5 | 30.2 | 2.41 | - | - | | 100 | 6.3 | 42.2 | 6.69 | - | - | | 150 | 3.3 | 44.2 | 13.51 | - | - | CPython 3.13.11 on AMD RYZEN AI MAX+ 395 w/ Radeon 8060S ---------- GitIgnoreSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 426.1 | 261.3 | 0.61 | 291.5 | 0.68 | | 5 | 159.6 | 230.2 | 1.44 | 288.5 | 1.81 | | 15 | 69.4 | 206.5 | 2.97 | 279.3 | 4.02 | | 25 | 45.8 | 76.5 | 1.67 | 275.9 | 6.02 | | 50 | 23.6 | 53.9 | 2.29 | 275.1 | 11.66 | | 100 | 13.4 | 92.4 | 6.91 | 271.3 | 20.29 | | 150 | 8.7 | 84.6 | 9.70 | 246.5 | 28.26 | PathSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 439.1 | 282.4 | 0.64 | 289.7 | 0.66 | | 5 | 163.3 | 244.6 | 1.50 | 291.8 | 1.79 | | 15 | 76.5 | 214.9 | 2.81 | 272.9 | 3.57 | | 25 | 48.0 | 78.3 | 1.63 | 271.8 | 5.66 | | 50 | 23.7 | 50.3 | 2.13 | 268.3 | 11.34 | | 100 | 13.1 | 77.2 | 5.88 | 264.0 | 20.10 | | 150 | 9.8 | 79.9 | 8.14 | 263.7 | 26.87 | PyPy 3.11.13 (7.3.20) on AMD RYZEN AI MAX+ 395 w/ Radeon 8060S ---------- GitIgnoreSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 898.6 | 179.0 | 0.20 | - | - | | 5 | 161.7 | 132.0 | 0.82 | - | - | | 15 | 62.8 | 120.1 | 1.91 | - | - | | 25 | 38.8 | 85.7 | 2.21 | - | - | | 50 | 20.8 | 63.2 | 3.03 | - | - | | 100 | 9.9 | 91.5 | 9.24 | - | - | | 150 | 5.2 | 82.5 | 15.79 | - | - | PathSpec.match_files(): 6.5k files | Patterns | simple
ops | hyperscan
ops |
x | re2
ops |
x | | --: | --: | --: | --: | --: | --: | | 1 | 913.0 | 176.9 | 0.19 | - | - | | 5 | 170.1 | 132.7 | 0.78 | - | - | | 15 | 69.7 | 121.2 | 1.74 | - | - | | 25 | 42.4 | 83.7 | 1.97 | - | - | | 50 | 21.0 | 59.1 | 2.81 | - | - | | 100 | 10.8 | 69.4 | 6.45 | - | - | | 150 | 5.7 | 74.1 | 13.07 | - | - | ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/doc/Makefile0000644000000000000000000000115515136034031012454 0ustar00# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = python -msphinx SPHINXPROJ = PathSpecification SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/doc/requirements.txt0000644000000000000000000000001615136034031014273 0ustar00Sphinx==9.1.0 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/doc/source/api.rst0000644000000000000000000000347115136034031013622 0ustar00:tocdepth: 3 API === pathspec -------- .. automodule:: pathspec pathspec.pathspec ----------------- .. automodule:: pathspec.pathspec .. autoclass:: PathSpec :members: :show-inheritance: :special-members: __init__, __eq__, __len__ .. autoclass:: Self pathspec.gitignore ------------------ .. automodule:: pathspec.gitignore .. autoclass:: GitIgnoreSpec :members: :inherited-members: :show-inheritance: :special-members: __init__, __eq__, __len__ .. autoclass:: Self pathspec.backend ---------------- .. automodule:: pathspec.backend :members: .. autoclass:: _Backend :show-inheritance: pathspec.pattern ---------------- .. automodule:: pathspec.pattern .. autoclass:: Pattern :members: :show-inheritance: :special-members: __init__ .. autoclass:: RegexPattern :members: :show-inheritance: :special-members: __init__, __eq__ .. autoclass:: RegexMatchResult :members: :show-inheritance: :special-members: __init__ pathspec.patterns.gitignore --------------------------- .. automodule:: pathspec.patterns.gitignore pathspec.patterns.gitignore.base -------------------------------- .. automodule:: pathspec.patterns.gitignore.base .. autoclass:: _GitIgnoreBasePattern :show-inheritance: .. autoclass:: GitIgnorePatternError :members: :show-inheritance: pathspec.patterns.gitignore.basic --------------------------------- .. automodule:: pathspec.patterns.gitignore.basic .. autoclass:: GitIgnoreBasicPattern :members: :inherited-members: :show-inheritance: pathspec.patterns.gitignore.spec -------------------------------- .. automodule:: pathspec.patterns.gitignore.spec .. autoclass:: GitIgnoreSpecPattern :members: :inherited-members: :show-inheritance: pathspec.util ------------- .. automodule:: pathspec.util :members: :show-inheritance: ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/doc/source/changes.rst0000644000000000000000000000004015136034031014446 0ustar00 .. include:: ../../CHANGES.rst ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/doc/source/conf.py0000644000000000000000000001224115136034031013611 0ustar00# -*- coding: utf-8 -*- # # Path Specification documentation build configuration file, created by # sphinx-quickstart on Fri Sep 8 09:37:49 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('../../')) from pathspec._meta import __author__, __copyright__ from pathspec._version import __version__ # -- General configuration ------------------------------------------------ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', ] # The autodoc extension doesn't understand the `Self` typehint. # To avoid documentation build errors, autodoc typehints must be disabled. autodoc_typehints = "signature" # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = "PathSpec" copyright = __copyright__.split("©")[1].strip() author = __author__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '.'.join(__version__.split('.', 2)[:2]) # The full version, including alpha/beta/rc tags. release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for the autodoc extension ----------------------------------------- autodoc_member_order = 'bysource' # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinxdoc' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { #'collapse_navigation': True, #'navigation_depth': 4 } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars ''' html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', 'donate.html', ] } ''' # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = '{}doc'.format(project) # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, '{}.tex'.format(project), '{} Documentation'.format(project), author, 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, project, '{} Documentation'.format(project), [author], 1), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/doc/source/index.rst0000644000000000000000000000075415136034031014161 0ustar00.. pathspec documentation master file, created by sphinx-quickstart on Fri Sep 8 09:37:49 2017. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to pathspec's documentation! ============================================== .. toctree:: :caption: Contents: :maxdepth: 2 readme api changes upgrading Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/doc/source/readme.rst0000644000000000000000000000003715136034031014301 0ustar00 .. include:: ../../README.rst ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/doc/source/upgrading.rst0000644000000000000000000000004215136034031015020 0ustar00 .. include:: ../../UPGRADING.rst ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/__init__.py0000644000000000000000000000260015136034031014163 0ustar00""" 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', ] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/__init__.py0000644000000000000000000000020215136034031016070 0ustar00""" WARNING: The *pathspec._backends* package is not part of the public API. Its contents and structure are likely to change. """ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/_utils.py0000644000000000000000000000205215136034031015635 0ustar00""" 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 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/agg.py0000644000000000000000000000471115136034031015100 0ustar00""" 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.") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/hyperscan/__init__.py0000644000000000000000000000000015136034031020060 0ustar00././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/hyperscan/_base.py0000644000000000000000000000326715136034031017414 0ustar00""" 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. """ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/hyperscan/base.py0000644000000000000000000000106315136034031017245 0ustar00""" 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. """ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/hyperscan/gitignore.py0000644000000000000000000001515115136034031020325 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/hyperscan/pathspec.py0000644000000000000000000001454415136034031020152 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/re2/__init__.py0000644000000000000000000000000015136034031016554 0ustar00././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/re2/_base.py0000644000000000000000000000414515136034031016104 0ustar00""" 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. """ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1769486360.718072 pathspec-1.0.4/pathspec/_backends/re2/base.py0000644000000000000000000000071615136034031015745 0ustar00""" 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. """ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/_backends/re2/gitignore.py0000644000000000000000000001174615136034031017027 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/_backends/re2/pathspec.py0000644000000000000000000001140715136034031016641 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/_backends/simple/__init__.py0000644000000000000000000000000015136034031017355 0ustar00././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/_backends/simple/gitignore.py0000644000000000000000000000546115136034031017625 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/_backends/simple/pathspec.py0000644000000000000000000000405715136034031017445 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/_meta.py0000644000000000000000000000557115136034031013523 0ustar00""" This module contains the project meta-data. """ __author__ = "Caleb P. Burns" __copyright__ = "Copyright © 2013-2026 Caleb P. Burns" __credits__ = [ "Hong Minhee ", "Brandon High ", "029xue ", "Michael Huynh ", "Nick Humrich ", "David Fraser ", "Charles Samborski ", "George Hickman ", "Vincent Driessen ", "Adrien Vergé ", "Anders Blomdell ", "Xavier Thomas ", "Wim Jeantine-Glenn ", "Hugo van Kemenade ", "Dan Cecile ", "MrOutis ", "Jon Dufresne ", "Greg Roodt ", "Florin T. ", "Ben Felder ", "Nicholas Hollander ", "KOLANICH ", "Jon Hays ", "Isaac0616 ", "Sebastiaan Zeeff ", "Roel Adriaans ", "Ravi Selker ", "Johan Vergeer ", "danjer ", "Jan-Hein Bührman ", "Wim-Peter Dirks ", "Karthikeyan Singaravelan ", "John Vandenberg ", "John T. Wodder II ", "Tomasz Kłoczko ", "Oren ", "SP Mohanty ", "Richard Si ", "Jakub Kuczys ", "Michał Górny ", "Bartłomiej Żak ", "Matthias ", "Avasam ", "Anıl Karagenç ", "Yannic Schröder ", "axesider ", "TomRuk ", "Oleh Prypin ", "Lumina ", "Kurt McKee ", "Dobatymo ", "Tomoki Nakamaru ", "Sebastien Eskenazi ", "Bar Vered ", "Tzach Shabtay ", "Adam Dangoor ", "Marcel Telka ", "Dmytro Kostochko ", ] __license__ = "MPL 2.0" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/_typing.py0000644000000000000000000000315215136034031014100 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/_version.py0000644000000000000000000000010015136034031014241 0ustar00""" This module defines the version. """ __version__ = "1.0.4" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/backend.py0000644000000000000000000000221115136034031014011 0ustar00""" 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/gitignore.py0000644000000000000000000001221715136034031014420 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/pathspec.py0000644000000000000000000003545715136034031014253 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/pattern.py0000644000000000000000000001504715136034031014112 0ustar00""" 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. """ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/patterns/__init__.py0000644000000000000000000000062415136034031016027 0ustar00""" 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/patterns/gitignore/__init__.py0000644000000000000000000000064615136034031020022 0ustar00""" 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', ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/patterns/gitignore/base.py0000644000000000000000000001112015136034031017162 0ustar00""" 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/patterns/gitignore/basic.py0000644000000000000000000002330415136034031017340 0ustar00""" 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/patterns/gitignore/spec.py0000644000000000000000000002371215136034031017214 0ustar00""" 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/patterns/gitwildmatch.py0000644000000000000000000000266715136034031016761 0ustar00""" .. 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/py.typed0000644000000000000000000000010415136034031013546 0ustar00# Marker file for PEP 561. The pathspec package uses inline types. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pathspec/util.py0000644000000000000000000006023015136034031013404 0ustar00""" 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 # . 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 # . 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 `. """ 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 `. """ 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/prebuild.py0000644000000000000000000000734715136034031012440 0ustar00""" This script generates files required for source and wheel distributions, and legacy installations. """ import argparse import configparser import re import sys from pathlib import ( Path) try: import tomllib # Added in 3.11. except ModuleNotFoundError: import tomli as tomllib CHANGES_0_IN_RST = Path("CHANGES_0.in.rst") CHANGES_1_IN_RST = Path("CHANGES_1.in.rst") CHANGES_RST = Path("CHANGES.rst") PYPROJECT_IN_TOML = Path("pyproject.in.toml") PYPROJECT_TOML = Path("pyproject.toml") README_DIST_RST = Path("README-dist.rst") README_RST = Path("README.rst") SETUP_CFG = Path("setup.cfg") VERSION_PY = Path("pathspec/_version.py") def generate_changes_rst() -> None: """ Generate the "CHANGES.rst" file from "CHANGES_0.in.rst" and "CHANGES_1.in.rst". """ output: list[str] = [] output.append("Change History\n") output.append("=" * 14) output.append("\n\n") print(f"Read: {CHANGES_1_IN_RST}") output.append(CHANGES_1_IN_RST.read_text()) print(f"Read: {CHANGES_0_IN_RST}") output.append("\n") output.append(CHANGES_0_IN_RST.read_text()) print(f"Write: {CHANGES_RST}") CHANGES_RST.write_text("".join(output)) def generate_pyproject_toml() -> None: """ Generate the "pyproject.toml" file from "pyproject.in.toml". """ # Flit will only statically extract the version from a predefined list of # files for an editable install for some odd reason. # - See . print(f"Read: {PYPROJECT_IN_TOML}") output = PYPROJECT_IN_TOML.read_text() print(f"Read: {VERSION_PY}") version_input = VERSION_PY.read_text() version = re.search( '^__version__\\s*=\\s*["\'](.+)["\']', version_input, re.M, ).group(1) # Replace version. output = output.replace("__VERSION__", version) print(f"Write: {PYPROJECT_TOML}") PYPROJECT_TOML.write_text(output) def generate_readme_dist() -> None: """ Generate the "README-dist.rst" file from "README.rst" and "CHANGES_1.in.rst". """ output: list[str] = [] print(f"Read: {README_RST}") output.append(README_RST.read_text()) print(f"Read: {CHANGES_1_IN_RST}") output.append("\n\n") output.append("Change History\n") output.append("=" * 14) output.append("\n\n") output.append(CHANGES_1_IN_RST.read_text()) print(f"Write: {README_DIST_RST}") README_DIST_RST.write_text("".join(output)) def generate_setup_cfg() -> None: """ Generate the "setup.cfg" file from "pyproject.toml" in order to support legacy installation with "setup.py". """ print(f"Read: {PYPROJECT_TOML}") with PYPROJECT_TOML.open('rb') as fh: config = tomllib.load(fh) print(f"Write: {SETUP_CFG}") output = configparser.ConfigParser() output['metadata'] = { 'author': config['project']['authors'][0]['name'], 'author_email': config['project']['authors'][0]['email'], 'classifiers': "\n" + "\n".join(config['project']['classifiers']), 'description': config['project']['description'], 'license': config['project']['license']['text'], 'long_description': f"file: {config['project']['readme']}", 'long_description_content_type': "text/x-rst", 'name': config['project']['name'], 'url': config['project']['urls']['Source Code'], 'version': "attr: pathspec._version.__version__", } output['options'] = { 'packages': "find:", 'python_requires': config['project']['requires-python'], 'setup_requires': "setuptools>=40.8.0", 'test_suite': "tests", } output['options.packages.find'] = { 'include': "pathspec, pathspec.*", } with SETUP_CFG.open('w') as fh: output.write(fh) def main() -> int: """ Run the script. """ # Parse command-line arguments. parser = argparse.ArgumentParser(description=__doc__) parser.parse_args() generate_changes_rst() generate_readme_dist() generate_pyproject_toml() generate_setup_cfg() return 0 if __name__ == '__main__': sys.exit(main()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pyproject.in.toml0000644000000000000000000000331615136034031013571 0ustar00[build-system] build-backend = "flit_core.buildapi" requires = ["flit_core >=3.2,<5"] [project] authors = [ {name = "Caleb P. Burns", email = "cpburnz@gmail.com"}, ] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities", ] description = "Utility library for gitignore style pattern matching of file paths." license = {text = "MPL 2.0"} name = "pathspec" readme = "README-dist.rst" requires-python = ">=3.9" version = "__VERSION__" [project.optional-dependencies] hyperscan = [ "hyperscan >=0.7", ] optional = [ "typing-extensions >=4", ] re2 = [ "google-re2 >=1.1", ] tests = [ "pytest >=9", "typing-extensions >=4.15", ] [project.urls] "Source Code" = "https://github.com/cpburnz/python-pathspec" "Documentation" = "https://python-path-specification.readthedocs.io/en/latest/index.html" "Issue Tracker" = "https://github.com/cpburnz/python-pathspec/issues" [tool.flit.sdist] include = [ "*.cfg", "*.in", "*.ini", "*.md", "*.py", "*.rst", "*.toml", "LICENSE", "benchmarks/", "doc/", "tests/", ] exclude = [ "doc/build/", ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/pyproject.toml0000644000000000000000000000331015136034031013156 0ustar00[build-system] build-backend = "flit_core.buildapi" requires = ["flit_core >=3.2,<5"] [project] authors = [ {name = "Caleb P. Burns", email = "cpburnz@gmail.com"}, ] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities", ] description = "Utility library for gitignore style pattern matching of file paths." license = {text = "MPL 2.0"} name = "pathspec" readme = "README-dist.rst" requires-python = ">=3.9" version = "1.0.4" [project.optional-dependencies] hyperscan = [ "hyperscan >=0.7", ] optional = [ "typing-extensions >=4", ] re2 = [ "google-re2 >=1.1", ] tests = [ "pytest >=9", "typing-extensions >=4.15", ] [project.urls] "Source Code" = "https://github.com/cpburnz/python-pathspec" "Documentation" = "https://python-path-specification.readthedocs.io/en/latest/index.html" "Issue Tracker" = "https://github.com/cpburnz/python-pathspec/issues" [tool.flit.sdist] include = [ "*.cfg", "*.in", "*.ini", "*.md", "*.py", "*.rst", "*.toml", "LICENSE", "benchmarks/", "doc/", "tests/", ] exclude = [ "doc/build/", ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/setup.cfg0000644000000000000000000000231615136034031012070 0ustar00[metadata] author = Caleb P. Burns author_email = cpburnz@gmail.com classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 Programming Language :: Python :: 3.14 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries :: Python Modules Topic :: Utilities description = Utility library for gitignore style pattern matching of file paths. license = MPL 2.0 long_description = file: README-dist.rst long_description_content_type = text/x-rst name = pathspec url = https://github.com/cpburnz/python-pathspec version = attr: pathspec._version.__version__ [options] packages = find: python_requires = >=3.9 setup_requires = setuptools>=40.8.0 test_suite = tests [options.packages.find] include = pathspec, pathspec.* ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/setup.py0000644000000000000000000000025215136034031011756 0ustar00""" This setup script only exists to support legacy installations where pip is cumbersome be used such as for system packages. """ from setuptools import setup setup() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/testpypi_prepublish.py0000644000000000000000000000272715136034031014745 0ustar00""" This script mangles the version in "pyproject.toml" to work around deficiencies with TestPyPI. """ import argparse import copy import re import subprocess import sys from pathlib import ( Path) from packaging.version import ( Version) PYPROJECT_TOML = Path("pyproject.toml") VERSION_PY = Path("pathspec/_version.py") def update_pyproject_toml() -> None: """ Update "pyproject.toml" by mangling its version. """ print("Get last tag.") tag = subprocess.check_output([ 'git', 'rev-list', '--tags', '--max-count=1', ], text=True).strip() print("Get commit count.") count = int(subprocess.check_output([ 'git', 'rev-list', f'{tag}..HEAD', '--count', ], text=True).strip()) print(f"Read: {VERSION_PY}") version_input = VERSION_PY.read_text() version = Version(re.search( '^__version__\\s*=\\s*["\'](.+)["\']', version_input, re.M, ).group(1)) if not version.is_postrelease: version = copy.replace(version, post=1) version = copy.replace(version, dev=count) print(f"Read: {PYPROJECT_TOML}") output = PYPROJECT_TOML.read_text() # Mangle version. output = re.sub( '^version\\s*=\\s*".+"', f'version = "{version}"', output, count=1, flags=re.M, ) print(f"Write: {PYPROJECT_TOML}") PYPROJECT_TOML.write_text(output) def main() -> int: """ Run the script. """ # Parse command-line arguments. parser = argparse.ArgumentParser(description=__doc__) parser.parse_args() update_pyproject_toml() return 0 if __name__ == '__main__': sys.exit(main()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/tests/__init__.py0000644000000000000000000000000015136034031013506 0ustar00././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7190719 pathspec-1.0.4/tests/test_01_util.py0000644000000000000000000003317115136034031014302 0ustar00""" This script tests utility functions. """ import errno import os import os.path import shutil import tempfile import unittest from collections.abc import ( Iterable) from functools import ( partial) from pathlib import ( Path, PurePath) from typing import ( ClassVar, Optional) # Replaced by `X | None` in 3.10. from pathspec.patterns.gitignore.basic import ( GitIgnoreBasicPattern) from pathspec.util import ( RecursionError, check_match_file, iter_tree_entries, iter_tree_files, match_file, normalize_file) from tests.util import ( get_paths_from_entries, make_dirs, make_files, make_links, mkfile, ospath) class CheckMatchFileTest(unittest.TestCase): """ The :class:`CheckMatchFileTest` class tests the :meth:`.check_match_file` function. """ def test_01_single_1_include(self): """ Test checking a single file that is included. """ patterns = list(enumerate(map(GitIgnoreBasicPattern, [ "*.txt", "!test/", ]))) include_index = check_match_file(patterns, "include.txt") self.assertEqual(include_index, (True, 0)) def test_01_single_2_exclude(self): """ Test checking a single file that is excluded. """ patterns = list(enumerate(map(GitIgnoreBasicPattern, [ "*.txt", "!test/", ]))) include_index = check_match_file(patterns, "test/exclude.txt") self.assertEqual(include_index, (False, 1)) def test_01_single_3_unmatch(self): """ Test checking a single file that is ignored. """ patterns = list(enumerate(map(GitIgnoreBasicPattern, [ "*.txt", "!test/", ]))) include_index = check_match_file(patterns, "unmatch.bin") self.assertEqual(include_index, (None, None)) def test_02_many(self): """ Test matching files individually. """ patterns = list(enumerate(map(GitIgnoreBasicPattern, [ '*.txt', '!b.txt', ]))) files = { 'X/a.txt', 'X/b.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/b.txt', 'Y/Z/c.txt', } includes = { __file for __file in files if check_match_file(patterns, __file)[0] } self.assertEqual(includes, { 'X/a.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/Z/c.txt', }) class IterTreeTest(unittest.TestCase): """ The :class:`IterTreeTest` class tests :meth:`.iter_tree_entries` and :meth:`.iter_tree_files` functions. """ no_symlink: ClassVar[bool] """ *no_symlink* (:class:`bool`) is whether symlinks are not supported. """ def make_dirs(self, dirs: Iterable[str]) -> None: """ Create the specified directories. """ make_dirs(self.temp_dir, dirs) def make_files(self, files: Iterable[str]) -> None: """ Create the specified files. """ make_files(self.temp_dir, files) def make_links(self, links: Iterable[tuple[str, str]]) -> None: """ Create the specified links. """ make_links(self.temp_dir, links) def require_symlink(self) -> None: """ Skips the test if `os.symlink` is not supported. """ if self.no_symlink: raise unittest.SkipTest("`os.symlink` is not supported.") def setUp(self) -> None: """ Called before each test. """ self.temp_dir = Path(tempfile.mkdtemp()) def tearDown(self) -> None: """ Called after each test. """ shutil.rmtree(self.temp_dir) def test_01_files_1_entries(self): """ Tests to make sure all files are found. """ self.make_dirs([ 'Empty', 'Dir', 'Dir/Inner', ]) self.make_files([ 'a', 'b', 'Dir/c', 'Dir/d', 'Dir/Inner/e', 'Dir/Inner/f', ]) results = get_paths_from_entries(iter_tree_entries(self.temp_dir)) self.assertEqual(results, set(map(ospath, [ 'a', 'b', 'Dir', 'Dir/Inner', 'Dir/Inner/e', 'Dir/Inner/f', 'Dir/c', 'Dir/d', 'Empty', ]))) def test_01_files_2_files(self): """ Tests to make sure all files are found. """ self.make_dirs([ 'Empty', 'Dir', 'Dir/Inner', ]) self.make_files([ 'a', 'b', 'Dir/c', 'Dir/d', 'Dir/Inner/e', 'Dir/Inner/f', ]) results = set(iter_tree_files(self.temp_dir)) self.assertEqual(results, set(map(ospath, [ 'a', 'b', 'Dir/c', 'Dir/d', 'Dir/Inner/e', 'Dir/Inner/f', ]))) def test_02_link_1_check_symlink(self): """ Tests whether links can be created. """ # NOTE: Windows Vista and greater supports `os.symlink` for Python 3.2+. no_symlink: Optional[bool] = None try: file = self.temp_dir / 'file' link = self.temp_dir / 'link' mkfile(file) try: os.symlink(file, link) except (AttributeError, NotImplementedError, OSError): no_symlink = True else: no_symlink = False finally: self.__class__.no_symlink = no_symlink def test_02_link_2_links_1_entries(self): """ Tests to make sure links to directories and files work. """ self.require_symlink() self.make_dirs([ 'Dir', ]) self.make_files([ 'a', 'b', 'Dir/c', 'Dir/d', ]) self.make_links([ ('ax', 'a'), ('bx', 'b'), ('Dir/cx', 'Dir/c'), ('Dir/dx', 'Dir/d'), ('DirX', 'Dir'), ]) results = get_paths_from_entries(iter_tree_entries(self.temp_dir)) self.assertEqual(results, set(map(ospath, [ 'a', 'ax', 'b', 'bx', 'Dir', 'Dir/c', 'Dir/cx', 'Dir/d', 'Dir/dx', 'DirX', 'DirX/c', 'DirX/cx', 'DirX/d', 'DirX/dx', ]))) def test_02_link_2_links_2_files(self): """ Tests to make sure links to directories and files work. """ self.require_symlink() self.make_dirs([ 'Dir', ]) self.make_files([ 'a', 'b', 'Dir/c', 'Dir/d', ]) self.make_links([ ('ax', 'a'), ('bx', 'b'), ('Dir/cx', 'Dir/c'), ('Dir/dx', 'Dir/d'), ('DirX', 'Dir'), ]) results = set(iter_tree_files(self.temp_dir)) self.assertEqual(results, set(map(ospath, [ 'a', 'ax', 'b', 'bx', 'Dir/c', 'Dir/cx', 'Dir/d', 'Dir/dx', 'DirX/c', 'DirX/cx', 'DirX/d', 'DirX/dx', ]))) def test_02_link_3_sideways_links_1_entries(self): """ Tests to make sure the same directory can be encountered multiple times via links. """ self.require_symlink() self.make_dirs([ 'Dir', 'Dir/Target', ]) self.make_files([ 'Dir/Target/file', ]) self.make_links([ ('Ax', 'Dir'), ('Bx', 'Dir'), ('Cx', 'Dir/Target'), ('Dx', 'Dir/Target'), ('Dir/Ex', 'Dir/Target'), ('Dir/Fx', 'Dir/Target'), ]) results = get_paths_from_entries(iter_tree_entries(self.temp_dir)) self.assertEqual(results, set(map(ospath, [ 'Ax', 'Ax/Ex', 'Ax/Ex/file', 'Ax/Fx', 'Ax/Fx/file', 'Ax/Target', 'Ax/Target/file', 'Bx', 'Bx/Ex', 'Bx/Ex/file', 'Bx/Fx', 'Bx/Fx/file', 'Bx/Target', 'Bx/Target/file', 'Cx', 'Cx/file', 'Dx', 'Dx/file', 'Dir', 'Dir/Ex', 'Dir/Ex/file', 'Dir/Fx', 'Dir/Fx/file', 'Dir/Target', 'Dir/Target/file', ]))) def test_02_link_3_sideways_links_2_files(self): """ Tests to make sure the same directory can be encountered multiple times via links. """ self.require_symlink() self.make_dirs([ 'Dir', 'Dir/Target', ]) self.make_files([ 'Dir/Target/file', ]) self.make_links([ ('Ax', 'Dir'), ('Bx', 'Dir'), ('Cx', 'Dir/Target'), ('Dx', 'Dir/Target'), ('Dir/Ex', 'Dir/Target'), ('Dir/Fx', 'Dir/Target'), ]) results = set(iter_tree_files(self.temp_dir)) self.assertEqual(results, set(map(ospath, [ 'Ax/Ex/file', 'Ax/Fx/file', 'Ax/Target/file', 'Bx/Ex/file', 'Bx/Fx/file', 'Bx/Target/file', 'Cx/file', 'Dx/file', 'Dir/Ex/file', 'Dir/Fx/file', 'Dir/Target/file', ]))) def test_02_link_4_recursive_links_1_entries(self): """ Tests detection of recursive links. """ self.require_symlink() self.make_dirs([ 'Dir', ]) self.make_files([ 'Dir/file', ]) self.make_links([ ('Dir/Self', 'Dir'), ]) with self.assertRaises(RecursionError) as context: set(iter_tree_entries(self.temp_dir)) self.assertEqual(context.exception.first_path, 'Dir') self.assertEqual(context.exception.second_path, ospath('Dir/Self')) def test_02_link_4_recursive_links_2_files(self): """ Tests detection of recursive links. """ self.require_symlink() self.make_dirs([ 'Dir', ]) self.make_files([ 'Dir/file', ]) self.make_links([ ('Dir/Self', 'Dir'), ]) with self.assertRaises(RecursionError) as context: set(iter_tree_files(self.temp_dir)) self.assertEqual(context.exception.first_path, 'Dir') self.assertEqual(context.exception.second_path, ospath('Dir/Self')) def test_02_link_5_recursive_circular_links_1_entries(self): """ Tests detection of recursion through circular links. """ self.require_symlink() self.make_dirs([ 'A', 'B', 'C', ]) self.make_files([ 'A/d', 'B/e', 'C/f', ]) self.make_links([ ('A/Bx', 'B'), ('B/Cx', 'C'), ('C/Ax', 'A'), ]) with self.assertRaises(RecursionError) as context: set(iter_tree_entries(self.temp_dir)) self.assertIn(context.exception.first_path, ('A', 'B', 'C')) self.assertEqual(context.exception.second_path, { 'A': ospath('A/Bx/Cx/Ax'), 'B': ospath('B/Cx/Ax/Bx'), 'C': ospath('C/Ax/Bx/Cx'), }[context.exception.first_path]) def test_02_link_5_recursive_circular_links_2_files(self): """ Tests detection of recursion through circular links. """ self.require_symlink() self.make_dirs([ 'A', 'B', 'C', ]) self.make_files([ 'A/d', 'B/e', 'C/f', ]) self.make_links([ ('A/Bx', 'B'), ('B/Cx', 'C'), ('C/Ax', 'A'), ]) with self.assertRaises(RecursionError) as context: set(iter_tree_files(self.temp_dir)) self.assertIn(context.exception.first_path, ('A', 'B', 'C')) self.assertEqual(context.exception.second_path, { 'A': ospath('A/Bx/Cx/Ax'), 'B': ospath('B/Cx/Ax/Bx'), 'C': ospath('C/Ax/Bx/Cx'), }[context.exception.first_path]) def test_02_link_6_detect_broken_links_1_entries(self): """ Tests that broken links are detected. """ def reraise(e): raise e self.require_symlink() self.make_links([ ('A', 'DOES_NOT_EXIST'), ]) with self.assertRaises(OSError) as context: set(iter_tree_entries(self.temp_dir, on_error=reraise)) self.assertEqual(context.exception.errno, errno.ENOENT) def test_02_link_6_detect_broken_links_2_files(self): """ Tests that broken links are detected. """ def reraise(e): raise e self.require_symlink() self.make_links([ ('A', 'DOES_NOT_EXIST'), ]) # `iter_tree_files` does not stat the link. set(iter_tree_files(self.temp_dir, on_error=reraise)) def test_02_link_7_ignore_broken_links_1_entries(self): """ Tests that broken links are ignored. """ self.require_symlink() self.make_links([ ('A', 'DOES_NOT_EXIST'), ]) results = get_paths_from_entries(iter_tree_entries(self.temp_dir)) self.assertEqual(results, set()) def test_02_link_7_ignore_broken_links_2_files(self): """ Tests that broken links are ignored. """ self.require_symlink() self.make_links([ ('A', 'DOES_NOT_EXIST'), ]) results = set(iter_tree_files(self.temp_dir)) self.assertEqual(results, set()) def test_02_link_8_no_follow_links_1_entries(self): """ Tests to make sure directory links can be ignored. """ self.require_symlink() self.make_dirs([ 'Dir', ]) self.make_files([ 'A', 'B', 'Dir/C', 'Dir/D', ]) self.make_links([ ('Ax', 'A'), ('Bx', 'B'), ('Dir/Cx', 'Dir/C'), ('Dir/Dx', 'Dir/D'), ('DirX', 'Dir'), ]) results = get_paths_from_entries(iter_tree_entries(self.temp_dir, follow_links=False)) self.assertEqual(results, set(map(ospath, [ 'A', 'Ax', 'B', 'Bx', 'Dir', 'Dir/C', 'Dir/Cx', 'Dir/D', 'Dir/Dx', 'DirX', ]))) def test_02_link_8_no_follow_links_2_files(self): """ Tests to make sure directory links can be ignored. """ self.require_symlink() self.make_dirs([ 'Dir', ]) self.make_files([ 'A', 'B', 'Dir/C', 'Dir/D', ]) self.make_links([ ('Ax', 'A'), ('Bx', 'B'), ('Dir/Cx', 'Dir/C'), ('Dir/Dx', 'Dir/D'), ('DirX', 'Dir'), ]) results = set(iter_tree_files(self.temp_dir, follow_links=False)) self.assertEqual(results, set(map(ospath, [ 'A', 'Ax', 'B', 'Bx', 'Dir/C', 'Dir/Cx', 'Dir/D', 'Dir/Dx', 'DirX', ]))) class MatchFileTest(unittest.TestCase): """ The :class:`MatchFileTest` class tests the :meth:`.match_file` function. """ def test_01_single_1_include(self): """ Test checking a single file that is included. """ patterns = list(map(GitIgnoreBasicPattern, [ "*.txt", "!test/", ])) include = match_file(patterns, "include.txt") self.assertIs(include, True) def test_01_single_2_exclude(self): """ Test checking a single file that is excluded. """ patterns = list(map(GitIgnoreBasicPattern, [ "*.txt", "!test/", ])) include = match_file(patterns, "test/exclude.txt") self.assertIs(include, False) def test_01_single_3_unmatch(self): """ Test checking a single file that is ignored. """ patterns = list(map(GitIgnoreBasicPattern, [ "*.txt", "!test/", ])) include = match_file(patterns, "unmatch.bin") self.assertIs(include, False) def test_02_many(self): """ Test matching files individually. """ patterns = list(map(GitIgnoreBasicPattern, [ '*.txt', '!b.txt', ])) files = { 'X/a.txt', 'X/b.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/b.txt', 'Y/Z/c.txt', } includes = set(filter(partial(match_file, patterns), files)) self.assertEqual(includes, { 'X/a.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/Z/c.txt', }) class NormalizeFileTest(unittest.TestCase): """ The :class:`NormalizeFileTest` class tests the :meth:`.normalize_file` function. """ def test_01_purepath(self): """ Tests normalizing a :class:`PurePath` as argument. """ first_spec = normalize_file(PurePath('a.txt')) second_spec = normalize_file('a.txt') self.assertEqual(first_spec, second_spec) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7200718 pathspec-1.0.4/tests/test_02_gitignore_basic.py0000644000000000000000000006017415136034031016461 0ustar00""" This script tests :class:`.GitIgnoreBasicPattern`. """ import re import unittest from pathspec.patterns.gitignore.base import ( GitIgnorePatternError, _BYTES_ENCODING) from pathspec.patterns.gitignore.basic import ( GitIgnoreBasicPattern) from pathspec.util import ( lookup_pattern) _DIR_OPT = '(?:/|$)' """ Optional directory ending. """ class GitIgnoreBasicPatternTest(unittest.TestCase): """ The :class:`GitIgnoreBasicPatternTest` class tests the :class:`GitIgnoreBasicPattern` implementation. """ def _check_invalid_pattern(self, git_ignore_pattern: str) -> None: expected_message_pattern = re.escape(git_ignore_pattern) with self.assertRaisesRegex(GitIgnorePatternError, expected_message_pattern): GitIgnoreBasicPattern(git_ignore_pattern) def test_00_empty(self): """ Tests an empty pattern. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('') self.assertIsNone(include) self.assertIsNone(regex) def test_01_absolute(self): """ Tests an absolute path pattern. This should match: an/absolute/file/path an/absolute/file/path/foo This should NOT match: foo/an/absolute/file/path """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('/an/absolute/file/path') self.assertTrue(include) self.assertEqual(regex, f'^an/absolute/file/path{_DIR_OPT}') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'an/absolute/file/path', 'an/absolute/file/path/foo', 'foo/an/absolute/file/path', ])) self.assertEqual(results, { 'an/absolute/file/path', 'an/absolute/file/path/foo', }) def test_01_absolute_ignore(self): """ Tests an ignore absolute path pattern. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('!/foo/build') self.assertIs(include, False) self.assertEqual(regex, f'^foo/build{_DIR_OPT}') # NOTE: The pattern match is backwards because the pattern itself # does not consider the include attribute. pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'build/file.py', 'foo/build/file.py', ])) self.assertEqual(results, { 'foo/build/file.py', }) def test_01_absolute_root_1(self): """ Tests a single root absolute path pattern. This should match any file, unlike git check-ignore. """ # NOTICE: The results from GitIgnoreBasicPattern will differ from # GitIgnoreSpecPattern. regex, include = GitIgnoreBasicPattern.pattern_to_regex('/') self.assertTrue(include) self.assertEqual(regex, '.') def test_01_absolute_root_2_double_asterisk(self): """ Tests the root path patterns: /** /**/** """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('/**') self.assertTrue(include) self.assertIsNotNone(regex) equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('/**/**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('/') self.assertTrue(include) self.assertEqual(equiv_regex, regex) equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) def test_01_relative(self): """ Tests a relative path pattern. This should match: spam spam/ foo/spam spam/foo foo/spam/bar """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('spam') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?spam{_DIR_OPT}') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'spam', 'spam/', 'foo/spam', 'spam/foo', 'foo/spam/bar', ])) self.assertEqual(results, { 'spam', 'spam/', 'foo/spam', 'spam/foo', 'foo/spam/bar', }) def test_01_relative_nested(self): """ Tests a relative nested path pattern. This should match: foo/spam foo/spam/bar This should **not** match (according to git check-ignore (v2.4.1)): bar/foo/spam """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('foo/spam') self.assertTrue(include) self.assertEqual(regex, f'^foo/spam{_DIR_OPT}') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'foo/spam', 'foo/spam/bar', 'bar/foo/spam', ])) self.assertEqual(results, { 'foo/spam', 'foo/spam/bar', }) def test_02_comment(self): """ Tests a comment pattern. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('# Cork soakers.') self.assertIsNone(include) self.assertIsNone(regex) def test_02_ignore(self): """ Tests an exclude pattern. This should NOT match (according to git check-ignore (v2.4.1)): temp/foo """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('!temp') self.assertIs(include, False) self.assertEqual(regex, f'^(?:.+/)?temp{_DIR_OPT}') # NOTE: The pattern match is backwards because the pattern itself # does not consider the include attribute. pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'temp/foo', ])) self.assertEqual(results, { 'temp/foo', }) def test_03_child_double_asterisk(self): """ Tests a directory name with a double-asterisk child directory. This should match: spam/bar This should **not** match (according to git check-ignore (v2.4.1)): foo/spam/bar """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('spam/**') self.assertTrue(include) self.assertEqual(regex, '^spam/') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'spam/bar', 'foo/spam/bar', ])) self.assertEqual(results, {'spam/bar'}) def test_03_inner_double_asterisk(self): """ Tests a path with an inner double-asterisk directory. This should match: left/right left/bar/right left/foo/bar/right left/bar/right/foo This should **not** match (according to git check-ignore (v2.4.1)): foo/left/bar/right """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('left/**/right') self.assertTrue(include) self.assertEqual(regex, f'^left(?:/.+)?/right{_DIR_OPT}') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'left/right', 'left/bar/right', 'left/foo/bar/right', 'left/bar/right/foo', 'foo/left/bar/right', ])) self.assertEqual(results, { 'left/right', 'left/bar/right', 'left/foo/bar/right', 'left/bar/right/foo', }) def test_03_only_double_asterisk(self): """ Tests a double-asterisk pattern which matches everything. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('**') self.assertTrue(include) self.assertEqual(regex, '.') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'x', 'y.py', 'A/x', 'A/y.py', 'A/B/x', 'A/B/y.py', 'A/B/C/x', 'A/B/C/y.py', ])) self.assertEqual(results, { 'x', 'y.py', 'A/x', 'A/y.py', 'A/B/x', 'A/B/y.py', 'A/B/C/x', 'A/B/C/y.py', }) def test_03_parent_double_asterisk(self): """ Tests a file name with a double-asterisk parent directory. This should match: spam foo/spam foo/spam/bar """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/spam') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?spam{_DIR_OPT}') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'spam', 'foo/spam', 'foo/spam/bar', ])) self.assertEqual(results, { 'spam', 'foo/spam', 'foo/spam/bar', }) def test_03_duplicate_leading_double_asterisk_edge_case(self): """ Regression test for duplicate leading **/ bug. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('**') self.assertTrue(include) self.assertEqual(regex, '.') equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/**/**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/api') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?api{_DIR_OPT}') equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/**/api') self.assertTrue(include) self.assertEqual(equiv_regex, regex) regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/api/') self.assertTrue(include) self.assertEqual(regex, '^(?:.+/)?api/') equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/**/api/') self.assertTrue(include) self.assertEqual(equiv_regex, regex) regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/api/**') self.assertTrue(include) self.assertEqual(regex, '^(?:.+/)?api/') equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/**/api/**/**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) def test_03_double_asterisk_trailing_slash_edge_case(self): """ Tests the edge-case **/ pattern. This should match everything except individual files in the root directory. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/') self.assertTrue(include) self.assertEqual(regex, '/') equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/**/') self.assertTrue(include) self.assertEqual(equiv_regex, regex) def test_04_infix_wildcard(self): """ Tests a pattern with an infix wildcard. This should match: foo--bar foo-hello-bar a/foo-hello-bar foo-hello-bar/b a/foo-hello-bar/b """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('foo-*-bar') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?foo\\-[^/]*\\-bar{_DIR_OPT}') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'foo--bar', 'foo-hello-bar', 'a/foo-hello-bar', 'foo-hello-bar/b', 'a/foo-hello-bar/b', ])) self.assertEqual(results, { 'foo--bar', 'foo-hello-bar', 'a/foo-hello-bar', 'foo-hello-bar/b', 'a/foo-hello-bar/b', }) def test_04_postfix_wildcard(self): """ Tests a pattern with a postfix wildcard. This should match: ~temp- ~temp-foo ~temp-foo/bar foo/~temp-bar foo/~temp-bar/baz """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('~temp-*') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?\\~temp\\-[^/]*{_DIR_OPT}') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ '~temp-', '~temp-foo', '~temp-foo/bar', 'foo/~temp-bar', 'foo/~temp-bar/baz', ])) self.assertEqual(results, { '~temp-', '~temp-foo', '~temp-foo/bar', 'foo/~temp-bar', 'foo/~temp-bar/baz', }) def test_04_prefix_wildcard(self): """ Tests a pattern with a prefix wildcard. This should match: bar.py bar.py/ foo/bar.py foo/bar.py/baz """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('*.py') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?[^/]*\\.py{_DIR_OPT}') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'bar.py', 'bar.py/', 'foo/bar.py', 'foo/bar.py/baz', ])) self.assertEqual(results, { 'bar.py', 'bar.py/', 'foo/bar.py', 'foo/bar.py/baz', }) def test_05_directory(self): """ Tests a directory pattern. This should match: dir/ foo/dir/ foo/dir/bar This should **not** match: dir """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('dir/') self.assertTrue(include) self.assertEqual(regex, '^(?:.+/)?dir/') pattern = GitIgnoreBasicPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'dir/', 'foo/dir/', 'foo/dir/bar', 'dir', ])) self.assertEqual(results, { 'dir/', 'foo/dir/', 'foo/dir/bar', }) def test_06_registered(self): """ Tests that the pattern is registered. """ self.assertIs(lookup_pattern('gitignore'), GitIgnoreBasicPattern) def test_07_encode_bytes(self): """ Test encoding bytes. """ encoded = "".join(map(chr, range(0, 256))).encode(_BYTES_ENCODING) expected = ( b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10' b'\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f' b' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\' b']^_`abcdefghijklmnopqrstuvwxyz{|}~' b'\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d' b'\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c' b'\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab' b'\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba' b'\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9' b'\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8' b'\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7' b'\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6' b'\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff' ) self.assertEqual(encoded, expected) def test_07_decode_bytes(self): """ Test decoding bytes. """ decoded = bytes(bytearray(range(0, 256))).decode(_BYTES_ENCODING) expected = ( '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10' '\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f' ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\' ']^_`abcdefghijklmnopqrstuvwxyz{|}~' '\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d' '\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c' '\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab' '\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba' '\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9' '\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8' '\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7' '\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6' '\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff' ) self.assertEqual(decoded, expected) def test_07_match_bytes_and_bytes(self): """ Test byte string patterns matching byte string paths. """ pattern = GitIgnoreBasicPattern(b'*.py') results = set(filter(pattern.match_file, [b'a.py'])) self.assertEqual(results, {b'a.py'}) def test_07_match_bytes_and_bytes_complete(self): """ Test byte string patterns matching byte string paths. """ encoded = bytes(bytearray(range(0, 256))) # Forward slashes cannot be escaped with the current implementation. # Remove ASCII 47. fs_ord = ord('/') encoded = encoded[:fs_ord] + encoded[fs_ord+1:] escaped = b''.join(b'\\' + encoded[i:i+1] for i in range(len(encoded))) pattern = GitIgnoreBasicPattern(escaped) results = set(filter(pattern.match_file, [encoded])) self.assertEqual(results, {encoded}) def test_07_match_bytes_and_unicode_fail(self): """ Test byte string patterns matching byte string paths. """ pattern = GitIgnoreBasicPattern(b'*.py') with self.assertRaises(TypeError): pattern.match_file('a.py') def test_07_match_unicode_and_bytes_fail(self): """ Test unicode patterns with byte paths. """ pattern = GitIgnoreBasicPattern('*.py') with self.assertRaises(TypeError): pattern.match_file(b'a.py') def test_07_match_unicode_and_unicode(self): """ Test unicode patterns with unicode paths. """ pattern = GitIgnoreBasicPattern('*.py') results = set(filter(pattern.match_file, ['a.py'])) self.assertEqual(results, {'a.py'}) def test_08_escape(self): """ Test escaping a string with meta-characters """ fname = 'file!with*weird#naming_[1].t?t' escaped = r'file\!with\*weird\#naming_\[1\].t\?t' result = GitIgnoreBasicPattern.escape(fname) self.assertEqual(result, escaped) def test_09_single_escape_fail(self): """ Test an escape on a line by itself. """ self._check_invalid_pattern('\\') def test_09_single_exclamation_mark_fail(self): """ Test an escape on a line by itself. """ self._check_invalid_pattern('!') def test_10_escape_asterisk_end(self): """ Test escaping an asterisk at the end of a line. """ pattern = GitIgnoreBasicPattern('asteris\\*') results = set(filter(pattern.match_file, [ 'asteris*', 'asterisk', ])) self.assertEqual(results, {'asteris*'}) def test_10_escape_asterisk_mid(self): """ Test escaping an asterisk in the middle of a line. """ pattern = GitIgnoreBasicPattern('as\\*erisk') results = set(filter(pattern.match_file, [ 'as*erisk', 'asterisk', ])) self.assertEqual(results, {'as*erisk'}) def test_10_escape_asterisk_start(self): """ Test escaping an asterisk at the start of a line. """ pattern = GitIgnoreBasicPattern('\\*sterisk') results = set(filter(pattern.match_file, [ '*sterisk', 'asterisk', ])) self.assertEqual(results, {'*sterisk'}) def test_10_escape_exclamation_mark_start(self): """ Test escaping an exclamation mark at the start of a line. """ pattern = GitIgnoreBasicPattern('\\!mark') results = set(filter(pattern.match_file, [ '!mark', ])) self.assertEqual(results, {'!mark'}) def test_10_escape_pound_start(self): """ Test escaping a pound sign at the start of a line. """ pattern = GitIgnoreBasicPattern('\\#sign') results = set(filter(pattern.match_file, [ '#sign', ])) self.assertEqual(results, {'#sign'}) def test_11_issue_19_directory_a(self): """ Test a directory discrepancy, scenario A. """ # NOTICE: The results from GitIgnoreBasicPattern will differ from # GitIgnoreSpecPattern. pattern = GitIgnoreBasicPattern('dirG/') results = set(filter(pattern.match_file, [ 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', ])) self.assertEqual(results, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }) def test_11_issue_19_directory_b(self): """ Test a directory discrepancy, scenario B. """ # NOTICE: The results from GitIgnoreBasicPattern will differ from # GitIgnoreSpecPattern. pattern = GitIgnoreBasicPattern('dirG/*') results = set(filter(pattern.match_file, [ 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', ])) self.assertEqual(results, { 'dirG/fileO', }) def test_11_issue_19_directory_c(self): """ Test a directory discrepancy, scenario C. """ # NOTICE: The results from GitIgnoreBasicPattern will differ from # GitIgnoreSpecPattern. pattern = GitIgnoreBasicPattern('dirG/**') results = set(filter(pattern.match_file, [ 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', ])) self.assertEqual(results, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }) def test_12_asterisk_1_regex(self): """ Test a relative asterisk path pattern's regular expression. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('*') self.assertTrue(include) self.assertEqual(regex, '.') def test_12_asterisk_2_regex_equivalent(self): """ Test a path pattern equivalent to the relative asterisk using double asterisk. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('*') self.assertTrue(include) equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('**/*') self.assertTrue(include) self.assertEqual(regex, equiv_regex) def test_12_asterisk_3_child(self): """ Test a relative asterisk path pattern matching a direct child path. """ pattern = GitIgnoreBasicPattern('*') self.assertTrue(pattern.match_file('file.txt')) def test_12_asterisk_4_descendant(self): """ Test a relative asterisk path pattern matching a descendant path. """ pattern = GitIgnoreBasicPattern('*') self.assertTrue(pattern.match_file('anydir/file.txt')) def test_12_issue_62(self): """ Test including all files, scenario A. """ pattern = GitIgnoreBasicPattern('*') results = set(filter(pattern.match_file, [ 'file.txt', 'anydir/file.txt', ])) self.assertEqual(results, { 'file.txt', 'anydir/file.txt', }) def test_13_issue_77_1_negate_with_caret(self): """ Test negation using the caret symbol ("^"). """ pattern = GitIgnoreBasicPattern('a[^gy]c') results = set(filter(pattern.match_file, [ 'agc', 'ayc', 'abc', 'adc', ])) self.assertEqual(results, { 'abc', 'adc', }) def test_13_issue_77_1_negate_with_exclamation_mark(self): """ Test negation using the exclamation mark ("!"). """ pattern = GitIgnoreBasicPattern('a[!gy]c') results = set(filter(pattern.match_file, [ 'agc', 'ayc', 'abc', 'adc', ])) self.assertEqual(results, { 'abc', 'adc', }) def test_13_issue_77_2_regex(self): """ Test the resulting regex for regex bracket expression negation. """ regex, include = GitIgnoreBasicPattern.pattern_to_regex('a[^b]c') self.assertTrue(include) equiv_regex, include = GitIgnoreBasicPattern.pattern_to_regex('a[!b]c') self.assertTrue(include) self.assertEqual(regex, equiv_regex) def test_14_issue_81_a(self): """ Test ignoring files in a directory, scenario A. """ pattern = GitIgnoreBasicPattern('!libfoo/**') self.assertEqual(pattern.regex.pattern, '^libfoo/') self.assertIs(pattern.include, False) self.assertTrue(pattern.match_file('libfoo/__init__.py')) def test_14_issue_81_b(self): """ Test ignoring files in a directory, scenario B. """ pattern = GitIgnoreBasicPattern('!libfoo/*') self.assertEqual(pattern.regex.pattern, f'^libfoo/[^/]+/?$') self.assertIs(pattern.include, False) self.assertTrue(pattern.match_file('libfoo/__init__.py')) def test_14_issue_81_c(self): """ Test ignoring files in a directory, scenario C. """ # NOTICE: GitIgnoreBasicPattern will match the file, but # GitIgnoreSpecPattern should not. pattern = GitIgnoreBasicPattern('!libfoo/') self.assertEqual(pattern.regex.pattern, '^(?:.+/)?libfoo/') self.assertIs(pattern.include, False) self.assertTrue(pattern.match_file('libfoo/__init__.py')) def test_15_issue_93_a_1(self): """ Test patterns with trailing double asterisks in a segment. """ pattern = GitIgnoreBasicPattern('foo**') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?foo[^/]*[^/]*{_DIR_OPT}') self.assertTrue(pattern.match_file('foosrodah')) def test_15_issue_93_a_2(self): """ Test patterns with trailing double asterisks in a segment. """ pattern = GitIgnoreBasicPattern('foo**/bar') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^foo[^/]*[^/]*/bar{_DIR_OPT}') self.assertFalse(pattern.match_file('foobar')) self.assertTrue(pattern.match_file('foosrodah/bar')) def test_15_issue_93_b_1_single(self): """ Test patterns with leading spaces. """ pattern = GitIgnoreBasicPattern(' foo') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?\\ foo{_DIR_OPT}') self.assertFalse(pattern.match_file('foo')) self.assertTrue(pattern.match_file(' foo')) def test_15_issue_93_b_2_double(self): """ Test patterns with leading spaces. """ pattern = GitIgnoreBasicPattern(' foo') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?\\ \\ foo{_DIR_OPT}') self.assertFalse(pattern.match_file('foo')) self.assertFalse(pattern.match_file(' foo')) self.assertTrue(pattern.match_file(' foo')) def test_15_issue_93_c_1(self): """ Test patterns with invalid range notation. """ # TODO BUG: This test is a placeholder for the current behavior. Git behaves # differently for this scenario. # - See . pattern = GitIgnoreBasicPattern('[') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?\\[{_DIR_OPT}') def test_15_issue_93_c_2(self): """ Test patterns with invalid range notation. """ # TODO BUG: This test is a placeholder for the current behavior. Git behaves # differently for this scenario. # - See . pattern = GitIgnoreBasicPattern('[!]') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?\\[!\\]{_DIR_OPT}') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7200718 pathspec-1.0.4/tests/test_03_gitignore_spec.py0000644000000000000000000006057715136034031016342 0ustar00""" This script tests :class:`.GitIgnoreSpecPattern`. """ import re import unittest from pathspec.patterns.gitignore.base import ( GitIgnorePatternError, _BYTES_ENCODING) from pathspec.patterns.gitignore.spec import ( GitIgnoreSpecPattern, _DIR_MARK_CG, _DIR_MARK_OPT) from pathspec.patterns.gitwildmatch import ( GitWildMatchPattern) from pathspec.util import ( lookup_pattern) class GitIgnoreSpecPatternTest(unittest.TestCase): """ The :class:`GitIgnoreSpecPatternTest` class tests the :class:`GitIgnoreSpecPattern` implementation. """ def _check_invalid_pattern(self, git_ignore_pattern): expected_message_pattern = re.escape(repr(git_ignore_pattern)) with self.assertRaisesRegex(GitIgnorePatternError, expected_message_pattern): GitIgnoreSpecPattern(git_ignore_pattern) def test_00_empty(self): """ Tests an empty pattern. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('') self.assertIsNone(include) self.assertIsNone(regex) def test_01_absolute(self): """ Tests an absolute path pattern. This should match: an/absolute/file/path an/absolute/file/path/foo This should NOT match: foo/an/absolute/file/path """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('/an/absolute/file/path') self.assertTrue(include) self.assertEqual(regex, f'^an/absolute/file/path{_DIR_MARK_OPT}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'an/absolute/file/path', 'an/absolute/file/path/foo', 'foo/an/absolute/file/path', ])) self.assertEqual(results, { 'an/absolute/file/path', 'an/absolute/file/path/foo', }) def test_01_absolute_ignore(self): """ Tests an ignore absolute path pattern. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('!/foo/build') self.assertIs(include, False) self.assertEqual(regex, f'^foo/build{_DIR_MARK_OPT}') # NOTE: The pattern match is backwards because the pattern itself # does not consider the include attribute. pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'build/file.py', 'foo/build/file.py', ])) self.assertEqual(results, { 'foo/build/file.py', }) def test_01_absolute_root_1(self): """ Tests a single root absolute path pattern. This should NOT match any file (according to git check-ignore v2.4.1). """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('/') self.assertIsNone(include) self.assertIsNone(regex) def test_01_absolute_root_2_double_asterisk(self): """ Tests the root path patterns: /** /**/** """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('/**') self.assertTrue(include) self.assertIsNotNone(regex) equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('/**/**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) reject_regex, include = GitIgnoreSpecPattern.pattern_to_regex('/') self.assertIsNone(include) self.assertNotEqual(reject_regex, regex) equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) def test_01_relative(self): """ Tests a relative path pattern. This should match: spam spam/ foo/spam spam/foo foo/spam/bar """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('spam') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?spam{_DIR_MARK_OPT}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'spam', 'spam/', 'foo/spam', 'spam/foo', 'foo/spam/bar', ])) self.assertEqual(results, { 'spam', 'spam/', 'foo/spam', 'spam/foo', 'foo/spam/bar', }) def test_01_relative_nested(self): """ Tests a relative nested path pattern. This should match: foo/spam foo/spam/bar This should **not** match (according to git check-ignore (v2.4.1)): bar/foo/spam """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('foo/spam') self.assertTrue(include) self.assertEqual(regex, f'^foo/spam{_DIR_MARK_OPT}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'foo/spam', 'foo/spam/bar', 'bar/foo/spam', ])) self.assertEqual(results, { 'foo/spam', 'foo/spam/bar', }) def test_02_comment(self): """ Tests a comment pattern. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('# Cork soakers.') self.assertIsNone(include) self.assertIsNone(regex) def test_02_ignore(self): """ Tests an exclude pattern. This should NOT match (according to git check-ignore (v2.4.1)): temp/foo """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('!temp') self.assertIs(include, False) self.assertEqual(regex, f'^(?:.+/)?temp{_DIR_MARK_OPT}') # NOTE: The pattern match is backwards because the pattern itself # does not consider the include attribute. pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'temp/foo', ])) self.assertEqual(results, { 'temp/foo', }) def test_03_child_double_asterisk(self): """ Tests a directory name with a double-asterisk child directory. This should match: spam/bar This should **not** match (according to git check-ignore (v2.4.1)): foo/spam/bar """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('spam/**') self.assertTrue(include) self.assertEqual(regex, '^spam/') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'spam/bar', 'foo/spam/bar', ])) self.assertEqual(results, {'spam/bar'}) def test_03_inner_double_asterisk(self): """ Tests a path with an inner double-asterisk directory. This should match: left/right left/bar/right left/foo/bar/right left/bar/right/foo This should **not** match (according to git check-ignore (v2.4.1)): foo/left/bar/right """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('left/**/right') self.assertTrue(include) self.assertEqual(regex, f'^left(?:/.+)?/right{_DIR_MARK_OPT}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'left/right', 'left/bar/right', 'left/foo/bar/right', 'left/bar/right/foo', 'foo/left/bar/right', ])) self.assertEqual(results, { 'left/right', 'left/bar/right', 'left/foo/bar/right', 'left/bar/right/foo', }) def test_03_only_double_asterisk(self): """ Tests a double-asterisk pattern which matches everything. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('**') self.assertTrue(include) self.assertEqual(regex, '.') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'x', 'y.py', 'A/x', 'A/y.py', 'A/B/x', 'A/B/y.py', 'A/B/C/x', 'A/B/C/y.py', ])) self.assertEqual(results, { 'x', 'y.py', 'A/x', 'A/y.py', 'A/B/x', 'A/B/y.py', 'A/B/C/x', 'A/B/C/y.py', }) def test_03_parent_double_asterisk(self): """ Tests a file name with a double-asterisk parent directory. This should match: spam foo/spam foo/spam/bar """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/spam') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?spam{_DIR_MARK_OPT}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'spam', 'foo/spam', 'foo/spam/bar', ])) self.assertEqual(results, { 'spam', 'foo/spam', 'foo/spam/bar', }) def test_03_duplicate_leading_double_asterisk_edge_case(self): """ Regression test for duplicate leading **/ bug. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('**') self.assertTrue(include) self.assertEqual(regex, '.') equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/**/**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/api') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?api{_DIR_MARK_OPT}') equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/**/api') self.assertTrue(include) self.assertEqual(equiv_regex, regex) regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/api/') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?api{_DIR_MARK_CG}') equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/**/api/') self.assertTrue(include) self.assertEqual(equiv_regex, regex) regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/api/**') self.assertTrue(include) self.assertEqual(regex, '^(?:.+/)?api/') equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/**/api/**/**') self.assertTrue(include) self.assertEqual(equiv_regex, regex) def test_03_double_asterisk_trailing_slash_edge_case(self): """ Tests the edge-case **/ pattern. This should match everything except individual files in the root directory. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/') self.assertTrue(include) self.assertEqual(regex, _DIR_MARK_CG) equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/**/') self.assertTrue(include) self.assertEqual(equiv_regex, regex) def test_04_infix_wildcard(self): """ Tests a pattern with an infix wildcard. This should match: foo--bar foo-hello-bar a/foo-hello-bar foo-hello-bar/b a/foo-hello-bar/b """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('foo-*-bar') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?foo\\-[^/]*\\-bar{_DIR_MARK_OPT}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'foo--bar', 'foo-hello-bar', 'a/foo-hello-bar', 'foo-hello-bar/b', 'a/foo-hello-bar/b', ])) self.assertEqual(results, { 'foo--bar', 'foo-hello-bar', 'a/foo-hello-bar', 'foo-hello-bar/b', 'a/foo-hello-bar/b', }) def test_04_postfix_wildcard(self): """ Tests a pattern with a postfix wildcard. This should match: ~temp- ~temp-foo ~temp-foo/bar foo/~temp-bar foo/~temp-bar/baz """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('~temp-*') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?\\~temp\\-[^/]*{_DIR_MARK_OPT}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ '~temp-', '~temp-foo', '~temp-foo/bar', 'foo/~temp-bar', 'foo/~temp-bar/baz', ])) self.assertEqual(results, { '~temp-', '~temp-foo', '~temp-foo/bar', 'foo/~temp-bar', 'foo/~temp-bar/baz', }) def test_04_prefix_wildcard(self): """ Tests a pattern with a prefix wildcard. This should match: bar.py bar.py/ foo/bar.py foo/bar.py/baz """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('*.py') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?[^/]*\\.py{_DIR_MARK_OPT}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'bar.py', 'bar.py/', 'foo/bar.py', 'foo/bar.py/baz', ])) self.assertEqual(results, { 'bar.py', 'bar.py/', 'foo/bar.py', 'foo/bar.py/baz', }) def test_05_directory(self): """ Tests a directory pattern. This should match: dir/ foo/dir/ foo/dir/bar This should **not** match: dir """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('dir/') self.assertTrue(include) self.assertEqual(regex, f'^(?:.+/)?dir{_DIR_MARK_CG}') pattern = GitIgnoreSpecPattern(re.compile(regex), include) results = set(filter(pattern.match_file, [ 'dir/', 'foo/dir/', 'foo/dir/bar', 'dir', ])) self.assertEqual(results, { 'dir/', 'foo/dir/', 'foo/dir/bar', }) def test_06_access_deprecated(self): """ Tests that the pattern is accessible from the root module using the deprecated alias. """ self.assertTrue(issubclass(GitWildMatchPattern, GitIgnoreSpecPattern)) def test_06_registered_deprecated(self): """ Tests that the pattern is registered under the deprecated alias. """ self.assertIs(lookup_pattern('gitwildmatch'), GitWildMatchPattern) def test_07_encode_bytes(self): """ Test encoding bytes. """ encoded = "".join(map(chr, range(0, 256))).encode(_BYTES_ENCODING) expected = ( b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10' b'\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f' b' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\' b']^_`abcdefghijklmnopqrstuvwxyz{|}~' b'\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d' b'\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c' b'\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab' b'\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba' b'\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9' b'\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8' b'\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7' b'\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6' b'\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff' ) self.assertEqual(encoded, expected) def test_07_decode_bytes(self): """ Test decoding bytes. """ decoded = bytes(bytearray(range(0, 256))).decode(_BYTES_ENCODING) expected = ( '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10' '\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f' ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\' ']^_`abcdefghijklmnopqrstuvwxyz{|}~' '\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d' '\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c' '\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab' '\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba' '\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9' '\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8' '\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7' '\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6' '\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff' ) self.assertEqual(decoded, expected) def test_07_match_bytes_and_bytes(self): """ Test byte string patterns matching byte string paths. """ pattern = GitIgnoreSpecPattern(b'*.py') results = set(filter(pattern.match_file, [b'a.py'])) self.assertEqual(results, {b'a.py'}) def test_07_match_bytes_and_bytes_complete(self): """ Test byte string patterns matching byte string paths. """ encoded = bytes(bytearray(range(0, 256))) # Forward slashes cannot be escaped with the current implementation. # Remove ASCII 47. fs_ord = ord('/') encoded = encoded[:fs_ord] + encoded[fs_ord+1:] escaped = b"".join(b'\\' + encoded[i:i+1] for i in range(len(encoded))) pattern = GitIgnoreSpecPattern(escaped) results = set(filter(pattern.match_file, [encoded])) self.assertEqual(results, {encoded}) def test_07_match_bytes_and_unicode_fail(self): """ Test byte string patterns matching byte string paths. """ pattern = GitIgnoreSpecPattern(b'*.py') with self.assertRaises(TypeError): pattern.match_file('a.py') def test_07_match_unicode_and_bytes_fail(self): """ Test unicode patterns with byte paths. """ pattern = GitIgnoreSpecPattern('*.py') with self.assertRaises(TypeError): pattern.match_file(b'a.py') def test_07_match_unicode_and_unicode(self): """ Test unicode patterns with unicode paths. """ pattern = GitIgnoreSpecPattern('*.py') results = set(filter(pattern.match_file, ['a.py'])) self.assertEqual(results, {'a.py'}) def test_08_escape(self): """ Test escaping a string with meta-characters """ fname = 'file!with*weird#naming_[1].t?t' escaped = r'file\!with\*weird\#naming_\[1\].t\?t' result = GitIgnoreSpecPattern.escape(fname) self.assertEqual(result, escaped) def test_09_single_escape_fail(self): """ Test an escape on a line by itself. """ self._check_invalid_pattern('\\') def test_09_single_exclamation_mark_fail(self): """ Test an escape on a line by itself. """ self._check_invalid_pattern('!') def test_10_escape_asterisk_end(self): """ Test escaping an asterisk at the end of a line. """ pattern = GitIgnoreSpecPattern('asteris\\*') results = set(filter(pattern.match_file, [ 'asteris*', 'asterisk', ])) self.assertEqual(results, {'asteris*'}) def test_10_escape_asterisk_mid(self): """ Test escaping an asterisk in the middle of a line. """ pattern = GitIgnoreSpecPattern('as\\*erisk') results = set(filter(pattern.match_file, [ 'as*erisk', 'asterisk', ])) self.assertEqual(results, {'as*erisk'}) def test_10_escape_asterisk_start(self): """ Test escaping an asterisk at the start of a line. """ pattern = GitIgnoreSpecPattern('\\*sterisk') results = set(filter(pattern.match_file, [ '*sterisk', 'asterisk', ])) self.assertEqual(results, {'*sterisk'}) def test_10_escape_exclamation_mark_start(self): """ Test escaping an exclamation mark at the start of a line. """ pattern = GitIgnoreSpecPattern('\\!mark') results = set(filter(pattern.match_file, [ '!mark', ])) self.assertEqual(results, {'!mark'}) def test_10_escape_pound_start(self): """ Test escaping a pound sign at the start of a line. """ pattern = GitIgnoreSpecPattern('\\#sign') results = set(filter(pattern.match_file, [ '#sign', ])) self.assertEqual(results, {'#sign'}) def test_11_issue_19_directory_a(self): """ Test a directory discrepancy, scenario A. """ # NOTE: The result from GitIgnoreSpecPattern will differ from GitIgnoreSpec. pattern = GitIgnoreSpecPattern('dirG/') results = set(filter(pattern.match_file, [ 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', ])) self.assertEqual(results, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }) def test_11_issue_19_directory_b(self): """ Test a directory discrepancy, scenario B. """ # NOTE: The result from GitIgnoreSpecPattern will differ from GitIgnoreSpec. pattern = GitIgnoreSpecPattern('dirG/*') results = set(filter(pattern.match_file, [ 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', ])) self.assertEqual(results, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }) def test_11_issue_19_directory_c(self): """ Test a directory discrepancy, scenario C. """ # NOTE: The result from GitIgnoreSpecPattern will differ from GitIgnoreSpec. pattern = GitIgnoreSpecPattern('dirG/**') results = set(filter(pattern.match_file, [ 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', ])) self.assertEqual(results, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }) def test_12_asterisk_1_regex(self): """ Test a relative asterisk path pattern's regular expression. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('*') self.assertTrue(include) self.assertEqual(regex, '.') def test_12_asterisk_2_regex_equivalent(self): """ Test a path pattern equivalent to the relative asterisk using double asterisk. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('*') self.assertTrue(include) equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('**/*') self.assertTrue(include) self.assertEqual(regex, equiv_regex) def test_12_asterisk_3_child(self): """ Test a relative asterisk path pattern matching a direct child path. """ pattern = GitIgnoreSpecPattern('*') self.assertTrue(pattern.match_file('file.txt')) def test_12_asterisk_4_descendant(self): """ Test a relative asterisk path pattern matching a descendant path. """ pattern = GitIgnoreSpecPattern('*') self.assertTrue(pattern.match_file('anydir/file.txt')) def test_12_issue_62(self): """ Test including all files, scenario A. """ pattern = GitIgnoreSpecPattern('*') results = set(filter(pattern.match_file, [ 'file.txt', 'anydir/file.txt', ])) self.assertEqual(results, { 'file.txt', 'anydir/file.txt', }) def test_13_issue_77_1_negate_with_caret(self): """ Test negation using the caret symbol ("^"). """ pattern = GitIgnoreSpecPattern('a[^gy]c') results = set(filter(pattern.match_file, [ 'agc', 'ayc', 'abc', 'adc', ])) self.assertEqual(results, { 'abc', 'adc', }) def test_13_issue_77_1_negate_with_exclamation_mark(self): """ Test negation using the exclamation mark ("!"). """ pattern = GitIgnoreSpecPattern('a[!gy]c') results = set(filter(pattern.match_file, [ 'agc', 'ayc', 'abc', 'adc', ])) self.assertEqual(results, { 'abc', 'adc', }) def test_13_issue_77_2_regex(self): """ Test the resulting regex for regex bracket expression negation. """ regex, include = GitIgnoreSpecPattern.pattern_to_regex('a[^b]c') self.assertTrue(include) equiv_regex, include = GitIgnoreSpecPattern.pattern_to_regex('a[!b]c') self.assertTrue(include) self.assertEqual(regex, equiv_regex) def test_14_issue_81_a(self): """ Test ignoring files in a directory, scenario A. """ pattern = GitIgnoreSpecPattern('!libfoo/**') self.assertEqual(pattern.regex.pattern, '^libfoo/') self.assertIs(pattern.include, False) self.assertTrue(pattern.match_file('libfoo/__init__.py')) def test_14_issue_81_b(self): """ Test ignoring files in a directory, scenario B. """ pattern = GitIgnoreSpecPattern('!libfoo/*') self.assertEqual(pattern.regex.pattern, f'^libfoo/[^/]+{_DIR_MARK_OPT}') self.assertIs(pattern.include, False) self.assertTrue(pattern.match_file('libfoo/__init__.py')) def test_14_issue_81_c(self): """ Test ignoring files in a directory, scenario C. """ # GitIgnoreSpecPattern will match the file, but GitIgnoreSpec should not. pattern = GitIgnoreSpecPattern('!libfoo/') self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?libfoo{_DIR_MARK_CG}') self.assertIs(pattern.include, False) self.assertTrue(pattern.match_file('libfoo/__init__.py')) def test_15_issue_93_a_1(self): """ Test patterns with trailing double asterisks in a segment. """ pattern = GitIgnoreSpecPattern('foo**') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?foo[^/]*[^/]*{_DIR_MARK_OPT}') self.assertTrue(pattern.match_file('foosrodah')) def test_15_issue_93_a_2(self): """ Test patterns with trailing double asterisks in a segment. """ pattern = GitIgnoreSpecPattern('foo**/bar') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^foo[^/]*[^/]*/bar{_DIR_MARK_OPT}') self.assertFalse(pattern.match_file('foobar')) self.assertTrue(pattern.match_file('foosrodah/bar')) def test_15_issue_93_b_1_single(self): """ Test patterns with leading spaces. """ pattern = GitIgnoreSpecPattern(' foo') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?\\ foo{_DIR_MARK_OPT}') self.assertFalse(pattern.match_file('foo')) self.assertTrue(pattern.match_file(' foo')) def test_15_issue_93_b_2_double(self): """ Test patterns with leading spaces. """ pattern = GitIgnoreSpecPattern(' foo') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?\\ \\ foo{_DIR_MARK_OPT}') self.assertFalse(pattern.match_file('foo')) self.assertFalse(pattern.match_file(' foo')) self.assertTrue(pattern.match_file(' foo')) def test_15_issue_93_c_1(self): """ Test patterns with invalid range notation. """ # TODO BUG: This test is a placeholder for the current behavior. Git behaves # differently for this scenario. # - See . pattern = GitIgnoreSpecPattern('[') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?\\[{_DIR_MARK_OPT}') def test_15_issue_93_c_2(self): """ Test patterns with invalid range notation. """ # TODO BUG: This test is a placeholder for the current behavior. Git behaves # differently for this scenario. # - See . pattern = GitIgnoreSpecPattern('[!]') self.assertIs(pattern.include, True) self.assertEqual(pattern.regex.pattern, f'^(?:.+/)?\\[!\\]{_DIR_MARK_OPT}') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7200718 pathspec-1.0.4/tests/test_04_pathspec.py0000644000000000000000000005554715136034031015152 0ustar00""" This script tests :class:`.PathSpec`. """ import os import shutil import tempfile import unittest from collections.abc import ( Iterable, Iterator, Sequence) from contextlib import ( AbstractContextManager, contextmanager) from functools import ( partial) from pathlib import ( Path) from typing import ( Callable, # Replaced by `collections.abc.Callable` in 3.9.2. Optional) # Replaced by `X | None` in 3.10. from unittest import ( SkipTest) from pathspec import ( PathSpec) from pathspec.backend import ( BackendNamesHint, _Backend) from pathspec._backends.hyperscan.pathspec import ( HyperscanPsBackend) from pathspec._backends.re2.pathspec import ( Re2PsBackend) from pathspec._backends.simple.pathspec import ( SimplePsBackend) from pathspec.pattern import ( Pattern) from pathspec.patterns.gitignore.base import ( GitIgnorePatternError) from pathspec._typing import ( AnyStr) # Removed in 3.18. from pathspec.util import ( iter_tree_entries) from .util import ( CheckResult, debug_includes, debug_results, get_includes, get_paths_from_entries, make_dirs, make_files, ospath, require_backend, reverse_inplace, shuffle_inplace) BACKENDS: list[BackendNamesHint] = [ 'hyperscan', 're2', ] """ The backend parameters. """ class PathSpecTest(unittest.TestCase): """ The :class:`PathSpecTest` class tests the :class:`.PathSpec` class. """ def clear_temp_dir(self) -> None: """ Clear the temp directory. """ for path in os.scandir(self.temp_dir): shutil.rmtree(path.path) def make_dirs(self, dirs: Iterable[str]) -> None: """ Create the specified directories. """ make_dirs(self.temp_dir, dirs) def make_files(self, files: Iterable[str]) -> None: """ Create the specified files. """ return make_files(self.temp_dir, files) def parameterize_from_lines( self, pattern_factory: str, lines: Iterable[AnyStr], ) -> Iterator[Callable[[], AbstractContextManager[PathSpec]]]: """ Parameterize `PathSpec.from_lines()` for each backend and configuration to begin a subtest. *pattern_factory* (:class:`str`) is the pattern factory. *lines* (:class:`Iterable` of :class:`str`) yields the lines. Returns an :class:`Iterator` yielding each subtest context for the :class:`PathSpec`. """ lines = list(lines) configs: list[tuple[ str, BackendNamesHint, Optional[Callable[[Sequence[Pattern]], _Backend]] ]] = [] # Simple backend, no optimizations. configs.append(( "simple (unopt)", 'simple', partial(SimplePsBackend, no_filter=True, no_reverse=True), )) # Simple backend, minimal optimizations. configs.append(( "simple (minopt)", 'simple', None, )) # Add additional backends. for backend in BACKENDS: if backend == 'hyperscan': configs.append(( f"hyperscan (forward)", backend, partial(HyperscanPsBackend, _debug_exprs=True), )) configs.append(( f"hyperscan (reverse)", backend, partial(HyperscanPsBackend, _debug_exprs=True, _test_sort=reverse_inplace) )) configs.append(( f"hyperscan (shuffle)", backend, partial(HyperscanPsBackend, _debug_exprs=True, _test_sort=shuffle_inplace) )) elif backend == 're2': configs.append(( f"re2 (forward)", backend, partial(Re2PsBackend, _debug_regex=True), )) configs.append(( f"re2 (reverse)", backend, partial(Re2PsBackend, _debug_regex=True, _test_sort=reverse_inplace), )) configs.append(( f"re2 (shuffle)", backend, partial(Re2PsBackend, _debug_regex=True, _test_sort=shuffle_inplace), )) else: configs.append(( backend, backend, None, )) for label, backend, backend_factory in configs: try: require_backend(backend) except SkipTest: with self.subTest(label): raise continue @contextmanager def _sub_test( backend=backend, backend_factory=backend_factory, label=label, ): self.clear_temp_dir() with self.subTest(label): yield PathSpec.from_lines( pattern_factory, lines, backend=backend, _test_backend_factory=backend_factory, ) yield _sub_test def setUp(self) -> None: """ Called before each test. """ self.temp_dir = Path(tempfile.mkdtemp()) def tearDown(self) -> None: """ Called after each test. """ shutil.rmtree(self.temp_dir) def test_01_absolute_dir_paths_1(self): """ Tests that absolute paths will be properly normalized and matched. """ for sub_test in self.parameterize_from_lines('gitignore', [ 'foo', ]): with sub_test() as spec: files = { '/a.py', '/foo/a.py', '/x/a.py', '/x/foo/a.py', 'a.py', 'foo/a.py', 'x/a.py', 'x/foo/a.py', } results = list(spec.check_files(files)) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, { '/foo/a.py', '/x/foo/a.py', 'foo/a.py', 'x/foo/a.py', }, debug) def test_01_absolute_dir_paths_2(self): """ Tests that absolute paths will be properly normalized and matched. """ for sub_test in self.parameterize_from_lines('gitignore', [ '/foo', ]): with sub_test() as spec: files = { '/a.py', '/foo/a.py', '/x/a.py', '/x/foo/a.py', 'a.py', 'foo/a.py', 'x/a.py', 'x/foo/a.py', } results = list(spec.check_files(files)) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, { '/foo/a.py', 'foo/a.py', }, debug) def test_01_check_file_1_include(self): """ Test checking a single file that is included. """ for sub_test in self.parameterize_from_lines('gitignore', [ "*.txt", "!test/", ]): with sub_test() as spec: result = spec.check_file("include.txt") debug = debug_results(spec, [result]) self.assertEqual(result, CheckResult("include.txt", True, 0), debug) def test_01_check_file_2_exclude(self): """ Test checking a single file that is excluded. """ for sub_test in self.parameterize_from_lines('gitignore', [ "*.txt", "!test/", ]): with sub_test() as spec: result = spec.check_file("test/exclude.txt") debug = debug_results(spec, [result]) self.assertEqual(result, CheckResult("test/exclude.txt", False, 1), debug) def test_01_check_file_3_unmatch(self): """ Test checking a single file that is unmatched. """ for sub_test in self.parameterize_from_lines('gitignore', [ "*.txt", "!test/", ]): with sub_test() as spec: result = spec.check_file("unmatch.bin") debug = debug_results(spec, [result]) self.assertEqual(result, CheckResult("unmatch.bin", None, None), debug) def test_01_check_file_4_many(self): """ Test that checking files one at a time yields the same results as checking multiples files at once. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!test1/', ]): with sub_test() as spec: files = { 'test1/a.txt', 'test1/b.txt', 'test1/c/c.txt', 'test2/a.txt', 'test2/b.txt', 'test2/c/c.txt', } single_results = set(map(spec.check_file, files)) multi_results = set(spec.check_files(files)) debug = debug_results(spec, single_results) self.assertEqual(single_results, multi_results, debug) def test_01_check_match_files(self): """ Test that checking files and matching files yield the same results. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!test1/**', ]): with sub_test() as spec: files = { 'src/test1/a.txt', 'src/test1/b.txt', 'src/test1/c/c.txt', 'src/test2/a.txt', 'src/test2/b.txt', 'src/test2/c/c.txt', } check_results = set(spec.check_files(files)) check_includes = get_includes(check_results) match_files = set(spec.match_files(files)) debug = debug_results(spec, check_results) self.assertEqual(check_includes, match_files, debug) def test_01_current_dir_paths(self): """ Tests that paths referencing the current directory will be properly normalized and matched. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!test1/', ]): with sub_test() as spec: files = { './src/test1/a.txt', './src/test1/b.txt', './src/test1/c/c.txt', './src/test2/a.txt', './src/test2/b.txt', './src/test2/c/c.txt', } results = list(spec.check_files(files)) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, { './src/test2/a.txt', './src/test2/b.txt', './src/test2/c/c.txt', }, debug) def test_01_empty_path_1(self): """ Tests that patterns that end with an escaped space will be treated properly. """ for sub_test in self.parameterize_from_lines('gitignore', [ '\\ ', 'abc\\ ', ]): with sub_test() as spec: files = { ' ', ' ', 'abc ', 'somefile', } results = list(spec.check_files(files)) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, { ' ', 'abc ', }, debug) def test_01_empty_path_2(self): """ Tests that patterns that end with an escaped space will be treated properly. """ with self.assertRaises(GitIgnorePatternError): # An escape with double spaces is invalid. Disallow it. Better to be # safe than sorry. PathSpec.from_lines('gitignore', [ '\\ ', ], backend='simple') def test_01_match_file_1_include(self): """ Test matching a single file that is included. """ for sub_test in self.parameterize_from_lines('gitignore', [ "*.txt", "!test/", ]): with sub_test() as spec: include = spec.match_file("include.txt") self.assertIs(include, True) def test_01_match_file_2_exclude(self): """ Test matching a single file that is excluded. """ for sub_test in self.parameterize_from_lines('gitignore', [ "*.txt", "!test/", ]): with sub_test() as spec: file = 'test/exclude.txt' include = spec.match_file(file) includes = {file} if include else {} debug = debug_includes(spec, {file}, includes) self.assertIs(include, False, debug) def test_01_match_file_3_unmatch(self): """ Test match a single file that is unmatched. """ for sub_test in self.parameterize_from_lines('gitignore', [ "*.txt", "!test/", ]): with sub_test() as spec: file = 'unmatch.bin' include = spec.match_file(file) includes = {file} if include else {} debug = debug_includes(spec, {file}, includes) self.assertIs(include, False, debug) def test_01_match_files(self): """ Test that matching files one at a time yields the same results as matching multiples files at once. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!test1/', ]): with sub_test() as spec: files = { 'test1/a.txt', 'test1/b.txt', 'test1/c/c.txt', 'test2/a.txt', 'test2/b.txt', 'test2/c/c.txt', } single_files = set(filter(spec.match_file, files)) multi_files = set(spec.match_files(files)) debug = debug_includes(spec, files, single_files) self.assertEqual(single_files, multi_files, debug) def test_01_windows_current_dir_paths(self): """ Tests that paths referencing the current directory will be properly normalized and matched. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!test1/', ]): with sub_test() as spec: files = { '.\\test1\\a.txt', '.\\test1\\b.txt', '.\\test1\\c\\c.txt', '.\\test2\\a.txt', '.\\test2\\b.txt', '.\\test2\\c\\c.txt', } results = list(spec.check_files(files, separators=['\\'])) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, { '.\\test2\\a.txt', '.\\test2\\b.txt', '.\\test2\\c\\c.txt', }, debug) def test_01_windows_paths(self): """ Tests that Windows paths will be properly normalized and matched. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!test1/', ]): with sub_test() as spec: files = { 'test1\\a.txt', 'test1\\b.txt', 'test1\\c\\c.txt', 'test2\\a.txt', 'test2\\b.txt', 'test2\\c\\c.txt', } results = list(spec.check_files(files, separators=['\\'])) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, { 'test2\\a.txt', 'test2\\b.txt', 'test2\\c\\c.txt', }, debug) def test_02_eq(self): """ Tests equality. """ first_spec = PathSpec.from_lines('gitignore', [ '*.txt', '!test1/**', ], backend='simple') second_spec = PathSpec.from_lines('gitignore', [ '*.txt', '!test1/**', ], backend='simple') self.assertEqual(first_spec, second_spec) def test_02_ne(self): """ Tests inequality. """ first_spec = PathSpec.from_lines('gitignore', [ '*.txt', ], backend='simple') second_spec = PathSpec.from_lines('gitignore', [ '!*.txt', ], backend='simple') self.assertNotEqual(first_spec, second_spec) def test_03_add(self): """ Test spec addition using :data:`+` operator. """ first_spec = PathSpec.from_lines('gitignore', [ 'test.png', 'test.txt', ], backend='simple') second_spec = PathSpec.from_lines('gitignore', [ 'test.html', 'test.jpg', ], backend='simple') combined_spec = first_spec + second_spec files = { 'test.html', 'test.jpg', 'test.png', 'test.txt', } results = list(combined_spec.check_files(files)) includes = get_includes(results) debug = debug_results(combined_spec, results) self.assertEqual(includes, { 'test.html', 'test.jpg', 'test.png', 'test.txt', }, debug) def test_03_iadd(self): """ Test spec addition using :data:`+=` operator. """ spec = PathSpec.from_lines('gitignore', [ 'test.png', 'test.txt', ], backend='simple') spec += PathSpec.from_lines('gitignore', [ 'test.html', 'test.jpg', ], backend='simple') files = { 'test.html', 'test.jpg', 'test.png', 'test.txt', } results = list(spec.check_files(files)) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, { 'test.html', 'test.jpg', 'test.png', 'test.txt', }, debug) def test_04_len(self): """ Test spec length. """ for sub_test in self.parameterize_from_lines('gitignore', [ 'foo', 'bar', ]): with sub_test() as spec: self.assertEqual(len(spec), 2) def test_05_match_entries(self): """ Test matching files collectively. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!b.txt', ]): with sub_test() as spec: self.make_dirs([ 'X', 'X/Z', 'Y', 'Y/Z', ]) self.make_files([ 'X/a.txt', 'X/b.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/b.txt', 'Y/Z/c.txt', ]) entries = iter_tree_entries(self.temp_dir) includes = get_paths_from_entries(spec.match_entries(entries)) self.assertEqual(includes, set(map(ospath, [ 'X/a.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/Z/c.txt', ]))) def test_05_match_file(self): """ Test matching files individually. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!b.txt', ]): with sub_test() as spec: files = { 'X/a.txt', 'X/b.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/b.txt', 'Y/Z/c.txt', } includes = set(filter(spec.match_file, files)) debug = debug_includes(spec, files, includes) self.assertEqual(includes, { 'X/a.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/Z/c.txt', }, debug) def test_05_match_files(self): """ Test matching files collectively. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!b.txt', ]): with sub_test() as spec: files = { 'X/a.txt', 'X/b.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/b.txt', 'Y/Z/c.txt', } includes = set(spec.match_files(files)) debug = debug_includes(spec, files, includes) self.assertEqual(includes, { 'X/a.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/Z/c.txt', }, debug) def test_05_match_tree_entries(self): """ Test matching a file tree. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!b.txt', ]): with sub_test() as spec: files = set(map(ospath, [ 'X/a.txt', 'X/b.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/b.txt', 'Y/Z/c.txt', ])) self.make_dirs([ 'X', 'X/Z', 'Y', 'Y/Z', ]) self.make_files(files) entries = spec.match_tree_entries(self.temp_dir) includes = get_paths_from_entries(entries) debug = debug_includes(spec, files, includes) self.assertEqual(includes, set(map(ospath, [ 'X/a.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/Z/c.txt', ])), debug) def test_05_match_tree_files(self): """ Test matching a file tree. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.txt', '!b.txt', ]): with sub_test() as spec: files = set(map(ospath, [ 'X/a.txt', 'X/b.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/b.txt', 'Y/Z/c.txt', ])) self.make_dirs([ 'X', 'X/Z', 'Y', 'Y/Z', ]) self.make_files(files) includes = set(spec.match_tree_files(self.temp_dir)) debug = debug_includes(spec, files, includes) self.assertEqual(includes, set(map(ospath, [ 'X/a.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/Z/c.txt', ])), debug) def test_06_issue_41_a(self): """ Test including a file and excluding a directory with the same name pattern, scenario A. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.yaml', '!*.yaml/', ]): with sub_test() as spec: files = { 'dir.yaml/file.sql', 'dir.yaml/file.yaml', 'dir.yaml/index.txt', 'dir/file.sql', 'dir/file.yaml', 'dir/index.txt', 'file.yaml', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { #'dir.yaml/file.yaml', # Discrepancy with Git. 'dir/file.yaml', 'file.yaml', }, debug) self.assertEqual(files - ignores, { 'dir.yaml/file.sql', 'dir.yaml/file.yaml', # Discrepancy with Git. 'dir.yaml/index.txt', 'dir/file.sql', 'dir/index.txt', }, debug) def test_06_issue_41_b(self): """ Test including a file and excluding a directory with the same name pattern, scenario B. """ for sub_test in self.parameterize_from_lines('gitignore', [ '!*.yaml/', '*.yaml', ]): with sub_test() as spec: files = { 'dir.yaml/file.sql', 'dir.yaml/file.yaml', 'dir.yaml/index.txt', 'dir/file.sql', 'dir/file.yaml', 'dir/index.txt', 'file.yaml', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'dir.yaml/file.sql', 'dir.yaml/file.yaml', 'dir.yaml/index.txt', 'dir/file.yaml', 'file.yaml', }, debug) self.assertEqual(files - ignores, { 'dir/file.sql', 'dir/index.txt', }, debug) def test_06_issue_41_c(self): """ Test including a file and excluding a directory with the same name pattern, scenario C. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.yaml', '!dir.yaml', ]): with sub_test() as spec: files = { 'dir.yaml/file.sql', 'dir.yaml/file.yaml', 'dir.yaml/index.txt', 'dir/file.sql', 'dir/file.yaml', 'dir/index.txt', 'file.yaml', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { #'dir.yaml/file.yaml', # Discrepancy with Git. 'dir/file.yaml', 'file.yaml', }, debug) self.assertEqual(files - ignores, { 'dir.yaml/file.sql', 'dir.yaml/file.yaml', # Discrepancy with Git. 'dir.yaml/index.txt', 'dir/file.sql', 'dir/index.txt', }, debug) def test_07_issue_62(self): """ Test including all files and excluding a directory. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*', '!product_dir/', ]): with sub_test() as spec: files = { 'anydir/file.txt', 'product_dir/file.txt', } results = list(spec.check_files(files)) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, { 'anydir/file.txt', }, debug) def test_08_issue_39(self): """ Test excluding files in a directory. """ for sub_test in self.parameterize_from_lines('gitignore', [ '*.log', '!important/*.log', 'trace.*', ]): with sub_test() as spec: files = { 'a.log', 'b.txt', 'important/d.log', 'important/e.txt', 'trace.c', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'a.log', 'trace.c', }, debug) self.assertEqual(files - ignores, { 'b.txt', 'important/d.log', 'important/e.txt', }, debug) def test_09_issue_80_a(self): """ Test negating patterns. """ for sub_test in self.parameterize_from_lines('gitignore', [ 'build', '*.log', '.*', '!.gitignore', ]): with sub_test() as spec: files = { '.c-tmp', # 3:.* '.gitignore', # 4:!.gitignore 'a.log', # 2:*.log 'b.txt', # - 'build/d.log', # 1:build 'build/trace.bin', # 1:build 'trace.c', # - } keeps = set(spec.match_files(files, negate=True)) includes = files - keeps debug = debug_includes(spec, files, includes) self.assertEqual(keeps, { '.gitignore', 'b.txt', 'trace.c', }, debug) def test_09_issue_80_b(self): """ Test negating patterns. """ for sub_test in self.parameterize_from_lines('gitignore', [ 'build', '*.log', '.*', '!.gitignore', ]): with sub_test() as spec: files = { '.c-tmp', # 3:.* '.gitignore', # 4:!.gitignore 'a.log', # 2:*.log 'b.txt', # - 'build/d.log', # 1:build 'build/trace.bin', # 1:build 'trace.c', # - } keeps = set(spec.match_files(files, negate=True)) ignores = set(spec.match_files(files)) self.assertEqual(files - ignores, keeps) self.assertEqual(files - keeps, ignores) def test_10_issue_100(self): """ Test an empty list of patterns. """ for sub_test in self.parameterize_from_lines('gitignore', []): with sub_test() as spec: files = {'foo'} results = list(spec.check_files(files)) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, set(), debug) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7200718 pathspec-1.0.4/tests/test_05_gitignore.py0000644000000000000000000003630015136034031015315 0ustar00""" This script tests :class:`.GitIgnoreSpec`. """ import unittest from collections.abc import ( Iterable, Iterator, Sequence) from contextlib import ( AbstractContextManager, contextmanager) from functools import ( partial) from typing import ( Callable, # Replaced by `collections.abc.Callable` in 3.9.2. Optional) # Replaced by `X | None` in 3.10. from unittest import ( SkipTest) from pathspec.backend import ( BackendNamesHint, _Backend) from pathspec._backends.hyperscan.gitignore import ( HyperscanGiBackend) from pathspec._backends.re2.gitignore import ( Re2GiBackend) from pathspec._backends.simple.gitignore import ( SimpleGiBackend) from pathspec.gitignore import ( GitIgnoreSpec) from pathspec.pattern import ( Pattern) from pathspec._typing import ( AnyStr) # Removed in 3.18. from .util import ( debug_results, get_includes, require_backend, reverse_inplace, shuffle_inplace) BACKENDS: list[BackendNamesHint] = [ 'hyperscan', 're2', ] """ The backend parameters. """ class GitIgnoreSpecTest(unittest.TestCase): """ The :class:`GitIgnoreSpecTest` class tests the :class:`.GitIgnoreSpec` class. """ def parameterize_from_lines( self, lines: Iterable[AnyStr], ) -> Iterator[Callable[[], AbstractContextManager[GitIgnoreSpec]]]: """ Parameterize `GitIgnoreSpec.from_lines()` for each backend and configuration to begin a subtest. *pattern_factory* (:class:`str`) is the pattern factory. *lines* (:class:`Iterable` of :class:`str`) yields the lines. Returns an :class:`Iterator` yielding each context for the :class:`GitIgnoreSpec`. """ lines = list(lines) configs: list[tuple[ str, BackendNamesHint, Optional[Callable[[Sequence[Pattern]], _Backend]] ]] = [] # Simple backend, no optimizations. configs.append(( "simple (unopt)", 'simple', partial(SimpleGiBackend, no_filter=True, no_reverse=True), )) # Simple backend, minimal optimizations. configs.append(( "simple (minopt)", 'simple', None, )) # Add additional backends. for backend in BACKENDS: if backend == 'hyperscan': configs.append(( f"hyperscan (forward)", backend, partial(HyperscanGiBackend, _debug_exprs=True), )) configs.append(( f"hyperscan (reverse)", backend, partial(HyperscanGiBackend, _debug_exprs=True, _test_sort=reverse_inplace) )) configs.append(( f"hyperscan (shuffle)", backend, partial(HyperscanGiBackend, _debug_exprs=True, _test_sort=shuffle_inplace) )) elif backend == 're2': configs.append(( f"re2 (forward)", backend, partial(Re2GiBackend, _debug_regex=True), )) configs.append(( f"re2 (reverse)", backend, partial(Re2GiBackend, _debug_regex=True, _test_sort=reverse_inplace) )) configs.append(( f"re2 (shuffle)", backend, partial(Re2GiBackend, _debug_regex=True, _test_sort=shuffle_inplace) )) else: configs.append(( backend, backend, None, )) for label, backend, backend_factory in configs: try: require_backend(backend) except SkipTest: with self.subTest(label): raise continue @contextmanager def _sub_test( backend=backend, backend_factory=backend_factory, label=label, ): with self.subTest(label): yield GitIgnoreSpec.from_lines( lines, backend=backend, _test_backend_factory=backend_factory, ) yield _sub_test def test_01_reversed_args(self): """ Test reversed args for `.from_lines()`. """ spec = GitIgnoreSpec.from_lines('gitignore', ['*.txt']) files = { 'a.txt', 'b.bin', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'a.txt', }, debug) def test_02_dir_exclusions(self): """ Test directory exclusions. """ for sub_test in self.parameterize_from_lines([ '*.txt', '!test1/', ]): with sub_test() as spec: files = { 'test1/a.txt', 'test1/b.bin', 'test1/c/c.txt', 'test2/a.txt', 'test2/b.bin', 'test2/c/c.txt', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'test1/a.txt', 'test1/c/c.txt', 'test2/a.txt', 'test2/c/c.txt', }, debug) self.assertEqual(files - ignores, { 'test1/b.bin', 'test2/b.bin', }, debug) def test_02_file_exclusions(self): """ Test file exclusions. """ for sub_test in self.parameterize_from_lines([ '*.txt', '!b.txt', ]): with sub_test() as spec: files = { 'X/a.txt', 'X/b.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/b.txt', 'Y/Z/c.txt', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'X/a.txt', 'X/Z/c.txt', 'Y/a.txt', 'Y/Z/c.txt', }, debug) self.assertEqual(files - ignores, { 'X/b.txt', 'Y/b.txt', }, debug) def test_02_issue_41_a(self): """ Test including a file and excluding a directory with the same name pattern, scenario A. """ for sub_test in self.parameterize_from_lines([ '*.yaml', '!*.yaml/', ]): with sub_test() as spec: # Confirmed results with git (v2.42.0). files = { 'dir.yaml/file.sql', # - 'dir.yaml/file.yaml', # 1:*.yaml 'dir.yaml/index.txt', # - 'dir/file.sql', # - 'dir/file.yaml', # 1:*.yaml 'dir/index.txt', # - 'file.yaml', # 1:*.yaml } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'dir.yaml/file.yaml', 'dir/file.yaml', 'file.yaml', }, debug) self.assertEqual(files - ignores, { 'dir.yaml/file.sql', 'dir.yaml/index.txt', 'dir/file.sql', 'dir/index.txt', }, debug) def test_02_issue_41_b(self): """ Test including a file and excluding a directory with the same name pattern, scenario B. """ for sub_test in self.parameterize_from_lines([ '!*.yaml/', '*.yaml', ]): with sub_test() as spec: # Confirmed results with git (v2.42.0). files = { 'dir.yaml/file.sql', # 2:*.yaml 'dir.yaml/file.yaml', # 2:*.yaml 'dir.yaml/index.txt', # 2:*.yaml 'dir/file.sql', # - 'dir/file.yaml', # 2:*.yaml 'dir/index.txt', # - 'file.yaml', # 2:*.yaml } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'dir.yaml/file.sql', 'dir.yaml/file.yaml', 'dir.yaml/index.txt', 'dir/file.yaml', 'file.yaml', }, debug) self.assertEqual(files - ignores, { 'dir/file.sql', 'dir/index.txt', }, debug) def test_02_issue_41_c(self): """ Test including a file and excluding a directory with the same name pattern, scenario C. """ for sub_test in self.parameterize_from_lines([ '*.yaml', '!dir.yaml', ]): with sub_test() as spec: # Confirmed results with git check-ignore (v2.42.0). files = { 'dir.yaml/file.sql', # - 'dir.yaml/file.yaml', # 1:*.yaml 'dir.yaml/index.txt', # - 'dir/file.sql', # - 'dir/file.yaml', # 1:*.yaml 'dir/index.txt', # - 'file.yaml', # 1:*.yaml } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'dir.yaml/file.yaml', 'dir/file.yaml', 'file.yaml', }, debug) self.assertEqual(files - ignores, { 'dir.yaml/file.sql', 'dir.yaml/index.txt', 'dir/file.sql', 'dir/index.txt', }, debug) def test_03_subdir(self): """ Test matching files in a subdirectory of an included directory. """ for sub_test in self.parameterize_from_lines([ "dirG/", ]): with sub_test() as spec: files = { 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }, debug) self.assertEqual(files - ignores, { 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', }, debug) def test_03_issue_19_a(self): """ Test matching files in a subdirectory of an included directory, scenario A. """ for sub_test in self.parameterize_from_lines([ "dirG/", ]): with sub_test() as spec: files = { 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }, debug) self.assertEqual(files - ignores, { 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', }, debug) def test_03_issue_19_b(self): """ Test matching files in a subdirectory of an included directory, scenario B. """ for sub_test in self.parameterize_from_lines([ "dirG/*", ]): with sub_test() as spec: files = { 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }, debug) self.assertEqual(files - ignores, { 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', }, debug) def test_03_issue_19_c(self): """ Test matching files in a subdirectory of an included directory, scenario C. """ for sub_test in self.parameterize_from_lines([ "dirG/**", ]): with sub_test() as spec: files = { 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'dirG/dirH/fileI', 'dirG/dirH/fileJ', 'dirG/fileO', }, debug) self.assertEqual(files - ignores, { 'fileA', 'fileB', 'dirD/fileE', 'dirD/fileF', }, debug) def test_04_issue_62(self): """ Test including all files and excluding a directory. """ for sub_test in self.parameterize_from_lines([ '*', '!product_dir/', ]): with sub_test() as spec: files = { 'anydir/file.txt', 'product_dir/file.txt', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'anydir/file.txt', 'product_dir/file.txt', }, debug) def test_05_issue_39(self): """ Test excluding files in a directory. """ for sub_test in self.parameterize_from_lines([ '*.log', '!important/*.log', 'trace.*', ]): with sub_test() as spec: files = { 'a.log', 'b.txt', 'important/d.log', 'important/e.txt', 'trace.c', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'a.log', 'trace.c', }, debug) self.assertEqual(files - ignores, { 'b.txt', 'important/d.log', 'important/e.txt', }, debug) def test_06_issue_64(self): """ Test using a double asterisk pattern. """ for sub_test in self.parameterize_from_lines([ "**", ]): with sub_test() as spec: files = { 'x', 'y.py', 'A/x', 'A/y.py', 'A/B/x', 'A/B/y.py', 'A/B/C/x', 'A/B/C/y.py', } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, files, debug) def test_07_issue_74(self): """ Test include directory should override exclude file. """ for sub_test in self.parameterize_from_lines([ '*', # Ignore all files by default '!*/', # but scan all directories '!*.txt', # Text files '/test1/**', # ignore all in the directory ]): with sub_test() as spec: files = { 'test1/a.txt', # 4:/test1/** 'test1/b.bin', # 4:/test1/** 'test1/c/c.txt', # 4:/test1/** 'test2/a.txt', # - 'test2/b.bin', # 1:* 'test2/c/c.txt', # - } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { 'test1/a.txt', 'test1/b.bin', 'test1/c/c.txt', 'test2/b.bin', }, debug) self.assertEqual(files - ignores, { 'test2/a.txt', 'test2/c/c.txt', }, debug) def test_08_issue_81_a(self): """ Test issue 81 whitelist, scenario A. """ for sub_test in self.parameterize_from_lines([ "*", "!libfoo", "!libfoo/**", ]): with sub_test() as spec: # Confirmed results with git (v2.42.0). files = { "ignore.txt", # 1:* "libfoo/__init__.py", # 3:!libfoo/** } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { "ignore.txt", }, debug) self.assertEqual(files - ignores, { "libfoo/__init__.py", }, debug) def test_08_issue_81_b(self): """ Test issue 81 whitelist, scenario B. """ for sub_test in self.parameterize_from_lines([ "*", "!libfoo", "!libfoo/*", ]): with sub_test() as spec: # Confirmed results with git (v2.42.0). files = { "ignore.txt", # 1:* "libfoo/__init__.py", # 3:!libfoo/* } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { "ignore.txt", }, debug) self.assertEqual(files - ignores, { "libfoo/__init__.py", }, debug) def test_08_issue_81_c(self): """ Test issue 81 whitelist, scenario C. """ for sub_test in self.parameterize_from_lines([ "*", "!libfoo", "!libfoo/", ]): with sub_test() as spec: # Confirmed results with git (v2.42.0). files = { "ignore.txt", # 1:* "libfoo/__init__.py", # 1:* } results = list(spec.check_files(files)) ignores = get_includes(results) debug = debug_results(spec, results) self.assertEqual(ignores, { "ignore.txt", "libfoo/__init__.py", }, debug) self.assertEqual(files - ignores, set()) def test_09_issue_100(self): """ Test an empty list of patterns. """ for sub_test in self.parameterize_from_lines([]): with sub_test() as spec: files = {'foo'} results = list(spec.check_files(files)) includes = get_includes(results) debug = debug_results(spec, results) self.assertEqual(includes, set(), debug) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7200718 pathspec-1.0.4/tests/util.py0000644000000000000000000001511615136034031012742 0ustar00""" This module provides utility functions shared by tests. """ from __future__ import annotations import itertools import os import os.path import pathlib from collections.abc import ( Iterable) from random import ( Random) from typing import ( Any, Optional, # Replaced by `X | None` in 3.10. cast) from unittest import ( SkipTest) from pathspec import ( PathSpec, RegexPattern) from pathspec.backend import ( BackendNamesHint) from pathspec._backends.hyperscan.base import ( hyperscan_error) from pathspec._backends.hyperscan._base import ( HyperscanExprDebug) from pathspec._backends.hyperscan.pathspec import ( HyperscanPsBackend) from pathspec._backends.re2.base import ( re2_error) from pathspec._backends.re2._base import ( Re2RegexDebug) from pathspec._backends.re2.pathspec import ( Re2PsBackend) from pathspec.util import ( CheckResult, TStrPath, TreeEntry) def debug_includes(spec: PathSpec, files: set[str], includes: set[str]) -> str: """ Format the match files message. *spec* (:class:`~pathspec.PathSpec`) is the path-spec. *files* (:class:`set` of :class:`str`) contains the source files. *includes* (:class:`set` of :class:`str`) contains the matched files. Returns the message (:class:`str`). """ results = [] for result in spec.check_files(files): assert (result.file in includes) == bool(result.include), { 'result': result, 'includes': includes, } results.append(result) return debug_results(spec, results) def debug_results(spec: PathSpec, results: Iterable[CheckResult[str]]) -> str: """ Format the check results message. *spec* (:class:`~pathspec.PathSpec`) is the path-spec. *results* (:class:`~collections.abc.Iterable` or :class:`~pathspec.util.CheckResult`) yields each file check result. Returns the message (:class:`str`). """ patterns = cast(list[RegexPattern], spec.patterns) pattern_table = [] if isinstance(spec._backend, HyperscanPsBackend) and spec._backend._debug_exprs: for expr_id, expr_dat in enumerate(spec._backend._expr_data, 1): assert isinstance(expr_dat, HyperscanExprDebug), expr_dat pattern = patterns[expr_dat.index] dir_col = 'd' if expr_dat.is_dir_pattern else '.' pattern_table.append(( f"{expr_dat.index+1}({expr_id}):{pattern.pattern}", f"{dir_col} {expr_dat.regex!r}", )) elif isinstance(spec._backend, Re2PsBackend) and spec._backend._debug_regex: for regex_id, regex_dat in enumerate(spec._backend._regex_data, 1): assert isinstance(regex_dat, Re2RegexDebug), regex_dat pattern = patterns[regex_dat.index] dir_col = 'd' if regex_dat.is_dir_pattern else '.' pattern_table.append(( f"{regex_dat.index+1}({regex_id}):{pattern.pattern}", f"{dir_col} {regex_dat.regex!r}", )) else: for index, pattern in enumerate(patterns, 1): pattern_table.append(( f"{index}:{pattern.pattern}", repr(pattern.regex.pattern), )) result_table = [] for result in results: if result.index is not None: pattern = patterns[result.index] result_table.append((f"{result.index + 1}:{pattern.pattern}", result.file)) else: result_table.append(("-", result.file)) result_table.sort(key=lambda r: r[1]) first_max_len = max(( len(__row[0]) for __row in itertools.chain(pattern_table, result_table) ), default=0) first_width = min(first_max_len, 20) pattern_lines = [] for row in pattern_table: pattern_lines.append(f" {row[0]:<{first_width}} {row[1]}") result_lines = [] for row in result_table: result_lines.append(f" {row[0]:<{first_width}} {row[1]}") return "\n".join([ "\n", " DEBUG ".center(32, "-"), *pattern_lines, "-"*32, *result_lines, "-"*32, ]) def get_includes(results: Iterable[CheckResult[TStrPath]]) -> set[TStrPath]: """ Get the included files from the check results. *results* (:class:`~collections.abc.Iterable` or :class:`~pathspec.util.CheckResult`) yields each file check result. Returns the included files (:class:`set` of :class:`str`). """ return {__res.file for __res in results if __res.include} def get_paths_from_entries(entries: Iterable[TreeEntry]) -> set[str]: """ Get the entry paths. *entries* (:class:`Iterable` of :class:`TreeEntry`) yields the entries. Returns the paths (:class:`set` of :class:`str`). """ return {__ent.path for __ent in entries} def make_dirs(temp_dir: pathlib.Path, dirs: Iterable[str]) -> None: """ Create the specified directories. *temp_dir* (:class:`pathlib.Path`) is the temporary directory to use. *dirs* (:class:`Iterable` of :class:`str`) is the POSIX directory paths (relative to *temp_dir*) to create. """ for dir in dirs: os.mkdir(temp_dir / ospath(dir)) def make_files(temp_dir: pathlib.Path, files: Iterable[str]) -> None: """ Create the specified files. *temp_dir* (:class:`pathlib.Path`) is the temporary directory to use. *files* (:class:`Iterable` of :class:`str`) is the POSIX file paths (relative to *temp_dir*) to create. """ for file in files: mkfile(temp_dir / ospath(file)) def make_links(temp_dir: pathlib.Path, links: Iterable[tuple[str, str]]) -> None: """ Create the specified links. *temp_dir* (:class:`pathlib.Path`) is the temporary directory to use. *links* (:class:`Iterable` of :class:`tuple`) contains the POSIX links to create relative to *temp_dir*. Each link (:class:`tuple`) contains the destination link path (:class:`str`) and source node path (:class:`str`). """ for link, node in links: src = temp_dir / ospath(node) dest = temp_dir / ospath(link) os.symlink(src, dest) def mkfile(file: pathlib.Path) -> None: """ Creates an empty file. *file* (:class:`pathlib.Path`) is the native file path to create. """ with open(file, 'wb'): pass def ospath(path: str) -> str: """ Convert the POSIX path to a native OS path. *path* (:class:`str`) is the POSIX path. Returns the native path (:class:`str`). """ return os.path.join(*path.split('/')) def require_backend(name: Optional[BackendNamesHint]) -> None: """ Skip the test if the backend library is not installed. *name* (:class:`str` or :data:`None`) is the backend name. Raises :class:`SkipTest` if the backend library is not installed. """ if name == 'hyperscan' and hyperscan_error is not None: raise SkipTest(str(hyperscan_error)) elif name == 're2' and re2_error is not None: raise SkipTest(str(re2_error)) def reverse_inplace(val: list[Any]) -> None: """ Reverse the list inplace. *val* (:class:`list`) is the list to sort. """ val.reverse() def shuffle_inplace(val: list[Any]) -> None: """ Shuffle the list inplace. The order will consistently be in the same random order between test runs. *val* (:class:`list`) is the list to sort. """ Random(0).shuffle(val) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1769486360.7200718 pathspec-1.0.4/tox.ini0000644000000000000000000000522015136034031011557 0ustar00[tox] ; Hyperscan does not have a wheel for Python 3.15 yet. ; Hyperscan does not have wheels for PyPy 3.9 and 3.11. envlist = py{39, 310, 311, 312, 313, 314, 315} py{39, 310, 311, 312, 313, 314}-hyperscan py{39, 310, 311, 312, 313, 314}-re2 pypy{39, 310, 311} pypy{310, 311}-hyperscan docs isolated_build = True [testenv] commands = python -m unittest discover -t . -s tests/ {posargs} package = wheel [testenv:docs] base_path = py313 commands = sphinx-build -aWEnqb html doc/source doc/build deps = -rdoc/requirements.txt extras = [testenv:ci-base] ; Placeholder env for base (only simple backend) for CI. [testenv:ci-hyperscan] ; Hyperscan env for CI. extras = hyperscan install_command = python -I -m pip install --only-binary=hyperscan {opts} {packages} [testenv:ci-re2] ; Re2 env for CI. extras = google-re2 install_command = python -I -m pip install --only-binary=google-re2 {opts} {packages} [testenv:py39-hyperscan] extras = hyperscan install_command = python -I -m pip install --only-binary=hyperscan {opts} {packages} [testenv:py39-re2] extras = google-re2 install_command = python -I -m pip install --only-binary=google-re2 {opts} {packages} [testenv:py310-hyperscan] extras = hyperscan install_command = python -I -m pip install --only-binary=hyperscan {opts} {packages} [testenv:py310-re2] extras = google-re2 install_command = python -I -m pip install --only-binary=google-re2 {opts} {packages} [testenv:py311-hyperscan] extras = hyperscan install_command = python -I -m pip install --only-binary=hyperscan {opts} {packages} [testenv:py311-re2] extras = google-re2 install_command = python -I -m pip install --only-binary=google-re2 {opts} {packages} [testenv:py312-hyperscan] extras = hyperscan install_command = python -I -m pip install --only-binary=hyperscan {opts} {packages} [testenv:py312-re2] extras = google-re2 install_command = python -I -m pip install --only-binary=google-re2 {opts} {packages} [testenv:py313-hyperscan] extras = hyperscan install_command = python -I -m pip install --only-binary=hyperscan {opts} {packages} [testenv:py313-re2] extras = google-re2 install_command = python -I -m pip install --only-binary=google-re2 {opts} {packages} [testenv:py314-hyperscan] extras = hyperscan install_command = python -I -m pip install --only-binary=hyperscan {opts} {packages} [testenv:py314-re2] extras = google-re2 install_command = python -I -m pip install --only-binary=google-re2 {opts} {packages} ;; Compiling hyperscan fails on PyPy 3.9. ;[testenv:pypy39-hyperscan] ;extras = hyperscan [testenv:pypy310-hyperscan] ; Compile hyperscan. extras = hyperscan [testenv:pypy311-hyperscan] ; Compile hyperscan. extras = hyperscan pathspec-1.0.4/PKG-INFO0000644000000000000000000003267300000000000011322 0ustar00Metadata-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" 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 `_. - 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/