ruby - ¿Lee y escriba archivos YAML sin destruir anclas y alias?
Necesito abrir un archivo YAML con alias usados dentro de él:
defaults: &defaults
foo: bar
zip: button
node:
<<: *defaults
foo: other
Esto obviamente se expande a un documento equivalente de YAML de:
defaults:
foo: bar
zip: button
node:
foo: other
zip: button
YAML::load
lee como.
Necesito establecer nuevas claves en este documento YAML y luego volver a escribirlas en el disco, conservando la estructura original tanto como sea posible.
He consultado YAML::Store , pero esto destruye completamente los alias y los anclajes.
¿Hay algo disponible que podría algo a lo largo de las líneas de:
thing = Thing.load("config.yml")
thing[:node][:foo] = "yet another"
Guardando el documento de nuevo como:
defaults: &defaults
foo: bar
zip: button
node:
<<: *defaults
foo: yet another
?
Opté por usar YAML para esto debido al hecho de que maneja bien este aliasing, pero escribir YAML que contiene alias parece ser un campo de juego de aspecto sombrío en la realidad.
El uso de <<
para indicar que un mapeo con alias debe fusionarse con el mapeo actual no es parte de la especificación central de Yaml, sino que es parte del repositorio de etiquetas .
La biblioteca actual de Yaml proporcionada por Ruby - Psych - proporciona los métodos de dump
y load
que permiten una fácil serialización y deserialización de los objetos de Ruby y usan varias conversiones de tipos implícitas en el repositorio de etiquetas, incluida <<
para fusionar hashes. También proporciona herramientas para hacer más procesamiento de Yaml de bajo nivel si lo necesita. Desafortunadamente, no permite fácilmente deshabilitar o habilitar de manera selectiva partes específicas del repositorio de etiquetas, es un asunto de todo o nada. En particular, el manejo de <<
está bastante relacionado con el manejo de hashes .
Una forma de lograr lo que desea es proporcionar su propia subclase de la clase ToRuby
de Psych y anular este método, de modo que solo trate las claves de mapeo de <<
como literales. Esto implica anular un método privado en Psych, por lo que debe tener un poco de cuidado:
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
Entonces lo usarías así:
tree = Psych.parse your_data
data = ToRubyNoMerge.new.accept tree
Con el Yaml de su ejemplo, los data
verían como
{"defaults"=>{"foo"=>"bar", "zip"=>"button"},
"node"=>{"<<"=>{"foo"=>"bar", "zip"=>"button"}, "foo"=>"other"}}
Tenga en cuenta la <<
como una clave literal. Además, el hash debajo de la clave de data["defaults"]
es el mismo hash que el que está debajo de la clave de data["node"]["<<"]
, es decir, tienen el mismo object_id
. Ahora puede manipular los datos como desee, y cuando los escriba como Yaml, los anclajes y alias seguirán en su lugar, aunque los nombres de los anclajes habrán cambiado:
data[''node''][''foo''] = "yet another"
puts Yaml.dump data
produce (Psych utiliza el object_id
del hash para asegurar nombres de ancla únicos (la versión actual de Psych ahora usa números secuenciales en lugar de object_id
))
---
defaults: &2151922820
foo: bar
zip: button
node:
<<: *2151922820
foo: yet another
Si desea tener control sobre los nombres de anclaje, puede proporcionar su propio Psych::Visitors::Emitter
. Aquí hay un ejemplo simple basado en su ejemplo y asumiendo que solo hay un ancla:
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
Cuando se usa con el hash de data
modificado desde arriba:
#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
la salida es:
---
defaults: &defaults
foo: bar
zip: button
node:
<<: *defaults
foo: yet another
( Actualización: otra pregunta preguntaba cómo hacer esto con más de un ancla, donde se me ocurrió una forma posiblemente mejor de mantener los nombres de ancla al serializar ).
YAML tiene alias y pueden redondearse, pero lo deshabilitas mediante la fusión de hash. <<
como clave de mapeo parece una extensión no estándar de YAML (tanto en 1.8''s syck como en 1.9''s psych).
require ''rubygems''
require ''yaml''
yaml = <<EOS
defaults: &defaults
foo: bar
zip: button
node: *defaults
EOS
data = YAML.load yaml
print data.to_yaml
huellas dactilares
---
defaults: &id001
zip: button
foo: bar
node: *id001
pero el <<
en sus datos combina el hash de alias en uno nuevo que ya no es un alias.