ruby parsing yaml psych emit

ruby - Lee y escribe archivos YAML sin destruir anclas y alias



parsing psych (3)

Esta pregunta se ha hecho antes: ¿ Leer y escribir archivos YAML sin destruir anclas y alias?

Me preguntaba cómo resolver ese problema con muchos anclajes y alias.

Gracias


aquí hay una versión ligeramente modificada para las versiones más nuevas de la gema psicológica. antes me dio el siguiente error:

NoMethodError - undefined method `[]='' for #<Psych::Visitors::YAMLTree::Registrar:0x007fa0db6ba4d0>

el método de register se movió a una subclase de YAMLTree , así que esto funciona ahora con respecto a todo lo que Matt dice en su respuesta:

class ToRubyNoMerge < Psych::Visitors::ToRuby def revive_hash hash, o if o.anchor @st[o.anchor] = hash hash.instance_variable_set "@_yaml_anchor_name", o.anchor end o.children.each_slice(2) { |k,v| key = accept(k) hash[key] = accept(v) } hash end end class MyYAMLTree < Psych::Visitors::YAMLTree class Registrar # record object for future, using ''@_yaml_anchor_name'' rather # than object_id if it exists def register target, node anchor_name = target.instance_variable_get(''@_yaml_anchor_name'') || target.object_id @obj_to_node[anchor_name] = node end end # check to see if this object has been seen before def accept target if anchor_name = target.instance_variable_get(''@_yaml_anchor_name'') if @st.key? anchor_name oid = anchor_name node = @st[oid] anchor = oid.to_s node.anchor = anchor return @emitter.alias anchor end end # accept is a pretty big method, call super to avoid copying # it all here. super will handle the cases when it''s an object # that''s been seen but doesn''t have ''@_yaml_anchor_name'' set super end end


Tuve que modificar aún más el código que @markus publicó para trabajar con Psych v2.0.17.

Esto es con lo que terminé. Espero que ayude a otra persona a ahorrar bastante tiempo. :-)

class ToRubyNoMerge < Psych::Visitors::ToRuby def revive_hash hash, o if o.anchor @st[o.anchor] = hash hash.instance_variable_set "@_yaml_anchor_name", o.anchor end o.children.each_slice(2) do |k,v| key = accept(k) hash[key] = accept(v) end hash end end class Psych::Visitors::YAMLTree::Registrar # record object for future, using ''@_yaml_anchor_name'' rather # than object_id if it exists def register target, node @targets << target @obj_to_node[_anchor_name(target)] = node end def key? target @obj_to_node.key? _anchor_name(target) rescue NoMethodError false end def node_for target @obj_to_node[_anchor_name(target)] end private def _anchor_name(target) target.instance_variable_get(''@_yaml_anchor_name'') || target.object_id end end class MyYAMLTree < Psych::Visitors::YAMLTree # check to see if this object has been seen before def accept target if anchor_name = target.instance_variable_get(''@_yaml_anchor_name'') if @st.key? target node = @st.node_for target node.anchor = anchor_name return @emitter.alias anchor_name end end # accept is a pretty big method, call super to avoid copying # it all here. super will handle the cases when it''s an object # that''s been seen but doesn''t have ''@_yaml_anchor_name'' set super end def visit_String o if o == ''<<'' style = Psych::Nodes::Scalar::PLAIN tag = ''tag:yaml.org,2002:str'' plain = true quote = false return @emitter.scalar o, nil, tag, plain, quote, style end # visit_String is a pretty big method, call super to avoid copying it all # here. super will handle the cases when it''s a string other than ''<<'' super end end


El problema aquí es que los anclajes y alias en Yaml son un detalle de serialización, por lo que no forman parte de los datos después de su análisis, por lo que el nombre del ancla original no se conoce al volver a escribir los datos en Yaml. Para conservar los nombres de los anclajes cuando se realiza un disparo circular, debe almacenarlos en algún lugar al analizarlos para que estén disponibles más adelante al serializar. En Ruby, cualquier objeto puede tener variables de instancia asociadas con él, por lo que una forma fácil de lograr esto sería almacenar el nombre del anclaje en una variable de instancia del objeto en cuestión.

Siguiendo con el ejemplo de la pregunta anterior , para los hashes podemos cambiar nuestro método redibujado revive_hash modo que si el hash es un ancla, además de registrar el nombre del anclaje en la variable @st para que posteriormente se reconozcan las alises, lo agreguemos como una variable de instancia en el hash.

class ToRubyNoMerge < Psych::Visitors::ToRuby def revive_hash hash, o if o.anchor @st[o.anchor] = hash hash.instance_variable_set "@_yaml_anchor_name", o.anchor end o.children.each_slice(2) { |k,v| key = accept(k) hash[key] = accept(v) } hash end end

Tenga en cuenta que esto solo afecta a las asignaciones yaml que son anclajes. Si desea tener otros tipos para conservar su nombre de anclaje, deberá consultar psych/visitors/to_ruby.rb y asegurarse de que el nombre se agregue en todos los casos. La mayoría de los tipos pueden incluirse anulando el register pero hay un par de otros; buscar @st .

Ahora que el hash tiene asociado el nombre de anclaje deseado, debe hacer que Psych lo use en lugar del Id. De objeto al serializarlo. Esto puede hacerse subclasificando YAMLTree . Cuando YAMLTree procesa un objeto, primero verifica si ese objeto ya se ha visto y, si lo tiene, emite un alias . Para cualquier objeto nuevo, registra que ha visto el objeto en caso de que necesite crear un alias más adelante . El object_id se utiliza como la clave en esto, por lo que debe anular esos dos métodos para verificar la variable de instancia y, en su lugar, usarla si existe:

class MyYAMLTree < Psych::Visitors::YAMLTree # check to see if this object has been seen before def accept target if anchor_name = target.instance_variable_get(''@_yaml_anchor_name'') if @st.key? anchor_name oid = anchor_name node = @st[oid] anchor = oid.to_s node.anchor = anchor return @emitter.alias anchor end end # accept is a pretty big method, call super to avoid copying # it all here. super will handle the cases when it''s an object # that''s been seen but doesn''t have ''@_yaml_anchor_name'' set super end # record object for future, using ''@_yaml_anchor_name'' rather # than object_id if it exists def register target, yaml_obj anchor_name = target.instance_variable_get(''@_yaml_anchor_name'') || target.object_id @st[anchor_name] = yaml_obj yaml_obj end end

Ahora puede usarlo así (a diferencia de la pregunta anterior, no necesita crear un emisor personalizado en este caso):

builder = MyYAMLTree.new builder << data tree = builder.tree puts tree.yaml # returns a string # alternativelty write direct to file: File.open(''a_file.yml'', ''r+'') do |f| tree.yaml f end