如果 srv2 是 YAML 中所有映射的唯一键,那么“简单”的方法是遍历 de 行,测试 de 剥离版本的行是否以 srv2: 开头,注意前导空格的数量并注释掉该行和后面的行,直到您注意到前导空格相等或更少的行。这样做的好处是,除了简单快速之外,它还可以处理不规则的缩进(如您的示例:srv1 之前的 4 个位置和some-volume 之前的 6 个位置)。
使用ruamel.yaml 也可以这样做,但不那么简单。您必须知道,当 round_trip_loading 时,ruamel.yaml 通常会将注释附加到已处理的最后一个结构(映射/序列),并且由于在您的示例中注释掉 srv1 的结果与 srv2 完全不同(即第一个键值对,如果被注释掉,则不同于所有其他键值对)。
如果您将预期输出标准化为四个缩进位置并在srv1 之前添加注释以进行分析,请加载该注释,您可以搜索注释结束的位置:
from ruamel.yaml.util import load_yaml_guess_indent
yaml_str = """\
version: '2'
services:
#a
#b
srv1:
image: alpine
container_name: srv1
volumes:
- some-volume:/some/path
#srv2:
# image: alpine
# container_name: srv2
# volumes_from:
# - some-volume
volumes:
some-volume:
"""
data, indent, block_seq_indent = load_yaml_guess_indent(yaml_str)
print('indent', indent, block_seq_indent)
c0 = data['services'].ca
print('c0:', c0)
c0_0 = c0.comment[1][0]
print('c0_0:', repr(c0_0.value), c0_0.start_mark.column)
c1 = data['services']['srv1']['volumes'].ca
print('c1:', c1)
c1_0 = c1.end[0]
print('c1_0:', repr(c1_0.value), c1_0.start_mark.column)
哪个打印:
indent 4 2
c0: Comment(comment=[None, [CommentToken(), CommentToken()]],
items={})
c0_0: '#a\n' 4
c1: Comment(comment=[None, None],
items={},
end=[CommentToken(), CommentToken(), CommentToken(), CommentToken(), CommentToken()])
c1_0: '#srv2:\n' 4
所以你“只有”,如果你注释掉第一个键值对,你必须创建第一个类型注释 (c0),如果你注释掉任何其他键,你必须创建另一个 (c1) -值对。 startmark 是 StreamMark()(来自 ruamel/yaml/error.py),创建 cmets 时该实例的唯一重要属性是 column。
幸运的是,这比上面显示的要容易一些,因为没有必要将 cmets 附加到 volumes 值的“末尾”,将它们附加到 srv1 值的末尾具有相同的效果。
在下面的comment_block 需要一个键列表,它是要被注释掉的元素的路径。
import sys
from copy import deepcopy
from ruamel.yaml import round_trip_dump
from ruamel.yaml.util import load_yaml_guess_indent
from ruamel.yaml.error import StreamMark
from ruamel.yaml.tokens import CommentToken
yaml_str = """\
version: '2'
services:
srv1:
image: alpine
container_name: srv1
volumes:
- some-volume:/some/path
srv2:
image: alpine
container_name: srv2 # second container
volumes_from:
- some-volume
volumes:
some-volume:
"""
def comment_block(d, key_index_list, ind, bsi):
parent = d
for ki in key_index_list[:-1]:
parent = parent[ki]
# don't just pop the value for key_index_list[-1] that way you lose comments
# in the original YAML, instead deepcopy and delete what is not needed
data = deepcopy(parent)
keys = list(data.keys())
found = False
previous_key = None
for key in keys:
if key != key_index_list[-1]:
if not found:
previous_key = key
del data[key]
else:
found = True
# now delete the key and its value
del parent[key_index_list[-1]]
if previous_key is None:
if parent.ca.comment is None:
parent.ca.comment = [None, []]
comment_list = parent.ca.comment[1]
else:
comment_list = parent[previous_key].ca.end = []
parent[previous_key].ca.comment = [None, None]
# startmark can be the same for all lines, only column attribute is used
start_mark = StreamMark(None, None, None, ind * (len(key_index_list) - 1))
for line in round_trip_dump(data, indent=ind, block_seq_indent=bsi).splitlines(True):
comment_list.append(CommentToken('#' + line, start_mark, None))
for srv in ['srv1', 'srv2']:
data, indent, block_seq_indent = load_yaml_guess_indent(yaml_str)
comment_block(data, ['services', srv], ind=indent, bsi=block_seq_indent)
round_trip_dump(data, sys.stdout,
indent=indent, block_seq_indent=block_seq_indent,
explicit_end=True,
)
哪个打印:
version: '2'
services:
#srv1:
# image: alpine
# container_name: srv1
# volumes:
# - some-volume:/some/path
srv2:
image: alpine
container_name: srv2 # second container
volumes_from:
- some-volume
volumes:
some-volume:
...
version: '2'
services:
srv1:
image: alpine
container_name: srv1
volumes:
- some-volume:/some/path
#srv2:
# image: alpine
# container_name: srv2 # second container
# volumes_from:
# - some-volume
volumes:
some-volume:
...
(explicit_end=True 不是必需的,这里使用它来自动区分两个 YAML 转储)。
也可以通过这种方式移除 cmets。递归搜索注释属性 (.ca) 以查找已注释掉的候选对象(可能会提示从何处开始)。从 cmets 中去除前导 # 并连接,然后是 round_trip_load。根据 cmets 的列,您可以确定在何处附加未注释的键值对。