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.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +104 -73
  3. data/lib/super_diff.rb +20 -11
  4. data/lib/super_diff/active_record.rb +21 -23
  5. data/lib/super_diff/active_record/diff_formatters/active_record_relation.rb +3 -3
  6. data/lib/super_diff/active_record/differs/active_record_relation.rb +3 -5
  7. data/lib/super_diff/active_record/monkey_patches.rb +9 -0
  8. data/lib/super_diff/active_record/object_inspection/inspectors/active_record_model.rb +32 -22
  9. data/lib/super_diff/active_record/object_inspection/inspectors/active_record_relation.rb +17 -7
  10. data/lib/super_diff/active_record/operation_tree_builders.rb +14 -0
  11. data/lib/super_diff/active_record/{operational_sequencers → operation_tree_builders}/active_record_model.rb +2 -2
  12. data/lib/super_diff/active_record/{operational_sequencers → operation_tree_builders}/active_record_relation.rb +4 -4
  13. data/lib/super_diff/active_record/{operation_sequences.rb → operation_trees.rb} +2 -2
  14. data/lib/super_diff/active_record/{operation_sequences → operation_trees}/active_record_relation.rb +2 -2
  15. data/lib/super_diff/active_support.rb +16 -19
  16. data/lib/super_diff/active_support/diff_formatters/hash_with_indifferent_access.rb +3 -3
  17. data/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb +3 -5
  18. data/lib/super_diff/active_support/object_inspection/inspectors/hash_with_indifferent_access.rb +17 -7
  19. data/lib/super_diff/active_support/operation_tree_builders.rb +10 -0
  20. data/lib/super_diff/active_support/{operational_sequencers → operation_tree_builders}/hash_with_indifferent_access.rb +2 -2
  21. data/lib/super_diff/active_support/{operation_sequences.rb → operation_trees.rb} +2 -2
  22. data/lib/super_diff/active_support/{operation_sequences → operation_trees}/hash_with_indifferent_access.rb +2 -2
  23. data/lib/super_diff/configuration.rb +60 -0
  24. data/lib/super_diff/csi.rb +4 -0
  25. data/lib/super_diff/diff_formatters.rb +3 -3
  26. data/lib/super_diff/diff_formatters/array.rb +3 -3
  27. data/lib/super_diff/diff_formatters/base.rb +3 -2
  28. data/lib/super_diff/diff_formatters/collection.rb +2 -2
  29. data/lib/super_diff/diff_formatters/custom_object.rb +3 -3
  30. data/lib/super_diff/diff_formatters/default_object.rb +6 -8
  31. data/lib/super_diff/diff_formatters/defaults.rb +10 -0
  32. data/lib/super_diff/diff_formatters/hash.rb +3 -3
  33. data/lib/super_diff/diff_formatters/main.rb +41 -0
  34. data/lib/super_diff/diff_formatters/multiline_string.rb +3 -3
  35. data/lib/super_diff/differs.rb +4 -11
  36. data/lib/super_diff/differs/array.rb +2 -11
  37. data/lib/super_diff/differs/base.rb +20 -3
  38. data/lib/super_diff/differs/custom_object.rb +2 -11
  39. data/lib/super_diff/differs/default_object.rb +2 -8
  40. data/lib/super_diff/differs/defaults.rb +12 -0
  41. data/lib/super_diff/differs/hash.rb +2 -11
  42. data/lib/super_diff/differs/main.rb +48 -0
  43. data/lib/super_diff/differs/multiline_string.rb +2 -14
  44. data/lib/super_diff/differs/time_like.rb +15 -0
  45. data/lib/super_diff/equality_matchers.rb +3 -9
  46. data/lib/super_diff/equality_matchers/array.rb +1 -7
  47. data/lib/super_diff/equality_matchers/base.rb +1 -1
  48. data/lib/super_diff/equality_matchers/default.rb +2 -8
  49. data/lib/super_diff/equality_matchers/defaults.rb +12 -0
  50. data/lib/super_diff/equality_matchers/hash.rb +1 -7
  51. data/lib/super_diff/equality_matchers/main.rb +21 -0
  52. data/lib/super_diff/equality_matchers/multiline_string.rb +1 -7
  53. data/lib/super_diff/errors.rb +16 -0
  54. data/lib/super_diff/errors/no_diff_formatter_available_error.rb +21 -0
  55. data/lib/super_diff/errors/no_differ_available_error.rb +24 -0
  56. data/lib/super_diff/errors/no_operational_sequencer_available_error.rb +22 -0
  57. data/lib/super_diff/implementation_checks.rb +19 -0
  58. data/lib/super_diff/object_inspection.rb +1 -10
  59. data/lib/super_diff/object_inspection/inspection_tree.rb +6 -2
  60. data/lib/super_diff/object_inspection/inspectors.rb +5 -1
  61. data/lib/super_diff/object_inspection/inspectors/array.rb +20 -10
  62. data/lib/super_diff/object_inspection/inspectors/base.rb +36 -0
  63. data/lib/super_diff/object_inspection/inspectors/custom_object.rb +24 -14
  64. data/lib/super_diff/object_inspection/inspectors/default_object.rb +44 -30
  65. data/lib/super_diff/object_inspection/inspectors/defaults.rb +15 -0
  66. data/lib/super_diff/object_inspection/inspectors/hash.rb +20 -10
  67. data/lib/super_diff/object_inspection/inspectors/main.rb +35 -0
  68. data/lib/super_diff/object_inspection/inspectors/primitive.rb +20 -5
  69. data/lib/super_diff/object_inspection/inspectors/string.rb +15 -5
  70. data/lib/super_diff/object_inspection/inspectors/time_like.rb +23 -0
  71. data/lib/super_diff/object_inspection/nodes/inspection.rb +9 -2
  72. data/lib/super_diff/operation_tree_builders.rb +18 -0
  73. data/lib/super_diff/{operational_sequencers → operation_tree_builders}/array.rb +38 -59
  74. data/lib/super_diff/operation_tree_builders/base.rb +98 -0
  75. data/lib/super_diff/{operational_sequencers → operation_tree_builders}/custom_object.rb +3 -3
  76. data/lib/super_diff/{operational_sequencers → operation_tree_builders}/default_object.rb +8 -3
  77. data/lib/super_diff/operation_tree_builders/defaults.rb +5 -0
  78. data/lib/super_diff/operation_tree_builders/hash.rb +226 -0
  79. data/lib/super_diff/operation_tree_builders/main.rb +42 -0
  80. data/lib/super_diff/{operational_sequencers → operation_tree_builders}/multiline_string.rb +3 -3
  81. data/lib/super_diff/operation_tree_builders/time_like.rb +34 -0
  82. data/lib/super_diff/operation_trees.rb +13 -0
  83. data/lib/super_diff/{operation_sequences → operation_trees}/array.rb +5 -1
  84. data/lib/super_diff/operation_trees/base.rb +31 -0
  85. data/lib/super_diff/{operation_sequences → operation_trees}/custom_object.rb +5 -1
  86. data/lib/super_diff/{operation_sequences → operation_trees}/default_object.rb +10 -8
  87. data/lib/super_diff/operation_trees/defaults.rb +5 -0
  88. data/lib/super_diff/{operation_sequences → operation_trees}/hash.rb +5 -1
  89. data/lib/super_diff/operation_trees/main.rb +35 -0
  90. data/lib/super_diff/operation_trees/multiline_string.rb +18 -0
  91. data/lib/super_diff/operations/unary_operation.rb +3 -0
  92. data/lib/super_diff/rspec.rb +54 -22
  93. data/lib/super_diff/rspec/augmented_matcher.rb +1 -1
  94. data/lib/super_diff/rspec/differ.rb +2 -17
  95. data/lib/super_diff/rspec/differs.rb +9 -3
  96. data/lib/super_diff/rspec/differs/collection_containing_exactly.rb +3 -8
  97. data/lib/super_diff/rspec/differs/collection_including.rb +18 -0
  98. data/lib/super_diff/rspec/differs/hash_including.rb +18 -0
  99. data/lib/super_diff/rspec/differs/object_having_attributes.rb +17 -0
  100. data/lib/super_diff/rspec/matcher_text_builders.rb +4 -0
  101. data/lib/super_diff/rspec/matcher_text_builders/be_predicate.rb +26 -7
  102. data/lib/super_diff/rspec/matcher_text_builders/have_predicate.rb +61 -0
  103. data/lib/super_diff/rspec/matcher_text_builders/match.rb +1 -1
  104. data/lib/super_diff/rspec/matcher_text_builders/raise_error.rb +13 -1
  105. data/lib/super_diff/rspec/matcher_text_builders/respond_to.rb +1 -1
  106. data/lib/super_diff/rspec/matcher_text_template.rb +1 -1
  107. data/lib/super_diff/rspec/monkey_patches.rb +226 -115
  108. data/lib/super_diff/rspec/object_inspection.rb +0 -1
  109. data/lib/super_diff/rspec/object_inspection/inspectors.rb +22 -6
  110. data/lib/super_diff/rspec/object_inspection/inspectors/collection_containing_exactly.rb +17 -8
  111. data/lib/super_diff/rspec/object_inspection/inspectors/collection_including.rb +28 -0
  112. data/lib/super_diff/rspec/object_inspection/inspectors/hash_including.rb +31 -0
  113. data/lib/super_diff/rspec/object_inspection/inspectors/instance_of.rb +23 -0
  114. data/lib/super_diff/rspec/object_inspection/inspectors/kind_of.rb +23 -0
  115. data/lib/super_diff/rspec/object_inspection/inspectors/object_having_attributes.rb +31 -0
  116. data/lib/super_diff/rspec/object_inspection/inspectors/primitive.rb +13 -0
  117. data/lib/super_diff/rspec/object_inspection/inspectors/value_within.rb +29 -0
  118. data/lib/super_diff/rspec/operation_tree_builders.rb +22 -0
  119. data/lib/super_diff/rspec/{operational_sequencers → operation_tree_builders}/collection_containing_exactly.rb +6 -6
  120. data/lib/super_diff/rspec/{operational_sequencers/partial_array.rb → operation_tree_builders/collection_including.rb} +4 -3
  121. data/lib/super_diff/rspec/operation_tree_builders/hash_including.rb +25 -0
  122. data/lib/super_diff/rspec/{operational_sequencers/partial_object.rb → operation_tree_builders/object_having_attributes.rb} +5 -11
  123. data/lib/super_diff/version.rb +1 -1
  124. data/spec/examples.txt +5 -350
  125. data/spec/integration/rails/active_record_spec.rb +1 -1
  126. data/spec/integration/rails/hash_with_indifferent_access_spec.rb +1 -1
  127. data/spec/integration/rspec/be_predicate_matcher_spec.rb +111 -59
  128. data/spec/integration/rspec/eq_matcher_spec.rb +1 -1
  129. data/spec/integration/rspec/have_attributes_matcher_spec.rb +354 -227
  130. data/spec/integration/rspec/have_predicate_matcher_spec.rb +484 -0
  131. data/spec/integration/rspec/include_matcher_spec.rb +2 -2
  132. data/spec/integration/rspec/match_array_matcher_spec.rb +372 -0
  133. data/spec/integration/rspec/match_matcher_spec.rb +8 -8
  134. data/spec/integration/rspec/raise_error_matcher_spec.rb +605 -226
  135. data/spec/integration/rspec/third_party_matcher_spec.rb +241 -0
  136. data/spec/integration/rspec/unhandled_errors_spec.rb +110 -58
  137. data/spec/spec_helper.rb +18 -7
  138. data/spec/support/command_runner.rb +3 -0
  139. data/spec/support/integration/helpers.rb +14 -90
  140. data/spec/support/integration/matchers.rb +143 -0
  141. data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +14 -29
  142. data/spec/support/integration/test_programs/base.rb +120 -0
  143. data/spec/support/integration/test_programs/plain.rb +13 -0
  144. data/spec/support/integration/test_programs/rspec_active_record.rb +17 -0
  145. data/spec/support/integration/test_programs/rspec_rails.rb +17 -0
  146. data/spec/support/models/active_record/person.rb +4 -11
  147. data/spec/support/models/active_record/query.rb +15 -0
  148. data/spec/support/models/active_record/shipping_address.rb +10 -14
  149. data/spec/support/object_id.rb +27 -0
  150. data/spec/support/ruby_versions.rb +4 -0
  151. data/spec/support/shared_examples/active_record.rb +71 -0
  152. data/spec/support/shared_examples/hash_with_indifferent_access.rb +724 -208
  153. data/spec/unit/{equality_matcher_spec.rb → equality_matchers/main_spec.rb} +165 -9
  154. data/spec/unit/object_inspection_spec.rb +94 -18
  155. data/spec/unit/rspec/matchers/have_predicate_spec.rb +21 -0
  156. data/spec/unit/rspec/matchers/match_array_spec.rb +11 -0
  157. data/spec/unit/rspec/matchers/raise_error_spec.rb +16 -0
  158. data/super_diff.gemspec +4 -6
  159. metadata +99 -82
  160. data/lib/super_diff/active_record/object_inspection/map_extension.rb +0 -18
  161. data/lib/super_diff/active_record/operational_sequencers.rb +0 -14
  162. data/lib/super_diff/active_support/object_inspection/map_extension.rb +0 -15
  163. data/lib/super_diff/active_support/operational_sequencers.rb +0 -10
  164. data/lib/super_diff/diff_formatter.rb +0 -32
  165. data/lib/super_diff/differ.rb +0 -51
  166. data/lib/super_diff/differs/time.rb +0 -24
  167. data/lib/super_diff/equality_matcher.rb +0 -32
  168. data/lib/super_diff/no_differ_available_error.rb +0 -22
  169. data/lib/super_diff/no_operational_sequencer_available_error.rb +0 -20
  170. data/lib/super_diff/object_inspection/inspector.rb +0 -27
  171. data/lib/super_diff/object_inspection/inspectors/time.rb +0 -13
  172. data/lib/super_diff/object_inspection/map.rb +0 -30
  173. data/lib/super_diff/operation_sequences.rb +0 -9
  174. data/lib/super_diff/operation_sequences/base.rb +0 -11
  175. data/lib/super_diff/operational_sequencer.rb +0 -48
  176. data/lib/super_diff/operational_sequencers.rb +0 -17
  177. data/lib/super_diff/operational_sequencers/base.rb +0 -89
  178. data/lib/super_diff/operational_sequencers/hash.rb +0 -85
  179. data/lib/super_diff/operational_sequencers/time_like.rb +0 -30
  180. data/lib/super_diff/rspec/configuration.rb +0 -31
  181. data/lib/super_diff/rspec/differs/partial_array.rb +0 -22
  182. data/lib/super_diff/rspec/differs/partial_hash.rb +0 -22
  183. data/lib/super_diff/rspec/differs/partial_object.rb +0 -22
  184. data/lib/super_diff/rspec/object_inspection/inspectors/partial_array.rb +0 -22
  185. data/lib/super_diff/rspec/object_inspection/inspectors/partial_hash.rb +0 -21
  186. data/lib/super_diff/rspec/object_inspection/inspectors/partial_object.rb +0 -21
  187. data/lib/super_diff/rspec/object_inspection/map_extension.rb +0 -23
  188. data/lib/super_diff/rspec/operational_sequencers.rb +0 -22
  189. data/lib/super_diff/rspec/operational_sequencers/partial_hash.rb +0 -32
@@ -1,5 +1,5 @@
1
1
  module SuperDiff
2
- module OperationalSequencers
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 build_operation_sequencer
10
+ def build_operation_tree
11
11
  # XXX This assumes that `expected` and `actual` are the same
12
- OperationSequences::CustomObject.new([], value_class: expected.class)
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 OperationalSequencers
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 build_operation_sequencer
25
+ def build_operation_tree
26
26
  # XXX This assumes that `expected` and `actual` are the same
27
- OperationSequences::DefaultObject.new([], value_class: expected.class)
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,5 @@
1
+ module SuperDiff
2
+ module OperationTreeBuilders
3
+ DEFAULTS = [Array, Hash, CustomObject].freeze
4
+ end
5
+ end
@@ -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 OperationalSequencers
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 build_operation_sequencer
34
- OperationSequences::Array.new([])
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"
@@ -1,6 +1,10 @@
1
1
  module SuperDiff
2
- module OperationSequences
2
+ module OperationTrees
3
3
  class Array < Base
4
+ def self.applies_to?(value)
5
+ value.is_a?(::Array)
6
+ end
7
+
4
8
  def to_diff(indent_level:, collection_prefix:, add_comma:)
5
9
  DiffFormatters::Array.call(
6
10
  self,
@@ -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 OperationSequences
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 OperationSequences
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, collection_prefix: nil)
11
- DiffFormatters::DefaultObject.call(
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