Files
ros_contorl/scripts/gen_site_example.py
T
2026-10-09 14:51:03 +08:00

151 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""从 ``config/site.yaml`` 生成 ``config/site.example.yaml``。
为什么要有这个脚本
------------------
``config/site.yaml`` 是现场唯一权威配置,也是全项目注释最全的文件。
``config/site.example.yaml`` 供新环境部署时复制参考。
过去两者是各写各的,结果是模板长期落后于现场配置:分析时发现模板缺了
114 项(``servo.*``、``hole_detect.stage2.*``、``sim.*`` 等),新人照着模板
配完会发现一堆功能不工作。
本脚本把「模板」变成「site.yaml 的匿名化副本」,于是模板永远不会漂移:
改完 site.yaml 重跑本脚本即可。
做什么
------
读 ``config/site.yaml``,把现场专属的值替换成占位值,其余**逐字节保留**
(包括全部中文注释与排版),然后写入 ``config/site.example.yaml``。
用法::
python3 scripts/gen_site_example.py # 生成 / 更新模板
python3 scripts/gen_site_example.py --check # 只检查是否已同步(CI / 测试用)
``--check`` 在不一致时以退出码 1 结束,方便接进测试。
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SITE = ROOT / "config" / "site.yaml"
EXAMPLE = ROOT / "config" / "site.example.yaml"
#: 生成文件顶部的说明。生成物是只读产物,手改会在下次生成时丢失。
HEADER = """\
# =============================================================================
# site.example.yaml — 新环境部署模板
#
# !本文件由 scripts/gen_site_example.py 从 config/site.yaml 自动生成,请勿手改。
# 要改模板内容,请改 config/site.yaml(它是唯一权威)后重跑:
# python3 scripts/gen_site_example.py
# 或在改完 site.yaml 后跑一次以确认模板同步:
# python3 scripts/gen_site_example.py --check
#
# 用法:复制本文件为 config/site.yaml,然后把所有 <占位符> 换成现场实际值。
# 现场值包括:机械臂 SN / IP、相机序列号、夹爪串口、传感器 USB 路径。
#
# 说明:site.yaml 里的路径出现 /home/USER 是因为生成时匿名化了用户名;
# 部署时请改成实际用户的 home 目录。
# =============================================================================
"""
#: 逐字替换。长串必须排在前面,否则会被短串规则截断。
LITERAL_REPLACEMENTS: list[tuple[str, str]] = [
# 头部那句"从 example 复制"与本文件的实际流程相反,改掉。
(
"# site.yaml — 本机唯一配置入口(不进 git;从 site.example.yaml 复制)",
"# site.yaml — 本机唯一配置入口(不进 git;从 site.example.yaml 复制而来)",
),
# 用户名匿名化。放在其它规则之前,避免路径被二次处理。
("/home/cc", "/home/USER"),
# 注释里裸写的机械臂编号(无引号,正则抓不到)。
# ⚠️ 这行是**逐字匹配**:site.yaml 里改一个字它就静默失效,SN 就漏进模板了
#(test_real_devices.test_example_contains_no_live_values 会挡下来)。
(
"# 本机 192.168.2.10 ↔ 063989(左89)=.101 / 063991(右91)=.100",
"# 本机 <PC_IP> ↔ <左臂SN>(左89)=.101 / <右臂SN>(右91)=.100",
),
]
#: 正则替换。按顺序应用。
REGEX_REPLACEMENTS: list[tuple[str, str]] = [
# 机械臂序列号:Rizon4-063989 -> Rizon4-XXXXXX
(r'"(Rizon4)-\d+"', r'"\1-XXXXXX"'),
# 同上,但**不带引号**(注释里的裸写)。留一条兜底,免得上面的逐字规则
# 一旦和 site.yaml 不同步就把 SN 漏出去。
(r"(Rizon4)-\d+", r"\1-XXXXXX"),
# sn 里的连字符在下划线形式下(话题名不许含 '-',broadcaster 用 '_'):
# 例 "/Rizon4_063989/external_wrench_in_tcp" -> "/Rizon4_XXXXXX/external_wrench_in_tcp"
(r"(Rizon4)_\d+", r"\1_XXXXXX"),
# USB 网卡的 iface 名是 MAC 派生的(enx + 12 位十六进制)= 现场值。
# enp4s0 这类板载名是通用的,不动。
(r"enx[0-9a-fA-F]{12}", "enxXXXXXXXXXXXX"),
# IP 地址(仅带引号的配置值;注释里的裸 IP 由上面的字面替换处理)
(r'"192\.168\.\d{1,3}\.\d{1,3}"', '"192.168.2.XXX"'),
# 8 位及以上的纯数字字符串 = 设备序列号(相机 12 位 / 夹爪 8 位)。
# 不会误伤别的值:本文件里没有其它 8 位以上的数字字符串字面量。
(r'"\d{8,}"', '"<序列号>"'),
# USB 视频设备的 by-path 稳定路径:端口号随机器而变。
(r"(usb-0:)\d+\.\d+\.\d+", r"\1X.Y.Z"),
]
def render(site_text: str) -> str:
"""把现场配置文本转换成匿名化后的模板文本。"""
out = site_text
for old, new in LITERAL_REPLACEMENTS:
out = out.replace(old, new)
for pattern, replacement in REGEX_REPLACEMENTS:
out = re.sub(pattern, replacement, out)
return HEADER + out
def read_site() -> str:
if not SITE.is_file():
print(f"错误: 找不到 {SITE}", file=sys.stderr)
raise SystemExit(2)
return SITE.read_text(encoding="utf-8")
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="从 config/site.yaml 生成 config/site.example.yaml",
)
parser.add_argument(
"--check",
action="store_true",
help="只检查模板是否与 site.yaml 同步;不同步则以退出码 1 结束(不写文件)",
)
args = parser.parse_args(argv)
expected = render(read_site())
if args.check:
actual = EXAMPLE.read_text(encoding="utf-8") if EXAMPLE.is_file() else ""
if actual == expected:
print(f"模板已同步: {EXAMPLE}")
return 0
print(
"模板与 config/site.yaml 不同步。\n"
" config/site.yaml 改动后,请重跑: python3 scripts/gen_site_example.py",
file=sys.stderr,
)
return 1
EXAMPLE.parent.mkdir(parents=True, exist_ok=True)
EXAMPLE.write_text(expected, encoding="utf-8")
print(f"已生成 {EXAMPLE}")
return 0
if __name__ == "__main__":
raise SystemExit(main())