File: differ.rb

package info (click to toggle)
ruby-rspec-expectations 2.14.2-1~bpo70%2B1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy-backports
  • size: 920 kB
  • sloc: ruby: 8,202; makefile: 4
file content (140 lines) | stat: -rw-r--r-- 4,227 bytes parent folder | download | duplicates (2)
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