101 lines
4.0 KiB
Python
Executable File
101 lines
4.0 KiB
Python
Executable File
#!/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())
|