Skip to content

Instantly share code, notes, and snippets.

@sandstrom
Forked from mislav/example.rb
Created September 6, 2017 11:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sandstrom/326cc1d84043b90ef8375db833346e32 to your computer and use it in GitHub Desktop.
Save sandstrom/326cc1d84043b90ef8375db833346e32 to your computer and use it in GitHub Desktop.
Dump objects into YAML using specific style
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
}
# 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
---
: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]
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment