142 lines
6.1 KiB
Python
142 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Freeze the source and exact bitmap inventory for the AXIS parity track."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
REPORT = ROOT / "coverage/axis-source-inventory.report.json"
|
|
CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1"
|
|
AXIS_TCL = Path("linuxcnc/share/axis/tcl/axis.tcl")
|
|
SOURCE_FILES = (
|
|
AXIS_TCL,
|
|
Path("linuxcnc/share/axis/tcl/support.tcl"),
|
|
Path("linuxcnc/share/axis/tcl/accel.tcl"),
|
|
Path("linuxcnc/share/axis/tcl/combobox.tcl"),
|
|
Path("linuxcnc/share/axis/tcl/sb.tcl"),
|
|
Path("linuxcnc/share/axis/tcl/dialog.tcl"),
|
|
Path("linuxcnc/src/emc/usr_intf/axis/README"),
|
|
Path("linuxcnc/lib/python/gremlin.py"),
|
|
Path("linuxcnc/lib/python/gremlin_view.py"),
|
|
Path("linuxcnc/lib/python/rs274/glcanon.py"),
|
|
)
|
|
TOOLBAR_ASSETS = (
|
|
"tool_estop.gif", "tool_power.gif", "tool_open.gif", "tool_reload.gif",
|
|
"tool_run.gif", "tool_step.gif", "tool_pause.gif", "resume_inhibit.gif",
|
|
"tool_stop.gif", "tool_blockdelete.gif", "tool_optpause.gif", "tool_zoomin.gif",
|
|
"tool_zoomout.gif", "tool_axis_z.gif", "tool_axis_z2.gif", "tool_axis_x.gif",
|
|
"tool_axis_y.gif", "tool_axis_y2.png", "tool_axis_p.gif", "tool_rotate.gif",
|
|
"tool_clear.gif",
|
|
)
|
|
IMAGE_ROOT = Path("linuxcnc/share/axis/images")
|
|
|
|
|
|
def sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
|
digest.update(block)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def canonical(value: Any) -> str:
|
|
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
|
|
|
|
def source_records() -> list[dict[str, Any]]:
|
|
records: list[dict[str, Any]] = []
|
|
for relative in SOURCE_FILES:
|
|
path = ROOT / relative
|
|
records.append({
|
|
"path": relative.as_posix(),
|
|
"bytes": path.stat().st_size if path.is_file() else None,
|
|
"sha256": sha256(path) if path.is_file() else None,
|
|
"status": "indexed" if path.is_file() else "missing",
|
|
})
|
|
return records
|
|
|
|
|
|
def parse_axis_tcl() -> dict[str, Any]:
|
|
text = (ROOT / AXIS_TCL).read_text(encoding="utf-8")
|
|
menu_items = re.findall(r"setup_menu_accel\s+[^\n]+\s+\[_\s+([^\n]+)", text)
|
|
widgets = re.findall(r"^(?:Button|button|checkbutton|radiobutton|combobox|scale|label|text|listbox)\s+([^\s\\]+)", text, re.MULTILINE)
|
|
notebooks = re.findall(r"NoteBook\s+([^\s\\]+)", text)
|
|
procedures = re.findall(r"^proc\s+([^\s{]+)", text, re.MULTILINE)
|
|
bindings = re.findall(r"^bind\s+[^\s]+\s+<([^>]+)>", text, re.MULTILINE)
|
|
toolbar_assets = re.findall(r"\[load_image\s+([^\]\s]+)", text)
|
|
return {
|
|
"menu_accelerator_entries": len(menu_items),
|
|
"menu_labels_sample": menu_items[:12],
|
|
"widget_declarations": len(widgets),
|
|
"widget_names_sample": widgets[:24],
|
|
"notebooks": notebooks,
|
|
"procedures": procedures,
|
|
"procedure_count": len(procedures),
|
|
"bindings": sorted(set(bindings)),
|
|
"binding_count": len(set(bindings)),
|
|
"toolbar_load_image_names": toolbar_assets,
|
|
"toolbar_load_image_count": len(toolbar_assets),
|
|
"line_count": len(text.splitlines()),
|
|
}
|
|
|
|
|
|
def asset_records() -> list[dict[str, Any]]:
|
|
result: list[dict[str, Any]] = []
|
|
for name in TOOLBAR_ASSETS:
|
|
path = ROOT / IMAGE_ROOT / name
|
|
result.append({
|
|
"name": name,
|
|
"path": (IMAGE_ROOT / name).as_posix(),
|
|
"bytes": path.stat().st_size if path.is_file() else None,
|
|
"sha256": sha256(path) if path.is_file() else None,
|
|
"status": "indexed" if path.is_file() else "missing",
|
|
})
|
|
return result
|
|
|
|
|
|
def generate() -> dict[str, Any]:
|
|
sources = source_records()
|
|
assets = asset_records()
|
|
axis = parse_axis_tcl()
|
|
report: dict[str, Any] = {
|
|
"schema": "axis-source-inventory.v1",
|
|
"task_id": "AXIS-INV-01",
|
|
"context_id": CONTEXT_ID,
|
|
"status": "verified" if all(row["status"] == "indexed" for row in sources + assets) else "blocked",
|
|
"implementation_status": "source_frozen",
|
|
"source": sources,
|
|
"assets": assets,
|
|
"axis_tcl": axis,
|
|
"anchors": [
|
|
{"id": "menus", "path": AXIS_TCL.as_posix(), "lines": "33-482"},
|
|
{"id": "toolbar", "path": AXIS_TCL.as_posix(), "lines": "484-787"},
|
|
{"id": "notebooks", "path": AXIS_TCL.as_posix(), "lines": "790-1312"},
|
|
{"id": "status_and_program", "path": AXIS_TCL.as_posix(), "lines": "1314-1709"},
|
|
{"id": "state_gating", "path": AXIS_TCL.as_posix(), "lines": "1715-1887"},
|
|
{"id": "keyboard_and_defaults", "path": AXIS_TCL.as_posix(), "lines": "1941-2048"},
|
|
{"id": "dynamic_help", "path": AXIS_TCL.as_posix(), "lines": "2250-2270"},
|
|
],
|
|
"checks": {
|
|
"source_paths": {"status": "verified" if all(row["status"] == "indexed" for row in sources) else "blocked"},
|
|
"toolbar_assets": {"status": "verified" if all(row["status"] == "indexed" for row in assets) else "blocked", "expected": len(TOOLBAR_ASSETS), "indexed": sum(row["status"] == "indexed" for row in assets)},
|
|
"source_line_count": {"status": "verified" if axis["line_count"] == 2300 else "blocked", "expected": 2300, "actual": axis["line_count"]},
|
|
"toolbar_names_declared": {"status": "verified" if all(name.removesuffix(".gif").removesuffix(".png") in axis["toolbar_load_image_names"] for name in TOOLBAR_ASSETS if name != "tool_axis_y2.png") else "blocked"},
|
|
},
|
|
"boundary": "inventory_only; no runtime or protocol mutation",
|
|
}
|
|
report["report_sha256"] = hashlib.sha256(canonical(report).encode("utf-8")).hexdigest()
|
|
REPORT.parent.mkdir(parents=True, exist_ok=True)
|
|
REPORT.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
|
return report
|
|
|
|
|
|
if __name__ == "__main__":
|
|
result = generate()
|
|
print(f"axis_source_inventory={result['status']} sources={len(result['source'])} assets={len(result['assets'])} report_sha256={result['report_sha256']}")
|