1"""
2This module provides an object oriented interface for pattern matching of files.
3"""
4
5from collections.abc import (
6 Collection as CollectionType)
7from itertools import (
8 zip_longest)
9from typing import (
10 AnyStr,
11 Callable, # Replaced by `collections.abc.Callable` in 3.9.
12 Collection, # Replaced by `collections.abc.Collection` in 3.9.
13 Iterable, # Replaced by `collections.abc.Iterable` in 3.9.
14 Iterator, # Replaced by `collections.abc.Iterator` in 3.9.
15 Optional, # Replaced by `X | None` in 3.10.
16 Type, # Replaced by `type` in 3.9.
17 TypeVar,
18 Union) # Replaced by `X | Y` in 3.10.
19
20from . import util
21from .pattern import (
22 Pattern)
23from .util import (
24 CheckResult,
25 StrPath,
26 TStrPath,
27 TreeEntry,
28 _filter_check_patterns,
29 _is_iterable,
30 normalize_file)
31
32Self = TypeVar("Self", bound="PathSpec")
33"""
34:class:`PathSpec` self type hint to support Python v<3.11 using PEP 673
35recommendation.
36"""
37
38
39class PathSpec(object):
40 """
41 The :class:`PathSpec` class is a wrapper around a list of compiled
42 :class:`.Pattern` instances.
43 """
44
45 def __init__(self, patterns: Iterable[Pattern]) -> None:
46 """
47 Initializes the :class:`PathSpec` instance.
48
49 *patterns* (:class:`~collections.abc.Collection` or :class:`~collections.abc.Iterable`)
50 yields each compiled pattern (:class:`.Pattern`).
51 """
52 if not isinstance(patterns, CollectionType):
53 patterns = list(patterns)
54
55 self.patterns: Collection[Pattern] = patterns
56 """
57 *patterns* (:class:`~collections.abc.Collection` of :class:`.Pattern`)
58 contains the compiled patterns.
59 """
60
61 def __eq__(self, other: object) -> bool:
62 """
63 Tests the equality of this path-spec with *other* (:class:`PathSpec`)
64 by comparing their :attr:`~PathSpec.patterns` attributes.
65 """
66 if isinstance(other, PathSpec):
67 paired_patterns = zip_longest(self.patterns, other.patterns)
68 return all(a == b for a, b in paired_patterns)
69 else:
70 return NotImplemented
71
72 def __len__(self) -> int:
73 """
74 Returns the number of compiled patterns this path-spec contains
75 (:class:`int`).
76 """
77 return len(self.patterns)
78
79 def __add__(self: Self, other: "PathSpec") -> Self:
80 """
81 Combines the :attr:`Pathspec.patterns` patterns from two
82 :class:`PathSpec` instances.
83 """
84 if isinstance(other, PathSpec):
85 return self.__class__(self.patterns + other.patterns)
86 else:
87 return NotImplemented
88
89 def __iadd__(self: Self, other: "PathSpec") -> Self:
90 """
91 Adds the :attr:`Pathspec.patterns` patterns from one :class:`PathSpec`
92 instance to this instance.
93 """
94 if isinstance(other, PathSpec):
95 self.patterns += other.patterns
96 return self
97 else:
98 return NotImplemented
99
100 def check_file(
101 self,
102 file: TStrPath,
103 separators: Optional[Collection[str]] = None,
104 ) -> CheckResult[TStrPath]:
105 """
106 Check the files against this path-spec.
107
108 *file* (:class:`str` or :class:`os.PathLike`) is the file path to be
109 matched against :attr:`self.patterns <PathSpec.patterns>`.
110
111 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
112 :data:`None`) optionally contains the path separators to normalize. See
113 :func:`~pathspec.util.normalize_file` for more information.
114
115 Returns the file check result (:class:`~pathspec.util.CheckResult`).
116 """
117 norm_file = normalize_file(file, separators)
118 include, index = self._match_file(enumerate(self.patterns), norm_file)
119 return CheckResult(file, include, index)
120
121 def check_files(
122 self,
123 files: Iterable[TStrPath],
124 separators: Optional[Collection[str]] = None,
125 ) -> Iterator[CheckResult[TStrPath]]:
126 """
127 Check the files against this path-spec.
128
129 *files* (:class:`~collections.abc.Iterable` of :class:`str` or
130 :class:`os.PathLike`) contains the file paths to be checked against
131 :attr:`self.patterns <PathSpec.patterns>`.
132
133 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
134 :data:`None`) optionally contains the path separators to normalize. See
135 :func:`~pathspec.util.normalize_file` for more information.
136
137 Returns an :class:`~collections.abc.Iterator` yielding each file check
138 result (:class:`~pathspec.util.CheckResult`).
139 """
140 if not _is_iterable(files):
141 raise TypeError(f"files:{files!r} is not an iterable.")
142
143 use_patterns = _filter_check_patterns(self.patterns)
144 for orig_file in files:
145 norm_file = normalize_file(orig_file, separators)
146 include, index = self._match_file(use_patterns, norm_file)
147 yield CheckResult(orig_file, include, index)
148
149 def check_tree_files(
150 self,
151 root: StrPath,
152 on_error: Optional[Callable[[OSError], None]] = None,
153 follow_links: Optional[bool] = None,
154 ) -> Iterator[CheckResult[str]]:
155 """
156 Walks the specified root path for all files and checks them against this
157 path-spec.
158
159 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to
160 search for files.
161
162 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally
163 is the error handler for file-system exceptions. It will be called with the
164 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
165 is :data:`None` to ignore file-system exceptions.
166
167 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
168 symbolic links that resolve to directories. Default is :data:`None` for
169 :data:`True`.
170
171 *negate* (:class:`bool` or :data:`None`) is whether to negate the match
172 results of the patterns. If :data:`True`, a pattern matching a file will
173 exclude the file rather than include it. Default is :data:`None` for
174 :data:`False`.
175
176 Returns an :class:`~collections.abc.Iterator` yielding each file check
177 result (:class:`~pathspec.util.CheckResult`).
178 """
179 files = util.iter_tree_files(root, on_error=on_error, follow_links=follow_links)
180 yield from self.check_files(files)
181
182 @classmethod
183 def from_lines(
184 cls: Type[Self],
185 pattern_factory: Union[str, Callable[[AnyStr], Pattern]],
186 lines: Iterable[AnyStr],
187 ) -> Self:
188 """
189 Compiles the pattern lines.
190
191 *pattern_factory* can be either the name of a registered pattern factory
192 (:class:`str`), or a :class:`~collections.abc.Callable` used to compile
193 patterns. It must accept an uncompiled pattern (:class:`str`) and return the
194 compiled pattern (:class:`.Pattern`).
195
196 *lines* (:class:`~collections.abc.Iterable`) yields each uncompiled pattern
197 (:class:`str`). This simply has to yield each line so that it can be a
198 :class:`io.TextIOBase` (e.g., from :func:`open` or :class:`io.StringIO`) or
199 the result from :meth:`str.splitlines`.
200
201 Returns the :class:`PathSpec` instance.
202 """
203 if isinstance(pattern_factory, str):
204 pattern_factory = util.lookup_pattern(pattern_factory)
205
206 if not callable(pattern_factory):
207 raise TypeError(f"pattern_factory:{pattern_factory!r} is not callable.")
208
209 if not _is_iterable(lines):
210 raise TypeError(f"lines:{lines!r} is not an iterable.")
211
212 patterns = [pattern_factory(line) for line in lines if line]
213 return cls(patterns)
214
215 def match_entries(
216 self,
217 entries: Iterable[TreeEntry],
218 separators: Optional[Collection[str]] = None,
219 *,
220 negate: Optional[bool] = None,
221 ) -> Iterator[TreeEntry]:
222 """
223 Matches the entries to this path-spec.
224
225 *entries* (:class:`~collections.abc.Iterable` of :class:`~pathspec.util.TreeEntry`)
226 contains the entries to be matched against :attr:`self.patterns <PathSpec.patterns>`.
227
228 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
229 :data:`None`) optionally contains the path separators to normalize. See
230 :func:`~pathspec.util.normalize_file` for more information.
231
232 *negate* (:class:`bool` or :data:`None`) is whether to negate the match
233 results of the patterns. If :data:`True`, a pattern matching a file will
234 exclude the file rather than include it. Default is :data:`None` for
235 :data:`False`.
236
237 Returns the matched entries (:class:`~collections.abc.Iterator` of
238 :class:`~pathspec.util.TreeEntry`).
239 """
240 if not _is_iterable(entries):
241 raise TypeError(f"entries:{entries!r} is not an iterable.")
242
243 use_patterns = _filter_check_patterns(self.patterns)
244 for entry in entries:
245 norm_file = normalize_file(entry.path, separators)
246 include, _index = self._match_file(use_patterns, norm_file)
247
248 if negate:
249 include = not include
250
251 if include:
252 yield entry
253
254 _match_file = staticmethod(util.check_match_file)
255 """
256 Match files using the `check_match_file()` utility function. Subclasses may
257 override this method as an instance method. It does not have to be a static
258 method. The signature for this method is subject to change.
259 """
260
261 def match_file(
262 self,
263 file: StrPath,
264 separators: Optional[Collection[str]] = None,
265 ) -> bool:
266 """
267 Matches the file to this path-spec.
268
269 *file* (:class:`str` or :class:`os.PathLike`) is the file path to be
270 matched against :attr:`self.patterns <PathSpec.patterns>`.
271
272 *separators* (:class:`~collections.abc.Collection` of :class:`str`)
273 optionally contains the path separators to normalize. See
274 :func:`~pathspec.util.normalize_file` for more information.
275
276 Returns :data:`True` if *file* matched; otherwise, :data:`False`.
277 """
278 norm_file = normalize_file(file, separators)
279 include, _index = self._match_file(enumerate(self.patterns), norm_file)
280 return bool(include)
281
282 def match_files(
283 self,
284 files: Iterable[StrPath],
285 separators: Optional[Collection[str]] = None,
286 *,
287 negate: Optional[bool] = None,
288 ) -> Iterator[StrPath]:
289 """
290 Matches the files to this path-spec.
291
292 *files* (:class:`~collections.abc.Iterable` of :class:`str` or
293 :class:`os.PathLike`) contains the file paths to be matched against
294 :attr:`self.patterns <PathSpec.patterns>`.
295
296 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
297 :data:`None`) optionally contains the path separators to normalize. See
298 :func:`~pathspec.util.normalize_file` for more information.
299
300 *negate* (:class:`bool` or :data:`None`) is whether to negate the match
301 results of the patterns. If :data:`True`, a pattern matching a file will
302 exclude the file rather than include it. Default is :data:`None` for
303 :data:`False`.
304
305 Returns the matched files (:class:`~collections.abc.Iterator` of
306 :class:`str` or :class:`os.PathLike`).
307 """
308 if not _is_iterable(files):
309 raise TypeError(f"files:{files!r} is not an iterable.")
310
311 use_patterns = _filter_check_patterns(self.patterns)
312 for orig_file in files:
313 norm_file = normalize_file(orig_file, separators)
314 include, _index = self._match_file(use_patterns, norm_file)
315
316 if negate:
317 include = not include
318
319 if include:
320 yield orig_file
321
322 def match_tree_entries(
323 self,
324 root: StrPath,
325 on_error: Optional[Callable[[OSError], None]] = None,
326 follow_links: Optional[bool] = None,
327 *,
328 negate: Optional[bool] = None,
329 ) -> Iterator[TreeEntry]:
330 """
331 Walks the specified root path for all files and matches them to this
332 path-spec.
333
334 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to
335 search.
336
337 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally
338 is the error handler for file-system exceptions. It will be called with the
339 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
340 is :data:`None` to ignore file-system exceptions.
341
342 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
343 symbolic links that resolve to directories. Default is :data:`None` for
344 :data:`True`.
345
346 *negate* (:class:`bool` or :data:`None`) is whether to negate the match
347 results of the patterns. If :data:`True`, a pattern matching a file will
348 exclude the file rather than include it. Default is :data:`None` for
349 :data:`False`.
350
351 Returns the matched files (:class:`~collections.abc.Iterator` of
352 :class:`.TreeEntry`).
353 """
354 entries = util.iter_tree_entries(root, on_error=on_error, follow_links=follow_links)
355 yield from self.match_entries(entries, negate=negate)
356
357 def match_tree_files(
358 self,
359 root: StrPath,
360 on_error: Optional[Callable[[OSError], None]] = None,
361 follow_links: Optional[bool] = None,
362 *,
363 negate: Optional[bool] = None,
364 ) -> Iterator[str]:
365 """
366 Walks the specified root path for all files and matches them to this
367 path-spec.
368
369 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to
370 search for files.
371
372 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally
373 is the error handler for file-system exceptions. It will be called with the
374 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
375 is :data:`None` to ignore file-system exceptions.
376
377 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
378 symbolic links that resolve to directories. Default is :data:`None` for
379 :data:`True`.
380
381 *negate* (:class:`bool` or :data:`None`) is whether to negate the match
382 results of the patterns. If :data:`True`, a pattern matching a file will
383 exclude the file rather than include it. Default is :data:`None` for
384 :data:`False`.
385
386 Returns the matched files (:class:`~collections.abc.Iterable` of
387 :class:`str`).
388 """
389 files = util.iter_tree_files(root, on_error=on_error, follow_links=follow_links)
390 yield from self.match_files(files, negate=negate)
391
392 # Alias `match_tree_files()` as `match_tree()` for backward compatibility
393 # before v0.3.2.
394 match_tree = match_tree_files