LoginSignup
3
0

More than 5 years have passed since last update.

RubyでYAMLをインライン形式で書き出す方法

Last updated at Posted at 2018-02-28

YAMLはコンピュータで読み込め人にも優しい形式ですが、最下層だけでもインライン形式で書き出してくれれば、さらに読みやすくなるように思いますので、その方法を紹介します。

その1

Psychのソースコードを読む限り対象クラスにencode_withメソッドを追加することでYAMLの出力を変えることができます。それを利用した方法です。

Hashクラスの場合

Hashクラスの場合をはじめに説明します。
基本的には、対象とするクラスにencode_withメソッドを追加するだけです。
最下位の要素だけをインライン形式にしたいので、全ての要素の値がNumericかStringである場合だけを対象にしています。

yaml1.rb
require 'yaml'

class Hash
  def encode_with(coder)
    if self.all?{|k, v| Numeric===v or String===v}
      coder.style = Psych::Nodes::Mapping::FLOW
    end
    coder.tag = nil
    coder.map = self
  end
end

puts [{"a"=>2, "b"=>3}, {"c"=>4}].to_yaml
# 出力結果
# ---
# - {a: 2, b: 3}
# - {c: 4}

Arrayクラスでは

Hashと同じようにしてもインライン形式で出力してくれませんでしたので、ここではemit_coderメソッドをオーバーライドしています。とは言ってもwhen :seqの次の1行を変更しているだけです。
おそらくPsychのバグではないかと思います。

yaml2.rb
require 'yaml'

module Psych
  module Visitors
    class YAMLTree < Psych::Visitors::Visitor
      alias_method :emit_coder_orig, :emit_coder
      def emit_coder c, o
        case c.type
        when :seq
          @emitter.start_sequence nil, c.tag, c.tag.nil?, c.style
          c.seq.each do |thing|
            accept thing
          end
          @emitter.end_sequence
        else
          emit_coder_orig c, o
        end
      end
    end
  end
end

class Array
  def encode_with(coder)
    if self.all?{|c| Numeric===c or String===c}
      coder.style = Psych::Nodes::Mapping::FLOW
    end
    coder.tag = nil
    coder.seq = self
  end
end

puts ({"a"=>[1,2,3], "b"=>[4,5]}).to_yaml
# 出力
# ---
# a: [1, 2, 3]
# b: [4, 5]

その2

(2018.5.19追記)
acceptをオーバーライドして強引?にインライン出力する方法です。
YAMLStyle.flow_style()の中にインライン出力したいオブジェクトを入れることで、そのオブジェクトIDを登録し、YAML展開するときにstyleをFLOWに書き換えています。
こちらの方が出力の自由度は高いです。

yaml3.rb
require "yaml"

class YAMLStyle
  @@flow_style_ids=[]
  def self.flow_style(obj)
     @@flow_style_ids << obj.__id__
     obj
  end
end

module Psych
  module Visitors
    class YAMLTree < Psych::Visitors::Visitor
      alias_method :accept_orig, :accept
      def accept target
        acpt=accept_orig target
        acpt.style=Psych::Nodes::Sequence::FLOW if YAMLStyle.class_variable_get(:@@flow_style_ids).include?(target.__id__)
        acpt
      end
    end
  end
end

puts [YAMLStyle.flow_style({"a"=>2, "b"=>3}), YAMLStyle.flow_style({"c"=>4})].to_yaml
puts ({"a"=>YAMLStyle.flow_style([1,2,3]), "b"=>YAMLStyle.flow_style([4,5])}).to_yaml
# 出力
# ---
# - {a: 2, b: 3}
# - {c: 4}
# ---
# a: [1, 2, 3]
# b: [4, 5]

使用したrubyのバージョンは2.5.1p57です。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0