59 lines
1.9 KiB
Python
59 lines
1.9 KiB
Python
import fnmatch
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
EXCLUDED_PARTS = {
|
|
"bin", "obj", ".git", ".vs", ".harness", "testresults",
|
|
"generated", "vendor", "third_party", "external",
|
|
}
|
|
|
|
|
|
def is_excluded_path(path: Path, root: Path, extra=()) -> bool:
|
|
root = root.resolve()
|
|
candidate = path if path.is_absolute() else root / path
|
|
try:
|
|
relative = candidate.relative_to(root)
|
|
resolved_relative = candidate.resolve().relative_to(root)
|
|
except ValueError:
|
|
return True
|
|
parts = tuple(part.lower() for part in relative.parts)
|
|
if any(part in EXCLUDED_PARTS for part in parts):
|
|
return True
|
|
if parts[:2] == ("tests", "fixtures"):
|
|
return True
|
|
resolved_parts = tuple(part.lower() for part in resolved_relative.parts)
|
|
if any(part in EXCLUDED_PARTS for part in resolved_parts) or resolved_parts[:2] == ("tests", "fixtures"):
|
|
return True
|
|
name = relative.as_posix().lower()
|
|
return any(
|
|
fnmatch.fnmatchcase(name, pattern.replace("\\", "/").lower())
|
|
or name.startswith(pattern.replace("\\", "/").lower().rstrip("/") + "/")
|
|
for pattern in extra
|
|
)
|
|
|
|
|
|
def iter_source_files(root: Path):
|
|
"""Walk repository inputs, pruning artifacts and external/cyclic links."""
|
|
root = root.resolve()
|
|
seen = set()
|
|
def fail(error):
|
|
raise error
|
|
|
|
for directory, directories, files in os.walk(root, followlinks=False, onerror=fail):
|
|
current = Path(directory)
|
|
resolved = current.resolve()
|
|
if resolved in seen or is_excluded_path(current, root):
|
|
directories[:] = []
|
|
continue
|
|
seen.add(resolved)
|
|
directories[:] = sorted(
|
|
name for name in directories
|
|
if not is_excluded_path(current / name, root)
|
|
and (current / name).resolve() not in seen
|
|
)
|
|
for name in sorted(files):
|
|
path = current / name
|
|
if not is_excluded_path(path, root):
|
|
yield path
|