chronaxis.configuration.models
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""配置加载期的原生数据、字段位置和语法错误,不参与分析执行或来源读取。"""2from dataclasses import dataclass, field3from collections.abc import Mapping4import json567def field_path_text(path):8 """把键/数组下标路径变成无歧义文本。参数: tuple。返回: JSON路径。异常: 非内部路径。"""9 result='$'10 for part in path:11 if type(part) is int:12 result+=f'[{part}]'13 elif type(part) is str:14 result+=('.'+part if part.isidentifier() else '['+json.dumps(part,ensure_ascii=False)+']')15 else:16 raise TypeError('配置字段路径只接受文本键或整数下标')17 return result181920@dataclass(frozen=True, slots=True)21class ConfigurationLocation:22 """一处源码位置;行列从1开始,列按Unicode字符计,不按UTF-8字节计。"""23 line: int24 column: int252627@dataclass(frozen=True, slots=True)28class ConfigurationOrigin:29 """展开字段的声明位置;只保存文件名、字段路径及行列,不保存第二份配置。"""30 source: str # 实际声明文件的词法绝对路径。31 field_path: tuple # 声明文件内部路径,索引尚未被合并重排。32 location: ConfigurationLocation | None # 原文一基行列;无法取得时为None。333435@dataclass(frozen=True, slots=True)36class ConfigurationDocument:37 """只在加载期间持有原生值和必要位置;调用者构造计划后释放本对象。"""38 data: object39 source: str40 format: str41 locations: Mapping42 origins: Mapping = field(default_factory=dict) # 仅文件导入展开时填充,计划构造后释放。4344 def origin_for(self, field_path):45 """查最近声明来源并保留缺失字段后缀。参数: tuple路径。返回: 来源或None。异常: 无。"""46 original=tuple(field_path)47 path=original48 # 缺少字段只能沿所属对象定位,不能捏造它在原文件中的行列。49 while path not in self.origins and path:50 path=path[:-1]51 origin=self.origins.get(path)52 if origin is None:return None53 return ConfigurationOrigin(origin.source,origin.field_path+original[len(path):],origin.location)5455 def location_for(self, field_path):56 """取具体值位置,缺少字段时回溯最近父对象。参数: tuple路径。返回: 位置或None。异常: 无。"""57 path=tuple(field_path)58 while path not in self.locations and path:59 path=path[:-1]60 return self.locations.get(path)616263class ConfigurationError(ValueError):64 """保存语法错误码、文件和字段位置;消息不打印整份配置或执行任意内容。"""6566 def __init__(self, source, field_path, location, code, message):67 """建立错误事实。参数: 来源/tuple路径/位置/代码/原因。返回: None。异常: 无。"""68 self.source=source69 self.field_path=tuple(field_path)70 self.location=location71 self.code=code72 self.message=message73 place=source if location is None else f'{source}:{location.line}:{location.column}'74 super().__init__(f'{place} {field_path_text(self.field_path)}: {message}')75