读取和写入YAML文件而不破坏锚点和别名?

时间:2012-07-26 09:05:25

标签: 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,但这完全破坏了别名和锚点。

是否有任何可用的内容:

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

将文档保存为:

defaults: &defaults
  foo: bar
  zip: button

node:
  <<: *defaults
  foo: yet another

我选择使用YAML,因为它很好地处理了这个别名,但是编写包含别名的YAML在现实中看起来有点像一个看起来很暗淡的游戏场。

3 个答案:

答案 0 :(得分:12)

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

Ruby-Psych提供的当前Yaml库提供了dumpload方法,这些方法允许轻松地对Ruby对象进行序列化和反序列化,并在标记库中使用各种隐式类型转换,包括{{ 1}}合并哈希。它还提供了在需要时进行更多低级Yaml处理的工具。不幸的是,它不容易选择性地禁用或启用标签存储库的特定部分 - 这是一个全有或全无的事情。特别是handling of << is pretty baked in to the handling of 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

使用您示例中的Yaml,tree = Psych.parse your_data data = ToRubyNoMerge.new.accept tree 将看起来像

data

请注意{"defaults"=>{"foo"=>"bar", "zip"=>"button"}, "node"=>{"<<"=>{"foo"=>"bar", "zip"=>"button"}, "foo"=>"other"}} 作为文字键。此外,<<密钥下的哈希是相同的哈希,与data["defaults"]密钥下的哈希相同,即它们具有相同的data["node"]["<<"]。您现在可以根据需要操作数据,当您将其写为Yaml时,锚点和别名仍将存在,尽管锚名称将会更改:

object_id

产生(Psych使用哈希的data['node']['foo'] = "yet another" puts Yaml.dump data 来确保唯一的锚名称(当前版本的Psych现在使用连续数字而不是object_id)):

object_id

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

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

与上面的修改后的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

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

答案 1 :(得分:3)

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

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

但是数据中的<<会将别名哈希合并为一个不再是别名的新哈希值。

答案 2 :(得分:1)

您试试Psych吗?心理上的另一个问题here