19

我需要打开一个 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,但这完全破坏了别名和锚点。

是否有任何可用的东西可以类似于以下内容:

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

将文档另存为:

defaults: &defaults
  foo: bar
  zip: button

node:
  <<: *defaults
  foo: yet another

?

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

4

3 回答 3

15

用于<<指示别名映射应合并到当前映射不是核心 Yaml 规范的一部分,但它是标签存储库的一部分。

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

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

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"]键下的散列相同data["node"]["<<"],即它们具有相同的object_id. 您现在可以根据需要操作数据,当您将其写为 Yaml 时,锚点和别名仍将保留,尽管锚点名称会发生​​变化:

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

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

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

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

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

更新: 另一个问题询问如何使用多个锚点来执行此操作,我想出了一种在序列化时保留锚点名称的可能更好的方法。)

于 2012-10-18T21:39:46.090 回答
3

YAML 有别名,它们可以往返,但你可以通过哈希合并禁用它。<<作为映射键似乎是 YAML 的非标准扩展(在 1.8 的 syck 和 1.9 的 psych 中)。

require 'rubygems'
require 'yaml'

yaml = <<EOS
defaults: &defaults
  foo: bar
  zip: button

node: *defaults
EOS

data = YAML.load yaml
print data.to_yaml

印刷

--- 
defaults: &id001 
  zip: button
  foo: bar
node: *id001

但是<<在您的数据中将别名哈希合并到一个不再是别名的新哈希中。

于 2012-10-18T16:36:23.577 回答
1

你试过心理吗?另一个关于心理的问题

于 2012-08-02T10:16:07.403 回答