1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
require 'diff/lcs'
require 'diff/lcs/hunk'
require 'pp'
module RSpec
module Expectations
class Differ
# This is snagged from diff/lcs/ldiff.rb (which is a commandline tool)
def diff_as_string(input_data_new, input_data_old)
output = matching_encoding("", input_data_old)
data_old = input_data_old.split(matching_encoding("\n", input_data_old)).map! { |e| e.chomp }
data_new = input_data_new.split(matching_encoding("\n", input_data_new)).map! { |e| e.chomp }
diffs = Diff::LCS.diff(data_old, data_new)
return output if diffs.empty?
oldhunk = hunk = nil
file_length_difference = 0
diffs.each do |piece|
begin
hunk = Diff::LCS::Hunk.new(
data_old, data_new, piece, context_lines, file_length_difference
)
file_length_difference = hunk.file_length_difference
next unless oldhunk
# Hunks may overlap, which is why we need to be careful when our
# diff includes lines of context. Otherwise, we might print
# redundant lines.
if (context_lines > 0) and hunk.overlaps?(oldhunk)
if hunk.respond_to?(:merge)
# diff-lcs 1.2.x
hunk.merge(oldhunk)
else
# diff-lcs 1.1.3
hunk.unshift(oldhunk)
end
else
output << matching_encoding(oldhunk.diff(format).to_s, output)
end
ensure
oldhunk = hunk
output << matching_encoding("\n", output)
end
end
#Handle the last remaining hunk
output << matching_encoding(oldhunk.diff(format).to_s,output)
output << matching_encoding("\n",output)
color_diff output
rescue Encoding::CompatibilityError
if input_data_new.encoding != input_data_old.encoding
"Could not produce a diff because the encoding of the actual string (#{input_data_old.encoding}) "+
"differs from the encoding of the expected string (#{input_data_new.encoding})"
else
"Could not produce a diff because of the encoding of the string (#{input_data_old.encoding})"
end
end
def diff_as_object(actual, expected)
actual_as_string = object_to_string(actual)
expected_as_string = object_to_string(expected)
if diff = diff_as_string(actual_as_string, expected_as_string)
color_diff diff
end
end
protected
def format
:unified
end
def context_lines
3
end
def color(text, color_code)
"\e[#{color_code}m#{text}\e[0m"
end
def red(text)
color(text, 31)
end
def green(text)
color(text, 32)
end
def blue(text)
color(text, 34)
end
def color_diff(diff)
return diff unless RSpec::Matchers.configuration.color?
diff.lines.map { |line|
case line[0].chr
when "+"
green line
when "-"
red line
when "@"
line[1].chr == "@" ? blue(line) : line
else
line
end
}.join
end
def object_to_string(object)
case object
when Hash
object.keys.sort_by { |k| k.to_s }.map do |key|
pp_key = PP.singleline_pp(key, "")
pp_value = PP.singleline_pp(object[key], "")
# on 1.9.3 PP seems to minimise to US-ASCII, ensure we're matching source encoding
#
# note, PP is used to ensure the ordering of the internal values of key/value e.g.
# <# a: b: c:> not <# c: a: b:>
matching_encoding("#{pp_key} => #{pp_value}", key)
end.join(",\n")
when String
object =~ /\n/ ? object : object.inspect
else
PP.pp(object,"")
end
end
if String.method_defined?(:encoding)
def matching_encoding(string, source)
string.encode(source.encoding)
end
else
def matching_encoding(string, source)
string
end
end
end
end
end
|