#!/usr/bin/env python3 """Build a deterministic inventory for the GMOCCAPY source and case assets.""" from __future__ import annotations import hashlib import json from pathlib import Path ROOT = Path(__file__).resolve().parents[1] OUT = Path(__file__).resolve().parent def sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def classify(rel: str) -> str: path = Path(rel) if "/configs/sim/gmoccapy/" in rel: if path.suffix == ".ini": return "simulation_ini" if path.suffix in {".hal", ".tbl", ".para", ".var", ".pref"}: return "simulation_runtime_data" if path.suffix in {".ngc", ".py", ".ui", ".glade", ".svg", ".txt", ".README"}: return "simulation_resource" return "simulation_asset" if "/src/emc/usr_intf/gmoccapy/" in rel or "/lib/python/gmoccapy/" in rel: return "implementation" if "/share/gmoccapy/" in rel: if "icons/" in rel: return "icon_asset" return "installed_ui_asset" if "/src/po/gmoccapy/" in rel: return "translation" if "/docs/src/gui/images/gmoccapy_" in rel or "/docs/src/user/images/gmoccapy_" in rel: return "documentation_asset" if "/docs/src/gui/gmoccapy" in rel or "/docs/man/man1/gmoccapy" in rel: return "documentation" return "cross_reference" def collect(paths: list[Path]) -> list[dict[str, object]]: files: list[Path] = [] for base in paths: if base.is_file(): files.append(base) elif base.is_dir(): files.extend(p for p in base.rglob("*") if p.is_file()) rows = [] for path in sorted(set(files)): rel = path.relative_to(ROOT).as_posix() rows.append( { "path": rel, "role": classify(rel), "bytes": path.stat().st_size, "sha256": sha256(path), } ) return rows def main() -> None: rows = collect( [ ROOT / "linuxcnc/configs/sim/gmoccapy", ROOT / "linuxcnc/src/emc/usr_intf/gmoccapy", ROOT / "linuxcnc/lib/python/gmoccapy", ROOT / "linuxcnc/share/gmoccapy", ROOT / "linuxcnc/src/po/gmoccapy", ROOT / "linuxcnc/docs/src/gui/gmoccapy.adoc", ROOT / "linuxcnc/docs/man/man1/gmoccapy.1", *sorted((ROOT / "linuxcnc/docs/src/gui/images").glob("gmoccapy_*")), *sorted((ROOT / "linuxcnc/docs/src/user/images").glob("gmoccapy_*")), ] ) summary: dict[str, int] = {} for row in rows: role = str(row["role"]) summary[role] = summary.get(role, 0) + 1 payload = { "schema": "gmoccapy-file-inventory.v1", "scope": [ "linuxcnc/configs/sim/gmoccapy/**", "linuxcnc/src/emc/usr_intf/gmoccapy/**", "linuxcnc/lib/python/gmoccapy/**", "linuxcnc/share/gmoccapy/**", "linuxcnc/src/po/gmoccapy/**", "linuxcnc/docs/src/gui/gmoccapy.adoc", "linuxcnc/docs/man/man1/gmoccapy.1", "linuxcnc/docs/src/gui/images/gmoccapy_*", "linuxcnc/docs/src/user/images/gmoccapy_*", ], "file_count": len(rows), "role_counts": dict(sorted(summary.items())), "files": rows, } (OUT / "gmoccapy-file-inventory.json").write_text( json.dumps(payload, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8", ) lines = [ "# GMOCCAPY 文件清单", "", f"清单范围内共 `{len(rows)}` 个文件;每项包含相对路径、角色、字节数和 SHA-256。", "", "| 角色 | 数量 |", "|---|---:|", ] lines.extend(f"| `{role}` | {count} |" for role, count in sorted(summary.items())) lines.extend(["", "完整逐文件数据见 [`gmoccapy-file-inventory.json`](gmoccapy-file-inventory.json)。", ""]) (OUT / "gmoccapy-file-inventory.md").write_text("\n".join(lines), encoding="utf-8") if __name__ == "__main__": main()