使用注释对 YAML 文件进行排序

问题描述 投票:0回答:1

1.总结

我找不到如何使用注释对 YAML 文件进行排序。

我能找到的解决方案:

  1. 删除评论,

  2. 不要在右行之后保存评论。


2.示例

我有一个文件

SashaSort.yaml
:

sasha: great
# Kristina comment
kristina: legendary
katya: 'Вечная память'

3.预期行为

katya: 'Вечная память'
# Kristina comment
kristina: legendary
sasha: great

必须保存非ASCII符号。)


4.没有帮助

  1. 这个问题的答案不保存评论如预期,
  2. yml-sorterPyYAML删除评论,
  3. 如果我使用i18n_yaml_sorter,我会得到输出:

    katya: 'Вечная память'
    kristina: legendary
    sasha: great
    # Kristina comment
    

    评论保存不要高于

    kristina: legendary
    线。

    写问题毫无用处,因为 i18n_yaml_sorter make 的最后一次提交是在 2011 年

  4. 我找不到,如何在

    ruamel.yaml
    文档中对YAML进行排序

  5. 我在
    ruamel.yaml.cmd
    中找不到排序选项。
yaml pyyaml ruamel.yaml
1个回答
0
投票

请参阅要点中的完整答案。

解决方案如下:

import sys
from typing import Any

import ruamel.yaml
from ruamel.yaml.comments import CommentedMap, CommentedSeq
from ruamel.yaml.tokens import CommentToken


def _comment_to_inline_after(
    comments: CommentToken | None,
) -> tuple[str | None, str | None]:
    """Split inline comment into two strings.

    First line is inline comment for current element.
    Second line and others are for next elements.

    Also remove comment starting sequence.
    """
    if comments is None:
        return None, None

    assert isinstance(comments, CommentToken)

    # first line - inline comment
    # second line and others - for next elements
    s = comments.value
    if s.startswith("# "):
        s = s[2:]
    s = s.replace("\n# ", "\n")
    # replace with None if second line is empty
    res = s.split("\n", 1)
    if len(res) > 1 and res[1] == "":
        res[1] = None
    return tuple(res)


def _comment_to_before(comments: Any) -> str | None:
    """Convert CommentToken or list of CommentToken into string.

    String contains comments before the line.

    Args:
        comments (Any):

    Returns:
        str | None: string with comments before the line.
    """
    if comments is None:
        return None

    tokens = []
    for token in comments:
        if token is None:
            continue
        elif isinstance(token, list):
            tokens.extend(token)
        else:
            tokens.append(token)
    comments = [token.value for token in tokens if token]
    comments = [
        comment[2:] if comment.startswith("# ") else comment for comment in comments
    ]
    return "".join(comments)


def map_sort_before(obj: CommentedMap, sorted_keys: list[Any]) -> CommentedMap:
    """Sort map with comments before a block

    Args:
        obj (CommentedMap): source object
        sorted_keys (list[Any]): list of keys for resulting map

    Returns:
        CommentedMap: target object
    """
    assert isinstance(obj, CommentedMap)

    comments: dict[
        Any, tuple[str | None, str | None]
    ] = {}  # tuple of (before, inline) values

    # Gather comments
    # First comment is handled specially
    comment_before = _comment_to_before(obj.ca.comment[1] if obj.ca.comment else None)
    comment_inline = None
    # Next lines' comments
    for key in obj.keys():
        comment_value = obj.ca.items.get(key)
        comment_inline, comment_after = _comment_to_inline_after(
            comment_value[2] if comment_value else None
        )
        comments[key] = (comment_before, comment_inline)
        comment_before = comment_after
    last_comment = comment_before

    # Create another map
    obj_sorted = CommentedMap()
    for key in sorted_keys:
        obj_sorted[key] = obj[key]
        comment_before, comment_inline = comments[key]
        if comment_before:
            obj_sorted.yaml_set_comment_before_after_key(
                key, before=comment_before, indent=0
            )
        is_last = key == sorted_keys[-1]
        if not comment_inline and not is_last:
            continue
        if is_last and last_comment is not None:
            if not last_comment.startswith("# "):
                last_comment = "# " + last_comment
            if comment_inline is None:
                comment_inline = ""
            comment_inline += "\n" + last_comment
        if comment_inline:
            obj_sorted.yaml_add_eol_comment(comment_inline, key, column=0)

    return obj_sorted


yaml = ruamel.yaml.YAML()
yaml_str = """\
sasha: great
# Kristina comment
kristina: legendary
katya: 'Вечная память'
"""

obj = yaml.load(yaml_str)
obj_sorted = map_sort_before(
    obj,
    # sort as you wish
    sorted_keys=sorted(obj.keys()),
)
yaml.dump(obj_sorted, sys.stdout)

输出是:

katya: Вечная память
# Kristina comment
kristina: legendary
sasha: great

对于反向排序:

obj_sorted = map_sort_before(
    obj,
    sorted_keys=sorted(obj.keys(), reverse=True),
)

输出是:

sasha: great
# Kristina comment
kristina: legendary
katya: Вечная память
© www.soinside.com 2019 - 2024. All rights reserved.