Chronaxis用户手册English
本页目录

chronaxis.configuration.imports

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""展开分析配置明确列出的一层本地公共定义。23主配置按imports顺序引入sources/signals/derived,最后追加本地声明;重复名称4直接拒绝。导入文件不能再导入文件,也不能声明运行、规则或输出字段。5仅打开配置文件,数据/DBC路径按各自声明文件转为词法绝对路径;不读取业务输入。6位置随本次加载保留,成功计划不持有原文或第二份配置,不执行环境替换或模板。7"""8from copy import deepcopy9from pathlib import Path10from types import MappingProxyType1112from . import load_configuration13from .diagnostics import locate_error14from .models import ConfigurationDocument, ConfigurationOrigin, field_path_text1516SECTIONS=('sources','signals','derived')  # 唯一允许共享的定义,不包含执行节点。171819def _reject(document, path, message, code='invalid_import', hint=None):20    """在实际声明处拒绝导入。参数: 文档、tuple路径、原因/代码/建议。返回: 不返回。异常: PlanValidationError。"""21    from ..planning.checks import PlanValidationError22    raise locate_error(PlanValidationError(field_path_text(path),message,code,hint=hint),document)232425def _source_paths(source, base, path, document):26    """固定一项来源中的公开文件路径。参数: 来源副本、声明目录、字段路径、原文档。返回: None。异常: 字段错误。2728    只改本次副本,metadata内任意path不解释为文件;不调用stat或resolve。29    """30    from ..planning.checks import absolute_path, array_at, PlanValidationError31    try:32        # 路径文字、列表和带offset的文件对象共用路径定位,不展开输入目录。33        from ..planning.input_paths import source_path_fields34        for owner,key in source_path_fields(source,field_path_text(path)):35            owner[key]=str(absolute_path(owner[key],base,field_path_text((*path,key))))36        if 'dbc_by_channel' in source:37            values=source['dbc_by_channel']38            if type(values) is not dict:39                _reject(document,(*path,'dbc_by_channel'),'DBC通道映射必须为对象')40            source['dbc_by_channel']={key:str(absolute_path(value,base,field_path_text((*path,'dbc_by_channel',key))))41                                      for key,value in values.items()}42    except PlanValidationError as error:43        raise locate_error(error,document)444546def _append_definitions(data, prefix, document, source_prefix, merged, locations, origins, names):47    """追加一个文件的定义并迁移字段位置。参数: 原定义/目标前缀/原文档/原前缀及本次容器。返回: None。异常: 定义/重复错误。4849    names保存首个同名声明的位置;所有修改仅发生在本次展开容器,无全局状态。50    """51    for section in SECTIONS:52        if section not in data:continue53        values=data[section]54        expected=dict if section=='derived' else list55        if type(values) is not expected:56            _reject(document,(*source_prefix,section),57                    'derived必须为名称到表达式的对象' if section=='derived' else '来源/信号定义必须为数组')58        offset=len(merged[section])59        entries=values.items() if section=='derived' else enumerate(values)60        for index,value in entries:61            path=(*source_prefix,section,index)62            if section!='derived' and type(value) is not dict:63                _reject(document,path,'每项公共定义必须为对象')64            if section=='signals' and 'signals' in value:65                children=value['signals']66                if type(children) is not list or any(type(child) is not dict for child in children):67                    _reject(document,(*path,'signals'),'组内signals必须为信号对象数组')68                declarations=[(child.get('name'),(*path,'signals',child_index,'name'))69                              for child_index,child in enumerate(children)]70            else:71                name=index if section=='derived' else value.get('name')72                declarations=[(name,path if section=='derived' else (*path,'name'))]73            for name,name_path in declarations:74                if type(name) is not str or not name:75                    _reject(document,name_path,'每项公共定义必须明确填写非空名称')76                # 集中声明和普通信号仍共用一个名字空间,组本身没有额外名称。77                identity=('sources' if section=='sources' else 'signals/derived',name)78                if identity in names:79                    first_source,first_path=names[identity]80                    _reject(document,name_path,'重复定义 '+name,'duplicate_definition',81                            '删除或改名;首次声明在 '+first_source+' '+field_path_text(first_path))82                names[identity]=(document.source,name_path)83            copied=deepcopy(value)84            if section=='sources':_source_paths(copied,Path(document.source).parent,path,document)85            if section=='derived':merged[section][name]=copied86            else:merged[section].append(copied)87        # 整个数组只遍历一次位置表,主文件项追加后仍指向主文件原索引。88        original_prefix=(*source_prefix,section)89        for original,location in document.locations.items():90            if original[:len(original_prefix)]!=original_prefix:continue91            suffix=original[len(original_prefix):]92            if not suffix:continue93            target=(*prefix,section,suffix[0] if section=='derived' else offset+suffix[0],*suffix[1:])94            locations[target]=location95            origins[target]=ConfigurationOrigin(document.source,original,location)969798def expand_document_imports(document):99    """展开当前分析配置的公共定义。参数: 已解析文档。返回: 可直接静态检查的文档。异常: 配置/读取错误。100101    未声明imports时原样返回,不增加复制或文件读取。批量文件通过每个子计划102    自行使用imports;纯字典API不调用此函数。版本错误由原入口拒绝,不读其导入。103    """104    data=document.data105    if type(data) is not dict:return document106    prefix=()107    analysis=data108    if type(analysis) is not dict or 'imports' not in analysis:return document109    if type(analysis.get('config_version')) is not int or analysis['config_version'] != 4:return document110    entries=analysis['imports']111    if type(entries) is not list:112        _reject(document,(*prefix,'imports'),'imports必须为本地配置路径数组')113    # 定义数组重新构造;先清除被移动数组的旧位置,避免缺失字段误用旧索引行列。114    raw=deepcopy(data)115    target=raw116    merged={'sources':[],'signals':[],'derived':{}}117    locations={path:location for path,location in document.locations.items()118               if not (path[:len(prefix)]==prefix and len(path)>len(prefix)+1119                       and path[len(prefix)] in SECTIONS)}120    origins={path:ConfigurationOrigin(document.source,path,location) for path,location in locations.items()}121    names={}122    for index,entry in enumerate(entries):123        path=(*prefix,'imports',index)124        if type(entry) is not str or not entry.strip() or '://' in entry or any(char in entry for char in '*?[]'):125            _reject(document,path,'导入路径必须为明确的本地JSON/YAML文件,不支持URL、通配符或空值')126        imported=load_configuration(Path(document.source).parent/entry)127        fragment=imported.data128        if type(fragment) is not dict or not fragment:129            _reject(imported,(),'公共定义文件必须是非空对象,只能包含sources、signals、derived')130        unknown=set(fragment)-set(SECTIONS)131        if unknown:132            key=next(key for key in fragment if key in unknown)133            _reject(imported,(key,),'公共定义只允许sources、signals、derived;不支持嵌套imports或版本包装')134        _append_definitions(fragment,prefix,imported,(),merged,locations,origins,names)135    _append_definitions(analysis,prefix,document,prefix,merged,locations,origins,names)136    # 展开后不保留imports,执行计划只见一份普通配置;来源位置只在加载期使用。137    target.pop('imports')138    for section in SECTIONS:139        if section!='derived' or merged[section] or section in target:140            target[section]=merged[section]141    from .overrides import apply_overrides142    apply_overrides(target,document,prefix,locations,origins)143    return ConfigurationDocument(raw,document.source,document.format,144                                 MappingProxyType(locations),MappingProxyType(origins))145
Chronaxis 用户手册 · 离线阅读