You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于预设模板重排YAML键并保留注释?

基于ruamel.yaml的YAML文件标准化处理方案

依赖准备

  • 先安装所需库:pip install ruamel.yaml

模板定义

先明确你的键顺序规则和行距要求,示例如下:

# 顶层键的优先级顺序,按此排列
KEY_ORDER = [
    'name',
    'version',
    'description',
    'dependencies',
    'scripts'
]

# 行距配置:数字代表空行数
SPACING_RULES = {
    'top_level': 1,  # 顶层键之间保留1个空行
    'sequence_item_end': 1  # 序列最后一项后添加1个空行
}

完整实现代码

from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedMap, CommentedSequence
import os

# 初始化YAML处理器,保留原引号格式
yaml = YAML()
yaml.preserve_quotes = True

def sort_commented_map(commented_map: CommentedMap, key_order: list) -> CommentedMap:
    """按指定顺序重排CommentedMap的键,保留键的注释"""
    # 先备份所有键的注释信息
    key_comment_backup = {}
    for key in commented_map:
        if key in commented_map.ca.items:
            key_comment_backup[key] = commented_map.ca.items[key]
    
    # 按模板顺序构建新的CommentedMap
    sorted_map = CommentedMap()
    # 先加入模板中定义的键
    for key in key_order:
        if key in commented_map:
            sorted_map[key] = commented_map[key]
            # 恢复对应键的注释
            if key in key_comment_backup:
                sorted_map.ca.items[key] = key_comment_backup[key]
    # 加入模板未定义的剩余键,放在末尾
    for key in commented_map:
        if key not in key_order:
            sorted_map[key] = commented_map[key]
            if key in key_comment_backup:
                sorted_map.ca.items[key] = key_comment_backup[key]
    
    return sorted_map

def process_commented_sequence(sequence: CommentedSequence, item_key_order: list) -> CommentedSequence:
    """处理CommentedSequence:内部项按模板排序,最后一项后加空行"""
    processed_seq = CommentedSequence()
    for item in sequence:
        if isinstance(item, CommentedMap):
            processed_seq.append(sort_commented_map(item, item_key_order))
        else:
            processed_seq.append(item)
    
    # 给最后一项添加后置换行,实现末尾空行
    if processed_seq:
        last_idx = len(processed_seq) - 1
        processed_seq.ca.items[last_idx] = [None, None, [('\n', None)], None]
    
    return processed_seq

def clean_empty_comments(commented_map: CommentedMap):
    """删除仅包含换行/空白的无效注释"""
    # 收集需要删除的空注释键
    empty_comment_keys = []
    for key, comment_tuple in commented_map.ca.items.items():
        # 检查前置注释是否为空
        if comment_tuple[0] is not None:
            comment_content = comment_tuple[0].value
            if comment_content.strip() == '':
                empty_comment_keys.append(key)
    # 删除空注释
    for key in empty_comment_keys:
        del commented_map.ca.items[key]
    
    # 递归处理嵌套的CommentedMap和CommentedSequence
    for value in commented_map.values():
        if isinstance(value, CommentedMap):
            clean_empty_comments(value)
        elif isinstance(value, CommentedSequence):
            for item in value:
                if isinstance(item, CommentedMap):
                    clean_empty_comments(item)

def apply_top_level_spacing(commented_map: CommentedMap, line_count: int):
    """给顶层键之间添加指定数量的空行"""
    keys = list(commented_map.keys())
    for idx in range(1, len(keys)):
        current_key = keys[idx]
        # 设置当前键的前置空行
        commented_map.ca.items[current_key] = [
            None,
            ('\n' * line_count, None),
            None,
            None
        ]

def process_single_yaml(input_path: str, output_path: str, key_order: list, spacing_rules: dict):
    """处理单个YAML文件的完整流程"""
    # 1. 加载YAML文件
    with open(input_path, 'r', encoding='utf-8') as f:
        data = yaml.load(f)
    
    if not isinstance(data, CommentedMap):
        return
    
    # 2. 按模板重排键
    sorted_data = sort_commented_map(data, key_order)
    
    # 处理嵌套的CommentedSequence
    for key, value in sorted_data.items():
        if isinstance(value, CommentedSequence):
            sorted_data[key] = process_commented_sequence(value, key_order)
    
    # 3. 清理空注释
    clean_empty_comments(sorted_data)
    
    # 4. 应用行距规则
    apply_top_level_spacing(sorted_data, spacing_rules['top_level'])
    
    # 5. 保存处理后的文件
    with open(output_path, 'w', encoding='utf-8') as f:
        yaml.dump(sorted_data, f)

# 批量处理仓库中的YAML文件
if __name__ == '__main__':
    target_dir = './your-repository-directory'  # 替换为你的仓库路径
    for root, _, files in os.walk(target_dir):
        for file in files:
            if file.endswith(('.yaml', '.yml')):
                file_path = os.path.join(root, file)
                # 直接覆盖原文件,若需备份可修改output_path为其他路径
                process_single_yaml(file_path, file_path, KEY_ORDER, SPACING_RULES)

关键逻辑说明

  • 键排序与注释保留:通过CommentedMap的.ca.items属性备份并恢复键的注释,确保排序后注释不丢失。
  • 空注释清理:遍历注释内容,过滤掉仅含空白字符的无效注释,避免冗余空行。
  • 行距控制:通过给键的前置注释添加指定数量的换行符实现键间空行;给序列最后一项添加后置换行实现序列末尾空行。
  • 嵌套结构处理:递归遍历嵌套的CommentedMap和CommentedSequence,确保所有层级都遵循模板规则。

自定义适配

  • 修改KEY_ORDER列表来定义你的键优先级顺序。
  • 调整SPACING_RULES中的数值控制空行数量,0表示无空行,1表示一个空行。
  • 若序列内的项需要独立的键顺序,可在调用process_commented_sequence时传入自定义的键顺序列表。

内容的提问来源于stack exchange,提问作者pinkfloydfan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 20:58:09