120 lines
5.3 KiB
Python
120 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate a compact, source-backed Web parity matrix by module."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
|
|
def revision(source_root: Path) -> str:
|
|
try:
|
|
return subprocess.run(["git", "-C", str(source_root), "rev-parse", "HEAD"], check=True, capture_output=True, text=True).stdout.strip()
|
|
except (OSError, subprocess.CalledProcessError):
|
|
return "unknown"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--source-root", type=Path, required=True)
|
|
parser.add_argument("--workbench-inventory", type=Path, required=True)
|
|
parser.add_argument("--object-inventory", type=Path, required=True)
|
|
parser.add_argument("--format-api-inventory", type=Path, required=True)
|
|
parser.add_argument("--json-output", type=Path, required=True)
|
|
parser.add_argument("--markdown-output", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
source_root = args.source_root.resolve()
|
|
state_path = args.json_output.parent / "current" / "task-state.json"
|
|
state_modules = {}
|
|
if state_path.exists():
|
|
state_modules = json.loads(state_path.read_text(encoding="utf-8")).get("module_status", {})
|
|
previous_modules = {}
|
|
if args.json_output.exists():
|
|
previous = json.loads(args.json_output.read_text(encoding="utf-8"))
|
|
previous_modules = {
|
|
row.get("module"): row.get("web_status", "pending")
|
|
for row in previous.get("rows", [])
|
|
}
|
|
workbenches = json.loads(args.workbench_inventory.read_text(encoding="utf-8"))["modules"]
|
|
objects = json.loads(args.object_inventory.read_text(encoding="utf-8"))["objects"]
|
|
format_api = json.loads(args.format_api_inventory.read_text(encoding="utf-8"))
|
|
objects_by_module: dict[str, list[str]] = defaultdict(list)
|
|
for item in objects:
|
|
objects_by_module[str(item["module"])].append(str(item["name"]))
|
|
api_by_module: dict[str, int] = defaultdict(int)
|
|
for item in format_api["python_api"]:
|
|
api_by_module[str(item["module"])] += 1
|
|
extensions = sorted({str(item["extension"]) for item in format_api["formats"] if "extension" in item})
|
|
formats_by_module: dict[str, set[str]] = defaultdict(set)
|
|
for item in format_api["formats"]:
|
|
if "extension" in item:
|
|
formats_by_module[str(item["module"])].add(str(item["extension"]))
|
|
rows = []
|
|
for module in workbenches:
|
|
module_id = str(module["id"])
|
|
module_objects = sorted(set(objects_by_module.get(module_id, [])))
|
|
actions = ["inspect/source-inventory"]
|
|
if module.get("has_app"):
|
|
actions.extend(["create/recompute", "save/reopen"])
|
|
if module_id in {"Import", "Mesh", "Part", "TechDraw", "BIM", "CAM"}:
|
|
actions.append("import/export")
|
|
if api_by_module.get(module_id):
|
|
actions.append("python-api")
|
|
rows.append(
|
|
{
|
|
"module": module_id,
|
|
"user_actions": sorted(set(actions)),
|
|
"object_type_count": len(module_objects),
|
|
"object_types": module_objects,
|
|
"format_extensions": sorted(formats_by_module.get(module_id, set())),
|
|
"python_api_count": api_by_module.get(module_id, 0),
|
|
"gui_dependency": bool(module.get("has_gui")),
|
|
"web_status": state_modules.get(module_id, previous_modules.get(module_id, "pending")),
|
|
"source_evidence": {
|
|
"workbench": str(args.workbench_inventory),
|
|
"objects": str(args.object_inventory),
|
|
"format_api": str(args.format_api_inventory),
|
|
},
|
|
"regression_entry": "FreeCAD/docs/tasks/FW-P3-001B.md",
|
|
}
|
|
)
|
|
report = {
|
|
"schema": "freecad-web-parity-matrix/v1",
|
|
"source_revision": revision(source_root),
|
|
"status_values": ["pending", "in_progress", "blocked", "completed"],
|
|
"module_count": len(rows),
|
|
"rows": rows,
|
|
}
|
|
args.json_output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.json_output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
lines = [
|
|
"# FreeCAD Web Parity Matrix",
|
|
"",
|
|
f"- Source revision: `{report['source_revision']}`",
|
|
f"- Module rows: `{len(rows)}`",
|
|
"- Web status is projected from the task-state ledger; `pending` means no complete module gate exists.",
|
|
"",
|
|
"| Module | User actions | Objects | Formats | Python API | GUI | Web status | Regression entry |",
|
|
"|---|---|---:|---|---:|---:|---|---|",
|
|
]
|
|
for row in rows:
|
|
lines.append(
|
|
f"| `{row['module']}` | {', '.join(row['user_actions'])} | {row['object_type_count']} | "
|
|
f"{', '.join(f'`{x}`' for x in row['format_extensions']) or '-'} | {row['python_api_count']} | "
|
|
f"{'yes' if row['gui_dependency'] else 'no'} | `{row['web_status']}` | `{row['regression_entry']}` |"
|
|
)
|
|
args.markdown_output.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
print(json.dumps({
|
|
"schema": report["schema"],
|
|
"module_count": report["module_count"],
|
|
"pending": sum(row["web_status"] == "pending" for row in rows),
|
|
}))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|