|
1 | 1 | #!/usr/bin/env python3 |
2 | 2 | """Re-apply type annotations from .pyi stubs to your codebase.""" |
3 | 3 |
|
| 4 | +import os |
4 | 5 | import re |
5 | 6 | import sys |
6 | 7 | import threading |
|
15 | 16 | from pathlib import Path |
16 | 17 |
|
17 | 18 | import click |
| 19 | +from pathspec import PathSpec |
18 | 20 | from typed_ast import ast3 |
19 | 21 |
|
20 | 22 | __version__ = "19.5.0" |
@@ -99,11 +101,18 @@ def retype_path( |
99 | 101 | ): |
100 | 102 | """Recursively retype files or directories given. Generate errors.""" |
101 | 103 | if src.is_dir(): |
102 | | - for child in src.iterdir(): |
103 | | - if child == pyi_dir or child == targets: |
104 | | - continue |
| 104 | + extra_ignore = [] |
| 105 | + for folder in [pyi_dir, targets]: |
| 106 | + try: |
| 107 | + extra_ignore.append("/{}".format(folder.relative_to(src))) |
| 108 | + except ValueError: |
| 109 | + pass |
| 110 | + for file in walk_not_git_ignored( |
| 111 | + src, lambda p: p.suffix == ".py", extra_ignore |
| 112 | + ): |
| 113 | + nested = file.relative_to(src).parent |
105 | 114 | yield from retype_path( |
106 | | - child, pyi_dir / src.name, targets / src.name, quiet=quiet, hg=hg |
| 115 | + file, pyi_dir / nested, targets / nested, quiet=quiet, hg=hg |
107 | 116 | ) |
108 | 117 | elif src.suffix == ".py" or src_explicitly_given: |
109 | 118 | try: |
@@ -1409,6 +1418,58 @@ def new(n, prefix=None): |
1409 | 1418 | return n |
1410 | 1419 |
|
1411 | 1420 |
|
| 1421 | +def _load_ignore(at_path, parent_spec, ignores): |
| 1422 | + ignore_file = at_path / ".gitignore" |
| 1423 | + if not ignore_file.exists(): |
| 1424 | + return parent_spec |
| 1425 | + lines = ignore_file.read_text().split(os.linesep) |
| 1426 | + spec = PathSpec.from_lines("gitwildmatch", lines) |
| 1427 | + spec = PathSpec(parent_spec.patterns + spec.patterns) |
| 1428 | + ignores[at_path] = spec |
| 1429 | + return spec |
| 1430 | + |
| 1431 | + |
| 1432 | +def walk_not_git_ignored(path, keep, extra_ignore): |
| 1433 | + path = path.absolute() |
| 1434 | + spec = PathSpec.from_lines("gitwildmatch", [".git"] + extra_ignore) |
| 1435 | + ignores = {} |
| 1436 | + # detect git folder, collect ignores up to root |
| 1437 | + at = path |
| 1438 | + while True: |
| 1439 | + git_exist = (at / ".git").exists() |
| 1440 | + if git_exist: |
| 1441 | + ignores[at.parent] = spec |
| 1442 | + # go down back and load all ignores |
| 1443 | + for part in (".",) + path.relative_to(at).parts: |
| 1444 | + at = at / part |
| 1445 | + spec = _load_ignore(at, spec, ignores) |
| 1446 | + break |
| 1447 | + if at == at.parent: |
| 1448 | + ignores[path] = spec |
| 1449 | + break |
| 1450 | + at = at.parent |
| 1451 | + |
| 1452 | + # now walk from root, collect new ignores and evaluate |
| 1453 | + for root, dirs, files in os.walk(str(path)): |
| 1454 | + root_path = Path(root).relative_to(path) # current path |
| 1455 | + current_path = path / root |
| 1456 | + parent_spec = ignores.get(current_path) or next( |
| 1457 | + ignores[p] for p in current_path.parents if p in ignores |
| 1458 | + ) |
| 1459 | + spec = _load_ignore(Path(root), parent_spec, ignores) |
| 1460 | + for file_name in files: |
| 1461 | + result = root_path / file_name |
| 1462 | + if ( |
| 1463 | + file_name != ".gitignore" |
| 1464 | + and keep(result) |
| 1465 | + and not spec.match_file(str(result)) |
| 1466 | + ): |
| 1467 | + yield path / result |
| 1468 | + for cur_dir in list(dirs): |
| 1469 | + if spec.match_file(str(root_path / cur_dir)): |
| 1470 | + dirs.remove(cur_dir) |
| 1471 | + |
| 1472 | + |
1412 | 1473 | _as = Leaf(token.NAME, "as", prefix=" ") |
1413 | 1474 | _colon = Leaf(token.COLON, ":") |
1414 | 1475 | _comma = Leaf(token.COMMA, ",") |
|
0 commit comments