super_diff 0.3.0 → 0.5.1
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 +4 -4
- data/README.md +104 -73
- data/lib/super_diff.rb +20 -11
- data/lib/super_diff/active_record.rb +21 -23
- data/lib/super_diff/active_record/diff_formatters/active_record_relation.rb +3 -3
- data/lib/super_diff/active_record/differs/active_record_relation.rb +3 -5
- data/lib/super_diff/active_record/monkey_patches.rb +9 -0
- data/lib/super_diff/active_record/object_inspection/inspectors/active_record_model.rb +32 -22
- data/lib/super_diff/active_record/object_inspection/inspectors/active_record_relation.rb +17 -7
- data/lib/super_diff/active_record/operation_tree_builders.rb +14 -0
- data/lib/super_diff/active_record/{operational_sequencers → operation_tree_builders}/active_record_model.rb +2 -2
- data/lib/super_diff/active_record/{operational_sequencers → operation_tree_builders}/active_record_relation.rb +4 -4
- data/lib/super_diff/active_record/{operation_sequences.rb → operation_trees.rb} +2 -2
- data/lib/super_diff/active_record/{operation_sequences → operation_trees}/active_record_relation.rb +2 -2
- data/lib/super_diff/active_support.rb +16 -19
- data/lib/super_diff/active_support/diff_formatters/hash_with_indifferent_access.rb +3 -3
- data/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb +3 -5
- data/lib/super_diff/active_support/object_inspection/inspectors/hash_with_indifferent_access.rb +17 -7
- data/lib/super_diff/active_support/operation_tree_builders.rb +10 -0
- data/lib/super_diff/active_support/{operational_sequencers → operation_tree_builders}/hash_with_indifferent_access.rb +2 -2
- data/lib/super_diff/active_support/{operation_sequences.rb → operation_trees.rb} +2 -2
- data/lib/super_diff/active_support/{operation_sequences → operation_trees}/hash_with_indifferent_access.rb +2 -2
- data/lib/super_diff/configuration.rb +60 -0
- data/lib/super_diff/csi.rb +4 -0
- data/lib/super_diff/diff_formatters.rb +3 -3
- data/lib/super_diff/diff_formatters/array.rb +3 -3
- data/lib/super_diff/diff_formatters/base.rb +3 -2
- data/lib/super_diff/diff_formatters/collection.rb +2 -2
- data/lib/super_diff/diff_formatters/custom_object.rb +3 -3
- data/lib/super_diff/diff_formatters/default_object.rb +6 -8
- data/lib/super_diff/diff_formatters/defaults.rb +10 -0
- data/lib/super_diff/diff_formatters/hash.rb +3 -3
- data/lib/super_diff/diff_formatters/main.rb +41 -0
- data/lib/super_diff/diff_formatters/multiline_string.rb +3 -3
- data/lib/super_diff/differs.rb +4 -11
- data/lib/super_diff/differs/array.rb +2 -11
- data/lib/super_diff/differs/base.rb +20 -3
- data/lib/super_diff/differs/custom_object.rb +2 -11
- data/lib/super_diff/differs/default_object.rb +2 -8
- data/lib/super_diff/differs/defaults.rb +12 -0
- data/lib/super_diff/differs/hash.rb +2 -11
- data/lib/super_diff/differs/main.rb +48 -0
- data/lib/super_diff/differs/multiline_string.rb +2 -14
- data/lib/super_diff/differs/time_like.rb +15 -0
- data/lib/super_diff/equality_matchers.rb +3 -9
- data/lib/super_diff/equality_matchers/array.rb +1 -7
- data/lib/super_diff/equality_matchers/base.rb +1 -1
- data/lib/super_diff/equality_matchers/default.rb +2 -8
- data/lib/super_diff/equality_matchers/defaults.rb +12 -0
- data/lib/super_diff/equality_matchers/hash.rb +1 -7
- data/lib/super_diff/equality_matchers/main.rb +21 -0
- data/lib/super_diff/equality_matchers/multiline_string.rb +1 -7
- data/lib/super_diff/errors.rb +16 -0
- data/lib/super_diff/errors/no_diff_formatter_available_error.rb +21 -0
- data/lib/super_diff/errors/no_differ_available_error.rb +24 -0
- data/lib/super_diff/errors/no_operational_sequencer_available_error.rb +22 -0
- data/lib/super_diff/implementation_checks.rb +19 -0
- data/lib/super_diff/object_inspection.rb +1 -10
- data/lib/super_diff/object_inspection/inspection_tree.rb +6 -2
- data/lib/super_diff/object_inspection/inspectors.rb +5 -1
- data/lib/super_diff/object_inspection/inspectors/array.rb +20 -10
- data/lib/super_diff/object_inspection/inspectors/base.rb +36 -0
- data/lib/super_diff/object_inspection/inspectors/custom_object.rb +24 -14
- data/lib/super_diff/object_inspection/inspectors/default_object.rb +44 -30
- data/lib/super_diff/object_inspection/inspectors/defaults.rb +15 -0
- data/lib/super_diff/object_inspection/inspectors/hash.rb +20 -10
- data/lib/super_diff/object_inspection/inspectors/main.rb +35 -0
- data/lib/super_diff/object_inspection/inspectors/primitive.rb +20 -5
- data/lib/super_diff/object_inspection/inspectors/string.rb +15 -5
- data/lib/super_diff/object_inspection/inspectors/time_like.rb +23 -0
- data/lib/super_diff/object_inspection/nodes/inspection.rb +9 -2
- data/lib/super_diff/operation_tree_builders.rb +18 -0
- data/lib/super_diff/{operational_sequencers → operation_tree_builders}/array.rb +38 -59
- data/lib/super_diff/operation_tree_builders/base.rb +98 -0
- data/lib/super_diff/{operational_sequencers → operation_tree_builders}/custom_object.rb +3 -3
- data/lib/super_diff/{operational_sequencers → operation_tree_builders}/default_object.rb +8 -3
- data/lib/super_diff/operation_tree_builders/defaults.rb +5 -0
- data/lib/super_diff/operation_tree_builders/hash.rb +226 -0
- data/lib/super_diff/operation_tree_builders/main.rb +42 -0
- data/lib/super_diff/{operational_sequencers → operation_tree_builders}/multiline_string.rb +3 -3
- data/lib/super_diff/operation_tree_builders/time_like.rb +34 -0
- data/lib/super_diff/operation_trees.rb +13 -0
- data/lib/super_diff/{operation_sequences → operation_trees}/array.rb +5 -1
- data/lib/super_diff/operation_trees/base.rb +31 -0
- data/lib/super_diff/{operation_sequences → operation_trees}/custom_object.rb +5 -1
- data/lib/super_diff/{operation_sequences → operation_trees}/default_object.rb +10 -8
- data/lib/super_diff/operation_trees/defaults.rb +5 -0
- data/lib/super_diff/{operation_sequences → operation_trees}/hash.rb +5 -1
- data/lib/super_diff/operation_trees/main.rb +35 -0
- data/lib/super_diff/operation_trees/multiline_string.rb +18 -0
- data/lib/super_diff/operations/unary_operation.rb +3 -0
- data/lib/super_diff/rspec.rb +54 -22
- data/lib/super_diff/rspec/augmented_matcher.rb +1 -1
- data/lib/super_diff/rspec/differ.rb +2 -17
- data/lib/super_diff/rspec/differs.rb +9 -3
- data/lib/super_diff/rspec/differs/collection_containing_exactly.rb +3 -8
- data/lib/super_diff/rspec/differs/collection_including.rb +18 -0
- data/lib/super_diff/rspec/differs/hash_including.rb +18 -0
- data/lib/super_diff/rspec/differs/object_having_attributes.rb +17 -0
- data/lib/super_diff/rspec/matcher_text_builders.rb +4 -0
- data/lib/super_diff/rspec/matcher_text_builders/be_predicate.rb +26 -7
- data/lib/super_diff/rspec/matcher_text_builders/have_predicate.rb +61 -0
- data/lib/super_diff/rspec/matcher_text_builders/match.rb +1 -1
- data/lib/super_diff/rspec/matcher_text_builders/raise_error.rb +13 -1
- data/lib/super_diff/rspec/matcher_text_builders/respond_to.rb +1 -1
- data/lib/super_diff/rspec/matcher_text_template.rb +1 -1
- data/lib/super_diff/rspec/monkey_patches.rb +226 -115
- data/lib/super_diff/rspec/object_inspection.rb +0 -1
- data/lib/super_diff/rspec/object_inspection/inspectors.rb +22 -6
- data/lib/super_diff/rspec/object_inspection/inspectors/collection_containing_exactly.rb +17 -8
- data/lib/super_diff/rspec/object_inspection/inspectors/collection_including.rb +28 -0
- data/lib/super_diff/rspec/object_inspection/inspectors/hash_including.rb +31 -0
- data/lib/super_diff/rspec/object_inspection/inspectors/instance_of.rb +23 -0
- data/lib/super_diff/rspec/object_inspection/inspectors/kind_of.rb +23 -0
- data/lib/super_diff/rspec/object_inspection/inspectors/object_having_attributes.rb +31 -0
- data/lib/super_diff/rspec/object_inspection/inspectors/primitive.rb +13 -0
- data/lib/super_diff/rspec/object_inspection/inspectors/value_within.rb +29 -0
- data/lib/super_diff/rspec/operation_tree_builders.rb +22 -0
- data/lib/super_diff/rspec/{operational_sequencers → operation_tree_builders}/collection_containing_exactly.rb +6 -6
- data/lib/super_diff/rspec/{operational_sequencers/partial_array.rb → operation_tree_builders/collection_including.rb} +4 -3
- data/lib/super_diff/rspec/operation_tree_builders/hash_including.rb +25 -0
- data/lib/super_diff/rspec/{operational_sequencers/partial_object.rb → operation_tree_builders/object_having_attributes.rb} +5 -11
- data/lib/super_diff/version.rb +1 -1
- data/spec/examples.txt +5 -350
- data/spec/integration/rails/active_record_spec.rb +1 -1
- data/spec/integration/rails/hash_with_indifferent_access_spec.rb +1 -1
- data/spec/integration/rspec/be_predicate_matcher_spec.rb +111 -59
- data/spec/integration/rspec/eq_matcher_spec.rb +1 -1
- data/spec/integration/rspec/have_attributes_matcher_spec.rb +354 -227
- data/spec/integration/rspec/have_predicate_matcher_spec.rb +484 -0
- data/spec/integration/rspec/include_matcher_spec.rb +2 -2
- data/spec/integration/rspec/match_array_matcher_spec.rb +372 -0
- data/spec/integration/rspec/match_matcher_spec.rb +8 -8
- data/spec/integration/rspec/raise_error_matcher_spec.rb +605 -226
- data/spec/integration/rspec/third_party_matcher_spec.rb +241 -0
- data/spec/integration/rspec/unhandled_errors_spec.rb +110 -58
- data/spec/spec_helper.rb +18 -7
- data/spec/support/command_runner.rb +3 -0
- data/spec/support/integration/helpers.rb +14 -90
- data/spec/support/integration/matchers.rb +143 -0
- data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +14 -29
- data/spec/support/integration/test_programs/base.rb +120 -0
- data/spec/support/integration/test_programs/plain.rb +13 -0
- data/spec/support/integration/test_programs/rspec_active_record.rb +17 -0
- data/spec/support/integration/test_programs/rspec_rails.rb +17 -0
- data/spec/support/models/active_record/person.rb +4 -11
- data/spec/support/models/active_record/query.rb +15 -0
- data/spec/support/models/active_record/shipping_address.rb +10 -14
- data/spec/support/object_id.rb +27 -0
- data/spec/support/ruby_versions.rb +4 -0
- data/spec/support/shared_examples/active_record.rb +71 -0
- data/spec/support/shared_examples/hash_with_indifferent_access.rb +724 -208
- data/spec/unit/{equality_matcher_spec.rb → equality_matchers/main_spec.rb} +165 -9
- data/spec/unit/object_inspection_spec.rb +94 -18
- data/spec/unit/rspec/matchers/have_predicate_spec.rb +21 -0
- data/spec/unit/rspec/matchers/match_array_spec.rb +11 -0
- data/spec/unit/rspec/matchers/raise_error_spec.rb +16 -0
- data/super_diff.gemspec +4 -6
- metadata +99 -82
- data/lib/super_diff/active_record/object_inspection/map_extension.rb +0 -18
- data/lib/super_diff/active_record/operational_sequencers.rb +0 -14
- data/lib/super_diff/active_support/object_inspection/map_extension.rb +0 -15
- data/lib/super_diff/active_support/operational_sequencers.rb +0 -10
- data/lib/super_diff/diff_formatter.rb +0 -32
- data/lib/super_diff/differ.rb +0 -51
- data/lib/super_diff/differs/time.rb +0 -24
- data/lib/super_diff/equality_matcher.rb +0 -32
- data/lib/super_diff/no_differ_available_error.rb +0 -22
- data/lib/super_diff/no_operational_sequencer_available_error.rb +0 -20
- data/lib/super_diff/object_inspection/inspector.rb +0 -27
- data/lib/super_diff/object_inspection/inspectors/time.rb +0 -13
- data/lib/super_diff/object_inspection/map.rb +0 -30
- data/lib/super_diff/operation_sequences.rb +0 -9
- data/lib/super_diff/operation_sequences/base.rb +0 -11
- data/lib/super_diff/operational_sequencer.rb +0 -48
- data/lib/super_diff/operational_sequencers.rb +0 -17
- data/lib/super_diff/operational_sequencers/base.rb +0 -89
- data/lib/super_diff/operational_sequencers/hash.rb +0 -85
- data/lib/super_diff/operational_sequencers/time_like.rb +0 -30
- data/lib/super_diff/rspec/configuration.rb +0 -31
- data/lib/super_diff/rspec/differs/partial_array.rb +0 -22
- data/lib/super_diff/rspec/differs/partial_hash.rb +0 -22
- data/lib/super_diff/rspec/differs/partial_object.rb +0 -22
- data/lib/super_diff/rspec/object_inspection/inspectors/partial_array.rb +0 -22
- data/lib/super_diff/rspec/object_inspection/inspectors/partial_hash.rb +0 -21
- data/lib/super_diff/rspec/object_inspection/inspectors/partial_object.rb +0 -21
- data/lib/super_diff/rspec/object_inspection/map_extension.rb +0 -23
- data/lib/super_diff/rspec/operational_sequencers.rb +0 -22
- data/lib/super_diff/rspec/operational_sequencers/partial_hash.rb +0 -32
@@ -1,5 +1,5 @@
|
|
1
1
|
module SuperDiff
|
2
|
-
module
|
2
|
+
module OperationTreeBuilders
|
3
3
|
class CustomObject < DefaultObject
|
4
4
|
def self.applies_to?(expected, actual)
|
5
5
|
expected.class == actual.class &&
|
@@ -7,9 +7,9 @@ module SuperDiff
|
|
7
7
|
actual.respond_to?(:attributes_for_super_diff)
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
10
|
+
def build_operation_tree
|
11
11
|
# XXX This assumes that `expected` and `actual` are the same
|
12
|
-
|
12
|
+
OperationTrees::CustomObject.new([], value_class: expected.class)
|
13
13
|
end
|
14
14
|
|
15
15
|
def attribute_names
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module SuperDiff
|
2
|
-
module
|
2
|
+
module OperationTreeBuilders
|
3
3
|
class DefaultObject < Base
|
4
4
|
def self.applies_to?(_expected, _actual)
|
5
5
|
true
|
@@ -22,9 +22,14 @@ module SuperDiff
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
25
|
+
def build_operation_tree
|
26
26
|
# XXX This assumes that `expected` and `actual` are the same
|
27
|
-
|
27
|
+
# TODO: Does this need to be find_operation_tree_for?
|
28
|
+
OperationTrees::DefaultObject.new([], value_class: expected.class)
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_operation_tree_for(value)
|
32
|
+
OperationTrees::Main.call(value)
|
28
33
|
end
|
29
34
|
|
30
35
|
def attribute_names
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationTreeBuilders
|
3
|
+
class Hash < Base
|
4
|
+
def self.applies_to?(expected, actual)
|
5
|
+
expected.is_a?(::Hash) && actual.is_a?(::Hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def unary_operations
|
11
|
+
unary_operations_using_variant_of_patience_algorithm
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_operation_tree
|
15
|
+
OperationTrees::Hash.new([])
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def unary_operations_using_variant_of_patience_algorithm
|
21
|
+
operations = []
|
22
|
+
aks, eks = actual.keys, expected.keys
|
23
|
+
previous_ei, ei = nil, 0
|
24
|
+
ai = 0
|
25
|
+
|
26
|
+
# When diffing a hash, we're more interested in the 'actual' version
|
27
|
+
# than the 'expected' version, because that's the ultimate truth.
|
28
|
+
# Therefore, the diff is presented from the perspective of the 'actual'
|
29
|
+
# hash, and we start off by looping over it.
|
30
|
+
while ai < aks.size
|
31
|
+
ak = aks[ai]
|
32
|
+
av, ev = actual[ak], expected[ak]
|
33
|
+
# While we iterate over 'actual' in order, we jump all over
|
34
|
+
# 'expected', trying to match up its keys with the keys in 'actual' as
|
35
|
+
# much as possible.
|
36
|
+
ei = eks.index(ak)
|
37
|
+
|
38
|
+
if should_add_noop_operation?(ak)
|
39
|
+
# (If we're here, it probably means that the key we're pointing to
|
40
|
+
# in the 'actual' and 'expected' hashes have the same value.)
|
41
|
+
|
42
|
+
if ei && previous_ei && (ei - previous_ei) > 1
|
43
|
+
# If we've jumped from one operation in the 'expected' hash to
|
44
|
+
# another operation later in 'expected' (due to the fact that the
|
45
|
+
# 'expected' hash is in a different order than 'actual'), collect
|
46
|
+
# any delete operations in between and add them to our operations
|
47
|
+
# array as deletes before adding the noop. If we don't do this
|
48
|
+
# now, then those deletes will disappear. (Again, we are mainly
|
49
|
+
# iterating over 'actual', so this is the only way to catch all of
|
50
|
+
# the keys in 'expected'.)
|
51
|
+
(previous_ei + 1).upto(ei - 1) do |ei2|
|
52
|
+
ek = eks[ei2]
|
53
|
+
ev2, av2 = expected[ek], actual[ek]
|
54
|
+
|
55
|
+
if (
|
56
|
+
(!actual.include?(ek) || ev != av2) &&
|
57
|
+
operations.none? { |operation|
|
58
|
+
[:delete, :noop].include?(operation.name) &&
|
59
|
+
operation.key == ek
|
60
|
+
}
|
61
|
+
)
|
62
|
+
operations << Operations::UnaryOperation.new(
|
63
|
+
name: :delete,
|
64
|
+
collection: expected,
|
65
|
+
key: ek,
|
66
|
+
value: ev2,
|
67
|
+
index: ei2,
|
68
|
+
index_in_collection: ei2,
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
operations << Operations::UnaryOperation.new(
|
75
|
+
name: :noop,
|
76
|
+
collection: actual,
|
77
|
+
key: ak,
|
78
|
+
value: av,
|
79
|
+
index: ai,
|
80
|
+
index_in_collection: ai,
|
81
|
+
)
|
82
|
+
else
|
83
|
+
# (If we're here, it probably means that the key in 'actual' isn't
|
84
|
+
# present in 'expected' or the values don't match.)
|
85
|
+
|
86
|
+
if (
|
87
|
+
(operations.empty? || operations.last.name == :noop) &&
|
88
|
+
(ai == 0 || eks.include?(aks[ai - 1]))
|
89
|
+
)
|
90
|
+
# If we go from a match in the last iteration to a missing or
|
91
|
+
# extra key in this one, or we're at the first key in 'actual' and
|
92
|
+
# it's missing or extra, look for deletes in the 'expected' hash
|
93
|
+
# and add them to our list of operations before we add the
|
94
|
+
# inserts. In most cases we will accomplish this by backtracking a
|
95
|
+
# bit to the key in 'expected' that matched the key in 'actual' we
|
96
|
+
# processed in the previous iteration (or just the first key in
|
97
|
+
# 'expected' if this is the first key in 'actual'), and then
|
98
|
+
# iterating from there through 'expected' until we reach the end
|
99
|
+
# or we hit some other condition (see below).
|
100
|
+
|
101
|
+
start_index =
|
102
|
+
if ai > 0
|
103
|
+
eks.index(aks[ai - 1]) + 1
|
104
|
+
else
|
105
|
+
0
|
106
|
+
end
|
107
|
+
|
108
|
+
start_index.upto(eks.size - 1) do |ei2|
|
109
|
+
ek = eks[ei2]
|
110
|
+
ev, av2 = expected[ek], actual[ek]
|
111
|
+
|
112
|
+
if actual.include?(ek) && ev == av2
|
113
|
+
# If the key in 'expected' we've landed on happens to be a
|
114
|
+
# match in 'actual', then stop, because it's going to be
|
115
|
+
# handled in some future iteration of the 'actual' loop.
|
116
|
+
break
|
117
|
+
elsif (
|
118
|
+
aks[ai + 1 .. -1].any? { |k|
|
119
|
+
expected.include?(k) && expected[k] != actual[k]
|
120
|
+
}
|
121
|
+
)
|
122
|
+
# While we backtracked a bit to iterate over 'expected', we
|
123
|
+
# now have to look ahead. If we will end up encountering a
|
124
|
+
# insert that matches this delete later, stop and go back to
|
125
|
+
# iterating over 'actual'. This is because the delete we would
|
126
|
+
# have added now will be added later when we encounter the
|
127
|
+
# associated insert, so we don't want to add it twice.
|
128
|
+
break
|
129
|
+
else
|
130
|
+
operations << Operations::UnaryOperation.new(
|
131
|
+
name: :delete,
|
132
|
+
collection: expected,
|
133
|
+
key: ek,
|
134
|
+
value: ev,
|
135
|
+
index: ei2,
|
136
|
+
index_in_collection: ei2,
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
if ek == ak && ev != av
|
141
|
+
# If we're pointing to the same key in 'expected' as in
|
142
|
+
# 'actual', but with different values, go ahead and add an
|
143
|
+
# insert now to accompany the delete added above. That way
|
144
|
+
# they appear together, which will be easier to read.
|
145
|
+
operations << Operations::UnaryOperation.new(
|
146
|
+
name: :insert,
|
147
|
+
collection: actual,
|
148
|
+
key: ak,
|
149
|
+
value: av,
|
150
|
+
index: ai,
|
151
|
+
index_in_collection: ai,
|
152
|
+
)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
if (
|
158
|
+
expected.include?(ak) &&
|
159
|
+
ev != av &&
|
160
|
+
operations.none? { |op| op.name == :delete && op.key == ak }
|
161
|
+
)
|
162
|
+
# If we're here, it means that we didn't encounter any delete
|
163
|
+
# operations above for whatever reason and so we need to add a
|
164
|
+
# delete to represent the fact that the value for this key has
|
165
|
+
# changed.
|
166
|
+
operations << Operations::UnaryOperation.new(
|
167
|
+
name: :delete,
|
168
|
+
collection: expected,
|
169
|
+
key: ak,
|
170
|
+
value: expected[ak],
|
171
|
+
index: ei,
|
172
|
+
index_in_collection: ei,
|
173
|
+
)
|
174
|
+
end
|
175
|
+
|
176
|
+
if operations.none? { |op| op.name == :insert && op.key == ak }
|
177
|
+
# If we're here, it means that we didn't encounter any insert
|
178
|
+
# operations above. Since we already handled delete, the only
|
179
|
+
# alternative is that this key must not exist in 'expected', so
|
180
|
+
# we need to add an insert.
|
181
|
+
operations << Operations::UnaryOperation.new(
|
182
|
+
name: :insert,
|
183
|
+
collection: actual,
|
184
|
+
key: ak,
|
185
|
+
value: av,
|
186
|
+
index: ai,
|
187
|
+
index_in_collection: ai,
|
188
|
+
)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
ai += 1
|
193
|
+
previous_ei = ei
|
194
|
+
end
|
195
|
+
|
196
|
+
# The last thing to do is this: if there are keys in 'expected' that
|
197
|
+
# aren't in 'actual', and they aren't associated with any inserts to
|
198
|
+
# where they would have been added above, tack those deletes onto the
|
199
|
+
# end of our operations array.
|
200
|
+
(eks - aks - operations.map(&:key)).each do |ek|
|
201
|
+
ei = eks.index(ek)
|
202
|
+
ev = expected[ek]
|
203
|
+
|
204
|
+
operations << Operations::UnaryOperation.new(
|
205
|
+
name: :delete,
|
206
|
+
collection: expected,
|
207
|
+
key: ek,
|
208
|
+
value: ev,
|
209
|
+
index: ei,
|
210
|
+
index_in_collection: ei,
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
operations
|
215
|
+
end
|
216
|
+
|
217
|
+
def should_add_noop_operation?(key)
|
218
|
+
expected.include?(key) && expected[key] == actual[key]
|
219
|
+
end
|
220
|
+
|
221
|
+
def all_keys
|
222
|
+
actual.keys | expected.keys
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationTreeBuilders
|
3
|
+
class Main
|
4
|
+
extend AttrExtras.mixin
|
5
|
+
|
6
|
+
method_object [:expected!, :actual!, :all_or_nothing!]
|
7
|
+
|
8
|
+
def call
|
9
|
+
if resolved_class
|
10
|
+
resolved_class.call(
|
11
|
+
expected: expected,
|
12
|
+
actual: actual,
|
13
|
+
)
|
14
|
+
elsif all_or_nothing?
|
15
|
+
raise NoOperationTreeBuilderAvailableError.create(expected, actual)
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_query :all_or_nothing?
|
24
|
+
|
25
|
+
def resolved_class
|
26
|
+
available_classes.find { |klass| klass.applies_to?(expected, actual) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def available_classes
|
30
|
+
classes =
|
31
|
+
SuperDiff.configuration.extra_operation_tree_builder_classes +
|
32
|
+
DEFAULTS
|
33
|
+
|
34
|
+
if all_or_nothing?
|
35
|
+
classes + [DefaultObject]
|
36
|
+
else
|
37
|
+
classes
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module SuperDiff
|
2
|
-
module
|
2
|
+
module OperationTreeBuilders
|
3
3
|
class MultilineString < Base
|
4
4
|
def self.applies_to?(expected, actual)
|
5
5
|
expected.is_a?(::String) && actual.is_a?(::String) &&
|
@@ -30,8 +30,8 @@ module SuperDiff
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
def
|
34
|
-
|
33
|
+
def build_operation_tree
|
34
|
+
OperationTrees::MultilineString.new([])
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationTreeBuilders
|
3
|
+
class TimeLike < CustomObject
|
4
|
+
def self.applies_to?(expected, actual)
|
5
|
+
SuperDiff.time_like?(expected) && SuperDiff.time_like?(actual)
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def attribute_names
|
11
|
+
base = [
|
12
|
+
"year",
|
13
|
+
"month",
|
14
|
+
"day",
|
15
|
+
"hour",
|
16
|
+
"min",
|
17
|
+
"sec",
|
18
|
+
"nsec",
|
19
|
+
"zone",
|
20
|
+
"gmt_offset",
|
21
|
+
]
|
22
|
+
|
23
|
+
# If timezones are different, also show a normalized timestamp at the
|
24
|
+
# end of the diff to help visualize why they are different moments in
|
25
|
+
# time.
|
26
|
+
if actual.zone != expected.zone
|
27
|
+
base + ["utc"]
|
28
|
+
else
|
29
|
+
base
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationTrees
|
3
|
+
autoload :Array, "super_diff/operation_trees/array"
|
4
|
+
autoload :Base, "super_diff/operation_trees/base"
|
5
|
+
autoload :CustomObject, "super_diff/operation_trees/custom_object"
|
6
|
+
autoload :DefaultObject, "super_diff/operation_trees/default_object"
|
7
|
+
autoload :Hash, "super_diff/operation_trees/hash"
|
8
|
+
autoload :Main, "super_diff/operation_trees/main"
|
9
|
+
autoload :MultilineString, "super_diff/operation_trees/multiline_string"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require "super_diff/operation_trees/defaults"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationTrees
|
3
|
+
class Base < SimpleDelegator
|
4
|
+
def self.applies_to?(_value)
|
5
|
+
unimplemented_class_method!
|
6
|
+
end
|
7
|
+
|
8
|
+
extend ImplementationChecks
|
9
|
+
|
10
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
11
|
+
def to_diff(indent_level:, add_comma: false, collection_prefix: nil)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
15
|
+
|
16
|
+
def pretty_print(pp)
|
17
|
+
pp.text "#{self.class.name}.new(["
|
18
|
+
pp.group_sub do
|
19
|
+
pp.nest(2) do
|
20
|
+
pp.breakable
|
21
|
+
pp.seplist(self) do |value|
|
22
|
+
pp.pp value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
pp.breakable
|
27
|
+
pp.text("])")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module SuperDiff
|
2
|
-
module
|
2
|
+
module OperationTrees
|
3
3
|
class CustomObject < DefaultObject
|
4
|
+
def self.applies_to?(value)
|
5
|
+
value.respond_to?(:attributes_for_super_diff)
|
6
|
+
end
|
7
|
+
|
4
8
|
def to_diff(indent_level:, add_comma: false, collection_prefix: nil)
|
5
9
|
DiffFormatters::CustomObject.call(
|
6
10
|
self,
|
@@ -1,25 +1,27 @@
|
|
1
1
|
module SuperDiff
|
2
|
-
module
|
2
|
+
module OperationTrees
|
3
3
|
class DefaultObject < Base
|
4
|
+
def self.applies_to?(_value)
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :value_class
|
9
|
+
|
10
|
+
# TODO: Default this to collection.class?
|
4
11
|
def initialize(collection, value_class:)
|
5
12
|
super(collection)
|
6
13
|
|
7
14
|
@value_class = value_class
|
8
15
|
end
|
9
16
|
|
10
|
-
def to_diff(indent_level:, add_comma: false,
|
11
|
-
DiffFormatters::
|
17
|
+
def to_diff(indent_level:, add_comma: false, **_rest)
|
18
|
+
DiffFormatters::Main.call(
|
12
19
|
self,
|
13
20
|
indent_level: indent_level,
|
14
|
-
collection_prefix: collection_prefix,
|
15
21
|
add_comma: add_comma,
|
16
22
|
value_class: value_class,
|
17
23
|
)
|
18
24
|
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
attr_reader :value_class
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|