SPB Git

spb/air Public MIT

AIR — The Language of Accounting.

Python 100%
2.6 KB · 75 lines python
Raw Blame History
1#!/usr/bin/env python32# =============================================================================3# Projet : AIR — Accounting Intermediate Representation4# Auteur : Simon-Pierre Boucher5# Contact : contact@spboucher.ai6# Fichier : check_headers.py7# Description : CI gate — fails if any project file is missing the author header.8# =============================================================================9"""Verify that every project file carries the mandatory author header.1011Usage: python3 scripts/check_headers.py [root]12Exit code 0 if all files pass, 1 otherwise (with a list of offenders).13"""14from __future__ import annotations1516import json17import sys18from pathlib import Path1920AUTHOR = "Simon-Pierre Boucher"21CONTACT = "contact@spboucher.ai"2223# Extensions checked for a comment header containing author + contact.24COMMENT_EXTS = {25    ".py", ".ts", ".js", ".rs", ".go", ".c", ".h", ".zig",26    ".md", ".yaml", ".yml", ".toml", ".sql", ".sh",27}28SKIP_DIRS = {".git", ".venv", "venv", "node_modules", "__pycache__",29             ".pytest_cache", ".mypy_cache", ".hypothesis", ".claude", "dist", "build"}30SKIP_FILES = {".gitignore", "LICENSE", "py.typed"}31HEADER_WINDOW = 15  # header must appear within the first N lines323334def has_header(path: Path) -> bool:35    if path.suffix == ".json":36        try:37            data = json.loads(path.read_text(encoding="utf-8"))38        except (json.JSONDecodeError, UnicodeDecodeError):39            return False40        return isinstance(data, dict) and CONTACT in str(data.get("_author", ""))41    try:42        head = "".join(43            path.read_text(encoding="utf-8").splitlines(keepends=True)[:HEADER_WINDOW]44        )45    except UnicodeDecodeError:46        return True  # binary-ish file, not subject to header rule47    return AUTHOR in head and CONTACT in head484950def main() -> int:51    root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parent.parent52    offenders: list[Path] = []53    for path in sorted(root.rglob("*")):54        if not path.is_file():55            continue56        if any(part in SKIP_DIRS for part in path.parts):57            continue58        if path.name in SKIP_FILES:59            continue60        if path.suffix not in COMMENT_EXTS and path.suffix != ".json":61            continue62        if not has_header(path):63            offenders.append(path.relative_to(root))64    if offenders:65        print("Missing author header (Simon-Pierre Boucher / contact@spboucher.ai):")66        for p in offenders:67            print(f"  - {p}")68        return 169    print("check_headers: OK — all files carry the author header.")70    return 0717273if __name__ == "__main__":74    raise SystemExit(main())75