first commit

This commit is contained in:
2026-03-21 22:09:36 +08:00
commit 5b22cacc13
3 changed files with 528 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
#!/usr/bin/env python3
"""Parse and summarize STM32CubeMX .ioc files."""
from __future__ import annotations
import argparse
import json
from collections import defaultdict
from pathlib import Path
from typing import TypedDict
StrMap = dict[str, str]
class ClassifiedIOC(TypedDict):
domains: dict[str, StrMap]
peripherals: dict[str, StrMap]
other: StrMap
def parse_ioc(ioc_path: Path) -> tuple[StrMap, list[str]]:
entries: StrMap = {}
anomalies: list[str] = []
lines = ioc_path.read_text(encoding="utf-8", errors="replace").splitlines()
for index, raw in enumerate(lines, start=1):
line = raw.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
anomalies.append(f"line {index}: no '=' separator")
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
if not key:
anomalies.append(f"line {index}: empty key")
continue
if key in entries:
anomalies.append(f"line {index}: duplicate key '{key}' (last value kept)")
entries[key] = value
return entries, anomalies
def classify(entries: StrMap) -> ClassifiedIOC:
domains: dict[str, StrMap] = {
"mcu": {},
"rcc": {},
"project_manager": {},
"pins": {},
"middleware": {},
}
peripherals: dict[str, StrMap] = defaultdict(dict)
other: StrMap = {}
middleware_roots = {
"freertos",
"fatfs",
"lwip",
"usb",
"touchgfx",
"azure_rtos",
"threadx",
"netxduo",
"filex",
"ux_device",
"ux_host",
}
for key, value in entries.items():
if key.startswith("Mcu."):
domains["mcu"][key] = value
continue
if key.startswith("RCC."):
domains["rcc"][key] = value
continue
if key.startswith("ProjectManager."):
domains["project_manager"][key] = value
continue
# Pin records usually look like PA0.Mode, PA0.Signal, PB6.GPIO_Label...
if len(key) >= 3 and key[0] == "P" and key[1].isalpha():
pin = key.split(".", 1)[0]
if len(pin) >= 3 and pin[2].isdigit():
domains["pins"][key] = value
continue
root = key.split(".", 1)[0]
root_l = root.lower()
if root_l in middleware_roots or root_l.startswith("cmsis"):
domains["middleware"][key] = value
continue
# Heuristic: uppercase root token often indicates a peripheral (USART1, I2C1, TIM3...)
if root and root.upper() == root and any(ch.isdigit() for ch in root):
peripherals[root][key] = value
continue
other[key] = value
return {
"domains": domains,
"peripherals": dict(sorted(peripherals.items())),
"other": other,
}
def summarize(classified: ClassifiedIOC, ioc_path: Path, anomalies: list[str]) -> str:
domains = classified["domains"]
peripherals = classified["peripherals"]
other = classified["other"]
mcu = domains["mcu"]
project_manager = domains["project_manager"]
rcc = domains["rcc"]
pins = domains["pins"]
middleware = domains["middleware"]
lines: list[str] = []
lines.append(f"IOC file: {ioc_path}")
lines.append("")
lines.append("== Project Overview ==")
lines.append(f"- MCU keys: {len(mcu)}")
lines.append(f"- ProjectManager keys: {len(project_manager)}")
lines.append(f"- RCC keys: {len(rcc)}")
top_overview = [
"Mcu.Name",
"Mcu.Package",
"Mcu.Family",
"Mcu.UserName",
"ProjectManager.ProjectName",
"ProjectManager.ToolChain",
"ProjectManager.TargetToolchain",
]
for key in top_overview:
if key in mcu:
lines.append(f"- {key}: {mcu[key]}")
elif key in project_manager:
lines.append(f"- {key}: {project_manager[key]}")
lines.append("")
lines.append("== Peripherals ==")
lines.append(f"- Count: {len(peripherals)}")
if peripherals:
lines.append("- Enabled/peripheral roots: " + ", ".join(peripherals.keys()))
lines.append("")
lines.append("== Pins ==")
lines.append(f"- Pin-related keys: {len(pins)}")
# Extract a compact map of pin -> signal
pin_signals: dict[str, str] = {}
for key, value in pins.items():
if key.endswith(".Signal"):
pin = key.split(".", 1)[0]
pin_signals[pin] = value
if pin_signals:
preview = sorted(pin_signals.items())[:20]
for pin, signal in preview:
lines.append(f"- {pin}: {signal}")
if len(pin_signals) > len(preview):
lines.append(f"- ... ({len(pin_signals) - len(preview)} more)")
lines.append("")
lines.append("== Middleware ==")
lines.append(f"- Middleware keys: {len(middleware)}")
lines.append("")
lines.append("== Diagnostics ==")
lines.append(f"- Other keys: {len(other)}")
lines.append(f"- Anomalies: {len(anomalies)}")
if anomalies:
for item in anomalies[:20]:
lines.append(f"- {item}")
if len(anomalies) > 20:
lines.append(f"- ... ({len(anomalies) - 20} more)")
return "\n".join(lines)
def build_json(
ioc_path: Path,
entries: StrMap,
classified: ClassifiedIOC,
anomalies: list[str],
) -> dict[str, object]:
return {
"ioc_path": str(ioc_path),
"counts": {
"entries": len(entries),
"mcu": len(classified["domains"]["mcu"]),
"rcc": len(classified["domains"]["rcc"]),
"project_manager": len(classified["domains"]["project_manager"]),
"pins": len(classified["domains"]["pins"]),
"middleware": len(classified["domains"]["middleware"]),
"peripherals": len(classified["peripherals"]),
"other": len(classified["other"]),
"anomalies": len(anomalies),
},
"domains": classified["domains"],
"peripherals": classified["peripherals"],
"other": classified["other"],
"anomalies": anomalies,
}
def main() -> int:
parser = argparse.ArgumentParser(
description="Read and summarize STM32CubeMX .ioc files"
)
parser.add_argument("--ioc", required=True, help="Path to .ioc file")
parser.add_argument("--json", action="store_true", help="Print JSON output")
parser.add_argument(
"--out", help="Optional output file path (for --json or text summary)"
)
args = parser.parse_args()
ioc_path = Path(args.ioc)
if not ioc_path.exists() or not ioc_path.is_file():
raise SystemExit(f"error: ioc file not found: {ioc_path}")
entries, anomalies = parse_ioc(ioc_path)
classified = classify(entries)
if args.json:
payload = build_json(ioc_path, entries, classified, anomalies)
rendered = json.dumps(payload, ensure_ascii=True, indent=2)
else:
rendered = summarize(classified, ioc_path, anomalies)
if args.out:
Path(args.out).write_text(rendered + "\n", encoding="utf-8")
else:
print(rendered)
return 0
if __name__ == "__main__":
raise SystemExit(main())