Created
January 20, 2018 21:59
-
-
Save rtweeks/064c5664aef6834db43d26294fce7798 to your computer and use it in GitHub Desktop.
Testing YAML Scalar Literal Roundtrips in Ruby
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' | |
# Construct one of these, use #find_problem!; if not nil, then use #apply_fix and repeat | |
class ScalarLiteralTester | |
class MethodInvocation | |
def initialize(name, args, block) | |
@name = name | |
@args = args | |
@block = block | |
end | |
attr_accessor :name, :args, :block | |
def inspect | |
if args.empty? | |
args_str = '' | |
else | |
args_str = "(#{args.map(&:inspect).join(', ')})" | |
end | |
"<#{self.class} #{name}#{args_str} #{block}>" | |
end | |
end | |
def initialize(value) | |
@value = value | |
@maxline = 0 | |
@test_stream = Psych.parse_stream("--- |\n foo\n") | |
@scalar_node = parse_tree_scalar(@test_stream) | |
end | |
attr_accessor :value, :fixes | |
def inspect | |
max_dispval_len = 25 | |
dispval = (max_dispval_len < value.length) ? value[0,max_dispval_len - 3] + '...' : value | |
"<#{self.class} @maxline=#{@maxline} (currently #{'in' unless current_lines_roundtrip_faithfully?}valid) value=#{dispval.inspect}>" | |
end | |
def last_current_line | |
@value.lines[@maxline] | |
end | |
def current_lines_roundtrip_faithfully? | |
@scalar_node.value = @value.lines[0..@maxline].join('') | |
output = @test_stream.to_yaml | |
rt_scalar = parse_tree_scalar(Psych.parse_stream(output)) | |
return @scalar_node.style == rt_scalar.style | |
end | |
def find_problem! | |
(@maxline...@value.lines.length).each do |new_maxline| | |
@maxline = new_maxline | |
return {@maxline => last_current_line} unless current_lines_roundtrip_faithfully? | |
end | |
return nil | |
end | |
def apply_fix(method_name, *args, blk: nil) | |
if blk.kind_of?(String) | |
# Allow args like `blk: %q{|a, b| ...}` | |
unless blk.start_with?('{') | |
blk = "{#{blk}}" | |
end | |
call_blk = eval "Proc.new #{blk}" | |
elsif blk | |
call_blk = blk | |
ex_type = ( | |
begin | |
MethodSource::SourceNotFoundError | |
rescue NameError | |
nil | |
end | |
) | |
if ex_type | |
begin | |
blk = blk.source | |
rescue ex_type | |
blk = "{|...| ... source at #{blk.source_location}}" | |
end | |
end | |
end | |
new_value = @value.send(method_name, *args, &call_blk) | |
raise "Line count changed" if @value.lines.length != new_value.lines.length | |
@value = new_value | |
(@fixes ||= []) << MethodInvocation.new(method_name , args, blk) | |
end | |
private | |
def parse_tree_scalar(stream) | |
stream.children[0].children[0] | |
end | |
end | |
# `String.include YamlStringHelpers` to get these helpers for String instances | |
module YamlStringHelpers | |
def expand_tabs(tabstops_every = 8) | |
self.lines.map do |l| | |
if l.include?("\t") | |
segs = l.split("\t") | |
segs[0...-1].map do |seg| | |
# seg length must _increase_ to a multiple of 8 | |
spaces_needed = tabstops_every - (seg.length + 1) % tabstops_every + 1 | |
seg + ' ' * spaces_needed | |
end.join('') + segs[-1] | |
else | |
l | |
end | |
end.join('') | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment