我找不到如何使用注释对 YAML 文件进行排序。
我能找到的解决方案:
删除评论,
或
不要在右行之后保存评论。
我有一个文件
SashaSort.yaml
:
sasha: great
# Kristina comment
kristina: legendary
katya: 'Вечная память'
katya: 'Вечная память'
# Kristina comment
kristina: legendary
sasha: great
(必须保存非ASCII符号。)
如果我使用i18n_yaml_sorter,我会得到输出:
katya: 'Вечная память'
kristina: legendary
sasha: great
# Kristina comment
评论保存不要高于
kristina: legendary
线。
写问题毫无用处,因为 i18n_yaml_sorter make 的最后一次提交是在 2011 年。
ruamel.yaml
文档中对YAML进行排序,ruamel.yaml.cmd
中找不到排序选项。请参阅要点中的完整答案。
解决方案如下:
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: Вечная память