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

249 statements  

1""" 

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

3""" 

4from __future__ import annotations 

5 

6import os 

7import os.path 

8import pathlib 

9import posixpath 

10import stat 

11from collections.abc import ( 

12 Collection, 

13 Iterable, 

14 Iterator, 

15 Sequence) 

16from dataclasses import ( 

17 dataclass) 

18from typing import ( 

19 Any, 

20 Callable, # Replaced by `collections.abc.Callable` in 3.9.2. 

21 Generic, 

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

23 TypeVar, 

24 Union, # Replaced by `X | Y` in 3.10. 

25 cast) 

26 

27from .pattern import ( 

28 Pattern) 

29from ._typing import ( 

30 AnyStr, # Removed in 3.18. 

31 deprecated) # Added in 3.13. 

32 

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

34 

35TPattern = TypeVar('TPattern', bound=Pattern) 

36""" 

37Type variable for :class:`.Pattern`. This is used by :class:`pathspec.pathspec.PathSpec` 

38to specialize the type of patterns. 

39""" 

40 

41TPattern_co = TypeVar('TPattern_co', bound=Pattern, covariant=True) 

42""" 

43Type variable for :class:`.Pattern` that is covariant. This is used by 

44:class:`pathspec.pathspec.PathSpec` to specialize the type of patterns. 

45""" 

46 

47TStrPath = TypeVar('TStrPath', bound=StrPath) 

48""" 

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

50""" 

51 

52NORMALIZE_PATH_SEPS = [ 

53 cast(str, __sep) 

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

55 if __sep and __sep != posixpath.sep 

56] 

57""" 

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

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

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

61:data:`os.altsep`. 

62""" 

63 

64_registered_patterns: dict[str, Callable[[Union[str, bytes]], Pattern]] = {} 

65""" 

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

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

68""" 

69 

70 

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

72 """ 

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

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

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

76 

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

78 

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

80 """ 

81 str_path = str(path) 

82 if path.is_dir(): 

83 str_path += os.sep 

84 

85 return str_path 

86 

87 

88def check_match_file( 

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

90 file: str, 

91 is_reversed: Optional[bool] = None, 

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

93 """ 

94 Check the file against the patterns. 

95 

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

97 (:class:`tuple`) which contains the pattern index (:class:`int`) and actua 

98 pattern (:class:`.Pattern`). 

99 

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

101 *patterns*. 

102 

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

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

105 Reversing the order of the patterns is an optimization. 

106 

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

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

109 :data:`None`). 

110 """ 

111 if is_reversed: 

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

113 # precedence. 

114 for index, pattern in patterns: 

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

116 return pattern.include, index 

117 

118 return None, None 

119 

120 else: 

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

122 out_include: Optional[bool] = None 

123 out_index: Optional[int] = None 

124 for index, pattern in patterns: 

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

126 out_include = pattern.include 

127 out_index = index 

128 

129 return out_include, out_index 

130 

131 

132def detailed_match_files( 

133 patterns: Iterable[Pattern], 

134 files: Iterable[str], 

135 all_matches: Optional[bool] = None, 

136) -> dict[str, MatchDetail]: 

137 """ 

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

139 files. 

140 

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

142 the patterns to use. 

143 

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

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

146 

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

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

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

150 

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

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

153 """ 

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

155 return_files: dict[str, MatchDetail] = {} 

156 for pattern in patterns: 

157 if pattern.include is not None: 

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

159 if pattern.include: 

160 # Add files and record pattern. 

161 for result_file in result_files: 

162 if result_file in return_files: 

163 # We know here that .patterns is a list, because we made it here 

164 if all_matches: 

165 return_files[result_file].patterns.append(pattern) # type: ignore[attr-defined] 

166 else: 

167 return_files[result_file].patterns[0] = pattern # type: ignore[index] 

168 else: 

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

170 

171 else: 

172 # Remove files. 

173 for file in result_files: 

174 del return_files[file] 

175 

176 return return_files 

177 

178 

179def _filter_check_patterns( 

180 patterns: Iterable[Pattern], 

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

182 """ 

183 Filters out null-patterns. 

184 

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

186 the patterns. 

187 

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

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

190 (:class:`.Pattern`). 

191 """ 

192 return [ 

193 (__index, __pat) 

194 for __index, __pat in enumerate(patterns) 

195 if __pat.include is not None 

196 ] 

197 

198 

199def _get_sub_path_safe(root: str, sub_dir: StrPath) -> str: 

200 """ 

201 Get the sub-directory path relative to the root directory. This ensures the 

202 root path cannot be escaped. 

203 

204 *root* (:class:`str`) is the root directory. 

205 

206 *sub_dir* (:class:`str` or :class:`os.PathLike`) is the sub-directory path. 

207 This path can be relative or absolute. 

208 

209 Returns the sub-directory path relative to the root directory (:class:`str`). 

210 """ 

211 sub_abs = os.path.normpath(os.path.join(root, sub_dir)) 

212 if sub_abs == root: 

213 return '' 

214 elif sub_abs.startswith(root + os.sep): 

215 return os.path.relpath(sub_abs, root) 

216 else: 

217 raise ValueError(f"{sub_dir=!r} must be relative to {root=!r}.") 

218 

219 

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

221 """ 

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

223 

224 *value* is the value to check. 

225 

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

227 """ 

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

229 

230 

231@deprecated(( 

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

233)) 

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

235 """ 

236 .. version-deprecated:: 0.10.0 

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

238 """ 

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

240 

241 

242def iter_tree_entries( 

243 root: StrPath, 

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

245 follow_links: Optional[bool] = None, 

246 subdir: Optional[StrPath] = None, 

247) -> Iterator['TreeEntry']: 

248 """ 

249 Walks the specified directory for all files and directories. 

250 

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

252 

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

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

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

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

257 

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

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

260 :data:`True`. 

261 

262 *subdir* (:class:`str`, :class:`os.PathLike`, or :data:`None`) is a sub 

263 directory of *root* to constrain searching to. If a relative path, it is 

264 treated as relative to *root*. If an absolute path, it must be a descendant of 

265 *root*. Default is :data:`None` to search the entire *root* directory tree. 

266 

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

268 

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

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

271 """ 

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

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

274 

275 if follow_links is None: 

276 follow_links = True 

277 

278 root_abs = os.path.abspath(root) 

279 

280 # Ensure sub_dir does not escape root. 

281 if subdir is not None: 

282 dir_rel = _get_sub_path_safe(root_abs, subdir) 

283 else: 

284 dir_rel = '' 

285 

286 yield from _iter_tree_entries_next(root_abs, dir_rel, {}, on_error, follow_links) 

287 

288 

289def _iter_tree_entries_next( 

290 root_full: str, 

291 dir_rel: str, 

292 memo: dict[str, str], 

293 on_error: Optional[Callable[[OSError], None]], 

294 follow_links: bool, 

295) -> Iterator['TreeEntry']: 

296 """ 

297 Scan the directory for all descendant files. 

298 

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

300 

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

302 *root_full*. 

303 

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

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

306 

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

308 the error handler for file-system exceptions. 

309 

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

311 to directories. 

312 

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

314 """ 

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

316 dir_real = os.path.realpath(dir_full) 

317 

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

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

320 if dir_real not in memo: 

321 memo[dir_real] = dir_rel 

322 else: 

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

324 

325 with os.scandir(dir_full) as scan_iter: 

326 node_ent: os.DirEntry 

327 for node_ent in scan_iter: 

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

329 

330 # Inspect child node. 

331 try: 

332 node_lstat = node_ent.stat(follow_symlinks=False) 

333 except OSError as e: 

334 if on_error is not None: 

335 on_error(e) 

336 continue 

337 

338 if node_ent.is_symlink(): 

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

340 try: 

341 node_stat = node_ent.stat() 

342 except OSError as e: 

343 if on_error is not None: 

344 on_error(e) 

345 continue 

346 else: 

347 node_stat = node_lstat 

348 

349 if node_ent.is_dir(follow_symlinks=follow_links): 

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

351 # files. 

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

353 

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

355 

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

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

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

359 

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

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

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

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

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

365 del memo[dir_real] 

366 

367 

368# TODO: Add tests for subdir. 

369def iter_tree_files( 

370 root: StrPath, 

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

372 follow_links: Optional[bool] = None, 

373 subdir: Optional[StrPath] = None, 

374) -> Iterator[str]: 

375 """ 

376 Walks the specified directory for all files. 

377 

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

379 for files. 

380 

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

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

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

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

385 

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

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

388 :data:`True`. 

389 

390 *subdir* (:class:`str`, :class:`os.PathLike`, or :data:`None`) is a sub 

391 directory of *root* to constrain searching to. If a relative path, it is 

392 treated as relative to *root*. If an absolute path, it must be a descendant of 

393 *root*. Default is :data:`None` to search the entire *root* directory tree. 

394 

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

396 

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

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

399 """ 

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

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

402 

403 if follow_links is None: 

404 follow_links = True 

405 

406 root_abs = os.path.abspath(root) 

407 

408 # Ensure subdir does not escape root. 

409 if subdir is not None: 

410 dir_rel = _get_sub_path_safe(root_abs, subdir) 

411 else: 

412 dir_rel = '' 

413 

414 yield from _iter_tree_files_next(root_abs, dir_rel, {}, on_error, follow_links) 

415 

416 

417def _iter_tree_files_next( 

418 root_full: str, 

419 dir_rel: str, 

420 memo: dict[str, str], 

421 on_error: Optional[Callable[[OSError], None]], 

422 follow_links: bool, 

423) -> Iterator[str]: 

424 """ 

425 Scan the directory for all descendant files. 

426 

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

428 

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

430 *root_full*. 

431 

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

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

434 

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

436 the error handler for file-system exceptions. 

437 

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

439 to directories. 

440 

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

442 """ 

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

444 dir_real = os.path.realpath(dir_full) 

445 

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

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

448 if dir_real not in memo: 

449 memo[dir_real] = dir_rel 

450 else: 

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

452 

453 with os.scandir(dir_full) as scan_iter: 

454 node_ent: os.DirEntry 

455 for node_ent in scan_iter: 

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

457 

458 if node_ent.is_dir(follow_symlinks=follow_links): 

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

460 # files. 

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

462 

463 elif node_ent.is_file(): 

464 # Child node is a file, yield it. 

465 yield node_rel 

466 

467 elif not follow_links and node_ent.is_symlink(): 

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

469 yield node_rel 

470 

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

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

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

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

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

476 del memo[dir_real] 

477 

478 

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

480 """ 

481 Looks up a registered pattern factory by name. 

482 

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

484 

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

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

487 """ 

488 return _registered_patterns[name] # type: ignore[return-value] 

489 

490 

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

492 """ 

493 Matches the file to the patterns. 

494 

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

496 the patterns to use. 

497 

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

499 *patterns*. 

500 

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

502 """ 

503 matched = False 

504 for pattern in patterns: 

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

506 matched = pattern.include 

507 

508 return matched 

509 

510 

511@deprecated(( 

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

513 "better results." 

514)) 

515def match_files( 

516 patterns: Iterable[Pattern], 

517 files: Iterable[str], 

518) -> set[str]: 

519 """ 

520 .. version-deprecated:: 0.10.0 

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

522 loop for better results. 

523 

524 Matches the files to the patterns. 

525 

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

527 the patterns to use. 

528 

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

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

531 

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

533 """ 

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

535 

536 return_files = set() 

537 for file in files: 

538 if match_file(use_patterns, file): 

539 return_files.add(file) 

540 

541 return return_files 

542 

543 

544def normalize_file( 

545 file: StrPath, 

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

547) -> str: 

548 """ 

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

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

551 

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

553 

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

555 :data:`None`) optionally contains the path separators to normalize. This does 

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

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

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

559 ``()``). 

560 

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

562 """ 

563 # Normalize path separators. 

564 if separators is None: 

565 separators = NORMALIZE_PATH_SEPS 

566 

567 assert separators is not None, separators 

568 

569 # Convert path object to string. 

570 norm_file: str = os.fspath(file) 

571 

572 for sep in separators: 

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

574 

575 if norm_file.startswith('/'): 

576 # Make path relative. 

577 norm_file = norm_file[1:] 

578 

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

580 # Remove current directory prefix. 

581 norm_file = norm_file[2:] 

582 

583 return norm_file 

584 

585 

586@deprecated(( 

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

588 "loop for better results." 

589)) 

590def normalize_files( 

591 files: Iterable[StrPath], 

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

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

594 """ 

595 .. version-deprecated:: 0.10.0 

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

597 with a loop for better results. 

598 

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

600 

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

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

603 

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

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

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

607 

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

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

610 :class:`os.PathLike`). 

611 """ 

612 norm_files: dict[str, list[StrPath]] = {} 

613 for path in files: 

614 norm_file = normalize_file(path, separators=separators) 

615 if norm_file in norm_files: 

616 norm_files[norm_file].append(path) 

617 else: 

618 norm_files[norm_file] = [path] 

619 

620 return norm_files 

621 

622 

623def register_pattern( 

624 name: str, 

625 pattern_factory: Union[Callable[[Union[str, bytes]], Pattern], type[Pattern]], 

626 override: Optional[bool] = None, 

627) -> None: 

628 """ 

629 Registers the specified pattern factory. 

630 

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

632 

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

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

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

636 

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

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

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

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

641 """ 

642 if not isinstance(name, str): 

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

644 

645 if not callable(pattern_factory): 

646 raise TypeError(f"{pattern_factory=!r} is not callable.") 

647 

648 if name in _registered_patterns and not override: 

649 raise AlreadyRegisteredError(name, _registered_patterns[name]) 

650 

651 _registered_patterns[name] = pattern_factory # type: ignore 

652 

653 

654class AlreadyRegisteredError(Exception): 

655 """ 

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

657 is registered under a name already in use. 

658 """ 

659 

660 def __init__( 

661 self, 

662 name: str, 

663 pattern_factory: Callable[[Union[str, bytes]], Pattern], 

664 ) -> None: 

665 """ 

666 Initializes the :exc:`AlreadyRegisteredError` instance. 

667 

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

669 

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

671 pattern factory. 

672 """ 

673 super().__init__(name, pattern_factory) 

674 

675 @property 

676 def message(self) -> str: 

677 """ 

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

679 """ 

680 return ( 

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

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

683 ) 

684 

685 @property 

686 def name(self) -> str: 

687 """ 

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

689 """ 

690 return self.args[0] 

691 

692 @property 

693 def pattern_factory(self) -> Callable[[Union[str, bytes]], Pattern]: 

694 """ 

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

696 pattern factory. 

697 """ 

698 return self.args[1] 

699 

700 

701# TODO: Rename to RecursivePathError because RecursionError is a built-in 

702# exception as of Python 3.5. Keep RecursionError as a deprecated alias. 

703class RecursionError(Exception): 

704 """ 

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

706 """ 

707 

708 def __init__( 

709 self, 

710 real_path: str, 

711 first_path: str, 

712 second_path: str, 

713 ) -> None: 

714 """ 

715 Initializes the :exc:`RecursionError` instance. 

716 

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

718 on. 

719 

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

721 

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

723 """ 

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

725 

726 @property 

727 def first_path(self) -> str: 

728 """ 

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

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

731 """ 

732 return self.args[1] 

733 

734 @property 

735 def message(self) -> str: 

736 """ 

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

738 """ 

739 return ( 

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

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

742 ) 

743 

744 @property 

745 def real_path(self) -> str: 

746 """ 

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

748 encountered on. 

749 """ 

750 return self.args[0] 

751 

752 @property 

753 def second_path(self) -> str: 

754 """ 

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

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

757 """ 

758 return self.args[2] 

759 

760 

761@dataclass(frozen=True) 

762class CheckResult(Generic[TStrPath]): 

763 """ 

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

765 pattern matched it. 

766 """ 

767 

768 # Make the class dict-less. 

769 __slots__ = ( 

770 'file', 

771 'include', 

772 'index', 

773 ) 

774 

775 file: TStrPath 

776 """ 

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

778 """ 

779 

780 include: Optional[bool] 

781 """ 

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

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

784 """ 

785 

786 index: Optional[int] 

787 """ 

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

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

790 """ 

791 

792 

793class MatchDetail(object): 

794 """ 

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

796 """ 

797 

798 # Make the class dict-less. 

799 __slots__ = ('patterns',) 

800 

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

802 """ 

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

804 

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

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

807 """ 

808 

809 self.patterns = patterns 

810 """ 

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

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

813 encountered. 

814 """ 

815 

816 

817class TreeEntry(object): 

818 """ 

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

820 """ 

821 

822 # Make the class dict-less. 

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

824 

825 def __init__( 

826 self, 

827 name: str, 

828 path: str, 

829 lstat: os.stat_result, 

830 stat: os.stat_result, 

831 ) -> None: 

832 """ 

833 Initialize the :class:`TreeEntry` instance. 

834 

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

836 

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

838 

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

840 

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

842 potentially linked. 

843 """ 

844 

845 self._lstat: os.stat_result = lstat 

846 """ 

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

848 """ 

849 

850 self.name: str = name 

851 """ 

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

853 """ 

854 

855 self.path: str = path 

856 """ 

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

858 """ 

859 

860 self._stat: os.stat_result = stat 

861 """ 

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

863 """ 

864 

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

866 """ 

867 Get whether the entry is a directory. 

868 

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

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

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

872 

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

874 """ 

875 if follow_links is None: 

876 follow_links = True 

877 

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

879 return stat.S_ISDIR(node_stat.st_mode) 

880 

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

882 """ 

883 Get whether the entry is a regular file. 

884 

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

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

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

888 

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

890 """ 

891 if follow_links is None: 

892 follow_links = True 

893 

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

895 return stat.S_ISREG(node_stat.st_mode) 

896 

897 def is_symlink(self) -> bool: 

898 """ 

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

900 """ 

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

902 

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

904 """ 

905 Get the cached stat result for the entry. 

906 

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

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

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

910 

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

912 """ 

913 if follow_links is None: 

914 follow_links = True 

915 

916 return self._stat if follow_links else self._lstat