mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Initial commit
This commit is contained in:
76
venv/Lib/site-packages/pathspec/__init__.py
Normal file
76
venv/Lib/site-packages/pathspec/__init__.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
The *pathspec* package provides pattern matching for file paths. So far
|
||||
this only includes Git's wildmatch pattern matching (the style used for
|
||||
".gitignore" files).
|
||||
|
||||
The following classes are imported and made available from the root of
|
||||
the `pathspec` package:
|
||||
|
||||
- :class:`pathspec.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` which is an alias for
|
||||
:func:`pathspec.util.iter_tree_files`.
|
||||
|
||||
- :func:`pathspec.util.match_files`
|
||||
"""
|
||||
|
||||
from .gitignore import (
|
||||
GitIgnoreSpec)
|
||||
from .pathspec import (
|
||||
PathSpec)
|
||||
from .pattern import (
|
||||
Pattern,
|
||||
RegexPattern)
|
||||
from .util import (
|
||||
RecursionError,
|
||||
iter_tree,
|
||||
lookup_pattern,
|
||||
match_files)
|
||||
|
||||
from ._meta import (
|
||||
__author__,
|
||||
__copyright__,
|
||||
__credits__,
|
||||
__license__,
|
||||
__version__,
|
||||
)
|
||||
|
||||
# Load pattern implementations.
|
||||
from . import patterns
|
||||
|
||||
# DEPRECATED: Expose the `GitIgnorePattern` class in the root module for
|
||||
# backward compatibility with v0.4.
|
||||
from .patterns.gitwildmatch import GitIgnorePattern
|
||||
|
||||
# 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__',
|
||||
'iter_tree',
|
||||
'lookup_pattern',
|
||||
'match_files',
|
||||
]
|
58
venv/Lib/site-packages/pathspec/_meta.py
Normal file
58
venv/Lib/site-packages/pathspec/_meta.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
This module contains the project meta-data.
|
||||
"""
|
||||
|
||||
__author__ = "Caleb P. Burns"
|
||||
__copyright__ = "Copyright © 2013-2023 Caleb P. Burns"
|
||||
__credits__ = [
|
||||
"dahlia <https://github.com/dahlia>",
|
||||
"highb <https://github.com/highb>",
|
||||
"029xue <https://github.com/029xue>",
|
||||
"mikexstudios <https://github.com/mikexstudios>",
|
||||
"nhumrich <https://github.com/nhumrich>",
|
||||
"davidfraser <https://github.com/davidfraser>",
|
||||
"demurgos <https://github.com/demurgos>",
|
||||
"ghickman <https://github.com/ghickman>",
|
||||
"nvie <https://github.com/nvie>",
|
||||
"adrienverge <https://github.com/adrienverge>",
|
||||
"AndersBlomdell <https://github.com/AndersBlomdell>",
|
||||
"thmxv <https://github.com/thmxv>",
|
||||
"wimglenn <https://github.com/wimglenn>",
|
||||
"hugovk <https://github.com/hugovk>",
|
||||
"dcecile <https://github.com/dcecile>",
|
||||
"mroutis <https://github.com/mroutis>",
|
||||
"jdufresne <https://github.com/jdufresne>",
|
||||
"groodt <https://github.com/groodt>",
|
||||
"ftrofin <https://github.com/ftrofin>",
|
||||
"pykong <https://github.com/pykong>",
|
||||
"nhhollander <https://github.com/nhhollander>",
|
||||
"KOLANICH <https://github.com/KOLANICH>",
|
||||
"JonjonHays <https://github.com/JonjonHays>",
|
||||
"Isaac0616 <https://github.com/Isaac0616>",
|
||||
"SebastiaanZ <https://github.com/SebastiaanZ>",
|
||||
"RoelAdriaans <https://github.com/RoelAdriaans>",
|
||||
"raviselker <https://github.com/raviselker>",
|
||||
"johanvergeer <https://github.com/johanvergeer>",
|
||||
"danjer <https://github.com/danjer>",
|
||||
"jhbuhrman <https://github.com/jhbuhrman>",
|
||||
"WPDOrdina <https://github.com/WPDOrdina>",
|
||||
"tirkarthi <https://github.com/tirkarthi>",
|
||||
"jayvdb <https://github.com/jayvdb>",
|
||||
"jwodder <https://github.com/jwodder>",
|
||||
"kloczek <https://github.com/kloczek>",
|
||||
"orens <https://github.com/orens>",
|
||||
"spMohanty <https://github.com/spMohanty>",
|
||||
"ichard26 <https://github.com/ichard26>",
|
||||
"jack1142 <https://github.com/jack1142>",
|
||||
"mgorny <https://github.com/mgorny>",
|
||||
"bzakdd <https://github.com/bzakdd>",
|
||||
"haimat <https://github.com/haimat>",
|
||||
"Avasam <https://github.com/Avasam>",
|
||||
"yschroeder <https://github.com/yschroeder>",
|
||||
"axesider <https://github.com/axesider>",
|
||||
"tomruk <https://github.com/tomruk>",
|
||||
"oprypin <https://github.com/oprypin>",
|
||||
"kurtmckee <https://github.com/kurtmckee>",
|
||||
]
|
||||
__license__ = "MPL 2.0"
|
||||
__version__ = "0.12.1"
|
157
venv/Lib/site-packages/pathspec/gitignore.py
Normal file
157
venv/Lib/site-packages/pathspec/gitignore.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
This module provides :class:`.GitIgnoreSpec` which replicates
|
||||
*.gitignore* behavior.
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
AnyStr,
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.
|
||||
Iterable, # Replaced by `collections.abc.Iterable` in 3.9.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
Tuple, # Replaced by `tuple` in 3.9.
|
||||
Type, # Replaced by `type` in 3.9.
|
||||
TypeVar,
|
||||
Union, # Replaced by `X | Y` in 3.10.
|
||||
cast,
|
||||
overload)
|
||||
|
||||
from .pathspec import (
|
||||
PathSpec)
|
||||
from .pattern import (
|
||||
Pattern)
|
||||
from .patterns.gitwildmatch import (
|
||||
GitWildMatchPattern,
|
||||
_DIR_MARK)
|
||||
from .util import (
|
||||
_is_iterable)
|
||||
|
||||
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.pathspec.PathSpec` to
|
||||
replicate *.gitignore* behavior.
|
||||
"""
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""
|
||||
Tests the equality of this gitignore-spec with *other* (:class:`GitIgnoreSpec`)
|
||||
by comparing their :attr:`~pathspec.pattern.Pattern`
|
||||
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]],
|
||||
lines: Iterable[AnyStr],
|
||||
) -> Self:
|
||||
...
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def from_lines(
|
||||
cls: Type[Self],
|
||||
lines: Iterable[AnyStr],
|
||||
pattern_factory: Union[str, Callable[[AnyStr], Pattern], None] = None,
|
||||
) -> Self:
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def from_lines(
|
||||
cls: Type[Self],
|
||||
lines: Iterable[AnyStr],
|
||||
pattern_factory: Union[str, Callable[[AnyStr], Pattern], None] = 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* can be :data:`None`, the name of a registered
|
||||
pattern factory (:class:`str`), or a :class:`~collections.abc.Callable`
|
||||
used to compile patterns. The callable must accept an uncompiled
|
||||
pattern (:class:`str`) and return the compiled pattern
|
||||
(:class:`pathspec.pattern.Pattern`).
|
||||
Default is :data:`None` for :class:`.GitWildMatchPattern`).
|
||||
|
||||
Returns the :class:`GitIgnoreSpec` instance.
|
||||
"""
|
||||
if pattern_factory is None:
|
||||
pattern_factory = GitWildMatchPattern
|
||||
|
||||
elif (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
|
||||
|
||||
self = super().from_lines(pattern_factory, lines)
|
||||
return cast(Self, self)
|
||||
|
||||
@staticmethod
|
||||
def _match_file(
|
||||
patterns: Iterable[Tuple[int, GitWildMatchPattern]],
|
||||
file: str,
|
||||
) -> Tuple[Optional[bool], Optional[int]]:
|
||||
"""
|
||||
Check the file against the patterns.
|
||||
|
||||
.. NOTE:: Subclasses of :class:`~pathspec.pathspec.PathSpec` may override
|
||||
this method as an instance method. It does not have to be a static
|
||||
method. The signature for this method is subject to change.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Iterable`) yields each indexed pattern
|
||||
(:class:`tuple`) which contains the pattern index (:class:`int`) and actual
|
||||
pattern (:class:`~pathspec.pattern.Pattern`).
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to be matched against
|
||||
*patterns*.
|
||||
|
||||
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`).
|
||||
"""
|
||||
out_include: Optional[bool] = None
|
||||
out_index: Optional[int] = None
|
||||
out_priority = 0
|
||||
for index, pattern in patterns:
|
||||
if pattern.include is not None:
|
||||
match = pattern.match_file(file)
|
||||
if match 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 pattern.include and dir_mark:
|
||||
out_include = pattern.include
|
||||
out_index = index
|
||||
out_priority = priority
|
||||
elif priority >= out_priority:
|
||||
out_include = pattern.include
|
||||
out_index = index
|
||||
out_priority = priority
|
||||
|
||||
return out_include, out_index
|
394
venv/Lib/site-packages/pathspec/pathspec.py
Normal file
394
venv/Lib/site-packages/pathspec/pathspec.py
Normal file
@@ -0,0 +1,394 @@
|
||||
"""
|
||||
This module provides an object oriented interface for pattern matching of files.
|
||||
"""
|
||||
|
||||
from collections.abc import (
|
||||
Collection as CollectionType)
|
||||
from itertools import (
|
||||
zip_longest)
|
||||
from typing import (
|
||||
AnyStr,
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.
|
||||
Collection, # Replaced by `collections.abc.Collection` in 3.9.
|
||||
Iterable, # Replaced by `collections.abc.Iterable` in 3.9.
|
||||
Iterator, # Replaced by `collections.abc.Iterator` in 3.9.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
Type, # Replaced by `type` in 3.9.
|
||||
TypeVar,
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
from . import util
|
||||
from .pattern import (
|
||||
Pattern)
|
||||
from .util import (
|
||||
CheckResult,
|
||||
StrPath,
|
||||
TStrPath,
|
||||
TreeEntry,
|
||||
_filter_check_patterns,
|
||||
_is_iterable,
|
||||
normalize_file)
|
||||
|
||||
Self = TypeVar("Self", bound="PathSpec")
|
||||
"""
|
||||
:class:`PathSpec` self type hint to support Python v<3.11 using PEP 673
|
||||
recommendation.
|
||||
"""
|
||||
|
||||
|
||||
class PathSpec(object):
|
||||
"""
|
||||
The :class:`PathSpec` class is a wrapper around a list of compiled
|
||||
:class:`.Pattern` instances.
|
||||
"""
|
||||
|
||||
def __init__(self, patterns: Iterable[Pattern]) -> None:
|
||||
"""
|
||||
Initializes the :class:`PathSpec` instance.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Collection` or :class:`~collections.abc.Iterable`)
|
||||
yields each compiled pattern (:class:`.Pattern`).
|
||||
"""
|
||||
if not isinstance(patterns, CollectionType):
|
||||
patterns = list(patterns)
|
||||
|
||||
self.patterns: Collection[Pattern] = patterns
|
||||
"""
|
||||
*patterns* (:class:`~collections.abc.Collection` of :class:`.Pattern`)
|
||||
contains the compiled patterns.
|
||||
"""
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""
|
||||
Tests the equality of this path-spec with *other* (:class:`PathSpec`)
|
||||
by comparing their :attr:`~PathSpec.patterns` attributes.
|
||||
"""
|
||||
if isinstance(other, PathSpec):
|
||||
paired_patterns = zip_longest(self.patterns, other.patterns)
|
||||
return all(a == b for a, b in paired_patterns)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Returns the number of compiled patterns this path-spec contains
|
||||
(:class:`int`).
|
||||
"""
|
||||
return len(self.patterns)
|
||||
|
||||
def __add__(self: Self, other: "PathSpec") -> Self:
|
||||
"""
|
||||
Combines the :attr:`Pathspec.patterns` patterns from two
|
||||
:class:`PathSpec` instances.
|
||||
"""
|
||||
if isinstance(other, PathSpec):
|
||||
return self.__class__(self.patterns + other.patterns)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __iadd__(self: Self, other: "PathSpec") -> Self:
|
||||
"""
|
||||
Adds the :attr:`Pathspec.patterns` patterns from one :class:`PathSpec`
|
||||
instance to this instance.
|
||||
"""
|
||||
if isinstance(other, PathSpec):
|
||||
self.patterns += other.patterns
|
||||
return self
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
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:`~pathspec.util.normalize_file` for more information.
|
||||
|
||||
Returns the file check result (:class:`~pathspec.util.CheckResult`).
|
||||
"""
|
||||
norm_file = normalize_file(file, separators)
|
||||
include, index = self._match_file(enumerate(self.patterns), 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:`~pathspec.util.normalize_file` for more information.
|
||||
|
||||
Returns an :class:`~collections.abc.Iterator` yielding each file check
|
||||
result (:class:`~pathspec.util.CheckResult`).
|
||||
"""
|
||||
if not _is_iterable(files):
|
||||
raise TypeError(f"files:{files!r} is not an iterable.")
|
||||
|
||||
use_patterns = _filter_check_patterns(self.patterns)
|
||||
for orig_file in files:
|
||||
norm_file = normalize_file(orig_file, separators)
|
||||
include, index = self._match_file(use_patterns, 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:`~pathspec.util.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],
|
||||
) -> 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`.
|
||||
|
||||
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)
|
||||
|
||||
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:`~pathspec.util.TreeEntry`)
|
||||
contains the entries to be matched against :attr:`self.patterns <PathSpec.patterns>`.
|
||||
|
||||
*separators* (:class:`~collections.abc.Collection` of :class:`str`; or
|
||||
:data:`None`) optionally contains the path separators to normalize. See
|
||||
:func:`~pathspec.util.normalize_file` for more information.
|
||||
|
||||
*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:`~pathspec.util.TreeEntry`).
|
||||
"""
|
||||
if not _is_iterable(entries):
|
||||
raise TypeError(f"entries:{entries!r} is not an iterable.")
|
||||
|
||||
use_patterns = _filter_check_patterns(self.patterns)
|
||||
for entry in entries:
|
||||
norm_file = normalize_file(entry.path, separators)
|
||||
include, _index = self._match_file(use_patterns, norm_file)
|
||||
|
||||
if negate:
|
||||
include = not include
|
||||
|
||||
if include:
|
||||
yield entry
|
||||
|
||||
_match_file = staticmethod(util.check_match_file)
|
||||
"""
|
||||
Match files using the `check_match_file()` utility function. Subclasses may
|
||||
override this method as an instance method. It does not have to be a static
|
||||
method. The signature for this method is subject to change.
|
||||
"""
|
||||
|
||||
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:`~pathspec.util.normalize_file` for more information.
|
||||
|
||||
Returns :data:`True` if *file* matched; otherwise, :data:`False`.
|
||||
"""
|
||||
norm_file = normalize_file(file, separators)
|
||||
include, _index = self._match_file(enumerate(self.patterns), 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:`~pathspec.util.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.")
|
||||
|
||||
use_patterns = _filter_check_patterns(self.patterns)
|
||||
for orig_file in files:
|
||||
norm_file = normalize_file(orig_file, separators)
|
||||
include, _index = self._match_file(use_patterns, 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)
|
||||
|
||||
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)
|
||||
|
||||
# Alias `match_tree_files()` as `match_tree()` for backward compatibility
|
||||
# before v0.3.2.
|
||||
match_tree = match_tree_files
|
213
venv/Lib/site-packages/pathspec/pattern.py
Normal file
213
venv/Lib/site-packages/pathspec/pattern.py
Normal file
@@ -0,0 +1,213 @@
|
||||
"""
|
||||
This module provides the base definition for patterns.
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import re
|
||||
import warnings
|
||||
from typing import (
|
||||
Any,
|
||||
AnyStr,
|
||||
Iterable, # Replaced by `collections.abc.Iterable` in 3.9.
|
||||
Iterator, # Replaced by `collections.abc.Iterator` in 3.9.
|
||||
Match as MatchHint, # Replaced by `re.Match` in 3.9.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
Pattern as PatternHint, # Replaced by `re.Pattern` in 3.9.
|
||||
Tuple, # Replaced by `tuple` in 3.9.
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
|
||||
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`).
|
||||
"""
|
||||
|
||||
def match(self, files: Iterable[str]) -> Iterator[str]:
|
||||
"""
|
||||
DEPRECATED: This method is no longer used and has been replaced by
|
||||
:meth:`.match_file`. Use the :meth:`.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 (e.g., ``"relative/path/to/file"``).
|
||||
|
||||
Returns an :class:`~collections.abc.Iterable` yielding each matched file
|
||||
path (:class:`str`).
|
||||
"""
|
||||
warnings.warn((
|
||||
"{cls.__module__}.{cls.__qualname__}.match() is deprecated. Use "
|
||||
"{cls.__module__}.{cls.__qualname__}.match_file() with a loop for "
|
||||
"similar results."
|
||||
).format(cls=self.__class__), DeprecationWarning, stacklevel=2)
|
||||
|
||||
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, PatternHint, 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."
|
||||
)
|
||||
|
||||
else:
|
||||
raise TypeError(f"pattern:{pattern!r} is not a string, re.Pattern, or None.")
|
||||
|
||||
super(RegexPattern, self).__init__(include)
|
||||
|
||||
self.pattern: Union[AnyStr, PatternHint, None] = pattern
|
||||
"""
|
||||
*pattern* (:class:`str`, :class:`bytes`, :class:`re.Pattern`, or
|
||||
:data:`None`) is the uncompiled, input pattern. This is for reference.
|
||||
"""
|
||||
|
||||
self.regex: PatternHint = regex
|
||||
"""
|
||||
*regex* (:class:`re.Pattern`) is the regular expression for the pattern.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
def match_file(self, file: str) -> Optional['RegexMatchResult']:
|
||||
"""
|
||||
Matches this pattern against the specified file.
|
||||
|
||||
*file* (:class:`str`) contains each file 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.match(file)
|
||||
if match is not None:
|
||||
return RegexMatchResult(match)
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def pattern_to_regex(cls, pattern: str) -> Tuple[str, bool]:
|
||||
"""
|
||||
Convert the pattern into an uncompiled regular expression.
|
||||
|
||||
*pattern* (:class:`str`) is the pattern to convert into a regular
|
||||
expression.
|
||||
|
||||
Returns the uncompiled regular expression (:class:`str` or :data:`None`),
|
||||
and whether matched files should be included (:data:`True`), excluded
|
||||
(:data:`False`), or is a null-operation (:data:`None`).
|
||||
|
||||
.. NOTE:: The default implementation simply returns *pattern* and
|
||||
:data:`True`.
|
||||
"""
|
||||
return pattern, True
|
||||
|
||||
|
||||
@dataclasses.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: MatchHint
|
||||
"""
|
||||
*match* (:class:`re.Match`) is the regex match result.
|
||||
"""
|
11
venv/Lib/site-packages/pathspec/patterns/__init__.py
Normal file
11
venv/Lib/site-packages/pathspec/patterns/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
The *pathspec.patterns* package contains the pattern matching
|
||||
implementations.
|
||||
"""
|
||||
|
||||
# Load pattern implementations.
|
||||
from . import gitwildmatch
|
||||
|
||||
# DEPRECATED: Expose the `GitWildMatchPattern` class in this module for
|
||||
# backward compatibility with v0.5.
|
||||
from .gitwildmatch import GitWildMatchPattern
|
421
venv/Lib/site-packages/pathspec/patterns/gitwildmatch.py
Normal file
421
venv/Lib/site-packages/pathspec/patterns/gitwildmatch.py
Normal file
@@ -0,0 +1,421 @@
|
||||
"""
|
||||
This module implements Git's wildmatch pattern matching which itself is derived
|
||||
from Rsync's wildmatch. Git uses wildmatch for its ".gitignore" files.
|
||||
"""
|
||||
|
||||
import re
|
||||
import warnings
|
||||
from typing import (
|
||||
AnyStr,
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
Tuple) # Replaced by `tuple` in 3.9.
|
||||
|
||||
from .. import util
|
||||
from ..pattern import RegexPattern
|
||||
|
||||
_BYTES_ENCODING = 'latin1'
|
||||
"""
|
||||
The encoding to use when parsing a byte string pattern.
|
||||
"""
|
||||
|
||||
_DIR_MARK = 'ps_d'
|
||||
"""
|
||||
The regex group name for the directory marker. This is only used by
|
||||
:class:`GitIgnoreSpec`.
|
||||
"""
|
||||
|
||||
|
||||
class GitWildMatchPatternError(ValueError):
|
||||
"""
|
||||
The :class:`GitWildMatchPatternError` indicates an invalid git wild match
|
||||
pattern.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class GitWildMatchPattern(RegexPattern):
|
||||
"""
|
||||
The :class:`GitWildMatchPattern` class represents a compiled Git wildmatch
|
||||
pattern.
|
||||
"""
|
||||
|
||||
# Keep the dict-less class hierarchy.
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def pattern_to_regex(
|
||||
cls,
|
||||
pattern: 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 the uncompiled regular expression (:class:`str`, :class:`bytes`, or
|
||||
:data:`None`); and whether matched files should be included (:data:`True`),
|
||||
excluded (:data:`False`), or if it is a null-operation (:data:`None`).
|
||||
"""
|
||||
if isinstance(pattern, 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 GitWildMatchPatternError(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 GitWildMatchPatternError(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:
|
||||
"""
|
||||
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}")
|
||||
|
||||
return regex
|
||||
|
||||
@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
|
||||
meta_characters = r"[]!*#?"
|
||||
|
||||
out_string = "".join("\\" + x if x in meta_characters else x for x in string)
|
||||
|
||||
if return_type is bytes:
|
||||
return out_string.encode(_BYTES_ENCODING)
|
||||
else:
|
||||
return out_string
|
||||
|
||||
util.register_pattern('gitwildmatch', GitWildMatchPattern)
|
||||
|
||||
|
||||
class GitIgnorePattern(GitWildMatchPattern):
|
||||
"""
|
||||
The :class:`GitIgnorePattern` class is deprecated by :class:`GitWildMatchPattern`.
|
||||
This class only exists to maintain compatibility with v0.4.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kw) -> None:
|
||||
"""
|
||||
Warn about deprecation.
|
||||
"""
|
||||
self._deprecated()
|
||||
super(GitIgnorePattern, self).__init__(*args, **kw)
|
||||
|
||||
@staticmethod
|
||||
def _deprecated() -> None:
|
||||
"""
|
||||
Warn about deprecation.
|
||||
"""
|
||||
warnings.warn((
|
||||
"GitIgnorePattern ('gitignore') is deprecated. Use GitWildMatchPattern "
|
||||
"('gitwildmatch') instead."
|
||||
), DeprecationWarning, stacklevel=3)
|
||||
|
||||
@classmethod
|
||||
def pattern_to_regex(cls, *args, **kw):
|
||||
"""
|
||||
Warn about deprecation.
|
||||
"""
|
||||
cls._deprecated()
|
||||
return super(GitIgnorePattern, cls).pattern_to_regex(*args, **kw)
|
||||
|
||||
# Register `GitIgnorePattern` as "gitignore" for backward compatibility with
|
||||
# v0.4.
|
||||
util.register_pattern('gitignore', GitIgnorePattern)
|
1
venv/Lib/site-packages/pathspec/py.typed
Normal file
1
venv/Lib/site-packages/pathspec/py.typed
Normal file
@@ -0,0 +1 @@
|
||||
# Marker file for PEP 561. The pathspec package uses inline types.
|
792
venv/Lib/site-packages/pathspec/util.py
Normal file
792
venv/Lib/site-packages/pathspec/util.py
Normal file
@@ -0,0 +1,792 @@
|
||||
"""
|
||||
This module provides utility methods for dealing with path-specs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import posixpath
|
||||
import stat
|
||||
import sys
|
||||
import warnings
|
||||
from collections.abc import (
|
||||
Collection as CollectionType,
|
||||
Iterable as IterableType)
|
||||
from dataclasses import (
|
||||
dataclass)
|
||||
from os import (
|
||||
PathLike)
|
||||
from typing import (
|
||||
Any,
|
||||
AnyStr,
|
||||
Callable, # Replaced by `collections.abc.Callable` in 3.9.
|
||||
Collection, # Replaced by `collections.abc.Collection` in 3.9.
|
||||
Dict, # Replaced by `dict` in 3.9.
|
||||
Generic,
|
||||
Iterable, # Replaced by `collections.abc.Iterable` in 3.9.
|
||||
Iterator, # Replaced by `collections.abc.Iterator` in 3.9.
|
||||
List, # Replaced by `list` in 3.9.
|
||||
Optional, # Replaced by `X | None` in 3.10.
|
||||
Sequence, # Replaced by `collections.abc.Sequence` in 3.9.
|
||||
Set, # Replaced by `set` in 3.9.
|
||||
Tuple, # Replaced by `tuple` in 3.9.
|
||||
TypeVar,
|
||||
Union) # Replaced by `X | Y` in 3.10.
|
||||
|
||||
from .pattern import (
|
||||
Pattern)
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
StrPath = Union[str, PathLike[str]]
|
||||
else:
|
||||
StrPath = Union[str, PathLike]
|
||||
|
||||
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,
|
||||
) -> 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 actual
|
||||
pattern (:class:`~pathspec.pattern.Pattern`).
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to be matched
|
||||
against *patterns*.
|
||||
|
||||
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`).
|
||||
"""
|
||||
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:`~pathspec.pattern.Pattern`)
|
||||
contains the patterns to use.
|
||||
|
||||
*files* (:class:`~collections.abc.Iterable` of :class:`str`) contains
|
||||
the normalized file paths to be matched against *patterns*.
|
||||
|
||||
*all_matches* (:class:`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, CollectionType) 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:`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:`~pathspec.pattern.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 a iterable (:class:`bool`).
|
||||
"""
|
||||
return isinstance(value, IterableType) and not isinstance(value, (str, bytes))
|
||||
|
||||
|
||||
def iter_tree_entries(
|
||||
root: StrPath,
|
||||
on_error: Optional[Callable[[OSError], None]] = None,
|
||||
follow_links: Optional[bool] = None,
|
||||
) -> Iterator['TreeEntry']:
|
||||
"""
|
||||
Walks the specified directory for all files and directories.
|
||||
|
||||
*root* (:class:`str` or :class:`os.PathLike`) is the root directory to
|
||||
search.
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`)
|
||||
optionally is the error handler for file-system exceptions. It will be
|
||||
called with the exception (:exc:`OSError`). Reraise the exception to
|
||||
abort the walk. Default is :data:`None` to ignore file-system
|
||||
exceptions.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) optionally is whether
|
||||
to walk symbolic links that resolve to directories. Default is
|
||||
:data:`None` for :data:`True`.
|
||||
|
||||
Raises :exc:`RecursionError` if recursion is detected.
|
||||
|
||||
Returns an :class:`~collections.abc.Iterator` yielding each file or
|
||||
directory entry (:class:`.TreeEntry`) relative to *root*.
|
||||
"""
|
||||
if on_error is not None and not callable(on_error):
|
||||
raise TypeError(f"on_error:{on_error!r} is not callable.")
|
||||
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
yield from _iter_tree_entries_next(os.path.abspath(root), '', {}, on_error, follow_links)
|
||||
|
||||
|
||||
def _iter_tree_entries_next(
|
||||
root_full: str,
|
||||
dir_rel: str,
|
||||
memo: Dict[str, str],
|
||||
on_error: Callable[[OSError], None],
|
||||
follow_links: bool,
|
||||
) -> Iterator['TreeEntry']:
|
||||
"""
|
||||
Scan the directory for all descendant files.
|
||||
|
||||
*root_full* (:class:`str`) the absolute path to the root directory.
|
||||
|
||||
*dir_rel* (:class:`str`) the path to the directory to scan relative to
|
||||
*root_full*.
|
||||
|
||||
*memo* (:class:`dict`) keeps track of ancestor directories
|
||||
encountered. Maps each ancestor real path (:class:`str`) to relative
|
||||
path (:class:`str`).
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`)
|
||||
optionally is the error handler for file-system exceptions.
|
||||
|
||||
*follow_links* (:class:`bool`) is whether to walk symbolic links that
|
||||
resolve to directories.
|
||||
|
||||
Yields each entry (:class:`.TreeEntry`).
|
||||
"""
|
||||
dir_full = os.path.join(root_full, dir_rel)
|
||||
dir_real = os.path.realpath(dir_full)
|
||||
|
||||
# Remember each encountered ancestor directory and its canonical
|
||||
# (real) path. If a canonical path is encountered more than once,
|
||||
# recursion has occurred.
|
||||
if dir_real not in memo:
|
||||
memo[dir_real] = dir_rel
|
||||
else:
|
||||
raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
|
||||
|
||||
with os.scandir(dir_full) as scan_iter:
|
||||
node_ent: os.DirEntry
|
||||
for node_ent in scan_iter:
|
||||
node_rel = os.path.join(dir_rel, node_ent.name)
|
||||
|
||||
# Inspect child node.
|
||||
try:
|
||||
node_lstat = node_ent.stat(follow_symlinks=False)
|
||||
except OSError as e:
|
||||
if on_error is not None:
|
||||
on_error(e)
|
||||
continue
|
||||
|
||||
if node_ent.is_symlink():
|
||||
# Child node is a link, inspect the target node.
|
||||
try:
|
||||
node_stat = node_ent.stat()
|
||||
except OSError as e:
|
||||
if on_error is not None:
|
||||
on_error(e)
|
||||
continue
|
||||
else:
|
||||
node_stat = node_lstat
|
||||
|
||||
if node_ent.is_dir(follow_symlinks=follow_links):
|
||||
# Child node is a directory, recurse into it and yield its
|
||||
# descendant files.
|
||||
yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
|
||||
|
||||
yield from _iter_tree_entries_next(root_full, node_rel, memo, on_error, follow_links)
|
||||
|
||||
elif node_ent.is_file() or node_ent.is_symlink():
|
||||
# Child node is either a file or an unfollowed link, yield it.
|
||||
yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
|
||||
|
||||
# NOTE: Make sure to remove the canonical (real) path of the directory
|
||||
# from the ancestors memo once we are done with it. This allows the
|
||||
# same directory to appear multiple times. If this is not done, the
|
||||
# second occurrence of the directory will be incorrectly interpreted
|
||||
# as a recursion. See <https://github.com/cpburnz/python-path-specification/pull/7>.
|
||||
del memo[dir_real]
|
||||
|
||||
|
||||
def iter_tree_files(
|
||||
root: StrPath,
|
||||
on_error: Optional[Callable[[OSError], None]] = None,
|
||||
follow_links: Optional[bool] = None,
|
||||
) -> Iterator[str]:
|
||||
"""
|
||||
Walks the specified directory for all files.
|
||||
|
||||
*root* (:class:`str` or :class:`os.PathLike`) is the root directory to
|
||||
search for files.
|
||||
|
||||
*on_error* (:class:`~collections.abc.Callable` or :data:`None`)
|
||||
optionally is the error handler for file-system exceptions. It will be
|
||||
called with the exception (:exc:`OSError`). Reraise the exception to
|
||||
abort the walk. Default is :data:`None` to ignore file-system
|
||||
exceptions.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) optionally is whether
|
||||
to walk symbolic links that resolve to directories. Default is
|
||||
:data:`None` for :data:`True`.
|
||||
|
||||
Raises :exc:`RecursionError` if recursion is detected.
|
||||
|
||||
Returns an :class:`~collections.abc.Iterator` yielding the path to
|
||||
each file (:class:`str`) relative to *root*.
|
||||
"""
|
||||
for entry in iter_tree_entries(root, on_error=on_error, follow_links=follow_links):
|
||||
if not entry.is_dir(follow_links):
|
||||
yield entry.path
|
||||
|
||||
|
||||
def iter_tree(root, on_error=None, follow_links=None):
|
||||
"""
|
||||
DEPRECATED: The :func:`.iter_tree` function is an alias for the
|
||||
:func:`.iter_tree_files` function.
|
||||
"""
|
||||
warnings.warn((
|
||||
"util.iter_tree() is deprecated. Use util.iter_tree_files() instead."
|
||||
), DeprecationWarning, stacklevel=2)
|
||||
return iter_tree_files(root, on_error=on_error, follow_links=follow_links)
|
||||
|
||||
|
||||
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:`~pathspec.pattern.Pattern`)
|
||||
contains the patterns to use.
|
||||
|
||||
*file* (:class:`str`) is the normalized file path to be matched
|
||||
against *patterns*.
|
||||
|
||||
Returns :data:`True` if *file* matched; otherwise, :data:`False`.
|
||||
"""
|
||||
matched = False
|
||||
for pattern in patterns:
|
||||
if pattern.include is not None and pattern.match_file(file) is not None:
|
||||
matched = pattern.include
|
||||
|
||||
return matched
|
||||
|
||||
|
||||
def match_files(
|
||||
patterns: Iterable[Pattern],
|
||||
files: Iterable[str],
|
||||
) -> Set[str]:
|
||||
"""
|
||||
DEPRECATED: This is an old function no longer used. Use the
|
||||
:func:`~pathspec.util.match_file` function with a loop for better results.
|
||||
|
||||
Matches the files to the patterns.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.Pattern`)
|
||||
contains the patterns to use.
|
||||
|
||||
*files* (:class:`~collections.abc.Iterable` of :class:`str`) contains
|
||||
the normalized file paths to be matched against *patterns*.
|
||||
|
||||
Returns the matched files (:class:`set` of :class:`str`).
|
||||
"""
|
||||
warnings.warn((
|
||||
f"{__name__}.match_files() is deprecated. Use {__name__}.match_file() with "
|
||||
f"a loop for better results."
|
||||
), DeprecationWarning, stacklevel=2)
|
||||
|
||||
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
|
||||
``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 ``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
|
||||
|
||||
|
||||
def normalize_files(
|
||||
files: Iterable[StrPath],
|
||||
separators: Optional[Collection[str]] = None,
|
||||
) -> Dict[str, List[StrPath]]:
|
||||
"""
|
||||
DEPRECATED: 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`).
|
||||
"""
|
||||
warnings.warn((
|
||||
"util.normalize_files() is deprecated. Use util.normalize_file() "
|
||||
"with a loop for better results."
|
||||
), DeprecationWarning, stacklevel=2)
|
||||
|
||||
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(AlreadyRegisteredError, self).__init__(name, pattern_factory)
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
"""
|
||||
*message* (:class:`str`) is the error message.
|
||||
"""
|
||||
return "{name!r} is already registered for pattern factory:{pattern_factory!r}.".format(
|
||||
name=self.name,
|
||||
pattern_factory=self.pattern_factory,
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> 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(RecursionError, self).__init__(real_path, first_path, second_path)
|
||||
|
||||
@property
|
||||
def first_path(self) -> str:
|
||||
"""
|
||||
*first_path* (:class:`str`) is the first path encountered for
|
||||
:attr:`self.real_path <RecursionError.real_path>`.
|
||||
"""
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
"""
|
||||
*message* (:class:`str`) is the error message.
|
||||
"""
|
||||
return "Real path {real!r} was encountered at {first!r} and then {second!r}.".format(
|
||||
real=self.real_path,
|
||||
first=self.first_path,
|
||||
second=self.second_path,
|
||||
)
|
||||
|
||||
@property
|
||||
def real_path(self) -> str:
|
||||
"""
|
||||
*real_path* (:class:`str`) is the real path that recursion was
|
||||
encountered on.
|
||||
"""
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def second_path(self) -> str:
|
||||
"""
|
||||
*second_path* (:class:`str`) is the second path encountered for
|
||||
:attr:`self.real_path <RecursionError.real_path>`.
|
||||
"""
|
||||
return self.args[2]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CheckResult(Generic[TStrPath]):
|
||||
"""
|
||||
The :class:`CheckResult` class contains information about the file and which
|
||||
pattern matched it.
|
||||
"""
|
||||
|
||||
# Make the class dict-less.
|
||||
__slots__ = (
|
||||
'file',
|
||||
'include',
|
||||
'index',
|
||||
)
|
||||
|
||||
file: TStrPath
|
||||
"""
|
||||
*file* (:class:`str` or :class:`os.PathLike`) is the file path.
|
||||
"""
|
||||
|
||||
include: Optional[bool]
|
||||
"""
|
||||
*include* (:class:`bool` or :data:`None`) is whether to include or exclude the
|
||||
file. If :data:`None`, no pattern matched.
|
||||
"""
|
||||
|
||||
index: Optional[int]
|
||||
"""
|
||||
*index* (:class:`int` or :data:`None`) is the index of the last pattern that
|
||||
matched. If :data:`None`, no pattern matched.
|
||||
"""
|
||||
|
||||
|
||||
class MatchDetail(object):
|
||||
"""
|
||||
The :class:`.MatchDetail` class contains information about
|
||||
"""
|
||||
|
||||
# Make the class dict-less.
|
||||
__slots__ = ('patterns',)
|
||||
|
||||
def __init__(self, patterns: Sequence[Pattern]) -> None:
|
||||
"""
|
||||
Initialize the :class:`.MatchDetail` instance.
|
||||
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`~pathspec.pattern.Pattern`)
|
||||
contains the patterns that matched the file in the order they were
|
||||
encountered.
|
||||
"""
|
||||
|
||||
self.patterns = patterns
|
||||
"""
|
||||
*patterns* (:class:`~collections.abc.Sequence` of :class:`~pathspec.pattern.Pattern`)
|
||||
contains the patterns that matched the file in the order they were
|
||||
encountered.
|
||||
"""
|
||||
|
||||
|
||||
class TreeEntry(object):
|
||||
"""
|
||||
The :class:`.TreeEntry` class contains information about a file-system
|
||||
entry.
|
||||
"""
|
||||
|
||||
# Make the class dict-less.
|
||||
__slots__ = ('_lstat', 'name', 'path', '_stat')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
path: str,
|
||||
lstat: os.stat_result,
|
||||
stat: os.stat_result,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the :class:`.TreeEntry` instance.
|
||||
|
||||
*name* (:class:`str`) is the base name of the entry.
|
||||
|
||||
*path* (:class:`str`) is the relative path of the entry.
|
||||
|
||||
*lstat* (:class:`os.stat_result`) is the stat result of the direct
|
||||
entry.
|
||||
|
||||
*stat* (:class:`os.stat_result`) is the stat result of the entry,
|
||||
potentially linked.
|
||||
"""
|
||||
|
||||
self._lstat: os.stat_result = lstat
|
||||
"""
|
||||
*_lstat* (:class:`os.stat_result`) is the stat result of the direct
|
||||
entry.
|
||||
"""
|
||||
|
||||
self.name: str = name
|
||||
"""
|
||||
*name* (:class:`str`) is the base name of the entry.
|
||||
"""
|
||||
|
||||
self.path: str = path
|
||||
"""
|
||||
*path* (:class:`str`) is the path of the entry.
|
||||
"""
|
||||
|
||||
self._stat: os.stat_result = stat
|
||||
"""
|
||||
*_stat* (:class:`os.stat_result`) is the stat result of the linked
|
||||
entry.
|
||||
"""
|
||||
|
||||
def is_dir(self, follow_links: Optional[bool] = None) -> bool:
|
||||
"""
|
||||
Get whether the entry is a directory.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) is whether to follow
|
||||
symbolic links. If this is :data:`True`, a symlink to a directory
|
||||
will result in :data:`True`. Default is :data:`None` for :data:`True`.
|
||||
|
||||
Returns whether the entry is a directory (:class:`bool`).
|
||||
"""
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
node_stat = self._stat if follow_links else self._lstat
|
||||
return stat.S_ISDIR(node_stat.st_mode)
|
||||
|
||||
def is_file(self, follow_links: Optional[bool] = None) -> bool:
|
||||
"""
|
||||
Get whether the entry is a regular file.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) is whether to follow
|
||||
symbolic links. If this is :data:`True`, a symlink to a regular file
|
||||
will result in :data:`True`. Default is :data:`None` for :data:`True`.
|
||||
|
||||
Returns whether the entry is a regular file (:class:`bool`).
|
||||
"""
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
node_stat = self._stat if follow_links else self._lstat
|
||||
return stat.S_ISREG(node_stat.st_mode)
|
||||
|
||||
def is_symlink(self) -> bool:
|
||||
"""
|
||||
Returns whether the entry is a symbolic link (:class:`bool`).
|
||||
"""
|
||||
return stat.S_ISLNK(self._lstat.st_mode)
|
||||
|
||||
def stat(self, follow_links: Optional[bool] = None) -> os.stat_result:
|
||||
"""
|
||||
Get the cached stat result for the entry.
|
||||
|
||||
*follow_links* (:class:`bool` or :data:`None`) is whether to follow
|
||||
symbolic links. If this is :data:`True`, the stat result of the
|
||||
linked file will be returned. Default is :data:`None` for :data:`True`.
|
||||
|
||||
Returns that stat result (:class:`os.stat_result`).
|
||||
"""
|
||||
if follow_links is None:
|
||||
follow_links = True
|
||||
|
||||
return self._stat if follow_links else self._lstat
|
Reference in New Issue
Block a user