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

228 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 

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. 

24 

25from .pattern import ( 

26 Pattern) 

27from ._typing import ( 

28 AnyStr, # Removed in 3.18. 

29 deprecated) # Added in 3.13. 

30 

31StrPath = Union[str, os.PathLike[str]] 

32 

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

34""" 

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

36""" 

37 

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""" 

49 

50_registered_patterns = {} 

51""" 

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

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

54""" 

55 

56 

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. 

62 

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

64 

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

66 """ 

67 str_path = str(path) 

68 if path.is_dir(): 

69 str_path += os.sep 

70 

71 return str_path 

72 

73 

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. 

81 

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`). 

85 

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

87 *patterns*. 

88 

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. 

92 

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 

103 

104 return None, None 

105 

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 

114 

115 return out_include, out_index 

116 

117 

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. 

126 

127 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains 

128 the patterns to use. 

129 

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

131 normalized file paths to be matched against *patterns*. 

132 

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`. 

136 

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]) 

156 

157 else: 

158 # Remove files. 

159 for file in result_files: 

160 del return_files[file] 

161 

162 return return_files 

163 

164 

165def _filter_check_patterns( 

166 patterns: Iterable[Pattern], 

167) -> list[tuple[int, Pattern]]: 

168 """ 

169 Filters out null-patterns. 

170 

171 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains 

172 the patterns. 

173 

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 ] 

183 

184 

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

186 """ 

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

188 

189 *value* is the value to check, 

190 

191 Returns whether *value* is an iterable (:class:`bool`). 

192 """ 

193 return isinstance(value, Iterable) and not isinstance(value, (str, bytes)) 

194 

195 

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) 

205 

206 

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. 

214 

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

216 

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. 

221 

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`. 

225 

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

227 

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.") 

233 

234 if follow_links is None: 

235 follow_links = True 

236 

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

238 

239 

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. 

249 

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

251 

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

253 *root_full*. 

254 

255 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps 

256 each ancestor real path (:class:`str`) to relative path (:class:`str`). 

257 

258 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is 

259 the error handler for file-system exceptions. 

260 

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

262 to directories. 

263 

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) 

268 

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) 

275 

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) 

280 

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 

288 

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 

299 

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) 

304 

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

306 

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) 

310 

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] 

317 

318 

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. 

326 

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

328 for files. 

329 

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. 

334 

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`. 

338 

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

340 

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.") 

346 

347 if follow_links is None: 

348 follow_links = True 

349 

350 yield from _iter_tree_files_next(os.path.abspath(root), '', {}, on_error, follow_links) 

351 

352 

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. 

362 

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

364 

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

366 *root_full*. 

367 

368 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps 

369 each ancestor real path (:class:`str`) to relative path (:class:`str`). 

370 

371 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is 

372 the error handler for file-system exceptions. 

373 

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

375 to directories. 

376 

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) 

381 

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) 

388 

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) 

393 

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) 

398 

399 elif node_ent.is_file(): 

400 # Child node is a file, yield it. 

401 yield node_rel 

402 

403 elif not follow_links and node_ent.is_symlink(): 

404 # Child node is an unfollowed link, yield it. 

405 yield node_rel 

406 

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] 

413 

414 

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

416 """ 

417 Lookups a registered pattern factory by name. 

418 

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

420 

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] 

425 

426 

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

428 """ 

429 Matches the file to the patterns. 

430 

431 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains 

432 the patterns to use. 

433 

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

435 *patterns*. 

436 

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 

443 

444 return matched 

445 

446 

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. 

459 

460 Matches the files to the patterns. 

461 

462 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains 

463 the patterns to use. 

464 

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

466 normalized file paths to be matched against *patterns*. 

467 

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] 

471 

472 return_files = set() 

473 for file in files: 

474 if match_file(use_patterns, file): 

475 return_files.add(file) 

476 

477 return return_files 

478 

479 

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 ``"/"``). 

487 

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

489 

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 ``()``). 

496 

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

498 """ 

499 # Normalize path separators. 

500 if separators is None: 

501 separators = NORMALIZE_PATH_SEPS 

502 

503 # Convert path object to string. 

504 norm_file: str = os.fspath(file) 

505 

506 for sep in separators: 

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

508 

509 if norm_file.startswith('/'): 

510 # Make path relative. 

511 norm_file = norm_file[1:] 

512 

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

514 # Remove current directory prefix. 

515 norm_file = norm_file[2:] 

516 

517 return norm_file 

518 

519 

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. 

532 

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

534 

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

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

537 

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. 

541 

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] 

553 

554 return norm_files 

555 

556 

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. 

564 

565 *name* (:class:`str`) is the name to register the pattern factory under. 

566 

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`). 

570 

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.") 

578 

579 if not callable(pattern_factory): 

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

581 

582 if name in _registered_patterns and not override: 

583 raise AlreadyRegisteredError(name, _registered_patterns[name]) 

584 

585 _registered_patterns[name] = pattern_factory 

586 

587 

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 """ 

593 

594 def __init__( 

595 self, 

596 name: str, 

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

598 ) -> None: 

599 """ 

600 Initializes the :exc:`AlreadyRegisteredError` instance. 

601 

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

603 

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

605 pattern factory. 

606 """ 

607 super().__init__(name, pattern_factory) 

608 

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 ) 

618 

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] 

625 

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] 

633 

634 

635class RecursionError(Exception): 

636 """ 

637 The :exc:`RecursionError` exception is raised when recursion is detected. 

638 """ 

639 

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. 

648 

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

650 on. 

651 

652 *first_path* (:class:`str`) is the first path encountered for *real_path*. 

653 

654 *second_path* (:class:`str`) is the second path encountered for *real_path*. 

655 """ 

656 super().__init__(real_path, first_path, second_path) 

657 

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] 

665 

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 ) 

675 

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] 

683 

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] 

691 

692 

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 """ 

699 

700 # Make the class dict-less. 

701 __slots__ = ( 

702 'file', 

703 'include', 

704 'index', 

705 ) 

706 

707 file: TStrPath 

708 """ 

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

710 """ 

711 

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 """ 

717 

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 """ 

723 

724 

725class MatchDetail(object): 

726 """ 

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

728 """ 

729 

730 # Make the class dict-less. 

731 __slots__ = ('patterns',) 

732 

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

734 """ 

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

736 

737 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`) 

738 contains the patterns that matched the file in the order they were encountered. 

739 """ 

740 

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 """ 

747 

748 

749class TreeEntry(object): 

750 """ 

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

752 """ 

753 

754 # Make the class dict-less. 

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

756 

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. 

766 

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

768 

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

770 

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

772 

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

774 potentially linked. 

775 """ 

776 

777 self._lstat: os.stat_result = lstat 

778 """ 

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

780 """ 

781 

782 self.name: str = name 

783 """ 

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

785 """ 

786 

787 self.path: str = path 

788 """ 

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

790 """ 

791 

792 self._stat: os.stat_result = stat 

793 """ 

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

795 """ 

796 

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

798 """ 

799 Get whether the entry is a directory. 

800 

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`. 

804 

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

806 """ 

807 if follow_links is None: 

808 follow_links = True 

809 

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

811 return stat.S_ISDIR(node_stat.st_mode) 

812 

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

814 """ 

815 Get whether the entry is a regular file. 

816 

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`. 

820 

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

822 """ 

823 if follow_links is None: 

824 follow_links = True 

825 

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

827 return stat.S_ISREG(node_stat.st_mode) 

828 

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) 

834 

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

836 """ 

837 Get the cached stat result for the entry. 

838 

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`. 

842 

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

844 """ 

845 if follow_links is None: 

846 follow_links = True 

847 

848 return self._stat if follow_links else self._lstat