require 'psych' require 'stringio' # Public: A Psych extension to enable choosing output styles for specific # objects. # # Thanks to Tenderlove for help in # # Examples # # data = { # response: { body: StyledYAML.literal(json_string), status: 200 }, # person: StyledYAML.inline({ 'name' => 'Stevie', 'age' => 12 }), # array: StyledYAML.inline(%w[ apples bananas oranges ]) # } # # StyledYAML.dump data, $stdout # module StyledYAML # Tag strings to be output using literal style def self.literal obj obj.extend LiteralScalar return obj end # http://www.yaml.org/spec/1.2/spec.html#id2795688 module LiteralScalar def yaml_style() Psych::Nodes::Scalar::LITERAL end end # Tag Hashes or Arrays to be output all on one line def self.inline obj case obj when Hash then obj.extend FlowMapping when Array then obj.extend FlowSequence else warn "#{self}: unrecognized type to inline (#{obj.class.name})" end return obj end # http://www.yaml.org/spec/1.2/spec.html#id2790832 module FlowMapping def yaml_style() Psych::Nodes::Mapping::FLOW end end # http://www.yaml.org/spec/1.2/spec.html#id2790320 module FlowSequence def yaml_style() Psych::Nodes::Sequence::FLOW end end # Custom tree builder class to recognize scalars tagged with `yaml_style` class TreeBuilder < Psych::TreeBuilder attr_writer :next_sequence_or_mapping_style def initialize(*args) super @next_sequence_or_mapping_style = nil end def next_sequence_or_mapping_style default_style style = @next_sequence_or_mapping_style || default_style @next_sequence_or_mapping_style = nil style end def scalar value, anchor, tag, plain, quoted, style if style_any?(style) and value.respond_to?(:yaml_style) and style = value.yaml_style if style_literal? style plain = false quoted = true end end super end def style_any?(style) Psych::Nodes::Scalar::ANY == style end def style_literal?(style) Psych::Nodes::Scalar::LITERAL == style end %w[sequence mapping].each do |type| class_eval <<-RUBY def start_#{type}(anchor, tag, implicit, style) style = next_sequence_or_mapping_style(style) super end RUBY end end # Custom tree class to handle Hashes and Arrays tagged with `yaml_style` class YAMLTree < Psych::Visitors::YAMLTree %w[Hash Array Psych_Set Psych_Omap].each do |klass| class_eval <<-RUBY def visit_#{klass} o if o.respond_to? :yaml_style @emitter.next_sequence_or_mapping_style = o.yaml_style end super end RUBY end end # A Psych.dump alternative that uses the custom TreeBuilder def self.dump obj, io = nil, options = {} real_io = io || StringIO.new(''.encode('utf-8')) visitor = YAMLTree.new(options, TreeBuilder.new) visitor << obj ast = visitor.tree begin ast.yaml real_io rescue # The `yaml` method was introduced in later versions, so fall back to # constructing a visitor Psych::Visitors::Emitter.new(real_io).accept ast end io ? io : real_io.string end end