自动扩展 YAML 合并的工具?

Tool to automatically expand YAML merges?

我正在寻找一种工具或流程,它可以轻松获取包含锚点、别名和合并键的 YAML 文件,并扩展别名并合并到一个平面 YAML 文件中。仍然有许多不完全支持合并的常用 YAML 解析。

我希望能够利用合并来保持干燥,但有些情况下需要将其构建到更详细的 "flat" YAML 文件中,以便可以使用通过依赖于不完整 YAML 解析器的其他工具。

示例源 YAML:

default: &DEFAULT
  URL: website.com
  mode: production  
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

development:
  <<: *DEFAULT
  URL: website.local
  mode: dev

test:
  <<: *DEFAULT
  URL: test.website.qa
  mode: test

期望的输出 YAML:

default:
  URL: website.com
  mode: production  
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

development:
  URL: website.local
  mode: dev
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

test:
  URL: test.website.qa
  mode: test
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

如果您的系统上安装了 python,您可以执行 pip install ruamel.yaml.cmd¹ 然后:

yaml merge-expand input.yaml output.yaml

(将 output.yaml 替换为 - 以写入标准输出)。这实现了保留键顺序和注释的合并扩展。

以上其实是利用ruamel.yaml¹的几行代码 因此,如果您有 Python(2.7 或 3.4+)并使用 pip install ruamel.yaml 安装它,并将以下内容保存为 expand.py:

import sys
from ruamel.yaml import YAML

yaml = YAML(typ='safe')
yaml.default_flow_style=False
with open(sys.argv[1]) as fp:
    data = yaml.load(fp)
with open(sys.argv[2], 'w') as fp:
    yaml.dump(data, fp)

你已经可以做到:

python expand.py input.yaml output.yaml

这将为您提供在语义上等同于您所请求内容的 YAML(在 output.yaml 中,映射的键已排序,在此程序输出中,它们未排序)。

以上假设您的 YAML 中没有任何标签,也不关心保留任何注释。其中大部分,以及键顺序,可以通过使用标准 YAML() 实例的补丁版本来保留。打补丁是必要的,因为标准 YAML() 实例也在往返过程中保留合并,这正是您不想要的:

import sys
from ruamel.yaml import YAML, SafeConstructor

yaml = YAML()

yaml.Constructor.flatten_mapping = SafeConstructor.flatten_mapping
yaml.default_flow_style=False
yaml.allow_duplicate_keys = True
# comment out next line if you want "normal" anchors/aliases in your output
yaml.representer.ignore_aliases = lambda x: True  

with open(sys.argv[1]) as fp:
    data = yaml.load(fp)
with open(sys.argv[2], 'w') as fp:
    yaml.dump(data, fp)

使用此输入:

default: &DEFAULT
  URL: website.com
  mode: production
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600  # an hour?

development:
  <<: *DEFAULT
  URL: website.local     # local web
  mode: dev

test:
  <<: *DEFAULT
  URL: test.website.qa
  mode: test

这将给出此输出(请注意,对合并键的注释会重复):

default:
  URL: website.com
  mode: production
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600  # an hour?

development:
  URL: website.local     # local web
  mode: dev

  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600  # an hour?

test:
  URL: test.website.qa
  mode: test
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600  # an hour?

以上是本答案开头提到的 yaml merge-expand 命令的作用。


¹ 免责声明:我是该软件包的作者。

更新:2019-03-13 12:41:05

  • 根据 Anthon 的评论修改了此答案,该评论正确识别了 PyYAML 的限制。 (见下文的陷阱)。

上下文

  • YAML 文件
  • Python 用于解析 YAML

问题

  • 用户 jtYamlEnthusiast 希望输出带有别名、锚点和合并键的 non-DRY 版本的 YAML 文件。

解决方案

  • 备选方案 1:使用 Anthon infra 推广的 ruamel 库。
  • 备选方案 2:使用 Python pprint.pformat 并简单地进行 load/dump 往返转换。

理由

  • 如果您可以自行决定安装除 pyyaml 之外的另一个 python 库,并且您希望高度控制 "round-trip" YAML 转换(例如例如,YAML 注释的保存)。
  • 如果您不需要对往返 YAML 进行严格控制,或者由于某些其他原因限制了 pyyaml,您可以直接加载和转储 YAML,以获得 "non-DRY" 输出。

陷阱

  • 在撰写本文时,PyYAML 相对于 ruamel 库在处理 YAML v1.1 和 YAML v1.2

  • 另请参阅

例子

    ##
    import pprint
    import yaml
    ##
    myrawyaml = '''
    default: &DEFAULT
      URL: website.com
      mode: production
      site_name: Website
      some_setting: h2i8yiuhef
      some_other_setting: 3600

    development:
      <<: *DEFAULT
      URL: website.local
      mode: dev

    test:
      <<: *DEFAULT
      URL: test.website.qa
      mode: test
    '''
    ##
    pynative  =   yaml.safe_load(myrawyaml)
    vout      =   pprint.pformat(pynative)
    print(vout)                             ##=> this is non-DRY and just happens to be well-formed YAML syntax
    print(yaml.safe_load(vout))             ##=> this proves we have well-formed YAML if it loads without exception

如果您出于某种原因需要将扩展​​的 YAML 作为 YAML 写回文件,您可以:

  • 使用@Anthon 的回答。但是,如上所述,如果您无法安装软件包,则此方法可能不可行。

  • 使用@dreftymac 的回答。看来这个答案对某些人有用,但对我没有用;根据我的理解,pprint.pformat returns 参数作为其 Python 表示的字符串,而 yaml.safe_load 期望 Python 表示本身。当然,您可以 eval pprint.pformat 返回的字符串,但即使对受信任的输入使用 eval 也会让人感到恶心。 (同样,答案有几个赞成票,所以也许我在这里遗漏了一些东西。)

或者,你也可以按照我的方法做:

import json
import yaml

def expand_yml(yml):
    return yaml.dump(json.loads(json.dumps(yml)))

expand_yml(my_yml_with_aliases)

由于 JSON 可以(有一些例外,例如别名)被视为 YAML 的严格子集,因此这种方法通常应该有效。但是,如果性能是一个问题,或者如果您正在处理更复杂的 YAML,这种方法可能不适合您。