#!/usr/bin/env python3 """Fail when source files exceed a configured line-count threshold.""" from __future__ import annotations import argparse from pathlib import Path DEFAULT_SKIP_PARTS = { ".git", ".venv", "venv", "build", "dist", "node_modules", "__pycache__", ".pytest_cache", } SOURCE_SUFFIXES = {".py", ".sh", ".json", ".yaml", ".yml"} def _read_waivers(path: Path) -> set[str]: if not path.exists(): return set() waived: set[str] = set() for line in path.read_text(encoding="utf-8").splitlines(): row = line.strip() if not row or row.startswith("#"): continue waived.add(row.split("\t", 1)[0].strip()) return waived def _iter_files(root: Path) -> list[Path]: if not root.exists(): return [] files: list[Path] = [] for path in root.rglob("*"): if not path.is_file(): continue if any(part in DEFAULT_SKIP_PARTS for part in path.parts): continue if path.suffix.lower() not in SOURCE_SUFFIXES and path.name != "Jenkinsfile": continue files.append(path) return files def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--roots", nargs="+", required=True) parser.add_argument("--max-lines", type=int, default=500) parser.add_argument("--waivers", default="ci/loc_hygiene_waivers.tsv") args = parser.parse_args() repo_root = Path.cwd().resolve() waived = _read_waivers(repo_root / args.waivers) offenders: list[tuple[int, str]] = [] for root_name in args.roots: for path in _iter_files(repo_root / root_name): rel = path.relative_to(repo_root).as_posix() if rel in waived: continue try: line_count = sum(1 for _ in path.open("r", encoding="utf-8", errors="ignore")) except OSError: continue if line_count > args.max_lines: offenders.append((line_count, rel)) if not offenders: print(f"[loc] ok: no files exceed {args.max_lines} lines") return 0 offenders.sort(reverse=True) print(f"[loc] failed: {len(offenders)} file(s) exceed {args.max_lines} lines") for lines, rel in offenders: print(f" - {rel}: {lines} lines") return 1 if __name__ == "__main__": raise SystemExit(main())