Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pathspec/util.py: 35%
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"""
5import os
6import os.path
7import pathlib
8import posixpath
9import stat
10from collections.abc import (
11 Collection,
12 Iterable,
13 Iterator,
14 Sequence)
15from dataclasses import (
16 dataclass)
17from typing import (
18 Any,
19 Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
20 Generic,
21 Optional, # Replaced by `X | None` in 3.10.
22 TypeVar,
23 Union) # Replaced by `X | Y` in 3.10.
25from .pattern import (
26 Pattern)
27from ._typing import (
28 AnyStr, # Removed in 3.18.
29 deprecated) # Added in 3.13.
31StrPath = Union[str, os.PathLike[str]]
33TStrPath = TypeVar("TStrPath", bound=StrPath)
34"""
35Type variable for :class:`str` or :class:`os.PathLike`.
36"""
38NORMALIZE_PATH_SEPS = [
39 __sep
40 for __sep in [os.sep, os.altsep]
41 if __sep and __sep != posixpath.sep
42]
43"""
44*NORMALIZE_PATH_SEPS* (:class:`list` of :class:`str`) contains the path
45separators that need to be normalized to the POSIX separator for the current
46operating system. The separators are determined by examining :data:`os.sep` and
47:data:`os.altsep`.
48"""
50_registered_patterns = {}
51"""
52*_registered_patterns* (:class:`dict`) maps a name (:class:`str`) to the
53registered pattern factory (:class:`~collections.abc.Callable`).
54"""
57def append_dir_sep(path: pathlib.Path) -> str:
58 """
59 Appends the path separator to the path if the path is a directory. This can be
60 used to aid in distinguishing between directories and files on the file-system
61 by relying on the presence of a trailing path separator.
63 *path* (:class:`pathlib.Path`) is the path to use.
65 Returns the path (:class:`str`).
66 """
67 str_path = str(path)
68 if path.is_dir():
69 str_path += os.sep
71 return str_path
74def check_match_file(
75 patterns: Iterable[tuple[int, Pattern]],
76 file: str,
77 is_reversed: Optional[bool] = None,
78) -> tuple[Optional[bool], Optional[int]]:
79 """
80 Check the file against the patterns.
82 *patterns* (:class:`~collections.abc.Iterable`) yields each indexed pattern
83 (:class:`tuple`) which contains the pattern index (:class:`int`) and actua
84 pattern (:class:`.Pattern`).
86 *file* (:class:`str`) is the normalized file path to be matched against
87 *patterns*.
89 *is_reversed* (:class:`bool` or :data:`None`) is whether the order of the
90 patterns has been reversed. Default is :data:`None` for :data:`False`.
91 Reversing the order of the patterns is an optimization.
93 Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
94 or :data:`None`), and the index of the last matched pattern (:class:`int` or
95 :data:`None`).
96 """
97 if is_reversed:
98 # Check patterns in reverse order. The first pattern that matches takes
99 # precedence.
100 for index, pattern in patterns:
101 if pattern.include is not None and pattern.match_file(file) is not None:
102 return pattern.include, index
104 return None, None
106 else:
107 # Check all patterns. The last pattern that matches takes precedence.
108 out_include: Optional[bool] = None
109 out_index: Optional[int] = None
110 for index, pattern in patterns:
111 if pattern.include is not None and pattern.match_file(file) is not None:
112 out_include = pattern.include
113 out_index = index
115 return out_include, out_index
118def detailed_match_files(
119 patterns: Iterable[Pattern],
120 files: Iterable[str],
121 all_matches: Optional[bool] = None,
122) -> dict[str, 'MatchDetail']:
123 """
124 Matches the files to the patterns, and returns which patterns matched the
125 files.
127 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
128 the patterns to use.
130 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
131 normalized file paths to be matched against *patterns*.
133 *all_matches* (:class:`bool` or :data:`None`) is whether to return all matches
134 patterns (:data:`True`), or only the last matched pattern (:data:`False`).
135 Default is :data:`None` for :data:`False`.
137 Returns the matched files (:class:`dict`) which maps each matched file
138 (:class:`str`) to the patterns that matched in order (:class:`.MatchDetail`).
139 """
140 all_files = files if isinstance(files, Collection) else list(files)
141 return_files = {}
142 for pattern in patterns:
143 if pattern.include is not None:
144 result_files = pattern.match(all_files) # TODO: Replace with `.match_file()`.
145 if pattern.include:
146 # Add files and record pattern.
147 for result_file in result_files:
148 if result_file in return_files:
149 # We know here that .patterns is a list, becasue we made it here
150 if all_matches:
151 return_files[result_file].patterns.append(pattern) # type: ignore[attr-defined]
152 else:
153 return_files[result_file].patterns[0] = pattern # type: ignore[index]
154 else:
155 return_files[result_file] = MatchDetail([pattern])
157 else:
158 # Remove files.
159 for file in result_files:
160 del return_files[file]
162 return return_files
165def _filter_check_patterns(
166 patterns: Iterable[Pattern],
167) -> list[tuple[int, Pattern]]:
168 """
169 Filters out null-patterns.
171 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
172 the patterns.
174 Returns a :class:`list` containing each indexed pattern (:class:`tuple`) which
175 contains the pattern index (:class:`int`) and the actual pattern
176 (:class:`.Pattern`).
177 """
178 return [
179 (__index, __pat)
180 for __index, __pat in enumerate(patterns)
181 if __pat.include is not None
182 ]
185def _is_iterable(value: Any) -> bool:
186 """
187 Check whether the value is an iterable (excludes strings).
189 *value* is the value to check,
191 Returns whether *value* is an iterable (:class:`bool`).
192 """
193 return isinstance(value, Iterable) and not isinstance(value, (str, bytes))
196@deprecated((
197 "pathspec.util.iter_tree() is deprecated. Use iter_tree_files() instead."
198))
199def iter_tree(root, on_error=None, follow_links=None):
200 """
201 .. version-deprecated:: 0.10.0
202 This is an alias for the :func:`.iter_tree_files` function.
203 """
204 return iter_tree_files(root, on_error=on_error, follow_links=follow_links)
207def iter_tree_entries(
208 root: StrPath,
209 on_error: Optional[Callable[[OSError], None]] = None,
210 follow_links: Optional[bool] = None,
211) -> Iterator['TreeEntry']:
212 """
213 Walks the specified directory for all files and directories.
215 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to search.
217 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
218 the error handler for file-system exceptions. It will be called with the
219 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
220 is :data:`None` to ignore file-system exceptions.
222 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
223 symbolic links that resolve to directories. Default is :data:`None` for
224 :data:`True`.
226 Raises :exc:`.RecursionError` if recursion is detected.
228 Returns an :class:`~collections.abc.Iterator` yielding each file or directory
229 entry (:class:`.TreeEntry`) relative to *root*.
230 """
231 if on_error is not None and not callable(on_error):
232 raise TypeError(f"on_error:{on_error!r} is not callable.")
234 if follow_links is None:
235 follow_links = True
237 yield from _iter_tree_entries_next(os.path.abspath(root), '', {}, on_error, follow_links)
240def _iter_tree_entries_next(
241 root_full: str,
242 dir_rel: str,
243 memo: dict[str, str],
244 on_error: Callable[[OSError], None],
245 follow_links: bool,
246) -> Iterator['TreeEntry']:
247 """
248 Scan the directory for all descendant files.
250 *root_full* (:class:`str`) the absolute path to the root directory.
252 *dir_rel* (:class:`str`) the path to the directory to scan relative to
253 *root_full*.
255 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
256 each ancestor real path (:class:`str`) to relative path (:class:`str`).
258 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
259 the error handler for file-system exceptions.
261 *follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
262 to directories.
264 Yields each entry (:class:`.TreeEntry`).
265 """
266 dir_full = os.path.join(root_full, dir_rel)
267 dir_real = os.path.realpath(dir_full)
269 # Remember each encountered ancestor directory and its canonical (real) path.
270 # If a canonical path is encountered more than once, recursion has occurred.
271 if dir_real not in memo:
272 memo[dir_real] = dir_rel
273 else:
274 raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
276 with os.scandir(dir_full) as scan_iter:
277 node_ent: os.DirEntry
278 for node_ent in scan_iter:
279 node_rel = os.path.join(dir_rel, node_ent.name)
281 # Inspect child node.
282 try:
283 node_lstat = node_ent.stat(follow_symlinks=False)
284 except OSError as e:
285 if on_error is not None:
286 on_error(e)
287 continue
289 if node_ent.is_symlink():
290 # Child node is a link, inspect the target node.
291 try:
292 node_stat = node_ent.stat()
293 except OSError as e:
294 if on_error is not None:
295 on_error(e)
296 continue
297 else:
298 node_stat = node_lstat
300 if node_ent.is_dir(follow_symlinks=follow_links):
301 # Child node is a directory, recurse into it and yield its descendant
302 # files.
303 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
305 yield from _iter_tree_entries_next(root_full, node_rel, memo, on_error, follow_links)
307 elif node_ent.is_file() or node_ent.is_symlink():
308 # Child node is either a file or an unfollowed link, yield it.
309 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
311 # NOTE: Make sure to remove the canonical (real) path of the directory from
312 # the ancestors memo once we are done with it. This allows the same directory
313 # to appear multiple times. If this is not done, the second occurrence of the
314 # directory will be incorrectly interpreted as a recursion. See
315 # <https://github.com/cpburnz/python-path-specification/pull/7>.
316 del memo[dir_real]
319def iter_tree_files(
320 root: StrPath,
321 on_error: Optional[Callable[[OSError], None]] = None,
322 follow_links: Optional[bool] = None,
323) -> Iterator[str]:
324 """
325 Walks the specified directory for all files.
327 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to search
328 for files.
330 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
331 the error handler for file-system exceptions. It will be called with the
332 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
333 is :data:`None` to ignore file-system exceptions.
335 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
336 symbolic links that resolve to directories. Default is :data:`None` for
337 :data:`True`.
339 Raises :exc:`.RecursionError` if recursion is detected.
341 Returns an :class:`~collections.abc.Iterator` yielding the path to each file
342 (:class:`str`) relative to *root*.
343 """
344 if on_error is not None and not callable(on_error):
345 raise TypeError(f"on_error:{on_error!r} is not callable.")
347 if follow_links is None:
348 follow_links = True
350 yield from _iter_tree_files_next(os.path.abspath(root), '', {}, on_error, follow_links)
353def _iter_tree_files_next(
354 root_full: str,
355 dir_rel: str,
356 memo: dict[str, str],
357 on_error: Callable[[OSError], None],
358 follow_links: bool,
359) -> Iterator[str]:
360 """
361 Scan the directory for all descendant files.
363 *root_full* (:class:`str`) the absolute path to the root directory.
365 *dir_rel* (:class:`str`) the path to the directory to scan relative to
366 *root_full*.
368 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
369 each ancestor real path (:class:`str`) to relative path (:class:`str`).
371 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
372 the error handler for file-system exceptions.
374 *follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
375 to directories.
377 Yields each file path (:class:`str`).
378 """
379 dir_full = os.path.join(root_full, dir_rel)
380 dir_real = os.path.realpath(dir_full)
382 # Remember each encountered ancestor directory and its canonical (real) path.
383 # If a canonical path is encountered more than once, recursion has occurred.
384 if dir_real not in memo:
385 memo[dir_real] = dir_rel
386 else:
387 raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
389 with os.scandir(dir_full) as scan_iter:
390 node_ent: os.DirEntry
391 for node_ent in scan_iter:
392 node_rel = os.path.join(dir_rel, node_ent.name)
394 if node_ent.is_dir(follow_symlinks=follow_links):
395 # Child node is a directory, recurse into it and yield its descendant
396 # files.
397 yield from _iter_tree_files_next(root_full, node_rel, memo, on_error, follow_links)
399 elif node_ent.is_file():
400 # Child node is a file, yield it.
401 yield node_rel
403 elif not follow_links and node_ent.is_symlink():
404 # Child node is an unfollowed link, yield it.
405 yield node_rel
407 # NOTE: Make sure to remove the canonical (real) path of the directory from
408 # the ancestors memo once we are done with it. This allows the same directory
409 # to appear multiple times. If this is not done, the second occurrence of the
410 # directory will be incorrectly interpreted as a recursion. See
411 # <https://github.com/cpburnz/python-path-specification/pull/7>.
412 del memo[dir_real]
415def lookup_pattern(name: str) -> Callable[[AnyStr], Pattern]:
416 """
417 Lookups a registered pattern factory by name.
419 *name* (:class:`str`) is the name of the pattern factory.
421 Returns the registered pattern factory (:class:`~collections.abc.Callable`).
422 If no pattern factory is registered, raises :exc:`KeyError`.
423 """
424 return _registered_patterns[name]
427def match_file(patterns: Iterable[Pattern], file: str) -> bool:
428 """
429 Matches the file to the patterns.
431 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
432 the patterns to use.
434 *file* (:class:`str`) is the normalized file path to be matched against
435 *patterns*.
437 Returns :data:`True` if *file* matched; otherwise, :data:`False`.
438 """
439 matched = False
440 for pattern in patterns:
441 if pattern.include is not None and pattern.match_file(file) is not None:
442 matched = pattern.include
444 return matched
447@deprecated((
448 "pathspec.util.match_files() is deprecated. Use match_file() with a loop for "
449 "better results."
450))
451def match_files(
452 patterns: Iterable[Pattern],
453 files: Iterable[str],
454) -> set[str]:
455 """
456 .. version-deprecated:: 0.10.0
457 This function is no longer used. Use the :func:`.match_file` function with a
458 loop for better results.
460 Matches the files to the patterns.
462 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
463 the patterns to use.
465 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
466 normalized file paths to be matched against *patterns*.
468 Returns the matched files (:class:`set` of :class:`str`).
469 """
470 use_patterns = [__pat for __pat in patterns if __pat.include is not None]
472 return_files = set()
473 for file in files:
474 if match_file(use_patterns, file):
475 return_files.add(file)
477 return return_files
480def normalize_file(
481 file: StrPath,
482 separators: Optional[Collection[str]] = None,
483) -> str:
484 """
485 Normalizes the file path to use the POSIX path separator (i.e., ``"/"``), and
486 make the paths relative (remove leading ``"/"``).
488 *file* (:class:`str` or :class:`os.PathLike`) is the file path.
490 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
491 :data:`None`) optionally contains the path separators to normalize. This does
492 not need to include the POSIX path separator (``"/"``), but including it will
493 not affect the results. Default is ``None`` for :data:`.NORMALIZE_PATH_SEPS`.
494 To prevent normalization, pass an empty container (e.g., an empty tuple
495 ``()``).
497 Returns the normalized file path (:class:`str`).
498 """
499 # Normalize path separators.
500 if separators is None:
501 separators = NORMALIZE_PATH_SEPS
503 # Convert path object to string.
504 norm_file: str = os.fspath(file)
506 for sep in separators:
507 norm_file = norm_file.replace(sep, posixpath.sep)
509 if norm_file.startswith('/'):
510 # Make path relative.
511 norm_file = norm_file[1:]
513 elif norm_file.startswith('./'):
514 # Remove current directory prefix.
515 norm_file = norm_file[2:]
517 return norm_file
520@deprecated((
521 "pathspec.util.normalize_files() is deprecated. Use normalize_file() with a "
522 "loop for better results."
523))
524def normalize_files(
525 files: Iterable[StrPath],
526 separators: Optional[Collection[str]] = None,
527) -> dict[str, list[StrPath]]:
528 """
529 .. version-deprecated:: 0.10.0
530 This function is no longer used. Use the :func:`.normalize_file` function
531 with a loop for better results.
533 Normalizes the file paths to use the POSIX path separator.
535 *files* (:class:`~collections.abc.Iterable` of :class:`str` or
536 :class:`os.PathLike`) contains the file paths to be normalized.
538 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
539 :data:`None`) optionally contains the path separators to normalize. See
540 :func:`.normalize_file` for more information.
542 Returns a :class:`dict` mapping each normalized file path (:class:`str`) to
543 the original file paths (:class:`list` of :class:`str` or
544 :class:`os.PathLike`).
545 """
546 norm_files = {}
547 for path in files:
548 norm_file = normalize_file(path, separators=separators)
549 if norm_file in norm_files:
550 norm_files[norm_file].append(path)
551 else:
552 norm_files[norm_file] = [path]
554 return norm_files
557def register_pattern(
558 name: str,
559 pattern_factory: Callable[[AnyStr], Pattern],
560 override: Optional[bool] = None,
561) -> None:
562 """
563 Registers the specified pattern factory.
565 *name* (:class:`str`) is the name to register the pattern factory under.
567 *pattern_factory* (:class:`~collections.abc.Callable`) is used to compile
568 patterns. It must accept an uncompiled pattern (:class:`str`) and return the
569 compiled pattern (:class:`.Pattern`).
571 *override* (:class:`bool` or :data:`None`) optionally is whether to allow
572 overriding an already registered pattern under the same name (:data:`True`),
573 instead of raising an :exc:`.AlreadyRegisteredError` (:data:`False`). Default
574 is :data:`None` for :data:`False`.
575 """
576 if not isinstance(name, str):
577 raise TypeError(f"name:{name!r} is not a string.")
579 if not callable(pattern_factory):
580 raise TypeError(f"pattern_factory:{pattern_factory!r} is not callable.")
582 if name in _registered_patterns and not override:
583 raise AlreadyRegisteredError(name, _registered_patterns[name])
585 _registered_patterns[name] = pattern_factory
588class AlreadyRegisteredError(Exception):
589 """
590 The :exc:`AlreadyRegisteredError` exception is raised when a pattern factory
591 is registered under a name already in use.
592 """
594 def __init__(
595 self,
596 name: str,
597 pattern_factory: Callable[[AnyStr], Pattern],
598 ) -> None:
599 """
600 Initializes the :exc:`AlreadyRegisteredError` instance.
602 *name* (:class:`str`) is the name of the registered pattern.
604 *pattern_factory* (:class:`~collections.abc.Callable`) is the registered
605 pattern factory.
606 """
607 super().__init__(name, pattern_factory)
609 @property
610 def message(self) -> str:
611 """
612 *message* (:class:`str`) is the error message.
613 """
614 return (
615 f"{self.name!r} is already registered for pattern factory="
616 f"{self.pattern_factory!r}."
617 )
619 @property
620 def name(self) -> str:
621 """
622 *name* (:class:`str`) is the name of the registered pattern.
623 """
624 return self.args[0]
626 @property
627 def pattern_factory(self) -> Callable[[AnyStr], Pattern]:
628 """
629 *pattern_factory* (:class:`~collections.abc.Callable`) is the registered
630 pattern factory.
631 """
632 return self.args[1]
635class RecursionError(Exception):
636 """
637 The :exc:`RecursionError` exception is raised when recursion is detected.
638 """
640 def __init__(
641 self,
642 real_path: str,
643 first_path: str,
644 second_path: str,
645 ) -> None:
646 """
647 Initializes the :exc:`RecursionError` instance.
649 *real_path* (:class:`str`) is the real path that recursion was encountered
650 on.
652 *first_path* (:class:`str`) is the first path encountered for *real_path*.
654 *second_path* (:class:`str`) is the second path encountered for *real_path*.
655 """
656 super().__init__(real_path, first_path, second_path)
658 @property
659 def first_path(self) -> str:
660 """
661 *first_path* (:class:`str`) is the first path encountered for
662 :attr:`self.real_path <RecursionError.real_path>`.
663 """
664 return self.args[1]
666 @property
667 def message(self) -> str:
668 """
669 *message* (:class:`str`) is the error message.
670 """
671 return (
672 f"Real path {self.real_path!r} was encountered at {self.first_path!r} "
673 f"and then {self.second_path!r}."
674 )
676 @property
677 def real_path(self) -> str:
678 """
679 *real_path* (:class:`str`) is the real path that recursion was
680 encountered on.
681 """
682 return self.args[0]
684 @property
685 def second_path(self) -> str:
686 """
687 *second_path* (:class:`str`) is the second path encountered for
688 :attr:`self.real_path <RecursionError.real_path>`.
689 """
690 return self.args[2]
693@dataclass(frozen=True)
694class CheckResult(Generic[TStrPath]):
695 """
696 The :class:`CheckResult` class contains information about the file and which
697 pattern matched it.
698 """
700 # Make the class dict-less.
701 __slots__ = (
702 'file',
703 'include',
704 'index',
705 )
707 file: TStrPath
708 """
709 *file* (:class:`str` or :class:`os.PathLike`) is the file path.
710 """
712 include: Optional[bool]
713 """
714 *include* (:class:`bool` or :data:`None`) is whether to include or exclude the
715 file. If :data:`None`, no pattern matched.
716 """
718 index: Optional[int]
719 """
720 *index* (:class:`int` or :data:`None`) is the index of the last pattern that
721 matched. If :data:`None`, no pattern matched.
722 """
725class MatchDetail(object):
726 """
727 The :class:`.MatchDetail` class contains information about
728 """
730 # Make the class dict-less.
731 __slots__ = ('patterns',)
733 def __init__(self, patterns: Sequence[Pattern]) -> None:
734 """
735 Initialize the :class:`.MatchDetail` instance.
737 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
738 contains the patterns that matched the file in the order they were encountered.
739 """
741 self.patterns = patterns
742 """
743 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
744 contains the patterns that matched the file in the order they were
745 encountered.
746 """
749class TreeEntry(object):
750 """
751 The :class:`TreeEntry` class contains information about a file-system entry.
752 """
754 # Make the class dict-less.
755 __slots__ = ('_lstat', 'name', 'path', '_stat')
757 def __init__(
758 self,
759 name: str,
760 path: str,
761 lstat: os.stat_result,
762 stat: os.stat_result,
763 ) -> None:
764 """
765 Initialize the :class:`TreeEntry` instance.
767 *name* (:class:`str`) is the base name of the entry.
769 *path* (:class:`str`) is the relative path of the entry.
771 *lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
773 *stat* (:class:`os.stat_result`) is the stat result of the entry,
774 potentially linked.
775 """
777 self._lstat: os.stat_result = lstat
778 """
779 *_lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
780 """
782 self.name: str = name
783 """
784 *name* (:class:`str`) is the base name of the entry.
785 """
787 self.path: str = path
788 """
789 *path* (:class:`str`) is the path of the entry.
790 """
792 self._stat: os.stat_result = stat
793 """
794 *_stat* (:class:`os.stat_result`) is the stat result of the linked entry.
795 """
797 def is_dir(self, follow_links: Optional[bool] = None) -> bool:
798 """
799 Get whether the entry is a directory.
801 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
802 links. If this is :data:`True`, a symlink to a directory will result in
803 :data:`True`. Default is :data:`None` for :data:`True`.
805 Returns whether the entry is a directory (:class:`bool`).
806 """
807 if follow_links is None:
808 follow_links = True
810 node_stat = self._stat if follow_links else self._lstat
811 return stat.S_ISDIR(node_stat.st_mode)
813 def is_file(self, follow_links: Optional[bool] = None) -> bool:
814 """
815 Get whether the entry is a regular file.
817 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
818 links. If this is :data:`True`, a symlink to a regular file will result in
819 :data:`True`. Default is :data:`None` for :data:`True`.
821 Returns whether the entry is a regular file (:class:`bool`).
822 """
823 if follow_links is None:
824 follow_links = True
826 node_stat = self._stat if follow_links else self._lstat
827 return stat.S_ISREG(node_stat.st_mode)
829 def is_symlink(self) -> bool:
830 """
831 Returns whether the entry is a symbolic link (:class:`bool`).
832 """
833 return stat.S_ISLNK(self._lstat.st_mode)
835 def stat(self, follow_links: Optional[bool] = None) -> os.stat_result:
836 """
837 Get the cached stat result for the entry.
839 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
840 links. If this is :data:`True`, the stat result of the linked file will be
841 returned. Default is :data:`None` for :data:`True`.
843 Returns that stat result (:class:`os.stat_result`).
844 """
845 if follow_links is None:
846 follow_links = True
848 return self._stat if follow_links else self._lstat