Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pathspec/util.py: 36%
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
11from collections.abc import (
12 Collection,
13 Iterable,
14 Iterator,
15 Sequence)
16from dataclasses import (
17 dataclass)
18from typing import (
19 Any,
20 Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
21 Generic,
22 Optional, # Replaced by `X | None` in 3.10.
23 TypeVar,
24 Union, # Replaced by `X | Y` in 3.10.
25 cast)
27from .pattern import (
28 Pattern)
29from ._typing import (
30 AnyStr, # Removed in 3.18.
31 deprecated) # Added in 3.13.
33StrPath = Union[str, os.PathLike[str]]
35TPattern = TypeVar('TPattern', bound=Pattern)
36"""
37Type variable for :class:`.Pattern`. This is used by :class:`pathspec.pathspec.PathSpec`
38to specialize the type of patterns.
39"""
41TPattern_co = TypeVar('TPattern_co', bound=Pattern, covariant=True)
42"""
43Type variable for :class:`.Pattern` that is covariant. This is used by
44:class:`pathspec.pathspec.PathSpec` to specialize the type of patterns.
45"""
47TStrPath = TypeVar('TStrPath', bound=StrPath)
48"""
49Type variable for :class:`str` or :class:`os.PathLike`.
50"""
52NORMALIZE_PATH_SEPS = [
53 cast(str, __sep)
54 for __sep in [os.sep, os.altsep]
55 if __sep and __sep != posixpath.sep
56]
57"""
58*NORMALIZE_PATH_SEPS* (:class:`list` of :class:`str`) contains the path
59separators that need to be normalized to the POSIX separator for the current
60operating system. The separators are determined by examining :data:`os.sep` and
61:data:`os.altsep`.
62"""
64_registered_patterns: dict[str, Callable[[Union[str, bytes]], Pattern]] = {}
65"""
66*_registered_patterns* (:class:`dict`) maps a name (:class:`str`) to the
67registered pattern factory (:class:`~collections.abc.Callable`).
68"""
71def append_dir_sep(path: pathlib.Path) -> str:
72 """
73 Appends the path separator to the path if the path is a directory. This can be
74 used to aid in distinguishing between directories and files on the file-system
75 by relying on the presence of a trailing path separator.
77 *path* (:class:`pathlib.Path`) is the path to use.
79 Returns the path (:class:`str`).
80 """
81 str_path = str(path)
82 if path.is_dir():
83 str_path += os.sep
85 return str_path
88def check_match_file(
89 patterns: Iterable[tuple[int, Pattern]],
90 file: str,
91 is_reversed: Optional[bool] = None,
92) -> tuple[Optional[bool], Optional[int]]:
93 """
94 Check the file against the patterns.
96 *patterns* (:class:`~collections.abc.Iterable`) yields each indexed pattern
97 (:class:`tuple`) which contains the pattern index (:class:`int`) and actua
98 pattern (:class:`.Pattern`).
100 *file* (:class:`str`) is the normalized file path to be matched against
101 *patterns*.
103 *is_reversed* (:class:`bool` or :data:`None`) is whether the order of the
104 patterns has been reversed. Default is :data:`None` for :data:`False`.
105 Reversing the order of the patterns is an optimization.
107 Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
108 or :data:`None`), and the index of the last matched pattern (:class:`int` or
109 :data:`None`).
110 """
111 if is_reversed:
112 # Check patterns in reverse order. The first pattern that matches takes
113 # precedence.
114 for index, pattern in patterns:
115 if pattern.include is not None and pattern.match_file(file) is not None:
116 return pattern.include, index
118 return None, None
120 else:
121 # Check all patterns. The last pattern that matches takes precedence.
122 out_include: Optional[bool] = None
123 out_index: Optional[int] = None
124 for index, pattern in patterns:
125 if pattern.include is not None and pattern.match_file(file) is not None:
126 out_include = pattern.include
127 out_index = index
129 return out_include, out_index
132def detailed_match_files(
133 patterns: Iterable[Pattern],
134 files: Iterable[str],
135 all_matches: Optional[bool] = None,
136) -> dict[str, MatchDetail]:
137 """
138 Matches the files to the patterns, and returns which patterns matched the
139 files.
141 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
142 the patterns to use.
144 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
145 normalized file paths to be matched against *patterns*.
147 *all_matches* (:class:`bool` or :data:`None`) is whether to return all matches
148 patterns (:data:`True`), or only the last matched pattern (:data:`False`).
149 Default is :data:`None` for :data:`False`.
151 Returns the matched files (:class:`dict`) which maps each matched file
152 (:class:`str`) to the patterns that matched in order (:class:`.MatchDetail`).
153 """
154 all_files = files if isinstance(files, Collection) else list(files)
155 return_files: dict[str, MatchDetail] = {}
156 for pattern in patterns:
157 if pattern.include is not None:
158 result_files = pattern.match(all_files) # TODO: Replace with `.match_file()`.
159 if pattern.include:
160 # Add files and record pattern.
161 for result_file in result_files:
162 if result_file in return_files:
163 # We know here that .patterns is a list, because we made it here
164 if all_matches:
165 return_files[result_file].patterns.append(pattern) # type: ignore[attr-defined]
166 else:
167 return_files[result_file].patterns[0] = pattern # type: ignore[index]
168 else:
169 return_files[result_file] = MatchDetail([pattern])
171 else:
172 # Remove files.
173 for file in result_files:
174 del return_files[file]
176 return return_files
179def _filter_check_patterns(
180 patterns: Iterable[Pattern],
181) -> list[tuple[int, Pattern]]:
182 """
183 Filters out null-patterns.
185 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
186 the patterns.
188 Returns a :class:`list` containing each indexed pattern (:class:`tuple`) which
189 contains the pattern index (:class:`int`) and the actual pattern
190 (:class:`.Pattern`).
191 """
192 return [
193 (__index, __pat)
194 for __index, __pat in enumerate(patterns)
195 if __pat.include is not None
196 ]
199def _is_iterable(value: Any) -> bool:
200 """
201 Check whether the value is an iterable (excludes strings).
203 *value* is the value to check,
205 Returns whether *value* is an iterable (:class:`bool`).
206 """
207 return isinstance(value, Iterable) and not isinstance(value, (str, bytes))
210@deprecated((
211 "pathspec.util.iter_tree() is deprecated. Use iter_tree_files() instead."
212))
213def iter_tree(root, on_error=None, follow_links=None):
214 """
215 .. version-deprecated:: 0.10.0
216 This is an alias for the :func:`.iter_tree_files` function.
217 """
218 return iter_tree_files(root, on_error=on_error, follow_links=follow_links)
221def iter_tree_entries(
222 root: StrPath,
223 on_error: Optional[Callable[[OSError], None]] = None,
224 follow_links: Optional[bool] = None,
225) -> Iterator['TreeEntry']:
226 """
227 Walks the specified directory for all files and directories.
229 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to search.
231 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
232 the error handler for file-system exceptions. It will be called with the
233 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
234 is :data:`None` to ignore file-system exceptions.
236 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
237 symbolic links that resolve to directories. Default is :data:`None` for
238 :data:`True`.
240 Raises :exc:`.RecursionError` if recursion is detected.
242 Returns an :class:`~collections.abc.Iterator` yielding each file or directory
243 entry (:class:`.TreeEntry`) relative to *root*.
244 """
245 if on_error is not None and not callable(on_error):
246 raise TypeError(f"on_error:{on_error!r} is not callable.")
248 if follow_links is None:
249 follow_links = True
251 yield from _iter_tree_entries_next(os.path.abspath(root), '', {}, on_error, follow_links)
254def _iter_tree_entries_next(
255 root_full: str,
256 dir_rel: str,
257 memo: dict[str, str],
258 on_error: Optional[Callable[[OSError], None]],
259 follow_links: bool,
260) -> Iterator['TreeEntry']:
261 """
262 Scan the directory for all descendant files.
264 *root_full* (:class:`str`) the absolute path to the root directory.
266 *dir_rel* (:class:`str`) the path to the directory to scan relative to
267 *root_full*.
269 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
270 each ancestor real path (:class:`str`) to relative path (:class:`str`).
272 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
273 the error handler for file-system exceptions.
275 *follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
276 to directories.
278 Yields each entry (:class:`.TreeEntry`).
279 """
280 dir_full = os.path.join(root_full, dir_rel)
281 dir_real = os.path.realpath(dir_full)
283 # Remember each encountered ancestor directory and its canonical (real) path.
284 # If a canonical path is encountered more than once, recursion has occurred.
285 if dir_real not in memo:
286 memo[dir_real] = dir_rel
287 else:
288 raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
290 with os.scandir(dir_full) as scan_iter:
291 node_ent: os.DirEntry
292 for node_ent in scan_iter:
293 node_rel = os.path.join(dir_rel, node_ent.name)
295 # Inspect child node.
296 try:
297 node_lstat = node_ent.stat(follow_symlinks=False)
298 except OSError as e:
299 if on_error is not None:
300 on_error(e)
301 continue
303 if node_ent.is_symlink():
304 # Child node is a link, inspect the target node.
305 try:
306 node_stat = node_ent.stat()
307 except OSError as e:
308 if on_error is not None:
309 on_error(e)
310 continue
311 else:
312 node_stat = node_lstat
314 if node_ent.is_dir(follow_symlinks=follow_links):
315 # Child node is a directory, recurse into it and yield its descendant
316 # files.
317 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
319 yield from _iter_tree_entries_next(root_full, node_rel, memo, on_error, follow_links)
321 elif node_ent.is_file() or node_ent.is_symlink():
322 # Child node is either a file or an unfollowed link, yield it.
323 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
325 # NOTE: Make sure to remove the canonical (real) path of the directory from
326 # the ancestors memo once we are done with it. This allows the same directory
327 # to appear multiple times. If this is not done, the second occurrence of the
328 # directory will be incorrectly interpreted as a recursion. See
329 # <https://github.com/cpburnz/python-path-specification/pull/7>.
330 del memo[dir_real]
333def iter_tree_files(
334 root: StrPath,
335 on_error: Optional[Callable[[OSError], None]] = None,
336 follow_links: Optional[bool] = None,
337) -> Iterator[str]:
338 """
339 Walks the specified directory for all files.
341 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to search
342 for files.
344 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
345 the error handler for file-system exceptions. It will be called with the
346 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
347 is :data:`None` to ignore file-system exceptions.
349 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
350 symbolic links that resolve to directories. Default is :data:`None` for
351 :data:`True`.
353 Raises :exc:`.RecursionError` if recursion is detected.
355 Returns an :class:`~collections.abc.Iterator` yielding the path to each file
356 (:class:`str`) relative to *root*.
357 """
358 if on_error is not None and not callable(on_error):
359 raise TypeError(f"on_error:{on_error!r} is not callable.")
361 if follow_links is None:
362 follow_links = True
364 yield from _iter_tree_files_next(os.path.abspath(root), '', {}, on_error, follow_links)
367def _iter_tree_files_next(
368 root_full: str,
369 dir_rel: str,
370 memo: dict[str, str],
371 on_error: Optional[Callable[[OSError], None]],
372 follow_links: bool,
373) -> Iterator[str]:
374 """
375 Scan the directory for all descendant files.
377 *root_full* (:class:`str`) the absolute path to the root directory.
379 *dir_rel* (:class:`str`) the path to the directory to scan relative to
380 *root_full*.
382 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
383 each ancestor real path (:class:`str`) to relative path (:class:`str`).
385 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
386 the error handler for file-system exceptions.
388 *follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
389 to directories.
391 Yields each file path (:class:`str`).
392 """
393 dir_full = os.path.join(root_full, dir_rel)
394 dir_real = os.path.realpath(dir_full)
396 # Remember each encountered ancestor directory and its canonical (real) path.
397 # If a canonical path is encountered more than once, recursion has occurred.
398 if dir_real not in memo:
399 memo[dir_real] = dir_rel
400 else:
401 raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
403 with os.scandir(dir_full) as scan_iter:
404 node_ent: os.DirEntry
405 for node_ent in scan_iter:
406 node_rel = os.path.join(dir_rel, node_ent.name)
408 if node_ent.is_dir(follow_symlinks=follow_links):
409 # Child node is a directory, recurse into it and yield its descendant
410 # files.
411 yield from _iter_tree_files_next(root_full, node_rel, memo, on_error, follow_links)
413 elif node_ent.is_file():
414 # Child node is a file, yield it.
415 yield node_rel
417 elif not follow_links and node_ent.is_symlink():
418 # Child node is an unfollowed link, yield it.
419 yield node_rel
421 # NOTE: Make sure to remove the canonical (real) path of the directory from
422 # the ancestors memo once we are done with it. This allows the same directory
423 # to appear multiple times. If this is not done, the second occurrence of the
424 # directory will be incorrectly interpreted as a recursion. See
425 # <https://github.com/cpburnz/python-path-specification/pull/7>.
426 del memo[dir_real]
429def lookup_pattern(name: str) -> Callable[[AnyStr], Pattern]:
430 """
431 Looks up a registered pattern factory by name.
433 *name* (:class:`str`) is the name of the pattern factory.
435 Returns the registered pattern factory (:class:`~collections.abc.Callable`).
436 If no pattern factory is registered, raises :exc:`KeyError`.
437 """
438 return _registered_patterns[name] # type: ignore[return-value]
441def match_file(patterns: Iterable[Pattern], file: str) -> bool:
442 """
443 Matches the file to the patterns.
445 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
446 the patterns to use.
448 *file* (:class:`str`) is the normalized file path to be matched against
449 *patterns*.
451 Returns :data:`True` if *file* matched; otherwise, :data:`False`.
452 """
453 matched = False
454 for pattern in patterns:
455 if pattern.include is not None and pattern.match_file(file) is not None:
456 matched = pattern.include
458 return matched
461@deprecated((
462 "pathspec.util.match_files() is deprecated. Use match_file() with a loop for "
463 "better results."
464))
465def match_files(
466 patterns: Iterable[Pattern],
467 files: Iterable[str],
468) -> set[str]:
469 """
470 .. version-deprecated:: 0.10.0
471 This function is no longer used. Use the :func:`.match_file` function with a
472 loop for better results.
474 Matches the files to the patterns.
476 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
477 the patterns to use.
479 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
480 normalized file paths to be matched against *patterns*.
482 Returns the matched files (:class:`set` of :class:`str`).
483 """
484 use_patterns = [__pat for __pat in patterns if __pat.include is not None]
486 return_files = set()
487 for file in files:
488 if match_file(use_patterns, file):
489 return_files.add(file)
491 return return_files
494def normalize_file(
495 file: StrPath,
496 separators: Optional[Collection[str]] = None,
497) -> str:
498 """
499 Normalizes the file path to use the POSIX path separator (i.e., ``"/"``), and
500 make the paths relative (remove leading ``"/"``).
502 *file* (:class:`str` or :class:`os.PathLike`) is the file path.
504 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
505 :data:`None`) optionally contains the path separators to normalize. This does
506 not need to include the POSIX path separator (``"/"``), but including it will
507 not affect the results. Default is ``None`` for :data:`.NORMALIZE_PATH_SEPS`.
508 To prevent normalization, pass an empty container (e.g., an empty tuple
509 ``()``).
511 Returns the normalized file path (:class:`str`).
512 """
513 # Normalize path separators.
514 if separators is None:
515 separators = NORMALIZE_PATH_SEPS
517 assert separators is not None, separators
519 # Convert path object to string.
520 norm_file: str = os.fspath(file)
522 for sep in separators:
523 norm_file = norm_file.replace(sep, posixpath.sep)
525 if norm_file.startswith('/'):
526 # Make path relative.
527 norm_file = norm_file[1:]
529 elif norm_file.startswith('./'):
530 # Remove current directory prefix.
531 norm_file = norm_file[2:]
533 return norm_file
536@deprecated((
537 "pathspec.util.normalize_files() is deprecated. Use normalize_file() with a "
538 "loop for better results."
539))
540def normalize_files(
541 files: Iterable[StrPath],
542 separators: Optional[Collection[str]] = None,
543) -> dict[str, list[StrPath]]:
544 """
545 .. version-deprecated:: 0.10.0
546 This function is no longer used. Use the :func:`.normalize_file` function
547 with a loop for better results.
549 Normalizes the file paths to use the POSIX path separator.
551 *files* (:class:`~collections.abc.Iterable` of :class:`str` or
552 :class:`os.PathLike`) contains the file paths to be normalized.
554 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
555 :data:`None`) optionally contains the path separators to normalize. See
556 :func:`.normalize_file` for more information.
558 Returns a :class:`dict` mapping each normalized file path (:class:`str`) to
559 the original file paths (:class:`list` of :class:`str` or
560 :class:`os.PathLike`).
561 """
562 norm_files: dict[str, list[StrPath]] = {}
563 for path in files:
564 norm_file = normalize_file(path, separators=separators)
565 if norm_file in norm_files:
566 norm_files[norm_file].append(path)
567 else:
568 norm_files[norm_file] = [path]
570 return norm_files
573def register_pattern(
574 name: str,
575 pattern_factory: Union[Callable[[Union[str, bytes]], Pattern], type[Pattern]],
576 override: Optional[bool] = None,
577) -> None:
578 """
579 Registers the specified pattern factory.
581 *name* (:class:`str`) is the name to register the pattern factory under.
583 *pattern_factory* (:class:`~collections.abc.Callable`) is used to compile
584 patterns. It must accept an uncompiled pattern (:class:`str`) and return the
585 compiled pattern (:class:`.Pattern`).
587 *override* (:class:`bool` or :data:`None`) optionally is whether to allow
588 overriding an already registered pattern under the same name (:data:`True`),
589 instead of raising an :exc:`.AlreadyRegisteredError` (:data:`False`). Default
590 is :data:`None` for :data:`False`.
591 """
592 if not isinstance(name, str):
593 raise TypeError(f"{name=!r} is not a string.")
595 if not callable(pattern_factory):
596 raise TypeError(f"{pattern_factory=!r} is not callable.")
598 if name in _registered_patterns and not override:
599 raise AlreadyRegisteredError(name, _registered_patterns[name])
601 _registered_patterns[name] = pattern_factory # type: ignore
604class AlreadyRegisteredError(Exception):
605 """
606 The :exc:`AlreadyRegisteredError` exception is raised when a pattern factory
607 is registered under a name already in use.
608 """
610 def __init__(
611 self,
612 name: str,
613 pattern_factory: Callable[[Union[str, bytes]], Pattern],
614 ) -> None:
615 """
616 Initializes the :exc:`AlreadyRegisteredError` instance.
618 *name* (:class:`str`) is the name of the registered pattern.
620 *pattern_factory* (:class:`~collections.abc.Callable`) is the registered
621 pattern factory.
622 """
623 super().__init__(name, pattern_factory)
625 @property
626 def message(self) -> str:
627 """
628 *message* (:class:`str`) is the error message.
629 """
630 return (
631 f"{self.name!r} is already registered for pattern factory="
632 f"{self.pattern_factory!r}."
633 )
635 @property
636 def name(self) -> str:
637 """
638 *name* (:class:`str`) is the name of the registered pattern.
639 """
640 return self.args[0]
642 @property
643 def pattern_factory(self) -> Callable[[Union[str, bytes]], Pattern]:
644 """
645 *pattern_factory* (:class:`~collections.abc.Callable`) is the registered
646 pattern factory.
647 """
648 return self.args[1]
651class RecursionError(Exception):
652 """
653 The :exc:`RecursionError` exception is raised when recursion is detected.
654 """
656 def __init__(
657 self,
658 real_path: str,
659 first_path: str,
660 second_path: str,
661 ) -> None:
662 """
663 Initializes the :exc:`RecursionError` instance.
665 *real_path* (:class:`str`) is the real path that recursion was encountered
666 on.
668 *first_path* (:class:`str`) is the first path encountered for *real_path*.
670 *second_path* (:class:`str`) is the second path encountered for *real_path*.
671 """
672 super().__init__(real_path, first_path, second_path)
674 @property
675 def first_path(self) -> str:
676 """
677 *first_path* (:class:`str`) is the first path encountered for
678 :attr:`self.real_path <RecursionError.real_path>`.
679 """
680 return self.args[1]
682 @property
683 def message(self) -> str:
684 """
685 *message* (:class:`str`) is the error message.
686 """
687 return (
688 f"Real path {self.real_path!r} was encountered at {self.first_path!r} "
689 f"and then {self.second_path!r}."
690 )
692 @property
693 def real_path(self) -> str:
694 """
695 *real_path* (:class:`str`) is the real path that recursion was
696 encountered on.
697 """
698 return self.args[0]
700 @property
701 def second_path(self) -> str:
702 """
703 *second_path* (:class:`str`) is the second path encountered for
704 :attr:`self.real_path <RecursionError.real_path>`.
705 """
706 return self.args[2]
709@dataclass(frozen=True)
710class CheckResult(Generic[TStrPath]):
711 """
712 The :class:`CheckResult` class contains information about the file and which
713 pattern matched it.
714 """
716 # Make the class dict-less.
717 __slots__ = (
718 'file',
719 'include',
720 'index',
721 )
723 file: TStrPath
724 """
725 *file* (:class:`str` or :class:`os.PathLike`) is the file path.
726 """
728 include: Optional[bool]
729 """
730 *include* (:class:`bool` or :data:`None`) is whether to include or exclude the
731 file. If :data:`None`, no pattern matched.
732 """
734 index: Optional[int]
735 """
736 *index* (:class:`int` or :data:`None`) is the index of the last pattern that
737 matched. If :data:`None`, no pattern matched.
738 """
741class MatchDetail(object):
742 """
743 The :class:`.MatchDetail` class contains information about
744 """
746 # Make the class dict-less.
747 __slots__ = ('patterns',)
749 def __init__(self, patterns: Sequence[Pattern]) -> None:
750 """
751 Initialize the :class:`.MatchDetail` instance.
753 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
754 contains the patterns that matched the file in the order they were encountered.
755 """
757 self.patterns = patterns
758 """
759 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
760 contains the patterns that matched the file in the order they were
761 encountered.
762 """
765class TreeEntry(object):
766 """
767 The :class:`TreeEntry` class contains information about a file-system entry.
768 """
770 # Make the class dict-less.
771 __slots__ = ('_lstat', 'name', 'path', '_stat')
773 def __init__(
774 self,
775 name: str,
776 path: str,
777 lstat: os.stat_result,
778 stat: os.stat_result,
779 ) -> None:
780 """
781 Initialize the :class:`TreeEntry` instance.
783 *name* (:class:`str`) is the base name of the entry.
785 *path* (:class:`str`) is the relative path of the entry.
787 *lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
789 *stat* (:class:`os.stat_result`) is the stat result of the entry,
790 potentially linked.
791 """
793 self._lstat: os.stat_result = lstat
794 """
795 *_lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
796 """
798 self.name: str = name
799 """
800 *name* (:class:`str`) is the base name of the entry.
801 """
803 self.path: str = path
804 """
805 *path* (:class:`str`) is the path of the entry.
806 """
808 self._stat: os.stat_result = stat
809 """
810 *_stat* (:class:`os.stat_result`) is the stat result of the linked entry.
811 """
813 def is_dir(self, follow_links: Optional[bool] = None) -> bool:
814 """
815 Get whether the entry is a directory.
817 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
818 links. If this is :data:`True`, a symlink to a directory will result in
819 :data:`True`. Default is :data:`None` for :data:`True`.
821 Returns whether the entry is a directory (: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_ISDIR(node_stat.st_mode)
829 def is_file(self, follow_links: Optional[bool] = None) -> bool:
830 """
831 Get whether the entry is a regular file.
833 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
834 links. If this is :data:`True`, a symlink to a regular file will result in
835 :data:`True`. Default is :data:`None` for :data:`True`.
837 Returns whether the entry is a regular file (:class:`bool`).
838 """
839 if follow_links is None:
840 follow_links = True
842 node_stat = self._stat if follow_links else self._lstat
843 return stat.S_ISREG(node_stat.st_mode)
845 def is_symlink(self) -> bool:
846 """
847 Returns whether the entry is a symbolic link (:class:`bool`).
848 """
849 return stat.S_ISLNK(self._lstat.st_mode)
851 def stat(self, follow_links: Optional[bool] = None) -> os.stat_result:
852 """
853 Get the cached stat result for the entry.
855 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
856 links. If this is :data:`True`, the stat result of the linked file will be
857 returned. Default is :data:`None` for :data:`True`.
859 Returns that stat result (:class:`os.stat_result`).
860 """
861 if follow_links is None:
862 follow_links = True
864 return self._stat if follow_links else self._lstat