|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Validate trafic.json files under `logo/` against the JSON Schema in `.github/models/trafic.schema.json`. |
| 3 | +
|
| 4 | +Usage: |
| 5 | + python .github/scripts/check_structure_trafic.py [--schema PATH] [--logo-dir PATH] |
| 6 | +
|
| 7 | +Exits with code 0 when all files are valid, otherwise exits with 1. |
| 8 | +""" |
| 9 | +from __future__ import annotations |
| 10 | + |
| 11 | +import argparse |
| 12 | +import json |
| 13 | +import sys |
| 14 | +from pathlib import Path |
| 15 | +from typing import List |
| 16 | + |
| 17 | +try: |
| 18 | + import jsonschema |
| 19 | + from jsonschema import Draft7Validator, FormatChecker |
| 20 | +except Exception: # pragma: no cover - helpful message when module missing |
| 21 | + print("ERROR: the 'jsonschema' package is required. Install with: pip install jsonschema", file=sys.stderr) |
| 22 | + raise |
| 23 | + |
| 24 | + |
| 25 | +def find_trafic_files(root: Path) -> List[Path]: |
| 26 | + """Return list of paths named 'trafic.json' under root/logo (recursively).""" |
| 27 | + logo_dir = root / "logo" |
| 28 | + if not logo_dir.exists(): |
| 29 | + return [] |
| 30 | + return list(logo_dir.rglob("trafic.json")) |
| 31 | + |
| 32 | + |
| 33 | +def load_json(path: Path): |
| 34 | + with path.open("r", encoding="utf-8") as fh: |
| 35 | + return json.load(fh) |
| 36 | + |
| 37 | + |
| 38 | +def validate_instance(schema: dict, instance: dict, instance_path: Path) -> List[str]: |
| 39 | + validator = Draft7Validator(schema, format_checker=FormatChecker()) |
| 40 | + errors = [] |
| 41 | + for err in sorted(validator.iter_errors(instance), key=lambda e: e.path): |
| 42 | + # build a readable location |
| 43 | + location = "".join(f"/{p}" for p in err.absolute_path) or "/" |
| 44 | + errors.append(f"{instance_path}: {location} -> {err.message}") |
| 45 | + return errors |
| 46 | + |
| 47 | + |
| 48 | +def main(argv: List[str] | None = None) -> int: |
| 49 | + parser = argparse.ArgumentParser(prog="check_structure_trafic.py", description="Validate trafic.json files against JSON Schema") |
| 50 | + parser.add_argument("--schema", "-s", type=Path, default=Path(__file__).resolve().parents[2] / ".github" / "models" / "trafic.schema.json", help="Path to trafic.schema.json") |
| 51 | + parser.add_argument("--root", "-r", type=Path, default=Path(__file__).resolve().parents[2], help="Repository root (default: repo root)") |
| 52 | + parser.add_argument("--logo-dir", type=Path, help="Optional explicit logo directory (overrides --root/logo)") |
| 53 | + parser.add_argument("--quiet", "-q", action="store_true", help="Only print errors and exit code") |
| 54 | + args = parser.parse_args(argv) |
| 55 | + |
| 56 | + schema_path = args.schema |
| 57 | + repo_root = args.root |
| 58 | + if args.logo_dir: |
| 59 | + logo_root = args.logo_dir |
| 60 | + else: |
| 61 | + logo_root = repo_root / "logo" |
| 62 | + |
| 63 | + if not schema_path.exists(): |
| 64 | + print(f"ERROR: schema file not found at {schema_path}", file=sys.stderr) |
| 65 | + return 2 |
| 66 | + |
| 67 | + try: |
| 68 | + schema = load_json(schema_path) |
| 69 | + except Exception as exc: |
| 70 | + print(f"ERROR: failed to load schema {schema_path}: {exc}", file=sys.stderr) |
| 71 | + return 2 |
| 72 | + |
| 73 | + files = find_trafic_files(repo_root) if not args.logo_dir else list(Path(args.logo_dir).rglob("trafic.json")) |
| 74 | + if not files: |
| 75 | + print(f"No trafic.json files found under {logo_root}") |
| 76 | + return 0 |
| 77 | + |
| 78 | + total = 0 |
| 79 | + invalid = 0 |
| 80 | + all_errors: List[str] = [] |
| 81 | + |
| 82 | + for f in sorted(files): |
| 83 | + total += 1 |
| 84 | + try: |
| 85 | + instance = load_json(f) |
| 86 | + except Exception as exc: |
| 87 | + invalid += 1 |
| 88 | + all_errors.append(f"{f}: JSON parse error: {exc}") |
| 89 | + continue |
| 90 | + |
| 91 | + errors = validate_instance(schema, instance, f) |
| 92 | + if errors: |
| 93 | + invalid += 1 |
| 94 | + all_errors.extend(errors) |
| 95 | + else: |
| 96 | + if not args.quiet: |
| 97 | + print(f"OK: {f}") |
| 98 | + |
| 99 | + if all_errors: |
| 100 | + print("\nValidation errors:") |
| 101 | + for e in all_errors: |
| 102 | + print(" -", e) |
| 103 | + |
| 104 | + print(f"\nChecked {total} files: {total - invalid} valid, {invalid} invalid") |
| 105 | + return 1 if invalid else 0 |
| 106 | + |
| 107 | + |
| 108 | +if __name__ == "__main__": |
| 109 | + raise SystemExit(main()) |
0 commit comments