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 if all_matches: 

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

151 else: 

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

153 else: 

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

155 

156 else: 

157 # Remove files. 

158 for file in result_files: 

159 del return_files[file] 

160 

161 return return_files 

162 

163 

164def _filter_check_patterns( 

165 patterns: Iterable[Pattern], 

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

167 """ 

168 Filters out null-patterns. 

169 

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

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

195@deprecated(( 

196 "pathspec.util.iter_tree() is deprecated. Use iter_tree_files() instead." 

197)) 

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

199 """ 

200 .. version-deprecated:: 0.10.0 

201 This is an alias for the :func:`.iter_tree_files` function. 

202 """ 

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

204 

205 

206def iter_tree_entries( 

207 root: StrPath, 

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

209 follow_links: Optional[bool] = None, 

210) -> Iterator['TreeEntry']: 

211 """ 

212 Walks the specified directory for all files and directories. 

213 

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

215 

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

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

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

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

220 

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

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

223 :data:`True`. 

224 

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

226 

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

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

229 """ 

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

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

232 

233 if follow_links is None: 

234 follow_links = True 

235 

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

237 

238 

239def _iter_tree_entries_next( 

240 root_full: str, 

241 dir_rel: str, 

242 memo: dict[str, str], 

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

244 follow_links: bool, 

245) -> Iterator['TreeEntry']: 

246 """ 

247 Scan the directory for all descendant files. 

248 

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

250 

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

252 *root_full*. 

253 

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

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

256 

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

258 the error handler for file-system exceptions. 

259 

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

261 to directories. 

262 

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

264 """ 

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

266 dir_real = os.path.realpath(dir_full) 

267 

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

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

270 if dir_real not in memo: 

271 memo[dir_real] = dir_rel 

272 else: 

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

274 

275 with os.scandir(dir_full) as scan_iter: 

276 node_ent: os.DirEntry 

277 for node_ent in scan_iter: 

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

279 

280 # Inspect child node. 

281 try: 

282 node_lstat = node_ent.stat(follow_symlinks=False) 

283 except OSError as e: 

284 if on_error is not None: 

285 on_error(e) 

286 continue 

287 

288 if node_ent.is_symlink(): 

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

290 try: 

291 node_stat = node_ent.stat() 

292 except OSError as e: 

293 if on_error is not None: 

294 on_error(e) 

295 continue 

296 else: 

297 node_stat = node_lstat 

298 

299 if node_ent.is_dir(follow_symlinks=follow_links): 

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

301 # files. 

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

303 

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

305 

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

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

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

309 

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

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

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

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

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

315 del memo[dir_real] 

316 

317 

318def iter_tree_files( 

319 root: StrPath, 

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

321 follow_links: Optional[bool] = None, 

322) -> Iterator[str]: 

323 """ 

324 Walks the specified directory for all files. 

325 

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

327 for files. 

328 

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

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

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

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

333 

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

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

336 :data:`True`. 

337 

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

339 

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

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

342 """ 

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

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

345 

346 if follow_links is None: 

347 follow_links = True 

348 

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

350 

351 

352def _iter_tree_files_next( 

353 root_full: str, 

354 dir_rel: str, 

355 memo: dict[str, str], 

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

357 follow_links: bool, 

358) -> Iterator[str]: 

359 """ 

360 Scan the directory for all descendant files. 

361 

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

363 

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

365 *root_full*. 

366 

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

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

369 

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

371 the error handler for file-system exceptions. 

372 

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

374 to directories. 

375 

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

377 """ 

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

379 dir_real = os.path.realpath(dir_full) 

380 

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

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

383 if dir_real not in memo: 

384 memo[dir_real] = dir_rel 

385 else: 

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

387 

388 with os.scandir(dir_full) as scan_iter: 

389 node_ent: os.DirEntry 

390 for node_ent in scan_iter: 

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

392 

393 if node_ent.is_dir(follow_symlinks=follow_links): 

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

395 # files. 

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

397 

398 elif node_ent.is_file(): 

399 # Child node is a file, yield it. 

400 yield node_rel 

401 

402 elif not follow_links and node_ent.is_symlink(): 

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

404 yield node_rel 

405 

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

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

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

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

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

411 del memo[dir_real] 

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:`.Pattern`) contains 

431 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 

446@deprecated(( 

447 "pathspec.util.match_files() is deprecated. Use match_file() with a loop for " 

448 "better results." 

449)) 

450def match_files( 

451 patterns: Iterable[Pattern], 

452 files: Iterable[str], 

453) -> set[str]: 

454 """ 

455 .. version-deprecated:: 0.10.0 

456 This function is no longer used. Use the :func:`.match_file` function with a 

457 loop for better results. 

458 

459 Matches the files to the patterns. 

460 

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

462 the patterns to use. 

463 

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

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

466 

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

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 :data:`None`) optionally contains the path separators to normalize. This does 

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

492 not affect the results. Default is ``None`` for :data:`.NORMALIZE_PATH_SEPS`. 

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

494 ``()``). 

495 

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

497 """ 

498 # Normalize path separators. 

499 if separators is None: 

500 separators = NORMALIZE_PATH_SEPS 

501 

502 # Convert path object to string. 

503 norm_file: str = os.fspath(file) 

504 

505 for sep in separators: 

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

507 

508 if norm_file.startswith('/'): 

509 # Make path relative. 

510 norm_file = norm_file[1:] 

511 

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

513 # Remove current directory prefix. 

514 norm_file = norm_file[2:] 

515 

516 return norm_file 

517 

518 

519@deprecated(( 

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

521 "loop for better results." 

522)) 

523def normalize_files( 

524 files: Iterable[StrPath], 

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

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

527 """ 

528 .. version-deprecated:: 0.10.0 

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

530 with a loop for better results. 

531 

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

533 

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

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

536 

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

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

539 :func:`.normalize_file` for more information. 

540 

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

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

543 :class:`os.PathLike`). 

544 """ 

545 norm_files = {} 

546 for path in files: 

547 norm_file = normalize_file(path, separators=separators) 

548 if norm_file in norm_files: 

549 norm_files[norm_file].append(path) 

550 else: 

551 norm_files[norm_file] = [path] 

552 

553 return norm_files 

554 

555 

556def register_pattern( 

557 name: str, 

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

559 override: Optional[bool] = None, 

560) -> None: 

561 """ 

562 Registers the specified pattern factory. 

563 

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

565 

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

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

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

569 

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

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

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

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

574 """ 

575 if not isinstance(name, str): 

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

577 

578 if not callable(pattern_factory): 

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

580 

581 if name in _registered_patterns and not override: 

582 raise AlreadyRegisteredError(name, _registered_patterns[name]) 

583 

584 _registered_patterns[name] = pattern_factory 

585 

586 

587class AlreadyRegisteredError(Exception): 

588 """ 

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

590 is registered under a name already in use. 

591 """ 

592 

593 def __init__( 

594 self, 

595 name: str, 

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

597 ) -> None: 

598 """ 

599 Initializes the :exc:`AlreadyRegisteredError` instance. 

600 

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

602 

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

604 pattern factory. 

605 """ 

606 super().__init__(name, pattern_factory) 

607 

608 @property 

609 def message(self) -> str: 

610 """ 

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

612 """ 

613 return ( 

614 f"{self.name!r} is already registered for pattern factory=" 

615 f"{self.pattern_factory!r}." 

616 ) 

617 

618 @property 

619 def name(self) -> str: 

620 """ 

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

622 """ 

623 return self.args[0] 

624 

625 @property 

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

627 """ 

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

629 pattern factory. 

630 """ 

631 return self.args[1] 

632 

633 

634class RecursionError(Exception): 

635 """ 

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

637 """ 

638 

639 def __init__( 

640 self, 

641 real_path: str, 

642 first_path: str, 

643 second_path: str, 

644 ) -> None: 

645 """ 

646 Initializes the :exc:`RecursionError` instance. 

647 

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

649 on. 

650 

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

652 

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

654 """ 

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

656 

657 @property 

658 def first_path(self) -> str: 

659 """ 

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

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

662 """ 

663 return self.args[1] 

664 

665 @property 

666 def message(self) -> str: 

667 """ 

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

669 """ 

670 return ( 

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

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

673 ) 

674 

675 @property 

676 def real_path(self) -> str: 

677 """ 

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

679 encountered on. 

680 """ 

681 return self.args[0] 

682 

683 @property 

684 def second_path(self) -> str: 

685 """ 

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

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

688 """ 

689 return self.args[2] 

690 

691 

692@dataclass(frozen=True) 

693class CheckResult(Generic[TStrPath]): 

694 """ 

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

696 pattern matched it. 

697 """ 

698 

699 # Make the class dict-less. 

700 __slots__ = ( 

701 'file', 

702 'include', 

703 'index', 

704 ) 

705 

706 file: TStrPath 

707 """ 

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

709 """ 

710 

711 include: Optional[bool] 

712 """ 

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

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

715 """ 

716 

717 index: Optional[int] 

718 """ 

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

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

721 """ 

722 

723 

724class MatchDetail(object): 

725 """ 

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

727 """ 

728 

729 # Make the class dict-less. 

730 __slots__ = ('patterns',) 

731 

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

733 """ 

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

735 

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

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

738 """ 

739 

740 self.patterns = patterns 

741 """ 

742 *patterns* (:class:`~collections.abc.Sequence` of :class:`.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 entry. 

751 """ 

752 

753 # Make the class dict-less. 

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

755 

756 def __init__( 

757 self, 

758 name: str, 

759 path: str, 

760 lstat: os.stat_result, 

761 stat: os.stat_result, 

762 ) -> None: 

763 """ 

764 Initialize the :class:`TreeEntry` instance. 

765 

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

767 

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

769 

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

771 

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

773 potentially linked. 

774 """ 

775 

776 self._lstat: os.stat_result = lstat 

777 """ 

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

779 """ 

780 

781 self.name: str = name 

782 """ 

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

784 """ 

785 

786 self.path: str = path 

787 """ 

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

789 """ 

790 

791 self._stat: os.stat_result = stat 

792 """ 

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

794 """ 

795 

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

797 """ 

798 Get whether the entry is a directory. 

799 

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

801 links. If this is :data:`True`, a symlink to a directory will result in 

802 :data:`True`. Default is :data:`None` for :data:`True`. 

803 

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

805 """ 

806 if follow_links is None: 

807 follow_links = True 

808 

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

810 return stat.S_ISDIR(node_stat.st_mode) 

811 

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

813 """ 

814 Get whether the entry is a regular file. 

815 

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

817 links. If this is :data:`True`, a symlink to a regular file will result in 

818 :data:`True`. Default is :data:`None` for :data:`True`. 

819 

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

821 """ 

822 if follow_links is None: 

823 follow_links = True 

824 

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

826 return stat.S_ISREG(node_stat.st_mode) 

827 

828 def is_symlink(self) -> bool: 

829 """ 

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

831 """ 

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

833 

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

835 """ 

836 Get the cached stat result for the entry. 

837 

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

839 links. If this is :data:`True`, the stat result of the linked file will be 

840 returned. Default is :data:`None` for :data:`True`. 

841 

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

843 """ 

844 if follow_links is None: 

845 follow_links = True 

846 

847 return self._stat if follow_links else self._lstat