#!/usr/bin/env python3 # file: scripts/audit_rust_crate_root_facades.py # version: 1 """Audit Rust library crate roots so `src/lib.rs` remains a facade.""" from __future__ import annotations import argparse import pathlib import re import sys from _rust_audit_common import line_depths, mask_source _ITEM = re.compile( r"^\s*(?:(?:pub(?:\([^)]*\))?)\s+)?" r"(?:(?:async|const|unsafe)\s+)*(?:extern\s+\"[^\"]+\"\s+)?" r"(?:fn|struct|enum|union|trait|impl|type|const|static|macro_rules!)\b" ) def _selected_lib_roots(raw_paths: list[str]) -> tuple[list[pathlib.Path], list[str]]: roots: set[pathlib.Path] = set() errors: list[str] = [] for raw_path in raw_paths: path = pathlib.Path(raw_path) if path.is_dir(): roots.update(candidate for candidate in path.rglob("src/lib.rs") if candidate.is_file()) elif path.is_file() and path.as_posix().endswith("src/lib.rs"): roots.add(path) else: errors.append(f"unsupported or missing library-root path: {path}") return sorted(roots), errors def _audit_file(path: pathlib.Path) -> list[str]: text = path.read_text(encoding="utf-8") masked, _ = mask_source(text) lines = masked.splitlines() depths = line_depths(masked) errors: list[str] = [] for index, line in enumerate(lines): if depths[index] != 0: continue if _ITEM.match(line) is not None: errors.append(f"{path}:{index + 1}: crate root must remain a facade; move this item to a responsibility module") return errors def main() -> int: """Run the read-only Knowledge library crate-root facade audit.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("paths", nargs="+", help="Crate roots or directories containing Rust library crates") arguments = parser.parse_args() files, errors = _selected_lib_roots(arguments.paths) if errors or not files: for error in errors or ["no Rust library crate roots selected"]: print(f"Rust crate-root facade audit: {error}", file=sys.stderr) return 2 for path in files: try: errors.extend(_audit_file(path)) except (OSError, UnicodeError) as error: print(f"Rust crate-root facade audit: cannot read {path}: {error}", file=sys.stderr) return 2 for error in errors: print(error, file=sys.stderr) if errors: print(f"Rust crate-root facade audit: {len(errors)} error(s) across {len(files)} crate root(s)", file=sys.stderr) return 1 print(f"Rust crate-root facade audit: clean ({len(files)} crate root(s))") return 0 if __name__ == "__main__": raise SystemExit(main())