|
2 | 2 | import re |
3 | 3 | import sys |
4 | 4 | from pathlib import Path, PurePath |
5 | | -from typing import Dict, Iterable, Iterator, NamedTuple, Optional, Reversible, Tuple |
| 5 | +from typing import Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Reversible, Tuple, Union |
| 6 | + |
| 7 | +from robotcode.core.utils.path import path_is_relative_to |
6 | 8 |
|
7 | 9 | _SEPARATORS = ["/"] |
8 | 10 | _SEPARATORS_GROUP = f"[{'|'.join(_SEPARATORS)}]" |
@@ -280,32 +282,92 @@ def _is_hidden(entry: Path) -> bool: |
280 | 282 |
|
281 | 283 |
|
282 | 284 | def iter_files( |
283 | | - root: Path, |
| 285 | + paths: Union[Path, Iterable[Path]], |
| 286 | + root: Optional[Path] = None, |
| 287 | + ignore_files: Iterable[str] = [GIT_IGNORE_FILE], |
| 288 | + include_hidden: bool = True, |
| 289 | + parent_spec: Optional[IgnoreSpec] = None, |
| 290 | + verbose_callback: Optional[Callable[[str], None]] = None, |
| 291 | +) -> Iterator[Path]: |
| 292 | + if isinstance(paths, Path): |
| 293 | + paths = [paths] |
| 294 | + |
| 295 | + for path in paths: |
| 296 | + yield from _iter_files( |
| 297 | + Path(os.path.abspath(path)), |
| 298 | + root=Path(os.path.abspath(root)) if root is not None else root, |
| 299 | + ignore_files=ignore_files, |
| 300 | + include_hidden=include_hidden, |
| 301 | + parent_spec=parent_spec, |
| 302 | + verbose_callback=verbose_callback, |
| 303 | + ) |
| 304 | + |
| 305 | + |
| 306 | +def _iter_files( |
| 307 | + path: Path, |
| 308 | + root: Optional[Path] = None, |
284 | 309 | ignore_files: Iterable[str] = [GIT_IGNORE_FILE], |
285 | 310 | include_hidden: bool = True, |
286 | 311 | parent_spec: Optional[IgnoreSpec] = None, |
| 312 | + verbose_callback: Optional[Callable[[str], None]] = None, |
287 | 313 | ) -> Iterator[Path]: |
288 | 314 |
|
| 315 | + if root is None: |
| 316 | + root = path if path.is_dir() else path.parent |
| 317 | + |
289 | 318 | if parent_spec is None: |
290 | | - parent_spec = IgnoreSpec.from_list(DEFAULT_SPEC_RULES, root) |
| 319 | + parent_spec = IgnoreSpec.from_list(DEFAULT_SPEC_RULES, path) |
| 320 | + |
| 321 | + if path_is_relative_to(path, root): |
| 322 | + parents: List[Path] = [] |
| 323 | + p = path if path.is_dir() else path.parent |
| 324 | + while True: |
| 325 | + p = p.parent |
| 326 | + |
| 327 | + if p < root: |
| 328 | + break |
| 329 | + |
| 330 | + parents.insert(0, p) |
| 331 | + |
| 332 | + for p in parents: |
| 333 | + ignore_file = next((p / f for f in ignore_files if (p / f).is_file()), None) |
| 334 | + |
| 335 | + if ignore_file is not None: |
| 336 | + if verbose_callback is not None: |
| 337 | + verbose_callback(f"using ignore file: '{ignore_file}'") |
| 338 | + parent_spec = parent_spec + IgnoreSpec.from_gitignore(ignore_file) |
| 339 | + ignore_files = [ignore_file.name] |
291 | 340 |
|
292 | | - ignore_file = next((root / f for f in ignore_files if (root / f).is_file()), None) |
| 341 | + ignore_file = next((path / f for f in ignore_files if (path / f).is_file()), None) |
293 | 342 |
|
294 | 343 | if ignore_file is not None: |
295 | | - gitignore = parent_spec + IgnoreSpec.from_gitignore(root / ignore_file) |
| 344 | + if verbose_callback is not None: |
| 345 | + verbose_callback(f"using ignore file: '{ignore_file}'") |
| 346 | + spec = parent_spec + IgnoreSpec.from_gitignore(ignore_file) |
296 | 347 | ignore_files = [ignore_file.name] |
297 | 348 | else: |
298 | | - gitignore = parent_spec |
| 349 | + spec = parent_spec |
299 | 350 |
|
300 | | - for path in root.iterdir(): |
| 351 | + if not path.is_dir(): |
| 352 | + if spec is not None and spec.matches(path): |
| 353 | + return |
| 354 | + yield path |
| 355 | + return |
301 | 356 |
|
302 | | - if not include_hidden and _is_hidden(path): |
| 357 | + for p in path.iterdir(): |
| 358 | + if not include_hidden and _is_hidden(p): |
303 | 359 | continue |
304 | 360 |
|
305 | | - if gitignore is not None and gitignore.matches(path): |
| 361 | + if spec is not None and spec.matches(p): |
306 | 362 | continue |
307 | 363 |
|
308 | | - if path.is_dir(): |
309 | | - yield from iter_files(path, ignore_files, include_hidden, gitignore) |
310 | | - elif path.is_file(): |
311 | | - yield path |
| 364 | + if p.is_dir(): |
| 365 | + yield from _iter_files( |
| 366 | + p, |
| 367 | + ignore_files=ignore_files, |
| 368 | + include_hidden=include_hidden, |
| 369 | + parent_spec=spec, |
| 370 | + verbose_callback=verbose_callback, |
| 371 | + ) |
| 372 | + elif p.is_file(): |
| 373 | + yield p |
0 commit comments