super_diff 0.4.2 → 0.5.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 +4 -4
- data/README.md +21 -8
- data/lib/super_diff.rb +20 -11
- data/lib/super_diff/active_record.rb +20 -24
- 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/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/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 +1 -7
- 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_sequences → operation_trees}/base.rb +7 -1
- 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 +45 -13
- 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/collection_containing_exactly.rb +2 -7
- data/lib/super_diff/rspec/differs/collection_including.rb +2 -7
- data/lib/super_diff/rspec/differs/hash_including.rb +2 -7
- data/lib/super_diff/rspec/differs/object_having_attributes.rb +2 -7
- data/lib/super_diff/rspec/matcher_text_builders/match.rb +1 -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/object_inspection.rb +0 -1
- data/lib/super_diff/rspec/object_inspection/inspectors.rb +16 -0
- 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 +15 -9
- data/lib/super_diff/rspec/object_inspection/inspectors/hash_including.rb +20 -10
- 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 +20 -11
- 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 +5 -5
- data/lib/super_diff/rspec/{operational_sequencers → operation_tree_builders}/collection_including.rb +2 -2
- data/lib/super_diff/rspec/{operational_sequencers → operation_tree_builders}/hash_including.rb +3 -11
- data/lib/super_diff/rspec/{operational_sequencers → operation_tree_builders}/object_having_attributes.rb +4 -8
- data/lib/super_diff/version.rb +1 -1
- data/spec/examples.txt +397 -393
- data/spec/integration/rspec/have_attributes_matcher_spec.rb +354 -227
- data/spec/integration/rspec/include_matcher_spec.rb +2 -2
- data/spec/integration/rspec/unhandled_errors_spec.rb +68 -12
- data/spec/support/command_runner.rb +3 -0
- data/spec/support/integration/helpers.rb +12 -96
- 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/shipping_address.rb +10 -14
- data/spec/support/object_id.rb +6 -5
- data/spec/tmp/integration_spec.rb +15 -0
- data/spec/unit/{equality_matcher_spec.rb → equality_matchers/main_spec.rb} +157 -1
- data/spec/unit/object_inspection_spec.rb +77 -1
- data/super_diff.gemspec +0 -1
- metadata +72 -64
- 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/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/object_inspection/map_extension.rb +0 -23
- data/lib/super_diff/rspec/operational_sequencers.rb +0 -22
@@ -2,9 +2,16 @@ module SuperDiff
|
|
2
2
|
module ObjectInspection
|
3
3
|
module Nodes
|
4
4
|
class Inspection < Base
|
5
|
-
def evaluate(
|
5
|
+
def evaluate(object, indent_level:, as_single_line:)
|
6
|
+
value =
|
7
|
+
if block
|
8
|
+
tree.evaluate_block(object, &block)
|
9
|
+
else
|
10
|
+
immediate_value
|
11
|
+
end
|
12
|
+
|
6
13
|
SuperDiff::ObjectInspection.inspect(
|
7
|
-
|
14
|
+
value,
|
8
15
|
indent_level: indent_level,
|
9
16
|
as_single_line: as_single_line,
|
10
17
|
)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationTreeBuilders
|
3
|
+
autoload :Array, "super_diff/operation_tree_builders/array"
|
4
|
+
autoload :Base, "super_diff/operation_tree_builders/base"
|
5
|
+
autoload :CustomObject, "super_diff/operation_tree_builders/custom_object"
|
6
|
+
autoload :DefaultObject, "super_diff/operation_tree_builders/default_object"
|
7
|
+
autoload :Hash, "super_diff/operation_tree_builders/hash"
|
8
|
+
autoload :Main, "super_diff/operation_tree_builders/main"
|
9
|
+
# TODO: Where is this used?
|
10
|
+
autoload(
|
11
|
+
:MultilineString,
|
12
|
+
"super_diff/operation_tree_builders/multiline_string",
|
13
|
+
)
|
14
|
+
autoload :TimeLike, "super_diff/operation_tree_builders/time_like"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require "super_diff/operation_tree_builders/defaults"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module SuperDiff
|
2
|
-
module
|
2
|
+
module OperationTreeBuilders
|
3
3
|
class Array < Base
|
4
4
|
def self.applies_to?(expected, actual)
|
5
5
|
expected.is_a?(::Array) && actual.is_a?(::Array)
|
@@ -7,72 +7,40 @@ module SuperDiff
|
|
7
7
|
|
8
8
|
def call
|
9
9
|
Diff::LCS.traverse_balanced(expected, actual, lcs_callbacks)
|
10
|
-
|
10
|
+
operation_tree
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
15
|
def lcs_callbacks
|
16
16
|
@_lcs_callbacks ||= LcsCallbacks.new(
|
17
|
-
|
17
|
+
operation_tree: operation_tree,
|
18
18
|
expected: expected,
|
19
19
|
actual: actual,
|
20
|
-
extra_operational_sequencer_classes: extra_operational_sequencer_classes,
|
21
|
-
extra_diff_formatter_classes: extra_diff_formatter_classes,
|
22
20
|
sequence: method(:sequence),
|
23
21
|
)
|
24
22
|
end
|
25
23
|
|
26
|
-
def
|
27
|
-
@
|
24
|
+
def operation_tree
|
25
|
+
@_operation_tree ||= OperationTrees::Array.new([])
|
28
26
|
end
|
29
27
|
|
30
28
|
class LcsCallbacks
|
31
29
|
extend AttrExtras.mixin
|
32
30
|
|
33
|
-
pattr_initialize
|
34
|
-
|
35
|
-
:operations!,
|
36
|
-
:expected!,
|
37
|
-
:actual!,
|
38
|
-
:extra_operational_sequencer_classes!,
|
39
|
-
:extra_diff_formatter_classes!,
|
40
|
-
:sequence!
|
41
|
-
],
|
42
|
-
)
|
43
|
-
public :operations
|
31
|
+
pattr_initialize [:operation_tree!, :expected!, :actual!, :sequence!]
|
32
|
+
public :operation_tree
|
44
33
|
|
45
34
|
def match(event)
|
46
|
-
|
47
|
-
name: :noop,
|
48
|
-
collection: actual,
|
49
|
-
key: event.new_position,
|
50
|
-
value: event.new_element,
|
51
|
-
index: event.new_position,
|
52
|
-
index_in_collection: actual.index(event.new_element),
|
53
|
-
)
|
35
|
+
add_noop_operation(event)
|
54
36
|
end
|
55
37
|
|
56
38
|
def discard_a(event)
|
57
|
-
|
58
|
-
name: :delete,
|
59
|
-
collection: expected,
|
60
|
-
key: event.old_position,
|
61
|
-
value: event.old_element,
|
62
|
-
index: event.old_position,
|
63
|
-
index_in_collection: expected.index(event.old_element),
|
64
|
-
)
|
39
|
+
add_delete_operation(event)
|
65
40
|
end
|
66
41
|
|
67
42
|
def discard_b(event)
|
68
|
-
|
69
|
-
name: :insert,
|
70
|
-
collection: actual,
|
71
|
-
key: event.new_position,
|
72
|
-
value: event.new_element,
|
73
|
-
index: event.new_position,
|
74
|
-
index_in_collection: actual.index(event.new_element),
|
75
|
-
)
|
43
|
+
add_insert_operation(event)
|
76
44
|
end
|
77
45
|
|
78
46
|
def change(event)
|
@@ -88,23 +56,8 @@ module SuperDiff
|
|
88
56
|
|
89
57
|
private
|
90
58
|
|
91
|
-
def add_change_operation(event, child_operations)
|
92
|
-
operations << ::SuperDiff::Operations::BinaryOperation.new(
|
93
|
-
name: :change,
|
94
|
-
left_collection: expected,
|
95
|
-
right_collection: actual,
|
96
|
-
left_key: event.old_position,
|
97
|
-
right_key: event.new_position,
|
98
|
-
left_value: event.old_element,
|
99
|
-
right_value: event.new_element,
|
100
|
-
left_index: event.old_position,
|
101
|
-
right_index: event.new_position,
|
102
|
-
child_operations: child_operations,
|
103
|
-
)
|
104
|
-
end
|
105
|
-
|
106
59
|
def add_delete_operation(event)
|
107
|
-
|
60
|
+
operation_tree << Operations::UnaryOperation.new(
|
108
61
|
name: :delete,
|
109
62
|
collection: expected,
|
110
63
|
key: event.old_position,
|
@@ -115,7 +68,7 @@ module SuperDiff
|
|
115
68
|
end
|
116
69
|
|
117
70
|
def add_insert_operation(event)
|
118
|
-
|
71
|
+
operation_tree << Operations::UnaryOperation.new(
|
119
72
|
name: :insert,
|
120
73
|
collection: actual,
|
121
74
|
key: event.new_position,
|
@@ -124,6 +77,32 @@ module SuperDiff
|
|
124
77
|
index_in_collection: actual.index(event.new_element),
|
125
78
|
)
|
126
79
|
end
|
80
|
+
|
81
|
+
def add_noop_operation(event)
|
82
|
+
operation_tree << Operations::UnaryOperation.new(
|
83
|
+
name: :noop,
|
84
|
+
collection: actual,
|
85
|
+
key: event.new_position,
|
86
|
+
value: event.new_element,
|
87
|
+
index: event.new_position,
|
88
|
+
index_in_collection: actual.index(event.new_element),
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_change_operation(event, child_operations)
|
93
|
+
operation_tree << Operations::BinaryOperation.new(
|
94
|
+
name: :change,
|
95
|
+
left_collection: expected,
|
96
|
+
right_collection: actual,
|
97
|
+
left_key: event.old_position,
|
98
|
+
right_key: event.new_position,
|
99
|
+
left_value: event.old_element,
|
100
|
+
right_value: event.new_element,
|
101
|
+
left_index: event.old_position,
|
102
|
+
right_index: event.new_position,
|
103
|
+
child_operations: child_operations,
|
104
|
+
)
|
105
|
+
end
|
127
106
|
end
|
128
107
|
end
|
129
108
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module SuperDiff
|
2
|
+
module OperationTreeBuilders
|
3
|
+
class Base
|
4
|
+
def self.applies_to?(_expected, _actual)
|
5
|
+
raise NotImplementedError
|
6
|
+
end
|
7
|
+
|
8
|
+
extend AttrExtras.mixin
|
9
|
+
include ImplementationChecks
|
10
|
+
|
11
|
+
method_object [:expected!, :actual!]
|
12
|
+
|
13
|
+
def call
|
14
|
+
operation_tree
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def unary_operations
|
20
|
+
unimplemented_instance_method!
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_operation_tree
|
24
|
+
unimplemented_instance_method!
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def operation_tree
|
30
|
+
unary_operations = self.unary_operations
|
31
|
+
operation_tree = build_operation_tree
|
32
|
+
unmatched_delete_operations = []
|
33
|
+
|
34
|
+
unary_operations.each_with_index do |operation, index|
|
35
|
+
if (
|
36
|
+
operation.name == :insert &&
|
37
|
+
(delete_operation = unmatched_delete_operations.find { |op| op.key == operation.key }) &&
|
38
|
+
(insert_operation = operation)
|
39
|
+
)
|
40
|
+
unmatched_delete_operations.delete(delete_operation)
|
41
|
+
|
42
|
+
if (child_operations = possible_comparison_of(
|
43
|
+
delete_operation,
|
44
|
+
insert_operation,
|
45
|
+
))
|
46
|
+
operation_tree.delete(delete_operation)
|
47
|
+
operation_tree << Operations::BinaryOperation.new(
|
48
|
+
name: :change,
|
49
|
+
left_collection: delete_operation.collection,
|
50
|
+
right_collection: insert_operation.collection,
|
51
|
+
left_key: delete_operation.key,
|
52
|
+
right_key: insert_operation.key,
|
53
|
+
left_value: delete_operation.collection[operation.key],
|
54
|
+
right_value: insert_operation.collection[operation.key],
|
55
|
+
left_index: delete_operation.index_in_collection,
|
56
|
+
right_index: insert_operation.index_in_collection,
|
57
|
+
child_operations: child_operations,
|
58
|
+
)
|
59
|
+
else
|
60
|
+
operation_tree << insert_operation
|
61
|
+
end
|
62
|
+
else
|
63
|
+
if operation.name == :delete
|
64
|
+
unmatched_delete_operations << operation
|
65
|
+
end
|
66
|
+
|
67
|
+
operation_tree << operation
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
operation_tree
|
72
|
+
end
|
73
|
+
|
74
|
+
def possible_comparison_of(operation, next_operation)
|
75
|
+
if should_compare?(operation, next_operation)
|
76
|
+
sequence(operation.value, next_operation.value)
|
77
|
+
else
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def should_compare?(operation, next_operation)
|
83
|
+
next_operation &&
|
84
|
+
operation.name == :delete &&
|
85
|
+
next_operation.name == :insert &&
|
86
|
+
next_operation.key == operation.key
|
87
|
+
end
|
88
|
+
|
89
|
+
def sequence(expected, actual)
|
90
|
+
OperationTreeBuilders::Main.call(
|
91
|
+
expected: expected,
|
92
|
+
actual: actual,
|
93
|
+
all_or_nothing: false,
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -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
|