- 修复 DMA 请求电平追踪、接收 FIFO 打包和禁用时的数据提交,更新 USART 的接收 DMA 请求 - 保留定时器复位前的 RCC 输入频率 - 正常退出时保存内部 Flash 已完成编程数据,过滤当前未完成编程字 - 使用独立进程组请求 Renode 正常退出,退出未完成时保留镜像锁和实例记录 - 将 controller.py 拆分为环境配置、Flash 镜像、进程管理和仿真启动模块,保留原命令入口 - 为 scripts 下的 Python 文件补充职责、生命周期和异常边界注释,并更新退出与持久化说明
66 lines
2.4 KiB
Python
66 lines
2.4 KiB
Python
"""读取和更新本地 .env 配置,不执行 Shell 表达式或展开变量"""
|
|
import json
|
|
import os
|
|
|
|
|
|
def read_env(path):
|
|
"""解析 KEY=value,支持引号内的空格与井号,格式错误时报告文件位置
|
|
|
|
仅整行井号作为注释,双引号值按 JSON 字符串解析,单引号值保持字面内容
|
|
"""
|
|
if not path.exists():
|
|
return {}
|
|
result = {}
|
|
for number, line in enumerate(path.read_text(encoding='utf-8-sig').splitlines(), 1):
|
|
line = line.strip()
|
|
if not line or line.startswith('#'):
|
|
continue
|
|
key, separator, value = line.partition('=')
|
|
key, value = key.strip(), value.strip()
|
|
if not separator or not key.isidentifier():
|
|
raise ValueError(f'{path}:{number}: expected KEY=value')
|
|
if value.startswith('"'):
|
|
value = json.loads(value)
|
|
elif value.startswith("'"):
|
|
if not value.endswith("'") or len(value) < 2:
|
|
raise ValueError(f'{path}:{number}: unterminated quoted value')
|
|
value = value[1:-1]
|
|
result[key] = value
|
|
return result
|
|
|
|
|
|
def get_setting(root, name, default):
|
|
"""依次采用进程环境变量、项目 .env 和调用方默认值"""
|
|
return os.environ.get(name, read_env(root / '.env').get(name, default))
|
|
|
|
|
|
def set_setting(root, name, value):
|
|
"""更新指定键的全部重复项,保留无关配置与注释,并转义写入的字符串"""
|
|
path = root / '.env'
|
|
lines = path.read_text(encoding='utf-8-sig').splitlines() if path.exists() else []
|
|
replacement = f'{name}={json.dumps(value, ensure_ascii=False)}'
|
|
matched = False
|
|
for index, line in enumerate(lines):
|
|
if line.partition('=')[0].strip() == name:
|
|
lines[index] = replacement
|
|
matched = True
|
|
if not matched:
|
|
lines.append(replacement)
|
|
path.write_text('\n'.join(lines) + '\n', encoding='utf-8')
|
|
|
|
|
|
def as_boolean(value):
|
|
"""仅接受约定的布尔字符串,避免拼写错误被静默解释为关闭"""
|
|
value = value.lower()
|
|
if value not in ('1', 'true', 'yes', 'on', '0', 'false', 'no', 'off'):
|
|
raise ValueError(f'Invalid boolean: {value}')
|
|
return value in ('1', 'true', 'yes', 'on')
|
|
|
|
|
|
def as_port(value):
|
|
"""转换并校验可显式绑定的 TCP 端口,不接受自动分配用的零值"""
|
|
port = int(value)
|
|
if not 1 <= port <= 65535:
|
|
raise ValueError(f'Invalid TCP port: {value}')
|
|
return port
|