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

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

229 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 warnings 

11from collections.abc import ( 

12 Callable, 

13 Collection, 

14 Iterable, 

15 Iterator, 

16 Sequence) 

17from dataclasses import ( 

18 dataclass) 

19from os import ( 

20 PathLike) 

21from typing import ( 

22 Any, 

23 AnyStr, 

24 Generic, 

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

26 TypeVar, 

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

28 

29from .pattern import ( 

30 Pattern) 

31 

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

33 

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

35""" 

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

37""" 

38 

39NORMALIZE_PATH_SEPS = [ 

40 __sep 

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

42 if __sep and __sep != posixpath.sep 

43] 

44""" 

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

46separators that need to be normalized to the POSIX separator for the current 

47operating system. The separators are determined by examining :data:`os.sep` and 

48:data:`os.altsep`. 

49""" 

50 

51_registered_patterns = {} 

52""" 

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

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

55""" 

56 

57 

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

59 """ 

60 Appends the path separator to the path if the path is a directory. This can be 

61 used to aid in distinguishing between directories and files on the file-system 

62 by relying on the presence of a trailing path separator. 

63 

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

65 

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

67 """ 

68 str_path = str(path) 

69 if path.is_dir(): 

70 str_path += os.sep 

71 

72 return str_path 

73 

74 

75def check_match_file( 

76 patterns: Iterable[tuple[int, Pattern]], 

77 file: str, 

78 is_reversed: Optional[bool] = None, 

79) -> tuple[Optional[bool], Optional[int]]: 

80 """ 

81 Check the file against the patterns. 

82 

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

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

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

86 

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

88 *patterns*. 

89 

90 *is_reversed* (:class:`bool` or :data:`None`) is whether the order of the 

91 patterns has been reversed. Default is :data:`None` for :data:`False`. 

92 Reversing the order of the patterns is an optimization. 

93 

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

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

96 :data:`None`). 

97 """ 

98 if is_reversed: 

99 # Check patterns in reverse order. The first pattern that matches takes 

100 # precedence. 

101 for index, pattern in patterns: 

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

103 return pattern.include, index 

104 

105 return None, None 

106 

107 else: 

108 # Check all patterns. The last pattern that matches takes precedence. 

109 out_include: Optional[bool] = None 

110 out_index: Optional[int] = None 

111 for index, pattern in patterns: 

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

113 out_include = pattern.include 

114 out_index = index 

115 

116 return out_include, out_index 

117 

118 

119def detailed_match_files( 

120 patterns: Iterable[Pattern], 

121 files: Iterable[str], 

122 all_matches: Optional[bool] = None, 

123) -> dict[str, 'MatchDetail']: 

124 """ 

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

126 files. 

127 

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

129 contains the patterns to use. 

130 

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

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

133 

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

135 patterns (:data:`True`), or only the last matched pattern (:data:`False`). 

136 Default is :data:`None` for :data:`False`. 

137 

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

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

140 """ 

141 all_files = files if isinstance(files, Collection) else list(files) 

142 return_files = {} 

143 for pattern in patterns: 

144 if pattern.include is not None: 

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

146 if pattern.include: 

147 # Add files and record pattern. 

148 for result_file in result_files: 

149 if result_file in return_files: 

150 if all_matches: 

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

152 else: 

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

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:`Iterable` of :class:`.Pattern`) contains the patterns. 

172 

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

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

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

176 """ 

177 return [ 

178 (__index, __pat) 

179 for __index, __pat in enumerate(patterns) 

180 if __pat.include is not None 

181 ] 

182 

183 

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

185 """ 

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

187 

188 *value* is the value to check, 

189 

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

191 """ 

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

193 

194 

195def iter_tree_entries( 

196 root: StrPath, 

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

198 follow_links: Optional[bool] = None, 

199) -> Iterator['TreeEntry']: 

200 """ 

201 Walks the specified directory for all files and directories. 

202 

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

204 

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

206 the error handler for file-system exceptions. It will be called with the 

207 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default 

208 is :data:`None` to ignore file-system exceptions. 

209 

210 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk 

211 symbolic links that resolve to directories. Default is :data:`None` for 

212 :data:`True`. 

213 

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

215 

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

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

218 """ 

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

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

221 

222 if follow_links is None: 

223 follow_links = True 

224 

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

226 

227 

228def _iter_tree_entries_next( 

229 root_full: str, 

230 dir_rel: str, 

231 memo: dict[str, str], 

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

233 follow_links: bool, 

234) -> Iterator['TreeEntry']: 

235 """ 

236 Scan the directory for all descendant files. 

237 

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

239 

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

241 *root_full*. 

242 

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

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

245 

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

247 the error handler for file-system exceptions. 

248 

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

250 to directories. 

251 

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

253 """ 

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

255 dir_real = os.path.realpath(dir_full) 

256 

257 # Remember each encountered ancestor directory and its canonical (real) path. 

258 # If a canonical path is encountered more than once, 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 descendant 

290 # 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 from 

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

301 # to appear multiple times. If this is not done, the second occurrence of the 

302 # directory will be incorrectly interpreted as a recursion. See 

303 # <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 search 

316 for files. 

317 

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

319 the error handler for file-system exceptions. It will be called with the 

320 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default 

321 is :data:`None` to ignore file-system exceptions. 

322 

323 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk 

324 symbolic links that resolve to directories. Default is :data:`None` for 

325 :data:`True`. 

326 

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

328 

329 Returns an :class:`~collections.abc.Iterator` yielding the path to each file 

330 (:class:`str`) relative to *root*. 

331 """ 

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

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

334 

335 if follow_links is None: 

336 follow_links = True 

337 

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

339 

340 

341def _iter_tree_files_next( 

342 root_full: str, 

343 dir_rel: str, 

344 memo: dict[str, str], 

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

346 follow_links: bool, 

347) -> Iterator[str]: 

348 """ 

349 Scan the directory for all descendant files. 

350 

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

352 

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

354 *root_full*. 

355 

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

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

358 

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

360 the error handler for file-system exceptions. 

361 

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

363 to directories. 

364 

365 Yields each file path (:class:`str`). 

366 """ 

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

368 dir_real = os.path.realpath(dir_full) 

369 

370 # Remember each encountered ancestor directory and its canonical (real) path. 

371 # If a canonical path is encountered more than once, recursion has occurred. 

372 if dir_real not in memo: 

373 memo[dir_real] = dir_rel 

374 else: 

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

376 

377 with os.scandir(dir_full) as scan_iter: 

378 node_ent: os.DirEntry 

379 for node_ent in scan_iter: 

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

381 

382 if node_ent.is_dir(follow_symlinks=follow_links): 

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

384 # files. 

385 yield from _iter_tree_files_next(root_full, node_rel, memo, on_error, follow_links) 

386 

387 elif node_ent.is_file(): 

388 # Child node is a file, yield it. 

389 yield node_rel 

390 

391 elif not follow_links and node_ent.is_symlink(): 

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

393 yield node_rel 

394 

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

396 # the ancestors memo once we are done with it. This allows the same directory 

397 # to appear multiple times. If this is not done, the second occurrence of the 

398 # directory will be incorrectly interpreted as a recursion. See 

399 # <https://github.com/cpburnz/python-path-specification/pull/7>. 

400 del memo[dir_real] 

401 

402 

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

404 """ 

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

406 :func:`.iter_tree_files` function. 

407 """ 

408 warnings.warn(( 

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

410 ), DeprecationWarning, stacklevel=2) 

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

412 

413 

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

415 """ 

416 Lookups a registered pattern factory by name. 

417 

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

419 

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

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

422 """ 

423 return _registered_patterns[name] 

424 

425 

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

427 """ 

428 Matches the file to the patterns. 

429 

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

431 contains the patterns to use. 

432 

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

434 *patterns*. 

435 

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

437 """ 

438 matched = False 

439 for pattern in patterns: 

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

441 matched = pattern.include 

442 

443 return matched 

444 

445 

446def match_files( 

447 patterns: Iterable[Pattern], 

448 files: Iterable[str], 

449) -> set[str]: 

450 """ 

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

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

453 

454 Matches the files to the patterns. 

455 

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

457 contains the patterns to use. 

458 

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

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

461 

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

463 """ 

464 warnings.warn(( 

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

466 f"a loop for better results." 

467 ), DeprecationWarning, stacklevel=2) 

468 

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

470 

471 return_files = set() 

472 for file in files: 

473 if match_file(use_patterns, file): 

474 return_files.add(file) 

475 

476 return return_files 

477 

478 

479def normalize_file( 

480 file: StrPath, 

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

482) -> str: 

483 """ 

484 Normalizes the file path to use the POSIX path separator (i.e., ``"/"``), and 

485 make the paths relative (remove leading ``"/"``). 

486 

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

488 

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

490 ``None``) optionally contains the path separators to normalize. This does not 

491 need to include the POSIX path separator (``"/"``), but including it will not 

492 affect the results. Default is ``None`` for ``NORMALIZE_PATH_SEPS``. To 

493 prevent normalization, pass an empty container (e.g., an empty tuple ``()``). 

494 

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

496 """ 

497 # Normalize path separators. 

498 if separators is None: 

499 separators = NORMALIZE_PATH_SEPS 

500 

501 # Convert path object to string. 

502 norm_file: str = os.fspath(file) 

503 

504 for sep in separators: 

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

506 

507 if norm_file.startswith('/'): 

508 # Make path relative. 

509 norm_file = norm_file[1:] 

510 

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

512 # Remove current directory prefix. 

513 norm_file = norm_file[2:] 

514 

515 return norm_file 

516 

517 

518def normalize_files( 

519 files: Iterable[StrPath], 

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

521) -> dict[str, list[StrPath]]: 

522 """ 

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

524 function with a loop for better results. 

525 

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

527 

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

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

530 

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

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

533 :func:`normalize_file` for more information. 

534 

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

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

537 :class:`os.PathLike`). 

538 """ 

539 warnings.warn(( 

540 "util.normalize_files() is deprecated. Use util.normalize_file() with a " 

541 "loop for better results." 

542 ), DeprecationWarning, stacklevel=2) 

543 

544 norm_files = {} 

545 for path in files: 

546 norm_file = normalize_file(path, separators=separators) 

547 if norm_file in norm_files: 

548 norm_files[norm_file].append(path) 

549 else: 

550 norm_files[norm_file] = [path] 

551 

552 return norm_files 

553 

554 

555def register_pattern( 

556 name: str, 

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

558 override: Optional[bool] = None, 

559) -> None: 

560 """ 

561 Registers the specified pattern factory. 

562 

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

564 

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

566 patterns. It must accept an uncompiled pattern (:class:`str`) and return the 

567 compiled pattern (:class:`.Pattern`). 

568 

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

570 overriding an already registered pattern under the same name (:data:`True`), 

571 instead of raising an :exc:`AlreadyRegisteredError` (:data:`False`). Default 

572 is :data:`None` for :data:`False`. 

573 """ 

574 if not isinstance(name, str): 

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

576 

577 if not callable(pattern_factory): 

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

579 

580 if name in _registered_patterns and not override: 

581 raise AlreadyRegisteredError(name, _registered_patterns[name]) 

582 

583 _registered_patterns[name] = pattern_factory 

584 

585 

586class AlreadyRegisteredError(Exception): 

587 """ 

588 The :exc:`AlreadyRegisteredError` exception is raised when a pattern factory 

589 is registered under a name already in use. 

590 """ 

591 

592 def __init__( 

593 self, 

594 name: str, 

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

596 ) -> None: 

597 """ 

598 Initializes the :exc:`AlreadyRegisteredError` instance. 

599 

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

601 

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

603 pattern factory. 

604 """ 

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

606 

607 @property 

608 def message(self) -> str: 

609 """ 

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

611 """ 

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

613 name=self.name, 

614 pattern_factory=self.pattern_factory, 

615 ) 

616 

617 @property 

618 def name(self) -> str: 

619 """ 

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

621 """ 

622 return self.args[0] 

623 

624 @property 

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

626 """ 

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

628 pattern factory. 

629 """ 

630 return self.args[1] 

631 

632 

633class RecursionError(Exception): 

634 """ 

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

636 """ 

637 

638 def __init__( 

639 self, 

640 real_path: str, 

641 first_path: str, 

642 second_path: str, 

643 ) -> None: 

644 """ 

645 Initializes the :exc:`RecursionError` instance. 

646 

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

648 on. 

649 

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

651 

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

653 """ 

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

655 

656 @property 

657 def first_path(self) -> str: 

658 """ 

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

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

661 """ 

662 return self.args[1] 

663 

664 @property 

665 def message(self) -> str: 

666 """ 

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

668 """ 

669 return ( 

670 f"Real path {self.real_path!r} was encountered at {self.first_path!r} " 

671 f"and then {self.second_path!r}." 

672 ) 

673 

674 @property 

675 def real_path(self) -> str: 

676 """ 

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

678 encountered on. 

679 """ 

680 return self.args[0] 

681 

682 @property 

683 def second_path(self) -> str: 

684 """ 

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

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

687 """ 

688 return self.args[2] 

689 

690 

691@dataclass(frozen=True) 

692class CheckResult(Generic[TStrPath]): 

693 """ 

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

695 pattern matched it. 

696 """ 

697 

698 # Make the class dict-less. 

699 __slots__ = ( 

700 'file', 

701 'include', 

702 'index', 

703 ) 

704 

705 file: TStrPath 

706 """ 

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

708 """ 

709 

710 include: Optional[bool] 

711 """ 

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

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

714 """ 

715 

716 index: Optional[int] 

717 """ 

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

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

720 """ 

721 

722 

723class MatchDetail(object): 

724 """ 

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

726 """ 

727 

728 # Make the class dict-less. 

729 __slots__ = ('patterns',) 

730 

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

732 """ 

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

734 

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

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

737 encountered. 

738 """ 

739 

740 self.patterns = patterns 

741 """ 

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

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

744 encountered. 

745 """ 

746 

747 

748class TreeEntry(object): 

749 """ 

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

751 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 

772 entry. 

773 

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

775 potentially linked. 

776 """ 

777 

778 self._lstat: os.stat_result = lstat 

779 """ 

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

781 entry. 

782 """ 

783 

784 self.name: str = name 

785 """ 

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

787 """ 

788 

789 self.path: str = path 

790 """ 

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

792 """ 

793 

794 self._stat: os.stat_result = stat 

795 """ 

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

797 entry. 

798 """ 

799 

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

801 """ 

802 Get whether the entry is a directory. 

803 

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

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

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

807 

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

809 """ 

810 if follow_links is None: 

811 follow_links = True 

812 

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

814 return stat.S_ISDIR(node_stat.st_mode) 

815 

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

817 """ 

818 Get whether the entry is a regular file. 

819 

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

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

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

823 

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

825 """ 

826 if follow_links is None: 

827 follow_links = True 

828 

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

830 return stat.S_ISREG(node_stat.st_mode) 

831 

832 def is_symlink(self) -> bool: 

833 """ 

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

835 """ 

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

837 

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

839 """ 

840 Get the cached stat result for the entry. 

841 

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

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

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

845 

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

847 """ 

848 if follow_links is None: 

849 follow_links = True 

850 

851 return self._stat if follow_links else self._lstat