在 Ruamel.yaml 中的键前插入评论

Insert a comment before a key in Ruamel.yaml

我正在使用 Ruamel Python 库以编程方式编辑人工编辑的 YAML 文件。

我很难理解如何将评论插入结构化数据。

我有一些数据:

a:
  b: banana
  c: apple
  d: orange

我想添加一条评论和一个新密钥:

a:
  b: banana
  c: apple
  d: orange
  # This is my comment
  e: pear

是否可以使用 ruamel.yaml 执行此操作,如果可以,如何操作?

是的,这是可能的,因为您可以通过往返检查:

import sys
import ruamel.yaml

with open('your_input.yaml') as fp:
    data = ruamel.yaml.round_trip_load(yaml_str)
ruamel.yaml.round_trip_dump(data, sys.stdout)

打印的输出将与您的输入相匹配,因此注释会以某种方式插入到结构的 data 层次结构中,保存并在转储时写出。

ruamel.yaml 中,注释附加到 listsdict 的包装器 classes 中,您可以使用 print(type(data['a']) 检查:它是一个CommentedMap(来自 ruamel.yaml.comment.py)。 a 的值的注释信息挂在属性 _yaml_comment 上,您可以通过 属性 ca:

访问
cm = data['a']
print(cm.ca)

给出:

items={'e': [None, [CommentToken(value='# This is my comment\n')], None, None]})

这表明评论与评论后面的键 e 相关联。不幸的是 CommentToken 不能像它所表示的那样通过调用它来创建(即 CommentToken(value='# This is my comment\n')),它需要更多的工作,因为它至少需要一个开始 Mark.

没有 "helper" 例程来创建这样的评论,但是通过查看 CommentedMap 及其基数 class CommentedBase 您可以得出以下结果 ¹ :

import sys
import ruamel.yaml

if not hasattr(ruamel.yaml.comments.CommentedMap, "yaml_set_comment_before_key"):
    def my_yaml_set_comment_before_key(self, key, comment, column=None,
                                       clear=False):
        """
        append comment to list of comment lines before key, '# ' is inserted
            before the comment
        column: determines indentation, if not specified take indentation from
                previous comment, otherwise defaults to 0
        clear: if True removes any existing comments instead of appending
        """
        key_comment = self.ca.items.setdefault(key, [None, [], None, None])
        if clear:
            key_comment[1] = []
        comment_list = key_comment[1]
        if comment:
            comment_start = '# '
            if comment[-1] == '\n':
                comment = comment[:-1]  # strip final newline if there
        else:
            comment_start = '#'
        if column is None:
            if comment_list:
                 # if there already are other comments get the column from them
                column = comment_list[-1].start_mark.column
            else:
                column = 0
        start_mark = ruamel.yaml.error.Mark(None, None, None, column, None, None)
        comment_list.append(ruamel.yaml.tokens.CommentToken(
            comment_start + comment + '\n', start_mark, None))
        return self

    ruamel.yaml.comments.CommentedMap.yaml_set_comment_before_key = \
        my_yaml_set_comment_before_key

使用此方法扩展 CommentedMap,然后您可以执行以下操作:

yaml_str = """\
a:
  b: banana
  c: apple
  d: orange
  e: pear
"""

data = ruamel.yaml.round_trip_load(yaml_str)
cm = data['a']

cm.yaml_set_comment_before_key('e', "This is Alex' comment", column=2)
cm.yaml_set_comment_before_key('e', 'and this mine')
ruamel.yaml.round_trip_dump(data, sys.stdout)

得到:

a:
  b: banana
  c: apple
  d: orange
  # This is Alex' comment
  # and this mine one
  e: pear

除非你看评论,否则无法查询cm是哪一栏 注释应该在其中,以使其与键 e 对齐(该列是在写出数据结构时确定的)。您可能想存储一个特殊值 (-1?) 并尝试在输出期间确定它,但在流出时您几乎没有上下文。您当然可以将列 determine/set 设置为嵌套级别 (1) 并将其乘以缩进(您给 round_trip_dump 的缩进,默认为 2

评论功能是来回保存的,最初不是为了修改或插入新的,所以不保证界面稳定。考虑到这一点,请确保围绕 yaml_set_comment_before_key() 创建单个例程或一组例程以进行更改,因此如果接口更改(能够附加一个评论不会消失,但是这样做的方法可能会改变)


¹ 也许不是你,但由于我是 ruamel.yaml 的作者,我应该能够在文档不足的代码中找到我的方法。