¿Lee y escribe 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 YAML equivalente de:

 defaults: foo: bar zip: button node: foo: other zip: button 

Qué YAML::load lee como.

Necesito establecer nuevas claves en este documento YAML y luego volver a escribirlas en el disco, preservando la estructura original tanto como sea posible.

Miré YAML :: Store , pero esto destruye por completo los alias y los anclajes.

¿Hay algo disponible que pueda ser algo así como:

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

Guardando el documento como sigue:

 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 un poco sombrío en realidad.

El uso de < < para indicar un mapeo con alias debe combinarse con el mapeo actual que no forma parte de la especificación central de Yaml, sino que forma parte del repository de tags .

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 utiliza la conversión de varios tipos implícitos en el repository de tags, que incluye < < para combinar hashes. También proporciona herramientas para hacer más procesamiento Yaml de bajo nivel si lo necesita. Desafortunadamente, no permite inhabilitar o habilitar de forma selectiva partes específicas del repository de tags, es un asunto de todo o nada. En particular, el manejo de < < está muy relacionado con el manejo de hashes .

Una forma de lograr lo que quiere es proporcionar su propia subclase de la clase ToRuby 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 más o menos como

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

Tenga en cuenta el < < como una clave literal. Además, el hash bajo la clave de data["defaults"] es el mismo hash que el 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 los 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 usa el object_id del hash para asegurar nombres únicos de anclaje (la versión actual de Psych ahora usa números secuenciales en vez 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 suponiendo que solo hay un anclaje:

 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 me preguntó cómo hacer esto con más de un ancla, donde se me ocurrió una forma posiblemente mejor de mantener nombres de anclaje al serializar ).

YAML tiene alias y pueden hacer un viaje de ida y vuelta, pero lo inhabilitas mediante la combinación de hash. < < como una clave de mapeo parece una extensión no estándar para YAML (tanto en el syck de 1.8 como en el psicológico de 1.9).

 require 'rubygems' require 'yaml' yaml = <  

huellas dactilares

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

pero el < < en sus datos fusiona el hash con alias en uno nuevo que ya no es un alias.

¿Has probado Psych ? Otra pregunta con psych aquí .