Skip to content

Instantly share code, notes, and snippets.

@rtweeks
Created January 20, 2018 21:59
Show Gist options
  • Save rtweeks/064c5664aef6834db43d26294fce7798 to your computer and use it in GitHub Desktop.
Save rtweeks/064c5664aef6834db43d26294fce7798 to your computer and use it in GitHub Desktop.
Testing YAML Scalar Literal Roundtrips in Ruby
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