使用每个密钥的行号加载YAML

Ger*_*ger 8 ruby yaml

假设我有一个YAML文件,如下所示:

  en:
    errors:
      # Some comment
      format: "%{attribute} %{message}"

      # One more comment
      messages:
        "1": "Message 1"
        "2": "Message 2"

    long_error_message: |
      This is a
      multiline message

    date:
      format: "YYYY-MM-DD"

我怎么能把这个读Hash成像这样的Ruby ?

{
  'en': {
    'errors': {
      'format': { value: '%{attribute} %{message}', line: 4 }
      'messages': {
        '1': { value: 'Message 1', line: 8 },
        '2': { value: 'Message 2', line: 9 }
      }
      'long_error_message' : { value: "This is a\nmultiline message", line: 11 }
    },
    'date': {
      'format': { value: 'YYYY-MM-DD', line: 16 }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我尝试过使用YAML中提到的提示:查找密钥的行号?作为一个起点并实现了一个Psych::Handler,但感觉我必须从Psych重写大量代码才能使其工作.

我有什么想法可以解决这个问题?

mat*_*att 5

看起来您想要采用任何标量值作为映射值,并将其替换为value包含原始值的line键和包含行号的键.

以下几乎可以工作,主要问题是多行字符串,其中给出的行号是Yaml中下一个事物的开始.问题是,当scalar调用处理程序方法时,解析器已经超出了感兴趣的标量,因此mark当它知道标量已经结束时给出位置的行.在大多数情况下,在您的示例中这并不重要,但在多行情况下,它会给出错误的值.我没有看到任何方法从mark标量的开头获取解析器信息而不进入Psych C代码.

require 'psych'

# Psych's first step is to parse the Yaml into an AST of Node objects
# so we open the Node class and add a way to track the line.
class Psych::Nodes::Node
  attr_accessor :line
end

# We need to provide a handler that will add the line to the node
# as it is parsed. TreeBuilder is the "usual" handler, that
# creates the AST.
class LineNumberHandler < Psych::TreeBuilder

  # The handler needs access to the parser in order to call mark
  attr_accessor :parser

  # We are only interested in scalars, so here we override 
  # the method so that it calls mark and adds the line info
  # to the node.
  def scalar value, anchor, tag, plain, quoted, style
    mark = parser.mark
    s = super
    s.line = mark.line
    s
  end
end

# The next step is to convert the AST to a Ruby object.
# Psych does this using the visitor pattern with the ToRuby
# visitor. Here we patch ToRuby rather than inherit from it
# as it makes the last step a little easier.
class Psych::Visitors::ToRuby

  # This is the method for creating hashes. There may be problems
  # with Yaml mappings that have tags.
  def revive_hash hash, o
    o.children.each_slice(2) { |k,v|
      key = accept(k)
      val = accept(v)

      # This is the important bit. If the value is a scalar,
      # we replace it with the desired hash.
      if v.is_a? ::Psych::Nodes::Scalar
        val = { "value" => val, "line" => v.line + 1} # line is 0 based, so + 1
      end

      # Code dealing with << (for merging hashes) omitted.
      # If you need this you will probably need to copy it
      # in here. See the method:
      # https://github.com/tenderlove/psych/blob/v2.0.13/lib/psych/visitors/to_ruby.rb#L333-L365

      hash[key] = val
    }
    hash
  end
end

yaml = get_yaml_from_wherever

# Put it all together    
handler = LineNumberHandler.new
parser =  Psych::Parser.new(handler)
# Provide the handler with a reference to the parser
handler.parser = parser

# The actual parsing
parser.parse yaml
# We patched ToRuby rather than inherit so we can use to_ruby here
puts handler.root.to_ruby
Run Code Online (Sandbox Code Playgroud)