]>
Commit | Line | Data |
---|---|---|
1 | """ | |
2 | This module provides :class:`.GitIgnoreSpec` which replicates | |
3 | *.gitignore* behavior. | |
4 | """ | |
5 | ||
6 | from typing import ( | |
7 | AnyStr, | |
8 | Callable, | |
9 | Collection, | |
10 | Iterable, | |
11 | Type, | |
12 | TypeVar, | |
13 | Union) | |
14 | ||
15 | from .pathspec import ( | |
16 | PathSpec) | |
17 | from .pattern import ( | |
18 | Pattern) | |
19 | from .patterns.gitwildmatch import ( | |
20 | GitWildMatchPattern, | |
21 | GitWildMatchPatternError, | |
22 | _DIR_MARK) | |
23 | from .util import ( | |
24 | _is_iterable) | |
25 | ||
26 | Self = TypeVar("Self", bound="GitIgnoreSpec") | |
27 | """ | |
28 | :class:`GitIgnoreSpec` self type hint to support Python v<3.11 using PEP | |
29 | 673 recommendation. | |
30 | """ | |
31 | ||
32 | ||
33 | class GitIgnoreSpec(PathSpec): | |
34 | """ | |
35 | The :class:`GitIgnoreSpec` class extends :class:`PathSpec` to | |
36 | replicate *.gitignore* behavior. | |
37 | """ | |
38 | ||
39 | def __eq__(self, other: object) -> bool: | |
40 | """ | |
41 | Tests the equality of this gitignore-spec with *other* | |
42 | (:class:`GitIgnoreSpec`) by comparing their :attr:`~PathSpec.patterns` | |
43 | attributes. A non-:class:`GitIgnoreSpec` will not compare equal. | |
44 | """ | |
45 | if isinstance(other, GitIgnoreSpec): | |
46 | return super().__eq__(other) | |
47 | elif isinstance(other, PathSpec): | |
48 | return False | |
49 | else: | |
50 | return NotImplemented | |
51 | ||
52 | @classmethod | |
53 | def from_lines( | |
54 | cls: Type[Self], | |
55 | lines: Iterable[AnyStr], | |
56 | pattern_factory: Union[str, Callable[[AnyStr], Pattern], None] = None, | |
57 | ) -> Self: | |
58 | """ | |
59 | Compiles the pattern lines. | |
60 | ||
61 | *lines* (:class:`~collections.abc.Iterable`) yields each uncompiled | |
62 | pattern (:class:`str`). This simply has to yield each line so it can | |
63 | be a :class:`io.TextIOBase` (e.g., from :func:`open` or | |
64 | :class:`io.StringIO`) or the result from :meth:`str.splitlines`. | |
65 | ||
66 | *pattern_factory* can be :data:`None`, the name of a registered | |
67 | pattern factory (:class:`str`), or a :class:`~collections.abc.Callable` | |
68 | used to compile patterns. The callable must accept an uncompiled | |
69 | pattern (:class:`str`) and return the compiled pattern (:class:`.Pattern`). | |
70 | Default is :data:`None` for :class:`.GitWildMatchPattern`). | |
71 | ||
72 | Returns the :class:`GitIgnoreSpec` instance. | |
73 | """ | |
74 | if pattern_factory is None: | |
75 | pattern_factory = GitWildMatchPattern | |
76 | ||
77 | elif (isinstance(lines, str) or callable(lines)) and _is_iterable(pattern_factory): | |
78 | # Support reversed order of arguments from PathSpec. | |
79 | pattern_factory, lines = lines, pattern_factory | |
80 | ||
81 | self = super().from_lines(pattern_factory, lines) | |
82 | return self # type: ignore | |
83 | ||
84 | @staticmethod | |
85 | def _match_file( | |
86 | patterns: Collection[GitWildMatchPattern], | |
87 | file: str, | |
88 | ) -> bool: | |
89 | """ | |
90 | Matches the file to the patterns. | |
91 | ||
92 | .. NOTE:: Subclasses of :class:`.PathSpec` may override this | |
93 | method as an instance method. It does not have to be a static | |
94 | method. | |
95 | ||
96 | *patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.Pattern`) | |
97 | contains the patterns to use. | |
98 | ||
99 | *file* (:class:`str`) is the normalized file path to be matched | |
100 | against *patterns*. | |
101 | ||
102 | Returns :data:`True` if *file* matched; otherwise, :data:`False`. | |
103 | """ | |
104 | out_matched = False | |
105 | out_priority = 0 | |
106 | for pattern in patterns: | |
107 | if pattern.include is not None: | |
108 | match = pattern.match_file(file) | |
109 | if match is not None: | |
110 | # Pattern matched. | |
111 | ||
112 | # Check for directory marker. | |
113 | try: | |
114 | dir_mark = match.match.group(_DIR_MARK) | |
115 | except IndexError as e: | |
116 | # NOTICE: The exact content of this error message is subject | |
117 | # to change. | |
118 | raise GitWildMatchPatternError(( | |
119 | f"Invalid git pattern: directory marker regex group is missing. " | |
120 | f"Debug: file={file!r} regex={pattern.regex!r} " | |
121 | f"group={_DIR_MARK!r} match={match.match!r}." | |
122 | )) from e | |
123 | ||
124 | if dir_mark: | |
125 | # Pattern matched by a directory pattern. | |
126 | priority = 1 | |
127 | else: | |
128 | # Pattern matched by a file pattern. | |
129 | priority = 2 | |
130 | ||
131 | if pattern.include and dir_mark: | |
132 | out_matched = pattern.include | |
133 | out_priority = priority | |
134 | elif priority >= out_priority: | |
135 | out_matched = pattern.include | |
136 | out_priority = priority | |
137 | ||
138 | return out_matched |