Created
March 12, 2012 18:59
-
-
Save mislav/2023978 to your computer and use it in GitHub Desktop.
Dump objects into YAML using specific style
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
full_data = { | |
response: {body: StyledYAML.literal(DATA.read), status: 200}, | |
person: StyledYAML.inline('name' => 'Steve', 'age' => 24), | |
array: StyledYAML.inline(%w[ apples bananas oranges ]) | |
} | |
StyledYAML.dump full_data, $stdout | |
__END__ | |
{ | |
"page": 1, | |
"results": [ | |
"item", "another" | |
], | |
"total_pages": 0 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# default boring output of Psych.dump | |
--- | |
:response: | |
:body: ! "{\n \"page\": 1,\n \"results\": [\n \"item\", \"another\"\n ],\n | |
\ \"total_pages\": 0\n}\n" | |
:status: 200 | |
:person: | |
name: Steve | |
age: 24 | |
:array: | |
- apples | |
- bananas | |
- oranges |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
:response: | |
# formatted string presented in literal style | |
:body: | | |
{ | |
"page": 1, | |
"results": [ | |
"item", "another" | |
], | |
"total_pages": 0 | |
} | |
:status: 200 | |
# inline hash | |
:person: {name: Steve, age: 24} | |
# inline array | |
:array: [apples, bananas, oranges] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'psych' | |
require 'stringio' | |
# Public: A Psych extension to enable choosing output styles for specific | |
# objects. | |
# | |
# Thanks to Tenderlove for help in <http://stackoverflow.com/q/9640277/11687> | |
# | |
# 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 |
Too clarify: if ruby/psych#281 gets fixed, you could replace the styled yaml with following:
require 'psych'
require 'stringio'
# Public: A Psych extension to enable choosing output styles for specific
# objects.
#
# Thanks to Tenderlove for help in <http://stackoverflow.com/q/9640277/11687>
#
# 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 encode_with(coder)
coder.style = Psych::Nodes::Scalar::LITERAL
coder.tag = nil
coder.scalar = self
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 encode_with(coder)
coder.style = Psych::Nodes::Mapping::FLOW
coder.tag = nil
coder.map = self
end
end
# http://www.yaml.org/spec/1.2/spec.html#id2790320
module FlowSequence
def encode_with(coder)
coder.style = Psych::Nodes::Sequence::FLOW
coder.tag = nil
coder.seq = self
end
end
def self.dump obj, io = nil, options = {}
Psych.dump(obj,io,options)
end
end
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It works, but I have strong suspicion that there would be much simpler way to get inline-formatted array, based on encode_with ... if the line in /usr/lib/ruby/2.1.0/psych/visitors/yaml_tree.rb would read
@emitter.start_sequence nil, c.tag, c.tag.nil?, c.style
and not
@emitter.start_sequence nil, c.tag, c.tag.nil?, Nodes::Sequence::BLOCK
Tested with: