# -*- coding: utf-8 -*- """core/config_paths.py —— 统一的配置文件路径解析与容错加载(P0-01 新增) 所有配置读取入口必须经过本模块,不得在别处直接定位 data/config.json: 1. 环境变量 HAOCODE_CONFIG_FILE 优先级最高(自动化测试用它指向临时文件); 2. 未设置环境变量时才回落到项目内 data/config.json(锚定 core/ 目录,不受 CWD 影响)。 行为约定(REPAIR_BACKLOG P0-01 硬约束): · 路径解析与加载过程只在出错时输出可见警告(只含路径与错误类型,不含配置内容); · 缺失 / 损坏 / 非 JSON 对象 → 返回安全空 dict 继续,不抛异常、不阻断 不需要该配置的源码路径; · import 本模块时不打印、不复制、不迁移任何配置内容。 """ import json import os ENV_KEY = "HAOCODE_CONFIG_FILE" _DEFAULT_CONFIG_PATH = os.path.join( os.path.dirname(os.path.abspath(__file__)), "..", "data", "config.json") def config_path() -> str: """返回当前生效的配置文件路径(环境变量优先,调用时解析)。""" p = os.environ.get(ENV_KEY, "").strip() return p if p else _DEFAULT_CONFIG_PATH def load_config() -> dict: """容错加载配置。缺失/损坏/非对象 → 可见警告 + 安全空 dict(绝不抛异常)。""" p = config_path() if not os.path.exists(p): print(f"[config] 配置文件缺失: {p} → 使用安全默认值(providers 为空)继续启动") return {} try: with open(p, "r", encoding="utf-8") as f: data = json.load(f) except Exception as e: print(f"[config] 配置文件读取/解析失败: {p}({type(e).__name__})" f" → 使用安全默认值继续启动") return {} if not isinstance(data, dict): print(f"[config] 配置文件内容不是 JSON 对象: {p}" f" → 使用安全默认值继续启动") return {} return data # ---------------------------------------------------------------------- # P1-01:渲染窗口配置解析(render_window_mode / render_window_size) # 规则(与 ui/web/render_window.js 的 JS 侧守卫保持一致): # size:只接受非布尔整数 10..200,缺失/布尔/字符串/小数/零/负数/越界 → 静默回落 40; # mode:只接受 "auto"/"manual",否则回落 "auto"。 # ---------------------------------------------------------------------- DEFAULT_RENDER_WINDOW_SIZE = 40 ALLOWED_RENDER_WINDOW_SIZES = (10, 40, 200) def render_window_settings(cfg: dict | None = None) -> dict: """解析渲染窗口配置。cfg 缺省时读取当前生效配置。永不抛异常。""" if cfg is None: cfg = load_config() if not isinstance(cfg, dict): cfg = {} raw_size = cfg.get("render_window_size", None) if isinstance(raw_size, bool) or not isinstance(raw_size, int) \ or raw_size < 10 or raw_size > 200: size = DEFAULT_RENDER_WINDOW_SIZE else: size = raw_size raw_mode = cfg.get("render_window_mode", None) mode = raw_mode if raw_mode in ("auto", "manual") else "auto" return {"mode": mode, "size": size}