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