Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pathspec/util.py: 32%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2This module provides utility methods for dealing with path-specs.
3"""
4from __future__ import annotations
6import os
7import os.path
8import pathlib
9import posixpath
10import stat
11import warnings
12from collections.abc import (
13 Collection,
14 Iterable,
15 Iterator,
16 Sequence)
17from dataclasses import (
18 dataclass)
19from typing import (
20 Any,
21 Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
22 Generic,
23 Optional, # Replaced by `X | None` in 3.10.
24 TypeVar,
25 Union, # Replaced by `X | Y` in 3.10.
26 cast)
28from .pattern import (
29 Pattern)
30from ._typing import (
31 AnyStr, # Removed in 3.18.
32 deprecated) # Added in 3.13.
34StrPath = Union[str, os.PathLike[str]]
36TPattern = TypeVar('TPattern', bound=Pattern)
37"""
38Type variable for :class:`.Pattern`. This is used by :class:`pathspec.pathspec.PathSpec`
39to specialize the type of patterns.
40"""
42TPattern_co = TypeVar('TPattern_co', bound=Pattern, covariant=True)
43"""
44Type variable for :class:`.Pattern` that is covariant. This is used by
45:class:`pathspec.pathspec.PathSpec` to specialize the type of patterns.
46"""
48TStrPath = TypeVar('TStrPath', bound=StrPath)
49"""
50Type variable for :class:`str` or :class:`os.PathLike`.
51"""
53NORMALIZE_PATH_SEPS = [
54 cast(str, __sep)
55 for __sep in [os.sep, os.altsep]
56 if __sep and __sep != posixpath.sep
57]
58"""
59*NORMALIZE_PATH_SEPS* (:class:`list` of :class:`str`) contains the path
60separators that need to be normalized to the POSIX separator for the current
61operating system. The separators are determined by examining :data:`os.sep` and
62:data:`os.altsep`.
63"""
65_registered_patterns: dict[str, Callable[[Union[str, bytes]], Pattern]] = {}
66"""
67*_registered_patterns* (:class:`dict`) maps a name (:class:`str`) to the
68registered pattern factory (:class:`~collections.abc.Callable`).
69"""
72def append_dir_sep(path: pathlib.Path) -> str:
73 """
74 Appends the path separator to the path if the path is a directory. This can be
75 used to aid in distinguishing between directories and files on the file-system
76 by relying on the presence of a trailing path separator.
78 *path* (:class:`pathlib.Path`) is the path to use.
80 Returns the path (:class:`str`).
81 """
82 str_path = str(path)
83 if path.is_dir():
84 str_path += os.sep
86 return str_path
89def check_match_file(
90 patterns: Iterable[tuple[int, Pattern]],
91 file: str,
92 is_reversed: Optional[bool] = None,
93) -> tuple[Optional[bool], Optional[int]]:
94 """
95 Check the file against the patterns.
97 *patterns* (:class:`~collections.abc.Iterable`) yields each indexed pattern
98 (:class:`tuple`) which contains the pattern index (:class:`int`) and actua
99 pattern (:class:`.Pattern`).
101 *file* (:class:`str`) is the normalized file path to be matched against
102 *patterns*.
104 *is_reversed* (:class:`bool` or :data:`None`) is whether the order of the
105 patterns has been reversed. Default is :data:`None` for :data:`False`.
106 Reversing the order of the patterns is an optimization.
108 Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
109 or :data:`None`), and the index of the last matched pattern (:class:`int` or
110 :data:`None`).
111 """
112 if is_reversed:
113 # Check patterns in reverse order. The first pattern that matches takes
114 # precedence.
115 for index, pattern in patterns:
116 if pattern.include is not None and pattern.match_file(file) is not None:
117 return pattern.include, index
119 return None, None
121 else:
122 # Check all patterns. The last pattern that matches takes precedence.
123 out_include: Optional[bool] = None
124 out_index: Optional[int] = None
125 for index, pattern in patterns:
126 if pattern.include is not None and pattern.match_file(file) is not None:
127 out_include = pattern.include
128 out_index = index
130 return out_include, out_index
133def detailed_match_files(
134 patterns: Iterable[Pattern],
135 files: Iterable[str],
136 all_matches: Optional[bool] = None,
137) -> dict[str, MatchDetail]:
138 """
139 Matches the files to the patterns, and returns which patterns matched the
140 files.
142 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
143 the patterns to use.
145 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
146 normalized file paths to be matched against *patterns*.
148 *all_matches* (:class:`bool` or :data:`None`) is whether to return all matches
149 patterns (:data:`True`), or only the last matched pattern (:data:`False`).
150 Default is :data:`None` for :data:`False`.
152 Returns the matched files (:class:`dict`) which maps each matched file
153 (:class:`str`) to the patterns that matched in order (:class:`.MatchDetail`).
154 """
155 all_files = files if isinstance(files, Collection) else list(files)
156 return_files: dict[str, MatchDetail] = {}
157 for pattern in patterns:
158 if pattern.include is not None:
159 result_files = pattern.match(all_files) # TODO: Replace with `.match_file()`.
160 if pattern.include:
161 # Add files and record pattern.
162 for result_file in result_files:
163 if result_file in return_files:
164 # We know here that .patterns is a list, because we made it here
165 if all_matches:
166 return_files[result_file].patterns.append(pattern) # type: ignore[attr-defined]
167 else:
168 return_files[result_file].patterns[0] = pattern # type: ignore[index]
169 else:
170 return_files[result_file] = MatchDetail([pattern])
172 else:
173 # Remove files.
174 for file in result_files:
175 return_files.pop(file, None)
177 return return_files
180def _filter_check_patterns(
181 patterns: Iterable[Pattern],
182) -> list[tuple[int, Pattern]]:
183 """
184 Filters out null-patterns.
186 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
187 the patterns.
189 Returns a :class:`list` containing each indexed pattern (:class:`tuple`) which
190 contains the pattern index (:class:`int`) and the actual pattern
191 (:class:`.Pattern`).
192 """
193 return [
194 (__index, __pat)
195 for __index, __pat in enumerate(patterns)
196 if __pat.include is not None
197 ]
200def _get_sub_path_safe(root: str, sub_dir: StrPath) -> str:
201 """
202 Get the sub-directory path relative to the root directory. This ensures the
203 root path cannot be escaped.
205 *root* (:class:`str`) is the root directory.
207 *sub_dir* (:class:`str` or :class:`os.PathLike`) is the sub-directory path.
208 This path can be relative or absolute.
210 Returns the sub-directory path relative to the root directory (:class:`str`).
211 """
212 sub_abs = os.path.normpath(os.path.join(root, sub_dir))
213 if os.path.normcase(sub_abs) == os.path.normcase(root):
214 return ''
215 elif os.path.commonpath((root, sub_abs)) == root:
216 return os.path.relpath(sub_abs, root)
217 else:
218 raise ValueError(f"{sub_dir=!r} must be relative to {root=!r}.")
221def _is_iterable(value: Any) -> bool:
222 """
223 Check whether the value is an iterable (excludes strings).
225 *value* is the value to check.
227 Returns whether *value* is an iterable (:class:`bool`).
228 """
229 return isinstance(value, Iterable) and not isinstance(value, (str, bytes))
232@deprecated((
233 "pathspec.util.iter_tree() is deprecated. Use iter_tree_files() instead."
234))
235def iter_tree(root, on_error=None, follow_links=None):
236 """
237 .. version-deprecated:: 0.10.0
238 This is an alias for the :func:`.iter_tree_files` function.
239 """
240 return iter_tree_files(root, on_error=on_error, follow_links=follow_links)
243def iter_tree_entries(
244 root: StrPath,
245 on_error: Optional[Callable[[OSError], None]] = None,
246 follow_links: Optional[bool] = None,
247 subdir: Optional[StrPath] = None,
248) -> Iterator[TreeEntry]:
249 """
250 Walks the specified directory for all files and directories.
252 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to search.
254 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
255 the error handler for file-system exceptions. It will be called with the
256 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
257 is :data:`None` to ignore file-system exceptions.
259 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
260 symbolic links that resolve to directories. Default is :data:`None` for
261 :data:`True`.
263 *subdir* (:class:`str`, :class:`os.PathLike`, or :data:`None`) is a sub
264 directory of *root* to constrain searching to. If a relative path, it is
265 treated as relative to *root*. If an absolute path, it must be a descendant of
266 *root*. Default is :data:`None` to search the entire *root* directory tree.
268 Raises :exc:`.RecursivePathError` if recursion is detected.
270 Returns an :class:`~collections.abc.Iterator` yielding each file or directory
271 entry (:class:`.TreeEntry`) relative to *root*.
272 """
273 if on_error is not None and not callable(on_error):
274 raise TypeError(f"on_error:{on_error!r} is not callable.")
276 if follow_links is None:
277 follow_links = True
279 root_abs = os.path.abspath(root)
281 # Ensure subdir does not escape root.
282 if subdir is not None:
283 dir_rel = _get_sub_path_safe(root_abs, subdir)
284 else:
285 dir_rel = ''
287 yield from _iter_tree_entries_next(root_abs, dir_rel, {}, on_error, follow_links)
290def _iter_tree_entries_next(
291 root_full: str,
292 dir_rel: str,
293 memo: dict[str, str],
294 on_error: Optional[Callable[[OSError], None]],
295 follow_links: bool,
296) -> Iterator[TreeEntry]:
297 """
298 Scan the directory for all descendant files.
300 *root_full* (:class:`str`) the absolute path to the root directory.
302 *dir_rel* (:class:`str`) the path to the directory to scan relative to
303 *root_full*.
305 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
306 each ancestor real path (:class:`str`) to relative path (:class:`str`).
308 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
309 the error handler for file-system exceptions.
311 *follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
312 to directories.
314 Yields each entry (:class:`.TreeEntry`).
315 """
316 dir_full = os.path.join(root_full, dir_rel)
317 dir_real = os.path.realpath(dir_full)
319 # Remember each encountered ancestor directory and its canonical (real) path.
320 # If a canonical path is encountered more than once, recursion has occurred.
321 if dir_real not in memo:
322 memo[dir_real] = dir_rel
323 else:
324 raise RecursivePathError(
325 real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel,
326 )
328 try:
329 scan_iter = os.scandir(dir_full)
330 except OSError as e:
331 del memo[dir_real]
332 if on_error is not None:
333 on_error(e)
334 return
336 with scan_iter:
337 node_ent: os.DirEntry
338 for node_ent in scan_iter:
339 node_rel = os.path.join(dir_rel, node_ent.name)
341 # Inspect child node.
342 try:
343 node_lstat = node_ent.stat(follow_symlinks=False)
344 is_dir = node_ent.is_dir(follow_symlinks=follow_links)
345 is_file = node_ent.is_file(follow_symlinks=follow_links)
346 is_symlink = node_ent.is_symlink()
347 except OSError as e:
348 if on_error is not None:
349 on_error(e)
350 continue
352 if follow_links and is_symlink:
353 # Child node is a link, inspect the target node.
354 try:
355 node_stat = node_ent.stat()
356 except OSError as e:
357 if on_error is not None:
358 on_error(e)
359 continue
360 else:
361 node_stat = node_lstat
363 if is_dir:
364 # Child node is a directory, recurse into it and yield its descendant
365 # files.
366 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
368 yield from _iter_tree_entries_next(root_full, node_rel, memo, on_error, follow_links)
370 elif is_file:
371 # Child node is a file, yield it.
372 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
374 elif not follow_links and is_symlink:
375 # Child node is an unfollowed link, yield it.
376 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
378 # NOTE: Make sure to remove the canonical (real) path of the directory from
379 # the ancestors memo once we are done with it. This allows the same directory
380 # to appear multiple times. If this is not done, the second occurrence of the
381 # directory will be incorrectly interpreted as a recursion. See
382 # <https://github.com/cpburnz/python-path-specification/pull/7>.
383 del memo[dir_real]
386# TODO: Add tests for subdir.
387def iter_tree_files(
388 root: StrPath,
389 on_error: Optional[Callable[[OSError], None]] = None,
390 follow_links: Optional[bool] = None,
391 subdir: Optional[StrPath] = None,
392) -> Iterator[str]:
393 """
394 Walks the specified directory for all files.
396 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to search
397 for files.
399 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
400 the error handler for file-system exceptions. It will be called with the
401 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
402 is :data:`None` to ignore file-system exceptions.
404 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
405 symbolic links that resolve to directories. Default is :data:`None` for
406 :data:`True`.
408 *subdir* (:class:`str`, :class:`os.PathLike`, or :data:`None`) is a sub
409 directory of *root* to constrain searching to. If a relative path, it is
410 treated as relative to *root*. If an absolute path, it must be a descendant of
411 *root*. Default is :data:`None` to search the entire *root* directory tree.
413 Raises :exc:`.RecursivePathError` if recursion is detected.
415 Returns an :class:`~collections.abc.Iterator` yielding the path to each file
416 (:class:`str`) relative to *root*.
417 """
418 if on_error is not None and not callable(on_error):
419 raise TypeError(f"on_error:{on_error!r} is not callable.")
421 if follow_links is None:
422 follow_links = True
424 root_abs = os.path.abspath(root)
426 # Ensure subdir does not escape root.
427 if subdir is not None:
428 dir_rel = _get_sub_path_safe(root_abs, subdir)
429 else:
430 dir_rel = ''
432 yield from _iter_tree_files_next(root_abs, dir_rel, {}, on_error, follow_links)
435def _iter_tree_files_next(
436 root_full: str,
437 dir_rel: str,
438 memo: dict[str, str],
439 on_error: Optional[Callable[[OSError], None]],
440 follow_links: bool,
441) -> Iterator[str]:
442 """
443 Scan the directory for all descendant files.
445 *root_full* (:class:`str`) the absolute path to the root directory.
447 *dir_rel* (:class:`str`) the path to the directory to scan relative to
448 *root_full*.
450 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
451 each ancestor real path (:class:`str`) to relative path (:class:`str`).
453 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
454 the error handler for file-system exceptions.
456 *follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
457 to directories.
459 Yields each file path (:class:`str`).
460 """
461 dir_full = os.path.join(root_full, dir_rel)
462 dir_real = os.path.realpath(dir_full)
464 # Remember each encountered ancestor directory and its canonical (real) path.
465 # If a canonical path is encountered more than once, recursion has occurred.
466 if dir_real not in memo:
467 memo[dir_real] = dir_rel
468 else:
469 raise RecursivePathError(
470 real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel,
471 )
473 try:
474 scan_iter = os.scandir(dir_full)
475 except OSError as e:
476 del memo[dir_real]
477 if on_error is not None:
478 on_error(e)
479 return
481 with scan_iter:
482 node_ent: os.DirEntry
483 for node_ent in scan_iter:
484 node_rel = os.path.join(dir_rel, node_ent.name)
486 try:
487 is_dir = node_ent.is_dir(follow_symlinks=follow_links)
488 is_file = node_ent.is_file(follow_symlinks=follow_links)
489 is_symlink = node_ent.is_symlink()
490 except OSError as e:
491 if on_error is not None:
492 on_error(e)
493 continue
495 if is_dir:
496 # Child node is a directory, recurse into it and yield its descendant
497 # files.
498 yield from _iter_tree_files_next(root_full, node_rel, memo, on_error, follow_links)
500 elif is_file:
501 # Child node is a file, yield it.
502 yield node_rel
504 elif not follow_links and is_symlink:
505 # Child node is an unfollowed link, yield it.
506 yield node_rel
508 # NOTE: Make sure to remove the canonical (real) path of the directory from
509 # the ancestors memo once we are done with it. This allows the same directory
510 # to appear multiple times. If this is not done, the second occurrence of the
511 # directory will be incorrectly interpreted as a recursion. See
512 # <https://github.com/cpburnz/python-path-specification/pull/7>.
513 del memo[dir_real]
516def lookup_pattern(name: str) -> Callable[[AnyStr], Pattern]:
517 """
518 Looks up a registered pattern factory by name.
520 *name* (:class:`str`) is the name of the pattern factory.
522 Returns the registered pattern factory (:class:`~collections.abc.Callable`).
523 If no pattern factory is registered, raises :exc:`KeyError`.
524 """
525 return _registered_patterns[name] # type: ignore[return-value]
528def match_file(patterns: Iterable[Pattern], file: str) -> bool:
529 """
530 Matches the file to the patterns.
532 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
533 the patterns to use.
535 *file* (:class:`str`) is the normalized file path to be matched against
536 *patterns*.
538 Returns :data:`True` if *file* matched; otherwise, :data:`False`.
539 """
540 matched = False
541 for pattern in patterns:
542 if pattern.include is not None and pattern.match_file(file) is not None:
543 matched = pattern.include
545 return matched
548@deprecated((
549 "pathspec.util.match_files() is deprecated. Use match_file() with a loop for "
550 "better results."
551))
552def match_files(
553 patterns: Iterable[Pattern],
554 files: Iterable[str],
555) -> set[str]:
556 """
557 .. version-deprecated:: 0.10.0
558 This function is no longer used. Use the :func:`.match_file` function with a
559 loop for better results.
561 Matches the files to the patterns.
563 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
564 the patterns to use.
566 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
567 normalized file paths to be matched against *patterns*.
569 Returns the matched files (:class:`set` of :class:`str`).
570 """
571 use_patterns = [__pat for __pat in patterns if __pat.include is not None]
573 return_files = set()
574 for file in files:
575 if match_file(use_patterns, file):
576 return_files.add(file)
578 return return_files
581def normalize_file(
582 file: StrPath,
583 separators: Optional[Collection[str]] = None,
584) -> str:
585 """
586 Normalizes the file path to use the POSIX path separator (i.e., ``"/"``), and
587 make the paths relative (remove leading ``"/"``).
589 *file* (:class:`str` or :class:`os.PathLike`) is the file path.
591 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
592 :data:`None`) optionally contains the path separators to normalize. This does
593 not need to include the POSIX path separator (``"/"``), but including it will
594 not affect the results. Default is ``None`` for :data:`.NORMALIZE_PATH_SEPS`.
595 To prevent normalization, pass an empty container (e.g., an empty tuple
596 ``()``).
598 Returns the normalized file path (:class:`str`).
599 """
600 # Normalize path separators.
601 if separators is None:
602 separators = NORMALIZE_PATH_SEPS
604 assert separators is not None, separators
606 # Convert path object to string.
607 norm_file: str = os.fspath(file)
609 for sep in separators:
610 norm_file = norm_file.replace(sep, posixpath.sep)
612 if norm_file.startswith('/'):
613 # Make path relative.
614 norm_file = norm_file[1:]
616 elif norm_file.startswith('./'):
617 # Remove current directory prefix.
618 norm_file = norm_file[2:]
620 return norm_file
623@deprecated((
624 "pathspec.util.normalize_files() is deprecated. Use normalize_file() with a "
625 "loop for better results."
626))
627def normalize_files(
628 files: Iterable[StrPath],
629 separators: Optional[Collection[str]] = None,
630) -> dict[str, list[StrPath]]:
631 """
632 .. version-deprecated:: 0.10.0
633 This function is no longer used. Use the :func:`.normalize_file` function
634 with a loop for better results.
636 Normalizes the file paths to use the POSIX path separator.
638 *files* (:class:`~collections.abc.Iterable` of :class:`str` or
639 :class:`os.PathLike`) contains the file paths to be normalized.
641 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
642 :data:`None`) optionally contains the path separators to normalize. See
643 :func:`.normalize_file` for more information.
645 Returns a :class:`dict` mapping each normalized file path (:class:`str`) to
646 the original file paths (:class:`list` of :class:`str` or
647 :class:`os.PathLike`).
648 """
649 norm_files: dict[str, list[StrPath]] = {}
650 for path in files:
651 norm_file = normalize_file(path, separators=separators)
652 if norm_file in norm_files:
653 norm_files[norm_file].append(path)
654 else:
655 norm_files[norm_file] = [path]
657 return norm_files
660def register_pattern(
661 name: str,
662 pattern_factory: Union[Callable[[Union[str, bytes]], Pattern], type[Pattern]],
663 override: Optional[bool] = None,
664) -> None:
665 """
666 Registers the specified pattern factory.
668 *name* (:class:`str`) is the name to register the pattern factory under.
670 *pattern_factory* (:class:`~collections.abc.Callable`) is used to compile
671 patterns. It must accept an uncompiled pattern (:class:`str`) and return the
672 compiled pattern (:class:`.Pattern`).
674 *override* (:class:`bool` or :data:`None`) optionally is whether to allow
675 overriding an already registered pattern under the same name (:data:`True`),
676 instead of raising an :exc:`.AlreadyRegisteredError` (:data:`False`). Default
677 is :data:`None` for :data:`False`.
678 """
679 if not isinstance(name, str):
680 raise TypeError(f"{name=!r} is not a string.")
682 if not callable(pattern_factory):
683 raise TypeError(f"{pattern_factory=!r} is not callable.")
685 if name in _registered_patterns and not override:
686 raise AlreadyRegisteredError(name, _registered_patterns[name])
688 _registered_patterns[name] = pattern_factory # type: ignore
691class AlreadyRegisteredError(Exception):
692 """
693 The :exc:`AlreadyRegisteredError` exception is raised when a pattern factory
694 is registered under a name already in use.
695 """
697 def __init__(
698 self,
699 name: str,
700 pattern_factory: Callable[[Union[str, bytes]], Pattern],
701 ) -> None:
702 """
703 Initializes the :exc:`AlreadyRegisteredError` instance.
705 *name* (:class:`str`) is the name of the registered pattern.
707 *pattern_factory* (:class:`~collections.abc.Callable`) is the registered
708 pattern factory.
709 """
710 super().__init__(name, pattern_factory)
712 @property
713 def message(self) -> str:
714 """
715 *message* (:class:`str`) is the error message.
716 """
717 return (
718 f"{self.name!r} is already registered for pattern factory="
719 f"{self.pattern_factory!r}."
720 )
722 @property
723 def name(self) -> str:
724 """
725 *name* (:class:`str`) is the name of the registered pattern.
726 """
727 return self.args[0]
729 @property
730 def pattern_factory(self) -> Callable[[Union[str, bytes]], Pattern]:
731 """
732 *pattern_factory* (:class:`~collections.abc.Callable`) is the registered
733 pattern factory.
734 """
735 return self.args[1]
738class RecursivePathError(Exception):
739 """
740 The :exc:`RecursivePathError` exception is raised when recursion is detected.
741 """
743 def __init__(
744 self,
745 real_path: str,
746 first_path: str,
747 second_path: str,
748 ) -> None:
749 """
750 Initializes the :exc:`RecursivePathError` instance.
752 *real_path* (:class:`str`) is the real path that recursion was encountered
753 on.
755 *first_path* (:class:`str`) is the first path encountered for *real_path*.
757 *second_path* (:class:`str`) is the second path encountered for *real_path*.
758 """
759 super().__init__(real_path, first_path, second_path)
761 @property
762 def first_path(self) -> str:
763 """
764 *first_path* (:class:`str`) is the first path encountered for
765 :attr:`self.real_path <RecursivePathError.real_path>`.
766 """
767 return self.args[1]
769 @property
770 def message(self) -> str:
771 """
772 *message* (:class:`str`) is the error message.
773 """
774 return (
775 f"Real path {self.real_path!r} was encountered at {self.first_path!r} "
776 f"and then {self.second_path!r}."
777 )
779 @property
780 def real_path(self) -> str:
781 """
782 *real_path* (:class:`str`) is the real path that recursion was
783 encountered on.
784 """
785 return self.args[0]
787 @property
788 def second_path(self) -> str:
789 """
790 *second_path* (:class:`str`) is the second path encountered for
791 :attr:`self.real_path <RecursivePathError.real_path>`.
792 """
793 return self.args[2]
796@dataclass(frozen=True)
797class CheckResult(Generic[TStrPath]):
798 """
799 The :class:`CheckResult` class contains information about the file and which
800 pattern matched it.
801 """
803 # Make the class dict-less.
804 __slots__ = (
805 'file',
806 'include',
807 'index',
808 )
810 file: TStrPath
811 """
812 *file* (:class:`str` or :class:`os.PathLike`) is the file path.
813 """
815 include: Optional[bool]
816 """
817 *include* (:class:`bool` or :data:`None`) is whether to include or exclude the
818 file. If :data:`None`, no pattern matched.
819 """
821 index: Optional[int]
822 """
823 *index* (:class:`int` or :data:`None`) is the index of the last pattern that
824 matched. If :data:`None`, no pattern matched.
825 """
828class MatchDetail(object):
829 """
830 The :class:`.MatchDetail` class contains information about
831 """
833 # Make the class dict-less.
834 __slots__ = ('patterns',)
836 def __init__(self, patterns: Sequence[Pattern]) -> None:
837 """
838 Initialize the :class:`.MatchDetail` instance.
840 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
841 contains the patterns that matched the file in the order they were encountered.
842 """
844 self.patterns = patterns
845 """
846 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
847 contains the patterns that matched the file in the order they were
848 encountered.
849 """
852class TreeEntry(object):
853 """
854 The :class:`TreeEntry` class contains information about a file-system entry.
855 """
857 # Make the class dict-less.
858 __slots__ = ('_lstat', 'name', 'path', '_stat')
860 def __init__(
861 self,
862 name: str,
863 path: str,
864 lstat: os.stat_result,
865 stat: os.stat_result,
866 ) -> None:
867 """
868 Initialize the :class:`TreeEntry` instance.
870 *name* (:class:`str`) is the base name of the entry.
872 *path* (:class:`str`) is the relative path of the entry.
874 *lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
876 *stat* (:class:`os.stat_result`) is the stat result of the entry,
877 potentially linked.
878 """
880 self._lstat: os.stat_result = lstat
881 """
882 *_lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
883 """
885 self.name: str = name
886 """
887 *name* (:class:`str`) is the base name of the entry.
888 """
890 self.path: str = path
891 """
892 *path* (:class:`str`) is the path of the entry.
893 """
895 self._stat: os.stat_result = stat
896 """
897 *_stat* (:class:`os.stat_result`) is the stat result of the linked entry.
898 """
900 def is_dir(self, follow_links: Optional[bool] = None) -> bool:
901 """
902 Get whether the entry is a directory.
904 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
905 links. If this is :data:`True`, a symlink to a directory will result in
906 :data:`True`. Default is :data:`None` for :data:`True`.
908 Returns whether the entry is a directory (:class:`bool`).
909 """
910 if follow_links is None:
911 follow_links = True
913 node_stat = self._stat if follow_links else self._lstat
914 return stat.S_ISDIR(node_stat.st_mode)
916 def is_file(self, follow_links: Optional[bool] = None) -> bool:
917 """
918 Get whether the entry is a regular file.
920 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
921 links. If this is :data:`True`, a symlink to a regular file will result in
922 :data:`True`. Default is :data:`None` for :data:`True`.
924 Returns whether the entry is a regular file (:class:`bool`).
925 """
926 if follow_links is None:
927 follow_links = True
929 node_stat = self._stat if follow_links else self._lstat
930 return stat.S_ISREG(node_stat.st_mode)
932 def is_symlink(self) -> bool:
933 """
934 Returns whether the entry is a symbolic link (:class:`bool`).
935 """
936 return stat.S_ISLNK(self._lstat.st_mode)
938 def stat(self, follow_links: Optional[bool] = None) -> os.stat_result:
939 """
940 Get the cached stat result for the entry.
942 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
943 links. If this is :data:`True`, the stat result of the linked file will be
944 returned. Default is :data:`None` for :data:`True`.
946 Returns that stat result (:class:`os.stat_result`).
947 """
948 if follow_links is None:
949 follow_links = True
951 return self._stat if follow_links else self._lstat
954def __getattr__(name: str):
955 """
956 Look up a deprecated module-level attribute.
957 """
958 if name == 'RecursionError':
959 # Deprecated since 1.2.0. It conflicts with Python 3.5's built-in
960 # RecursionError.
961 warnings.warn((
962 "pathspec.util.RecursionError is deprecated, use RecursivePathError "
963 "instead."
964 ), DeprecationWarning, stacklevel=2)
965 return RecursivePathError
967 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")