读取和写入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在现实中看起来有点像一个看起来很暗淡的游戏领域。
使用<<
来表示别名映射应该合并到当前映射中不是核心Yaml规范的一部分 ,但它是标记存储库的一部分 。
Ruby-Psych提供的当前Yaml库提供了dump
和load
方法,可以轻松地对Ruby对象进行序列化和反序列化,并在标记库中使用各种隐式类型转换,包括<<
合并哈希。 它还提供了在需要时进行更多低级Yaml处理的工具。 不幸的是,它不容易选择性地禁用或启用标签存储库的特定部分 - 这是一个全有或全无的事情。 特别是对<<
处理哈希的处理非常好 。
实现你想要的东西的一种方法是提供你自己的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
( 更新: 另一个问题是如何使用多个锚点进行此操作,我在序列化时提出了一种更好的方法来保留锚名称 。)
YAML有别名,他们可以往返,但你通过哈希合并禁用它。 <<
作为映射键似乎是YAML的非标准扩展(在1.8的syck和1.9的心理中)。
require 'rubygems' require 'yaml' yaml = <
版画
--- defaults: &id001 zip: button foo: bar node: *id001
但是你的数据中的<<
将别名哈希合并为一个不再是别名的新哈希。
你有没有尝试过Psych ? 这里有另一个心理问题 。