Chronaxis用户手册English
本页目录

chronaxis.planning.input_paths

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""将文件、目录或通配符展开为有序输入,不读取报文内容。23明确文件列表保持用户顺序;每个目录或通配符内部采用自然排序。4只枚举显式请求的目录,不递归追踪子目录;递归必须通过**通配符提出。5展开最多4096项,空匹配及无效路径报配置错误,不悄悄跳过缺失目录。6"""78from glob import iglob9from pathlib import Path10import re1112from .checks import PlanValidationError, absolute_path, array_at, coordinate_at, object_at, text_at1314# 明确后缀的文件只做词法定位,保持现有静态配置不访问业务文件的行为。15FILE_SUFFIXES = frozenset(('.asc', '.blf', '.csv', '.txt'))16SOURCE_SUFFIXES = {'can_blf': {'.blf'}, 'can_asc': {'.asc'},17                   'raw_can_csv': {'.csv', '.txt'}, 'signal_csv': {'.csv', '.txt'}}18MAX_INPUT_FILES = 4096192021def natural_path_key(path):22    """产生稳定自然序键,使file2排在file10前。2324    参数: path为Path。返回: 数字/文字分段键及原路径平局键。25    异常: 无主动异常;不访问文件系统。26    """27    text = str(path)28    # 类型标签避免数字与文字直接比较;补原文本使01/1及大小写平局可复现。29    parts = tuple((1, int(part)) if part.isdigit() else (0, part.casefold())30                  for part in re.split(r'(\d+)', text))31    return parts, text.casefold(), text323334def expand_source_files(raw, base, where):35    """按声明展开来源路径;目录只取该来源支持的格式。3637    参数: raw为来源字典,base为配置文件目录,where为来源错误位置。38    返回: (绝对Path元组, 可选逐文件偏移字符串元组)。异常: PlanValidationError表示缺项、空匹配或超限;39        OSError由目录读取产生并转换为带配置位置的错误。40    副作用: 仅目录/通配符声明枚举文件名;明确文件不stat、不读数据内容。41    """42    if ('path' in raw) == ('paths' in raw):43        raise PlanValidationError(where, 'path与paths必须且只能提供一个')44    key = 'path' if 'path' in raw else 'paths'45    value = raw[key]46    entries = [value] if type(value) in (str, dict) else array_at(value, where + '.' + key)47    if not entries:48        raise PlanValidationError(where + '.' + key, '至少需要一个输入文件或目录')49    allowed = SOURCE_SUFFIXES.get(raw.get('source_type'), {'.asc', '.blf'})50    result, shifts, has_shifts = [], [], False51    for index, entry in enumerate(entries):52        location = f'{where}.{key}[{index}]'53        # 文件自己的偏移跟在同一条声明内,展开目录时该偏移作用于其中各文件。54        shift = '0s'55        if type(entry) is dict:56            item = object_at(entry, location, ('path',), ('offset',))57            text = text_at(item['path'], location + '.path')58            if 'offset' in item:59                coordinate_at(item['offset'], location + '.offset')60                shift = item['offset']61                has_shifts = True62        else:63            text = text_at(entry, location)64        target = absolute_path(text, base, location)65        # 仅*和?启动通配符模式,普通文件名中的方括号保持字面含义。66        wildcard = any(char in text for char in '*?')67        # 常规文件不访问磁盘;目录名像文件时可用末尾分隔符明确目录含义。68        if not wildcard and target.suffix.lower() in FILE_SUFFIXES and not text.endswith(('/', '\\')):69            selected = [target]70        else:71            selected = []72            try:73                pattern = str(target)74                suffix = target.suffix75                if wildcard and suffix.lower() in FILE_SUFFIXES:76                    # 只统一明确扩展名的大小写;主体、隐藏文件和**仍遵循原glob规则。77                    extension = ''.join('[' + char.lower() + char.upper() + ']'78                                        if char.isalpha() else char for char in suffix)79                    pattern = pattern[:-len(suffix)] + extension80                candidates = (Path(item) for item in iglob(pattern, recursive='**' in text)) if wildcard else target.iterdir()81                for candidate in candidates:82                    if candidate.suffix.lower() in allowed and candidate.is_file():83                        selected.append(candidate)84                        if len(result) + len(selected) > MAX_INPUT_FILES:85                            raise PlanValidationError(location, '输入文件超过4096项')86            except OSError as error:87                raise PlanValidationError(location, '无法枚举输入目录:' + str(error)) from error88            if not selected:89                raise PlanValidationError(location, '目录或通配符没有匹配的输入文件')90            selected.sort(key=natural_path_key)91        # 不重排显式列表、不合并不同声明项,也不因内容相同而删除真实文件。92        result.extend(selected)93        shifts.extend([shift] * len(selected))94        if len(result) > MAX_INPUT_FILES:95            raise PlanValidationError(location, '输入文件超过4096项')96    if has_shifts and 'offsets' in raw:97        raise PlanValidationError(where, '文件内offset与来源offsets不能同时填写')98    return tuple(result), tuple(shifts) if has_shifts else None99100101def expand_input_paths(raw, base, where):102    """只取展开文件路径,供无需文件偏移的调用方使用。103104    参数: 来源配置、基准目录、错误位置。返回: Path元组。105    异常: 与expand_source_files一致;目录扫描只执行一次。106    """107    return expand_source_files(raw, base, where)[0]108109110def source_path_fields(source, where='$.sources'):111    """枚举文件声明中的路径文字,供转换/导入工具固定路径基准。112113    参数: source为来源字典,where为错误位置。返回: (容器,键)迭代器。114    异常: PlanValidationError为错误结构。不扫描目录,不改变offset或其他元数据。115    """116    for key in ('path', 'paths'):117        if key not in source:118            continue119        value = source[key]120        if type(value) is str:121            yield source, key122            continue123        # 每项可以是路径文字或带自己偏移的文件声明,工具不能把对象转成字符串。124        entries = [value] if type(value) is dict else array_at(value, where + '.' + key)125        for index, item in enumerate(entries):126            if type(item) is dict:127                object_at(item, f'{where}.{key}[{index}]', ('path',), ('offset',))128                yield item, 'path'129            else:130                text_at(item, f'{where}.{key}[{index}]')131                yield entries, index132
Chronaxis 用户手册 · 离线阅读