#!/usr/bin/env python3 """Validate normalized pinmux data and write the final project CSV.""" import argparse import csv import json import re import sys from pathlib import Path HEADERS = ["引脚号", "功能名称", "引脚名称", "有效电平", "FMUX寄存器", "FMUX当前值", "FMUX默认值", "FMUX_list"] INPUT_FIELDS = ["pin_number", "function_name", "pin_name", "is_gpio", "active_level", "fmux_register", "fmux_current", "fmux_default", "fmux_list"] NUMBERED_MUX_RE = re.compile(r"(?:^|;\s*)([0-7])\s*:") UNSAFE_FILENAME_RE = re.compile(r"[/\\:*?\"<>|]+") def text(value): return "null" if value is None else str(value).strip() def safe_name(value): value = UNSAFE_FILENAME_RE.sub("_", value.strip()) value = re.sub(r"\s+", "_", value) value = re.sub(r"_+", "_", value).strip(" ._") if not value: raise ValueError("project_name 清理后为空") return value def normalize(raw, index, seen): if not isinstance(raw, dict): raise ValueError(f"rows[{index}] 必须是对象") missing = [key for key in INPUT_FIELDS if key not in raw] if missing: raise ValueError(f"rows[{index}] 缺少字段: {', '.join(missing)}") pin = text(raw["pin_number"]) if not pin or pin == "null": raise ValueError(f"rows[{index}] pin_number 为空") if pin in seen: raise ValueError(f"rows[{index}] 引脚号重复: {pin}") seen.add(pin) if not isinstance(raw["is_gpio"], bool): raise ValueError(f"rows[{index}] is_gpio 必须是布尔值") active = text(raw["active_level"]) if raw["is_gpio"] and active not in {"高有效", "低有效", "待确认"}: raise ValueError(f"rows[{index}] GPIO 有效电平无效") if not raw["is_gpio"] and active != "null": raise ValueError(f"rows[{index}] 非 GPIO 有效电平必须为 null") mux_list = text(raw["fmux_list"]) mux_numbers = set(NUMBERED_MUX_RE.findall(mux_list)) current = text(raw["fmux_current"]) if current in set("01234567") and current not in mux_numbers: raise ValueError(f"rows[{index}] 引脚 {pin}: FMUX当前值 {current} 未出现在 FMUX_list") row = { "引脚号": pin, "功能名称": text(raw["function_name"]), "引脚名称": text(raw["pin_name"]), "有效电平": active, "FMUX寄存器": text(raw["fmux_register"]), "FMUX当前值": current, "FMUX默认值": text(raw["fmux_default"]), "FMUX_list": mux_list, } return row, len(mux_numbers) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--input", required=True, type=Path) parser.add_argument("--project-name") parser.add_argument("--output-dir", type=Path, default=Path.cwd()) parser.add_argument("--output", type=Path) args = parser.parse_args() try: with args.input.open(encoding="utf-8-sig") as handle: payload = json.load(handle) if not isinstance(payload, dict) or not isinstance(payload.get("rows"), list): raise ValueError("JSON 顶层必须是对象并包含 rows 数组") project = args.project_name or text(payload.get("project_name", "")) rows, seen = [], set() for index, raw in enumerate(payload["rows"]): row, mux_count = normalize(raw, index, seen) if mux_count >= 2: rows.append(row) output = args.output or args.output_dir / f"{safe_name(project)}_pinmux.csv" output.parent.mkdir(parents=True, exist_ok=True) with output.open("w", encoding="utf-8-sig", newline="") as handle: writer = csv.DictWriter(handle, fieldnames=HEADERS) writer.writeheader() writer.writerows(rows) print(f"已输出 {len(rows)} 行(输入 {len(payload['rows'])} 行): {output}") return 0 except (OSError, ValueError, json.JSONDecodeError) as exc: print(f"ERROR: {exc}", file=sys.stderr) return 2 if __name__ == "__main__": raise SystemExit(main())