Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pathspec/util.py: 38%

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

209 statements  

1""" 

2This module provides utility methods for dealing with path-specs. 

3""" 

4 

5import os 

6import os.path 

7import pathlib 

8import posixpath 

9import stat 

10import sys 

11import warnings 

12from collections.abc import ( 

13 Collection as CollectionType, 

14 Iterable as IterableType) 

15from dataclasses import ( 

16 dataclass) 

17from os import ( 

18 PathLike) 

19from typing import ( 

20 Any, 

21 AnyStr, 

22 Callable, # Replaced by `collections.abc.Callable` in 3.9. 

23 Collection, # Replaced by `collections.abc.Collection` in 3.9. 

24 Dict, # Replaced by `dict` in 3.9. 

25 Generic, 

26 Iterable, # Replaced by `collections.abc.Iterable` in 3.9. 

27 Iterator, # Replaced by `collections.abc.Iterator` in 3.9. 

28 List, # Replaced by `list` in 3.9. 

29 Optional, # Replaced by `X | None` in 3.10. 

30 Sequence, # Replaced by `collections.abc.Sequence` in 3.9. 

31 Set, # Replaced by `set` in 3.9. 

32 Tuple, # Replaced by `tuple` in 3.9. 

33 TypeVar, 

34 Union) # Replaced by `X | Y` in 3.10. 

35 

36from .pattern import ( 

37 Pattern) 

38 

39if sys.version_info >= (3, 9): 

40 StrPath = Union[str, PathLike[str]] 

41else: 

42 StrPath = Union[str, PathLike] 

43 

44TStrPath = TypeVar("TStrPath", bound=StrPath) 

45""" 

46Type variable for :class:`str` or :class:`os.PathLike`. 

47""" 

48 

49NORMALIZE_PATH_SEPS = [ 

50 __sep 

51 for __sep in [os.sep, os.altsep] 

52 if __sep and __sep != posixpath.sep 

53] 

54""" 

55*NORMALIZE_PATH_SEPS* (:class:`list` of :class:`str`) contains the path 

56separators that need to be normalized to the POSIX separator for the 

57current operating system. The separators are determined by examining 

58:data:`os.sep` and :data:`os.altsep`. 

59""" 

60 

61_registered_patterns = {} 

62""" 

63*_registered_patterns* (:class:`dict`) maps a name (:class:`str`) to the 

64registered pattern factory (:class:`~collections.abc.Callable`). 

65""" 

66 

67 

68def append_dir_sep(path: pathlib.Path) -> str: 

69 """ 

70 Appends the path separator to the path if the path is a directory. 

71 This can be used to aid in distinguishing between directories and 

72 files on the file-system by relying on the presence of a trailing path 

73 separator. 

74 

75 *path* (:class:`pathlib.Path`) is the path to use. 

76 

77 Returns the path (:class:`str`). 

78 """ 

79 str_path = str(path) 

80 if path.is_dir(): 

81 str_path += os.sep 

82 

83 return str_path 

84 

85 

86def check_match_file( 

87 patterns: Iterable[Tuple[int, Pattern]], 

88 file: str, 

89) -> Tuple[Optional[bool], Optional[int]]: 

90 """ 

91 Check the file against the patterns. 

92 

93 *patterns* (:class:`~collections.abc.Iterable`) yields each indexed pattern 

94 (:class:`tuple`) which contains the pattern index (:class:`int`) and actual 

95 pattern (:class:`~pathspec.pattern.Pattern`). 

96 

97 *file* (:class:`str`) is the normalized file path to be matched 

98 against *patterns*. 

99 

100 Returns a :class:`tuple` containing whether to include *file* (:class:`bool` 

101 or :data:`None`), and the index of the last matched pattern (:class:`int` or 

102 :data:`None`). 

103 """ 

104 out_include: Optional[bool] = None 

105 out_index: Optional[int] = None 

106 for index, pattern in patterns: 

107 if pattern.include is not None and pattern.match_file(file) is not None: 

108 out_include = pattern.include 

109 out_index = index 

110 

111 return out_include, out_index 

112 

113 

114def detailed_match_files( 

115 patterns: Iterable[Pattern], 

116 files: Iterable[str], 

117 all_matches: Optional[bool] = None, 

118) -> Dict[str, 'MatchDetail']: 

119 """ 

120 Matches the files to the patterns, and returns which patterns matched 

121 the files. 

122 

123 *patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.Pattern`) 

124 contains the patterns to use. 

125 

126 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains 

127 the normalized file paths to be matched against *patterns*. 

128 

129 *all_matches* (:class:`bool` or :data:`None`) is whether to return all 

130 matches patterns (:data:`True`), or only the last matched pattern 

131 (:data:`False`). Default is :data:`None` for :data:`False`. 

132 

133 Returns the matched files (:class:`dict`) which maps each matched file 

134 (:class:`str`) to the patterns that matched in order (:class:`.MatchDetail`). 

135 """ 

136 all_files = files if isinstance(files, CollectionType) else list(files) 

137 return_files = {} 

138 for pattern in patterns: 

139 if pattern.include is not None: 

140 result_files = pattern.match(all_files) # TODO: Replace with `.match_file()`. 

141 if pattern.include: 

142 # Add files and record pattern. 

143 for result_file in result_files: 

144 if result_file in return_files: 

145 if all_matches: 

146 return_files[result_file].patterns.append(pattern) 

147 else: 

148 return_files[result_file].patterns[0] = pattern 

149 else: 

150 return_files[result_file] = MatchDetail([pattern]) 

151 

152 else: 

153 # Remove files. 

154 for file in result_files: 

155 del return_files[file] 

156 

157 return return_files 

158 

159 

160def _filter_check_patterns( 

161 patterns: Iterable[Pattern], 

162) -> List[Tuple[int, Pattern]]: 

163 """ 

164 Filters out null-patterns. 

165 

166 *patterns* (:class:`Iterable` of :class:`.Pattern`) contains the 

167 patterns. 

168 

169 Returns a :class:`list` containing each indexed pattern (:class:`tuple`) which 

170 contains the pattern index (:class:`int`) and the actual pattern 

171 (:class:`~pathspec.pattern.Pattern`). 

172 """ 

173 return [ 

174 (__index, __pat) 

175 for __index, __pat in enumerate(patterns) 

176 if __pat.include is not None 

177 ] 

178 

179 

180def _is_iterable(value: Any) -> bool: 

181 """ 

182 Check whether the value is an iterable (excludes strings). 

183 

184 *value* is the value to check, 

185 

186 Returns whether *value* is a iterable (:class:`bool`). 

187 """ 

188 return isinstance(value, IterableType) and not isinstance(value, (str, bytes)) 

189 

190 

191def iter_tree_entries( 

192 root: StrPath, 

193 on_error: Optional[Callable[[OSError], None]] = None, 

194 follow_links: Optional[bool] = None, 

195) -> Iterator['TreeEntry']: 

196 """ 

197 Walks the specified directory for all files and directories. 

198 

199 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to 

200 search. 

201 

202 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) 

203 optionally is the error handler for file-system exceptions. It will be 

204 called with the exception (:exc:`OSError`). Reraise the exception to 

205 abort the walk. Default is :data:`None` to ignore file-system 

206 exceptions. 

207 

208 *follow_links* (:class:`bool` or :data:`None`) optionally is whether 

209 to walk symbolic links that resolve to directories. Default is 

210 :data:`None` for :data:`True`. 

211 

212 Raises :exc:`RecursionError` if recursion is detected. 

213 

214 Returns an :class:`~collections.abc.Iterator` yielding each file or 

215 directory entry (:class:`.TreeEntry`) relative to *root*. 

216 """ 

217 if on_error is not None and not callable(on_error): 

218 raise TypeError(f"on_error:{on_error!r} is not callable.") 

219 

220 if follow_links is None: 

221 follow_links = True 

222 

223 yield from _iter_tree_entries_next(os.path.abspath(root), '', {}, on_error, follow_links) 

224 

225 

226def _iter_tree_entries_next( 

227 root_full: str, 

228 dir_rel: str, 

229 memo: Dict[str, str], 

230 on_error: Callable[[OSError], None], 

231 follow_links: bool, 

232) -> Iterator['TreeEntry']: 

233 """ 

234 Scan the directory for all descendant files. 

235 

236 *root_full* (:class:`str`) the absolute path to the root directory. 

237 

238 *dir_rel* (:class:`str`) the path to the directory to scan relative to 

239 *root_full*. 

240 

241 *memo* (:class:`dict`) keeps track of ancestor directories 

242 encountered. Maps each ancestor real path (:class:`str`) to relative 

243 path (:class:`str`). 

244 

245 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) 

246 optionally is the error handler for file-system exceptions. 

247 

248 *follow_links* (:class:`bool`) is whether to walk symbolic links that 

249 resolve to directories. 

250 

251 Yields each entry (:class:`.TreeEntry`). 

252 """ 

253 dir_full = os.path.join(root_full, dir_rel) 

254 dir_real = os.path.realpath(dir_full) 

255 

256 # Remember each encountered ancestor directory and its canonical 

257 # (real) path. If a canonical path is encountered more than once, 

258 # recursion has occurred. 

259 if dir_real not in memo: 

260 memo[dir_real] = dir_rel 

261 else: 

262 raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel) 

263 

264 with os.scandir(dir_full) as scan_iter: 

265 node_ent: os.DirEntry 

266 for node_ent in scan_iter: 

267 node_rel = os.path.join(dir_rel, node_ent.name) 

268 

269 # Inspect child node. 

270 try: 

271 node_lstat = node_ent.stat(follow_symlinks=False) 

272 except OSError as e: 

273 if on_error is not None: 

274 on_error(e) 

275 continue 

276 

277 if node_ent.is_symlink(): 

278 # Child node is a link, inspect the target node. 

279 try: 

280 node_stat = node_ent.stat() 

281 except OSError as e: 

282 if on_error is not None: 

283 on_error(e) 

284 continue 

285 else: 

286 node_stat = node_lstat 

287 

288 if node_ent.is_dir(follow_symlinks=follow_links): 

289 # Child node is a directory, recurse into it and yield its 

290 # descendant files. 

291 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat) 

292 

293 yield from _iter_tree_entries_next(root_full, node_rel, memo, on_error, follow_links) 

294 

295 elif node_ent.is_file() or node_ent.is_symlink(): 

296 # Child node is either a file or an unfollowed link, yield it. 

297 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat) 

298 

299 # NOTE: Make sure to remove the canonical (real) path of the directory 

300 # from the ancestors memo once we are done with it. This allows the 

301 # same directory to appear multiple times. If this is not done, the 

302 # second occurrence of the directory will be incorrectly interpreted 

303 # as a recursion. See <https://github.com/cpburnz/python-path-specification/pull/7>. 

304 del memo[dir_real] 

305 

306 

307def iter_tree_files( 

308 root: StrPath, 

309 on_error: Optional[Callable[[OSError], None]] = None, 

310 follow_links: Optional[bool] = None, 

311) -> Iterator[str]: 

312 """ 

313 Walks the specified directory for all files. 

314 

315 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to 

316 search for files. 

317 

318 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) 

319 optionally is the error handler for file-system exceptions. It will be 

320 called with the exception (:exc:`OSError`). Reraise the exception to 

321 abort the walk. Default is :data:`None` to ignore file-system 

322 exceptions. 

323 

324 *follow_links* (:class:`bool` or :data:`None`) optionally is whether 

325 to walk symbolic links that resolve to directories. Default is 

326 :data:`None` for :data:`True`. 

327 

328 Raises :exc:`RecursionError` if recursion is detected. 

329 

330 Returns an :class:`~collections.abc.Iterator` yielding the path to 

331 each file (:class:`str`) relative to *root*. 

332 """ 

333 for entry in iter_tree_entries(root, on_error=on_error, follow_links=follow_links): 

334 if not entry.is_dir(follow_links): 

335 yield entry.path 

336 

337 

338def iter_tree(root, on_error=None, follow_links=None): 

339 """ 

340 DEPRECATED: The :func:`.iter_tree` function is an alias for the 

341 :func:`.iter_tree_files` function. 

342 """ 

343 warnings.warn(( 

344 "util.iter_tree() is deprecated. Use util.iter_tree_files() instead." 

345 ), DeprecationWarning, stacklevel=2) 

346 return iter_tree_files(root, on_error=on_error, follow_links=follow_links) 

347 

348 

349def lookup_pattern(name: str) -> Callable[[AnyStr], Pattern]: 

350 """ 

351 Lookups a registered pattern factory by name. 

352 

353 *name* (:class:`str`) is the name of the pattern factory. 

354 

355 Returns the registered pattern factory (:class:`~collections.abc.Callable`). 

356 If no pattern factory is registered, raises :exc:`KeyError`. 

357 """ 

358 return _registered_patterns[name] 

359 

360 

361def match_file(patterns: Iterable[Pattern], file: str) -> bool: 

362 """ 

363 Matches the file to the patterns. 

364 

365 *patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.Pattern`) 

366 contains the patterns to use. 

367 

368 *file* (:class:`str`) is the normalized file path to be matched 

369 against *patterns*. 

370 

371 Returns :data:`True` if *file* matched; otherwise, :data:`False`. 

372 """ 

373 matched = False 

374 for pattern in patterns: 

375 if pattern.include is not None and pattern.match_file(file) is not None: 

376 matched = pattern.include 

377 

378 return matched 

379 

380 

381def match_files( 

382 patterns: Iterable[Pattern], 

383 files: Iterable[str], 

384) -> Set[str]: 

385 """ 

386 DEPRECATED: This is an old function no longer used. Use the 

387 :func:`~pathspec.util.match_file` function with a loop for better results. 

388 

389 Matches the files to the patterns. 

390 

391 *patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.Pattern`) 

392 contains the patterns to use. 

393 

394 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains 

395 the normalized file paths to be matched against *patterns*. 

396 

397 Returns the matched files (:class:`set` of :class:`str`). 

398 """ 

399 warnings.warn(( 

400 f"{__name__}.match_files() is deprecated. Use {__name__}.match_file() with " 

401 f"a loop for better results." 

402 ), DeprecationWarning, stacklevel=2) 

403 

404 use_patterns = [__pat for __pat in patterns if __pat.include is not None] 

405 

406 return_files = set() 

407 for file in files: 

408 if match_file(use_patterns, file): 

409 return_files.add(file) 

410 

411 return return_files 

412 

413 

414def normalize_file( 

415 file: StrPath, 

416 separators: Optional[Collection[str]] = None, 

417) -> str: 

418 """ 

419 Normalizes the file path to use the POSIX path separator (i.e., 

420 ``"/"``), and make the paths relative (remove leading ``"/"``). 

421 

422 *file* (:class:`str` or :class:`os.PathLike`) is the file path. 

423 

424 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or 

425 ``None``) optionally contains the path separators to normalize. 

426 This does not need to include the POSIX path separator (``"/"``), 

427 but including it will not affect the results. Default is ``None`` 

428 for ``NORMALIZE_PATH_SEPS``. To prevent normalization, pass an 

429 empty container (e.g., an empty tuple ``()``). 

430 

431 Returns the normalized file path (:class:`str`). 

432 """ 

433 # Normalize path separators. 

434 if separators is None: 

435 separators = NORMALIZE_PATH_SEPS 

436 

437 # Convert path object to string. 

438 norm_file: str = os.fspath(file) 

439 

440 for sep in separators: 

441 norm_file = norm_file.replace(sep, posixpath.sep) 

442 

443 if norm_file.startswith('/'): 

444 # Make path relative. 

445 norm_file = norm_file[1:] 

446 

447 elif norm_file.startswith('./'): 

448 # Remove current directory prefix. 

449 norm_file = norm_file[2:] 

450 

451 return norm_file 

452 

453 

454def normalize_files( 

455 files: Iterable[StrPath], 

456 separators: Optional[Collection[str]] = None, 

457) -> Dict[str, List[StrPath]]: 

458 """ 

459 DEPRECATED: This function is no longer used. Use the :func:`.normalize_file` 

460 function with a loop for better results. 

461 

462 Normalizes the file paths to use the POSIX path separator. 

463 

464 *files* (:class:`~collections.abc.Iterable` of :class:`str` or 

465 :class:`os.PathLike`) contains the file paths to be normalized. 

466 

467 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or 

468 :data:`None`) optionally contains the path separators to normalize. 

469 See :func:`normalize_file` for more information. 

470 

471 Returns a :class:`dict` mapping each normalized file path (:class:`str`) 

472 to the original file paths (:class:`list` of :class:`str` or 

473 :class:`os.PathLike`). 

474 """ 

475 warnings.warn(( 

476 "util.normalize_files() is deprecated. Use util.normalize_file() " 

477 "with a loop for better results." 

478 ), DeprecationWarning, stacklevel=2) 

479 

480 norm_files = {} 

481 for path in files: 

482 norm_file = normalize_file(path, separators=separators) 

483 if norm_file in norm_files: 

484 norm_files[norm_file].append(path) 

485 else: 

486 norm_files[norm_file] = [path] 

487 

488 return norm_files 

489 

490 

491def register_pattern( 

492 name: str, 

493 pattern_factory: Callable[[AnyStr], Pattern], 

494 override: Optional[bool] = None, 

495) -> None: 

496 """ 

497 Registers the specified pattern factory. 

498 

499 *name* (:class:`str`) is the name to register the pattern factory 

500 under. 

501 

502 *pattern_factory* (:class:`~collections.abc.Callable`) is used to 

503 compile patterns. It must accept an uncompiled pattern (:class:`str`) 

504 and return the compiled pattern (:class:`.Pattern`). 

505 

506 *override* (:class:`bool` or :data:`None`) optionally is whether to 

507 allow overriding an already registered pattern under the same name 

508 (:data:`True`), instead of raising an :exc:`AlreadyRegisteredError` 

509 (:data:`False`). Default is :data:`None` for :data:`False`. 

510 """ 

511 if not isinstance(name, str): 

512 raise TypeError(f"name:{name!r} is not a string.") 

513 

514 if not callable(pattern_factory): 

515 raise TypeError(f"pattern_factory:{pattern_factory!r} is not callable.") 

516 

517 if name in _registered_patterns and not override: 

518 raise AlreadyRegisteredError(name, _registered_patterns[name]) 

519 

520 _registered_patterns[name] = pattern_factory 

521 

522 

523class AlreadyRegisteredError(Exception): 

524 """ 

525 The :exc:`AlreadyRegisteredError` exception is raised when a pattern 

526 factory is registered under a name already in use. 

527 """ 

528 

529 def __init__( 

530 self, 

531 name: str, 

532 pattern_factory: Callable[[AnyStr], Pattern], 

533 ) -> None: 

534 """ 

535 Initializes the :exc:`AlreadyRegisteredError` instance. 

536 

537 *name* (:class:`str`) is the name of the registered pattern. 

538 

539 *pattern_factory* (:class:`~collections.abc.Callable`) is the 

540 registered pattern factory. 

541 """ 

542 super(AlreadyRegisteredError, self).__init__(name, pattern_factory) 

543 

544 @property 

545 def message(self) -> str: 

546 """ 

547 *message* (:class:`str`) is the error message. 

548 """ 

549 return "{name!r} is already registered for pattern factory:{pattern_factory!r}.".format( 

550 name=self.name, 

551 pattern_factory=self.pattern_factory, 

552 ) 

553 

554 @property 

555 def name(self) -> str: 

556 """ 

557 *name* (:class:`str`) is the name of the registered pattern. 

558 """ 

559 return self.args[0] 

560 

561 @property 

562 def pattern_factory(self) -> Callable[[AnyStr], Pattern]: 

563 """ 

564 *pattern_factory* (:class:`~collections.abc.Callable`) is the 

565 registered pattern factory. 

566 """ 

567 return self.args[1] 

568 

569 

570class RecursionError(Exception): 

571 """ 

572 The :exc:`RecursionError` exception is raised when recursion is 

573 detected. 

574 """ 

575 

576 def __init__( 

577 self, 

578 real_path: str, 

579 first_path: str, 

580 second_path: str, 

581 ) -> None: 

582 """ 

583 Initializes the :exc:`RecursionError` instance. 

584 

585 *real_path* (:class:`str`) is the real path that recursion was 

586 encountered on. 

587 

588 *first_path* (:class:`str`) is the first path encountered for 

589 *real_path*. 

590 

591 *second_path* (:class:`str`) is the second path encountered for 

592 *real_path*. 

593 """ 

594 super(RecursionError, self).__init__(real_path, first_path, second_path) 

595 

596 @property 

597 def first_path(self) -> str: 

598 """ 

599 *first_path* (:class:`str`) is the first path encountered for 

600 :attr:`self.real_path <RecursionError.real_path>`. 

601 """ 

602 return self.args[1] 

603 

604 @property 

605 def message(self) -> str: 

606 """ 

607 *message* (:class:`str`) is the error message. 

608 """ 

609 return "Real path {real!r} was encountered at {first!r} and then {second!r}.".format( 

610 real=self.real_path, 

611 first=self.first_path, 

612 second=self.second_path, 

613 ) 

614 

615 @property 

616 def real_path(self) -> str: 

617 """ 

618 *real_path* (:class:`str`) is the real path that recursion was 

619 encountered on. 

620 """ 

621 return self.args[0] 

622 

623 @property 

624 def second_path(self) -> str: 

625 """ 

626 *second_path* (:class:`str`) is the second path encountered for 

627 :attr:`self.real_path <RecursionError.real_path>`. 

628 """ 

629 return self.args[2] 

630 

631 

632@dataclass(frozen=True) 

633class CheckResult(Generic[TStrPath]): 

634 """ 

635 The :class:`CheckResult` class contains information about the file and which 

636 pattern matched it. 

637 """ 

638 

639 # Make the class dict-less. 

640 __slots__ = ( 

641 'file', 

642 'include', 

643 'index', 

644 ) 

645 

646 file: TStrPath 

647 """ 

648 *file* (:class:`str` or :class:`os.PathLike`) is the file path. 

649 """ 

650 

651 include: Optional[bool] 

652 """ 

653 *include* (:class:`bool` or :data:`None`) is whether to include or exclude the 

654 file. If :data:`None`, no pattern matched. 

655 """ 

656 

657 index: Optional[int] 

658 """ 

659 *index* (:class:`int` or :data:`None`) is the index of the last pattern that 

660 matched. If :data:`None`, no pattern matched. 

661 """ 

662 

663 

664class MatchDetail(object): 

665 """ 

666 The :class:`.MatchDetail` class contains information about 

667 """ 

668 

669 # Make the class dict-less. 

670 __slots__ = ('patterns',) 

671 

672 def __init__(self, patterns: Sequence[Pattern]) -> None: 

673 """ 

674 Initialize the :class:`.MatchDetail` instance. 

675 

676 *patterns* (:class:`~collections.abc.Sequence` of :class:`~pathspec.pattern.Pattern`) 

677 contains the patterns that matched the file in the order they were 

678 encountered. 

679 """ 

680 

681 self.patterns = patterns 

682 """ 

683 *patterns* (:class:`~collections.abc.Sequence` of :class:`~pathspec.pattern.Pattern`) 

684 contains the patterns that matched the file in the order they were 

685 encountered. 

686 """ 

687 

688 

689class TreeEntry(object): 

690 """ 

691 The :class:`.TreeEntry` class contains information about a file-system 

692 entry. 

693 """ 

694 

695 # Make the class dict-less. 

696 __slots__ = ('_lstat', 'name', 'path', '_stat') 

697 

698 def __init__( 

699 self, 

700 name: str, 

701 path: str, 

702 lstat: os.stat_result, 

703 stat: os.stat_result, 

704 ) -> None: 

705 """ 

706 Initialize the :class:`.TreeEntry` instance. 

707 

708 *name* (:class:`str`) is the base name of the entry. 

709 

710 *path* (:class:`str`) is the relative path of the entry. 

711 

712 *lstat* (:class:`os.stat_result`) is the stat result of the direct 

713 entry. 

714 

715 *stat* (:class:`os.stat_result`) is the stat result of the entry, 

716 potentially linked. 

717 """ 

718 

719 self._lstat: os.stat_result = lstat 

720 """ 

721 *_lstat* (:class:`os.stat_result`) is the stat result of the direct 

722 entry. 

723 """ 

724 

725 self.name: str = name 

726 """ 

727 *name* (:class:`str`) is the base name of the entry. 

728 """ 

729 

730 self.path: str = path 

731 """ 

732 *path* (:class:`str`) is the path of the entry. 

733 """ 

734 

735 self._stat: os.stat_result = stat 

736 """ 

737 *_stat* (:class:`os.stat_result`) is the stat result of the linked 

738 entry. 

739 """ 

740 

741 def is_dir(self, follow_links: Optional[bool] = None) -> bool: 

742 """ 

743 Get whether the entry is a directory. 

744 

745 *follow_links* (:class:`bool` or :data:`None`) is whether to follow 

746 symbolic links. If this is :data:`True`, a symlink to a directory 

747 will result in :data:`True`. Default is :data:`None` for :data:`True`. 

748 

749 Returns whether the entry is a directory (:class:`bool`). 

750 """ 

751 if follow_links is None: 

752 follow_links = True 

753 

754 node_stat = self._stat if follow_links else self._lstat 

755 return stat.S_ISDIR(node_stat.st_mode) 

756 

757 def is_file(self, follow_links: Optional[bool] = None) -> bool: 

758 """ 

759 Get whether the entry is a regular file. 

760 

761 *follow_links* (:class:`bool` or :data:`None`) is whether to follow 

762 symbolic links. If this is :data:`True`, a symlink to a regular file 

763 will result in :data:`True`. Default is :data:`None` for :data:`True`. 

764 

765 Returns whether the entry is a regular file (:class:`bool`). 

766 """ 

767 if follow_links is None: 

768 follow_links = True 

769 

770 node_stat = self._stat if follow_links else self._lstat 

771 return stat.S_ISREG(node_stat.st_mode) 

772 

773 def is_symlink(self) -> bool: 

774 """ 

775 Returns whether the entry is a symbolic link (:class:`bool`). 

776 """ 

777 return stat.S_ISLNK(self._lstat.st_mode) 

778 

779 def stat(self, follow_links: Optional[bool] = None) -> os.stat_result: 

780 """ 

781 Get the cached stat result for the entry. 

782 

783 *follow_links* (:class:`bool` or :data:`None`) is whether to follow 

784 symbolic links. If this is :data:`True`, the stat result of the 

785 linked file will be returned. Default is :data:`None` for :data:`True`. 

786 

787 Returns that stat result (:class:`os.stat_result`). 

788 """ 

789 if follow_links is None: 

790 follow_links = True 

791 

792 return self._stat if follow_links else self._lstat