super_diff 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +174 -0
- data/lib/super_diff/csi/color_helper.rb +52 -0
- data/lib/super_diff/csi/eight_bit_color.rb +131 -0
- data/lib/super_diff/csi/eight_bit_sequence.rb +27 -0
- data/lib/super_diff/csi/four_bit_color.rb +80 -0
- data/lib/super_diff/csi/four_bit_sequence.rb +24 -0
- data/lib/super_diff/csi/reset_sequence.rb +9 -0
- data/lib/super_diff/csi/sequence.rb +22 -0
- data/lib/super_diff/csi/twenty_four_bit_color.rb +41 -0
- data/lib/super_diff/csi/twenty_four_bit_sequence.rb +27 -0
- data/lib/super_diff/csi.rb +29 -0
- data/lib/super_diff/diff_formatter.rb +37 -0
- data/lib/super_diff/diff_formatters/array.rb +21 -0
- data/lib/super_diff/diff_formatters/base.rb +37 -0
- data/lib/super_diff/diff_formatters/collection.rb +107 -0
- data/lib/super_diff/diff_formatters/hash.rb +34 -0
- data/lib/super_diff/diff_formatters/multi_line_string.rb +31 -0
- data/lib/super_diff/diff_formatters/object.rb +27 -0
- data/lib/super_diff/diff_formatters.rb +5 -0
- data/lib/super_diff/differ.rb +48 -0
- data/lib/super_diff/differs/array.rb +24 -0
- data/lib/super_diff/differs/base.rb +42 -0
- data/lib/super_diff/differs/empty.rb +13 -0
- data/lib/super_diff/differs/hash.rb +24 -0
- data/lib/super_diff/differs/multi_line_string.rb +27 -0
- data/lib/super_diff/differs/object.rb +68 -0
- data/lib/super_diff/differs.rb +5 -0
- data/lib/super_diff/equality_matcher.rb +45 -0
- data/lib/super_diff/equality_matchers/array.rb +44 -0
- data/lib/super_diff/equality_matchers/base.rb +42 -0
- data/lib/super_diff/equality_matchers/hash.rb +44 -0
- data/lib/super_diff/equality_matchers/multi_line_string.rb +44 -0
- data/lib/super_diff/equality_matchers/object.rb +18 -0
- data/lib/super_diff/equality_matchers/single_line_string.rb +28 -0
- data/lib/super_diff/equality_matchers.rb +5 -0
- data/lib/super_diff/errors.rb +20 -0
- data/lib/super_diff/helpers.rb +96 -0
- data/lib/super_diff/operation_sequences/array.rb +14 -0
- data/lib/super_diff/operation_sequences/base.rb +11 -0
- data/lib/super_diff/operation_sequences/hash.rb +14 -0
- data/lib/super_diff/operation_sequences/object.rb +14 -0
- data/lib/super_diff/operational_sequencer.rb +43 -0
- data/lib/super_diff/operational_sequencers/array.rb +127 -0
- data/lib/super_diff/operational_sequencers/base.rb +97 -0
- data/lib/super_diff/operational_sequencers/hash.rb +82 -0
- data/lib/super_diff/operational_sequencers/multi_line_string.rb +85 -0
- data/lib/super_diff/operational_sequencers/object.rb +96 -0
- data/lib/super_diff/operational_sequencers.rb +5 -0
- data/lib/super_diff/operations/binary_operation.rb +47 -0
- data/lib/super_diff/operations/unary_operation.rb +25 -0
- data/lib/super_diff/rspec/differ.rb +30 -0
- data/lib/super_diff/rspec/monkey_patches.rb +122 -0
- data/lib/super_diff/rspec.rb +19 -0
- data/lib/super_diff/value_inspection.rb +11 -0
- data/lib/super_diff/version.rb +3 -0
- data/lib/super_diff.rb +50 -0
- data/spec/examples.txt +46 -0
- data/spec/integration/rspec_spec.rb +261 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/color_helper.rb +49 -0
- data/spec/support/command_runner.rb +279 -0
- data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +76 -0
- data/spec/support/person.rb +23 -0
- data/spec/support/person_diff_formatter.rb +15 -0
- data/spec/support/person_operation_sequence.rb +14 -0
- data/spec/support/person_operational_sequencer.rb +19 -0
- data/spec/unit/equality_matcher_spec.rb +1233 -0
- data/super_diff.gemspec +23 -0
- metadata +153 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module EqualityMatchers
|
3
|
+
class MultiLineString < Base
|
4
|
+
def self.applies_to?(value)
|
5
|
+
value.is_a?(::String) && value.include?("\n")
|
6
|
+
end
|
7
|
+
|
8
|
+
def fail
|
9
|
+
<<~OUTPUT.strip
|
10
|
+
Differing strings.
|
11
|
+
|
12
|
+
#{
|
13
|
+
Helpers.style(
|
14
|
+
:deleted,
|
15
|
+
"Expected: #{Helpers.inspect_object(expected)}",
|
16
|
+
)
|
17
|
+
}
|
18
|
+
#{
|
19
|
+
Helpers.style(
|
20
|
+
:inserted,
|
21
|
+
" Actual: #{Helpers.inspect_object(actual)}",
|
22
|
+
)
|
23
|
+
}
|
24
|
+
|
25
|
+
Diff:
|
26
|
+
|
27
|
+
#{diff}
|
28
|
+
OUTPUT
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def diff
|
34
|
+
Differs::MultiLineString.call(
|
35
|
+
expected,
|
36
|
+
actual,
|
37
|
+
indent_level: 0,
|
38
|
+
extra_operational_sequencer_classes: extra_operational_sequencer_classes,
|
39
|
+
extra_diff_formatter_classes: extra_diff_formatter_classes,
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module EqualityMatchers
|
3
|
+
class Object < Base
|
4
|
+
def self.applies_to?(value)
|
5
|
+
value.class == ::Object
|
6
|
+
end
|
7
|
+
|
8
|
+
def fail
|
9
|
+
<<~OUTPUT.strip
|
10
|
+
Differing #{Helpers.plural_type_for(actual)}.
|
11
|
+
|
12
|
+
#{Helpers.style :deleted, "Expected: #{expected.inspect}"}
|
13
|
+
#{Helpers.style :inserted, " Actual: #{actual.inspect}"}
|
14
|
+
OUTPUT
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module EqualityMatchers
|
3
|
+
class SingleLineString < Base
|
4
|
+
def self.applies_to?(value)
|
5
|
+
value.class == ::String
|
6
|
+
end
|
7
|
+
|
8
|
+
def fail
|
9
|
+
<<~OUTPUT.strip
|
10
|
+
Differing strings.
|
11
|
+
|
12
|
+
#{
|
13
|
+
Helpers.style(
|
14
|
+
:deleted,
|
15
|
+
"Expected: #{Helpers.inspect_object(expected)}",
|
16
|
+
)
|
17
|
+
}
|
18
|
+
#{
|
19
|
+
Helpers.style(
|
20
|
+
:inserted,
|
21
|
+
" Actual: #{Helpers.inspect_object(actual)}",
|
22
|
+
)
|
23
|
+
}
|
24
|
+
OUTPUT
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
class NoOperationalSequencerAvailableError < StandardError
|
3
|
+
def self.create(expected, actual)
|
4
|
+
allocate.tap do |error|
|
5
|
+
error.expected = expected
|
6
|
+
error.actual = actual
|
7
|
+
error.__send__(:initialize)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :expected, :actual
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super(<<-MESSAGE)
|
15
|
+
There is no operational sequencer available to handle an "expected" value of
|
16
|
+
type #{expected.class} and an "actual" value of type #{actual.class}.
|
17
|
+
MESSAGE
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module Helpers
|
3
|
+
COLORS = { normal: :plain, inserted: :green, deleted: :red }.freeze
|
4
|
+
|
5
|
+
def self.style(style_name, text)
|
6
|
+
Csi::ColorHelper.public_send(COLORS.fetch(style_name), text)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.plural_type_for(value)
|
10
|
+
case value
|
11
|
+
when Numeric then "numbers"
|
12
|
+
when String then "strings"
|
13
|
+
when Symbol then "symbols"
|
14
|
+
else "objects"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.inspect_object(value_to_inspect, single_line: true)
|
19
|
+
case value_to_inspect
|
20
|
+
when ::Hash
|
21
|
+
inspect_hash(value_to_inspect, single_line: single_line)
|
22
|
+
when String
|
23
|
+
inspect_string(value_to_inspect)
|
24
|
+
when ::Array
|
25
|
+
inspect_array(value_to_inspect)
|
26
|
+
else
|
27
|
+
inspect_unclassified_object(value_to_inspect, single_line: single_line)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.inspect_hash(hash, single_line: true)
|
32
|
+
contents = hash.map do |key, value|
|
33
|
+
if key.is_a?(Symbol)
|
34
|
+
"#{key}: #{inspect_object(value)}"
|
35
|
+
else
|
36
|
+
"#{inspect_object(key)} => #{inspect_object(value)}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if single_line
|
41
|
+
["{", contents.join(", "), "}"].join(" ")
|
42
|
+
else
|
43
|
+
ValueInspection.new(
|
44
|
+
beginning: "{",
|
45
|
+
middle: contents.map.with_index do |line, index|
|
46
|
+
if index < contents.size - 1
|
47
|
+
line + ","
|
48
|
+
else
|
49
|
+
line
|
50
|
+
end
|
51
|
+
end,
|
52
|
+
end: "}",
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
private_class_method :inspect_hash
|
57
|
+
|
58
|
+
def self.inspect_string(string)
|
59
|
+
newline = "⏎"
|
60
|
+
string.gsub(/\r\n/, newline).gsub(/\n/, newline).inspect
|
61
|
+
end
|
62
|
+
private_class_method :inspect_string
|
63
|
+
|
64
|
+
def self.inspect_array(array)
|
65
|
+
"[" + array.map { |element| inspect_object(element) }.join(", ") + "]"
|
66
|
+
end
|
67
|
+
private_class_method :inspect_array
|
68
|
+
|
69
|
+
def self.inspect_unclassified_object(object, single_line: true)
|
70
|
+
if object.respond_to?(:attributes_for_super_diff)
|
71
|
+
attributes = object.attributes_for_super_diff
|
72
|
+
inspected_attributes =
|
73
|
+
attributes.map.with_index do |(key, value), index|
|
74
|
+
"#{key}: #{value.inspect}".tap do |line|
|
75
|
+
if index < attributes.size - 1
|
76
|
+
line << ","
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if single_line
|
82
|
+
"#<#{object.class} #{inspected_attributes.join(" ")}>"
|
83
|
+
else
|
84
|
+
ValueInspection.new(
|
85
|
+
beginning: "#<#{object.class} {",
|
86
|
+
middle: inspected_attributes,
|
87
|
+
end: "}>",
|
88
|
+
)
|
89
|
+
end
|
90
|
+
else
|
91
|
+
object.inspect
|
92
|
+
end
|
93
|
+
end
|
94
|
+
private_class_method :inspect_unclassified_object
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationSequences
|
3
|
+
class Array < Base
|
4
|
+
def to_diff(indent_level:, collection_prefix:, add_comma:)
|
5
|
+
DiffFormatters::Array.call(
|
6
|
+
self,
|
7
|
+
indent_level: indent_level,
|
8
|
+
collection_prefix: collection_prefix,
|
9
|
+
add_comma: add_comma,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationSequences
|
3
|
+
class Base < SimpleDelegator
|
4
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
5
|
+
def to_diff(indent_level:, add_comma:, collection_prefix: nil)
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
8
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationSequences
|
3
|
+
class Hash < Base
|
4
|
+
def to_diff(indent_level:, collection_prefix:, add_comma:)
|
5
|
+
DiffFormatters::Hash.call(
|
6
|
+
self,
|
7
|
+
indent_level: indent_level,
|
8
|
+
collection_prefix: collection_prefix,
|
9
|
+
add_comma: add_comma,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationSequences
|
3
|
+
class Object < Base
|
4
|
+
def to_diff(indent_level:, collection_prefix:, add_comma:)
|
5
|
+
DiffFormatters::Object.call(
|
6
|
+
self,
|
7
|
+
indent_level: indent_level,
|
8
|
+
collection_prefix: collection_prefix,
|
9
|
+
add_comma: add_comma,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
class OperationalSequencer
|
3
|
+
def self.call(*args)
|
4
|
+
new(*args).call
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(
|
8
|
+
expected:,
|
9
|
+
actual:,
|
10
|
+
extra_classes: [],
|
11
|
+
extra_diff_formatter_classes: []
|
12
|
+
)
|
13
|
+
@expected = expected
|
14
|
+
@actual = actual
|
15
|
+
@extra_classes = extra_classes
|
16
|
+
@extra_diff_formatter_classes = extra_diff_formatter_classes
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
if resolved_class
|
21
|
+
resolved_class.call(
|
22
|
+
expected: expected,
|
23
|
+
actual: actual,
|
24
|
+
extra_operational_sequencer_classes: extra_classes,
|
25
|
+
extra_diff_formatter_classes: extra_diff_formatter_classes,
|
26
|
+
)
|
27
|
+
else
|
28
|
+
raise NoOperationalSequencerAvailableError.create(expected, actual)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :expected, :actual, :extra_classes,
|
35
|
+
:extra_diff_formatter_classes
|
36
|
+
|
37
|
+
def resolved_class
|
38
|
+
(OperationalSequencers::DEFAULTS + extra_classes).find do |klass|
|
39
|
+
klass.applies_to?(expected) && klass.applies_to?(actual)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationalSequencers
|
3
|
+
class Array < Base
|
4
|
+
def self.applies_to?(value)
|
5
|
+
value.is_a?(::Array)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
super(*args)
|
10
|
+
|
11
|
+
@lcs_callbacks = LcsCallbacks.new(
|
12
|
+
expected: expected,
|
13
|
+
actual: actual,
|
14
|
+
extra_operational_sequencer_classes: extra_operational_sequencer_classes,
|
15
|
+
extra_diff_formatter_classes: extra_diff_formatter_classes,
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
Diff::LCS.traverse_balanced(expected, actual, lcs_callbacks)
|
21
|
+
lcs_callbacks.operations
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :lcs_callbacks
|
27
|
+
|
28
|
+
class LcsCallbacks
|
29
|
+
attr_reader :operations
|
30
|
+
|
31
|
+
def initialize(
|
32
|
+
expected:,
|
33
|
+
actual:,
|
34
|
+
extra_operational_sequencer_classes:,
|
35
|
+
extra_diff_formatter_classes:
|
36
|
+
)
|
37
|
+
@expected = expected
|
38
|
+
@actual = actual
|
39
|
+
@operations = OperationSequences::Array.new([])
|
40
|
+
@extra_operational_sequencer_classes =
|
41
|
+
extra_operational_sequencer_classes
|
42
|
+
@extra_diff_formatter_classes = extra_diff_formatter_classes
|
43
|
+
end
|
44
|
+
|
45
|
+
def match(event)
|
46
|
+
operations << ::SuperDiff::Operations::UnaryOperation.new(
|
47
|
+
name: :noop,
|
48
|
+
collection: actual,
|
49
|
+
key: event.new_position,
|
50
|
+
value: event.new_element,
|
51
|
+
index: event.new_position,
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def discard_a(event)
|
56
|
+
operations << ::SuperDiff::Operations::UnaryOperation.new(
|
57
|
+
name: :delete,
|
58
|
+
collection: expected,
|
59
|
+
key: event.old_position,
|
60
|
+
value: event.old_element,
|
61
|
+
index: event.old_position,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def discard_b(event)
|
66
|
+
operations << ::SuperDiff::Operations::UnaryOperation.new(
|
67
|
+
name: :insert,
|
68
|
+
collection: actual,
|
69
|
+
key: event.new_position,
|
70
|
+
value: event.new_element,
|
71
|
+
index: event.new_position,
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def change(event)
|
76
|
+
child_operations = sequence(event.old_element, event.new_element)
|
77
|
+
|
78
|
+
if child_operations
|
79
|
+
operations << ::SuperDiff::Operations::BinaryOperation.new(
|
80
|
+
name: :change,
|
81
|
+
left_collection: expected,
|
82
|
+
right_collection: actual,
|
83
|
+
left_key: event.old_position,
|
84
|
+
right_key: event.new_position,
|
85
|
+
left_value: event.old_element,
|
86
|
+
right_value: event.new_element,
|
87
|
+
left_index: event.old_position,
|
88
|
+
right_index: event.new_position,
|
89
|
+
child_operations: child_operations,
|
90
|
+
)
|
91
|
+
else
|
92
|
+
operations << Operations::UnaryOperation.new(
|
93
|
+
name: :delete,
|
94
|
+
collection: expected,
|
95
|
+
key: event.old_position,
|
96
|
+
value: event.old_element,
|
97
|
+
index: event.old_position,
|
98
|
+
)
|
99
|
+
operations << Operations::UnaryOperation.new(
|
100
|
+
name: :insert,
|
101
|
+
collection: actual,
|
102
|
+
key: event.new_position,
|
103
|
+
value: event.new_element,
|
104
|
+
index: event.new_position,
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
attr_reader :expected, :actual, :extra_operational_sequencer_classes,
|
112
|
+
:extra_diff_formatter_classes
|
113
|
+
|
114
|
+
def sequence(expected, actual)
|
115
|
+
OperationalSequencer.call(
|
116
|
+
expected: expected,
|
117
|
+
actual: actual,
|
118
|
+
extra_classes: extra_operational_sequencer_classes,
|
119
|
+
extra_diff_formatter_classes: extra_diff_formatter_classes,
|
120
|
+
)
|
121
|
+
rescue NoOperationalSequencerAvailableError
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationalSequencers
|
3
|
+
class Base
|
4
|
+
def self.applies_to?(_value)
|
5
|
+
raise NotImplementedError
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.call(*args)
|
9
|
+
new(*args).call
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
expected:,
|
14
|
+
actual:,
|
15
|
+
extra_operational_sequencer_classes: [],
|
16
|
+
extra_diff_formatter_classes: []
|
17
|
+
)
|
18
|
+
@expected = expected
|
19
|
+
@actual = actual
|
20
|
+
@extra_operational_sequencer_classes =
|
21
|
+
extra_operational_sequencer_classes
|
22
|
+
@extra_diff_formatter_classes = extra_diff_formatter_classes
|
23
|
+
end
|
24
|
+
|
25
|
+
def call
|
26
|
+
i = 0
|
27
|
+
operations = operation_sequence_class.new([])
|
28
|
+
|
29
|
+
while i < unary_operations.length
|
30
|
+
operation = unary_operations[i]
|
31
|
+
next_operation = unary_operations[i + 1]
|
32
|
+
child_operations = possible_comparison_of(operation, next_operation)
|
33
|
+
|
34
|
+
if child_operations
|
35
|
+
operations << Operations::BinaryOperation.new(
|
36
|
+
name: :change,
|
37
|
+
left_collection: operation.collection,
|
38
|
+
right_collection: next_operation.collection,
|
39
|
+
left_key: operation.key,
|
40
|
+
right_key: operation.key,
|
41
|
+
left_value: operation.collection[operation.key],
|
42
|
+
right_value: next_operation.collection[operation.key],
|
43
|
+
left_index: operation.index,
|
44
|
+
right_index: operation.index,
|
45
|
+
child_operations: child_operations,
|
46
|
+
)
|
47
|
+
i += 2
|
48
|
+
else
|
49
|
+
operations << operation
|
50
|
+
i += 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
operations
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def unary_operations
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
def operation_sequence_class
|
64
|
+
raise NotImplementedError
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
attr_reader :expected, :actual, :extra_operational_sequencer_classes,
|
70
|
+
:extra_diff_formatter_classes
|
71
|
+
|
72
|
+
def possible_comparison_of(operation, next_operation)
|
73
|
+
if should_compare?(operation, next_operation)
|
74
|
+
sequence(operation, next_operation)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def should_compare?(operation, next_operation)
|
79
|
+
next_operation &&
|
80
|
+
operation.name == :delete &&
|
81
|
+
next_operation.name == :insert &&
|
82
|
+
next_operation.index == operation.index
|
83
|
+
end
|
84
|
+
|
85
|
+
def sequence(operation, next_operation)
|
86
|
+
OperationalSequencer.call(
|
87
|
+
expected: operation.value,
|
88
|
+
actual: next_operation.value,
|
89
|
+
extra_classes: extra_operational_sequencer_classes,
|
90
|
+
extra_diff_formatter_classes: extra_diff_formatter_classes,
|
91
|
+
)
|
92
|
+
rescue NoOperationalSequencerAvailableError
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationalSequencers
|
3
|
+
class Hash < Base
|
4
|
+
def self.applies_to?(value)
|
5
|
+
value.is_a?(::Hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def unary_operations
|
11
|
+
all_keys.reduce([]) do |operations, key|
|
12
|
+
possibly_add_noop_to(operations, key)
|
13
|
+
possibly_add_delete_to(operations, key)
|
14
|
+
possibly_add_insert_to(operations, key)
|
15
|
+
operations
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def operation_sequence_class
|
20
|
+
OperationSequences::Hash
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def all_keys
|
26
|
+
(expected.keys | actual.keys)
|
27
|
+
end
|
28
|
+
|
29
|
+
def possibly_add_noop_to(operations, key)
|
30
|
+
if should_add_noop_operation?(key)
|
31
|
+
operations << Operations::UnaryOperation.new(
|
32
|
+
name: :noop,
|
33
|
+
collection: actual,
|
34
|
+
key: key,
|
35
|
+
index: all_keys.index(key),
|
36
|
+
value: actual[key],
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def should_add_noop_operation?(key)
|
42
|
+
expected.include?(key) &&
|
43
|
+
actual.include?(key) &&
|
44
|
+
expected[key] == actual[key]
|
45
|
+
end
|
46
|
+
|
47
|
+
def possibly_add_delete_to(operations, key)
|
48
|
+
if should_add_delete_operation?(key)
|
49
|
+
operations << Operations::UnaryOperation.new(
|
50
|
+
name: :delete,
|
51
|
+
collection: expected,
|
52
|
+
key: key,
|
53
|
+
index: all_keys.index(key),
|
54
|
+
value: expected[key],
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def should_add_delete_operation?(key)
|
60
|
+
expected.include?(key) &&
|
61
|
+
(!actual.include?(key) || expected[key] != actual[key])
|
62
|
+
end
|
63
|
+
|
64
|
+
def possibly_add_insert_to(operations, key)
|
65
|
+
if should_add_insert_operation?(key)
|
66
|
+
operations << Operations::UnaryOperation.new(
|
67
|
+
name: :insert,
|
68
|
+
collection: actual,
|
69
|
+
key: key,
|
70
|
+
index: all_keys.index(key),
|
71
|
+
value: actual[key],
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def should_add_insert_operation?(key)
|
77
|
+
!expected.include?(key) ||
|
78
|
+
(actual.include?(key) && expected[key] != actual[key])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|