ruby - 在不破坏 anchor 和别名的情况下读写 YAML 文件?

标签 ruby yaml

我需要打开一个 YAML 文件,其中使用了别名:

defaults: &defaults
  foo: bar
  zip: button

node:
  <<: *defaults
  foo: other

这显然扩展为等效的 YAML 文档:

defaults:
  foo: bar
  zip: button

node:
  foo: other
  zip: button

YAML::load 将其读取为。

我需要在此 YAML 文档中设置新键,然后将其写回磁盘,尽可能保留原始结构。

我看过YAML::Store , 但这完全破坏了别名和 anchor 。

是否有任何可用的东西:

thing = Thing.load("config.yml")
thing[:node][:foo] = "yet another"

将文档另存为:

defaults: &defaults
  foo: bar
  zip: button

node:
  <<: *defaults
  foo: yet another

?

我选择为此使用 YAML,因为它可以很好地处理这种别名,但编写包含别名的 YAML 在现实中似乎有点惨淡。

最佳答案

<<的使用指示应将别名映射合并到当前映射中不是核心 Yaml 规范的一部分,但它是 part of the tag repository .

Ruby 提供的当前 Yaml 库 – Psych – 提供 dumpload允许轻松序列化和反序列化 Ruby 对象的方法,并在标签存储库中使用各种隐式类型转换,包括 <<合并哈希。如果需要,它还提供工具来执行更多低级 Yaml 处理。不幸的是,它不允许选择性地禁用或启用标签存储库的特定部分——这是一个全有或全无的事情。特别是 handling of << is pretty baked in to the handling of hashes .

实现您想要的一种方法是提供您自己的 Psych 的子类 ToRuby类并覆盖此方法,以便它只处理 << 的映射键作为文字。这涉及覆盖 Psych 中的私有(private)方法,因此您需要小心一点:

require 'psych'

class ToRubyNoMerge < Psych::Visitors::ToRuby
  def revive_hash hash, o
    @st[o.anchor] = hash if o.anchor

    o.children.each_slice(2) { |k,v|
      key = accept(k)
      hash[key] = accept(v)
    }
    hash
  end
end

然后你会像这样使用它:

tree = Psych.parse your_data
data = ToRubyNoMerge.new.accept tree

使用您示例中的 Yaml,data然后看起来像

{"defaults"=>{"foo"=>"bar", "zip"=>"button"},
 "node"=>{"<<"=>{"foo"=>"bar", "zip"=>"button"}, "foo"=>"other"}}

注意 <<作为文字键。还有 data["defaults"] 下的哈希 key 是与 data["node"]["<<"] 下的相同哈希键,即它们具有相同的 object_id .您现在可以根据需要操作数据,当您将其写为 Yaml 时, anchor 和别名仍然存在,尽管 anchor 名称会发生​​变化:

data['node']['foo'] = "yet another"
puts Yaml.dump data

产生(Psych 使用哈希的 object_id 来确保唯一的 anchor 名称(当前版本的 Psych 现在使用序列号而不是 object_id )):

---
defaults: &2151922820
  foo: bar
  zip: button
node:
  <<: *2151922820
  foo: yet another

如果您想控制 anchor 名称,您可以提供自己的 Psych::Visitors::Emitter .这是一个基于您的示例并假设只有一个 anchor 的简单示例:

class MyEmitter < Psych::Visitors::Emitter
  def visit_Psych_Nodes_Mapping o
    o.anchor = 'defaults' if o.anchor
    super
  end

  def visit_Psych_Nodes_Alias o
    o.anchor = 'defaults' if o.anchor
    super
  end
end

与修改后的 data 一起使用时上面的散列:

#create an AST based on the Ruby data structure
builder = Psych::Visitors::YAMLTree.new
builder << data
ast = builder.tree

# write out the tree using the custom emitter
MyEmitter.new($stdout).accept ast

输出是:

---
defaults: &defaults
  foo: bar
  zip: button
node:
  <<: *defaults
  foo: yet another

(更新: another question 询问如何使用多个 anchor 执行此操作,我想出了一个 possibly better way to keep anchor names when serializing 。)

关于ruby - 在不破坏 anchor 和别名的情况下读写 YAML 文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11665897/

相关文章:

ruby - 顶级方法中 `self` 的规则是什么?

python - Snakemake 无法将多个文件识别为输入

arrays - 如何覆盖 ansibles 数组变量的子集?

java - 从 Yaml 文件为请求和响应对象生成 Java 类

yaml - yq 添加带有名称和子字段的数组元素

ruby - 如何按条件排除未使用的案例

ruby - Git 用 CRLF 替换了我所有的 LF - 我该如何解决这个问题?

ruby-on-rails - ActiveRecord 模型名称的约定

ruby - 在条件中使用逻辑或运算符

java - SnakeYAML 转储嵌套键