super_diff 0.11.0 → 0.12.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 (231) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -166
  3. data/lib/super_diff/active_record/differs/active_record_relation.rb +1 -1
  4. data/lib/super_diff/active_record/inspection_tree_builders/active_record_model.rb +57 -0
  5. data/lib/super_diff/active_record/inspection_tree_builders/active_record_relation.rb +34 -0
  6. data/lib/super_diff/active_record/inspection_tree_builders.rb +14 -0
  7. data/lib/super_diff/active_record/monkey_patches.rb +6 -3
  8. data/lib/super_diff/active_record/object_inspection.rb +16 -4
  9. data/lib/super_diff/active_record/operation_tree_builders/active_record_model.rb +6 -2
  10. data/lib/super_diff/active_record/operation_tree_builders/active_record_relation.rb +1 -1
  11. data/lib/super_diff/active_record/operation_tree_flatteners/active_record_relation.rb +1 -1
  12. data/lib/super_diff/active_record/operation_trees/active_record_relation.rb +1 -1
  13. data/lib/super_diff/active_record.rb +12 -16
  14. data/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb +1 -1
  15. data/lib/super_diff/active_support/inspection_tree_builders/hash_with_indifferent_access.rb +44 -0
  16. data/lib/super_diff/active_support/inspection_tree_builders/ordered_options.rb +44 -0
  17. data/lib/super_diff/active_support/inspection_tree_builders.rb +14 -0
  18. data/lib/super_diff/active_support/object_inspection.rb +16 -4
  19. data/lib/super_diff/active_support/operation_tree_builders/hash_with_indifferent_access.rb +1 -1
  20. data/lib/super_diff/active_support/operation_tree_flatteners/hash_with_indifferent_access.rb +1 -1
  21. data/lib/super_diff/active_support/operation_trees/hash_with_indifferent_access.rb +1 -1
  22. data/lib/super_diff/active_support.rb +11 -15
  23. data/lib/super_diff/basic/diff_formatters/collection.rb +135 -0
  24. data/lib/super_diff/basic/diff_formatters/multiline_string.rb +34 -0
  25. data/lib/super_diff/basic/diff_formatters.rb +11 -0
  26. data/lib/super_diff/basic/differs/array.rb +17 -0
  27. data/lib/super_diff/basic/differs/custom_object.rb +19 -0
  28. data/lib/super_diff/basic/differs/date_like.rb +17 -0
  29. data/lib/super_diff/basic/differs/default_object.rb +24 -0
  30. data/lib/super_diff/basic/differs/hash.rb +17 -0
  31. data/lib/super_diff/basic/differs/multiline_string.rb +18 -0
  32. data/lib/super_diff/basic/differs/time_like.rb +17 -0
  33. data/lib/super_diff/basic/differs.rb +24 -0
  34. data/lib/super_diff/{object_inspection → basic}/inspection_tree_builders/array.rb +3 -3
  35. data/lib/super_diff/{object_inspection → basic}/inspection_tree_builders/custom_object.rb +3 -3
  36. data/lib/super_diff/{object_inspection → basic}/inspection_tree_builders/date_like.rb +3 -3
  37. data/lib/super_diff/{object_inspection → basic}/inspection_tree_builders/default_object.rb +5 -7
  38. data/lib/super_diff/{object_inspection → basic}/inspection_tree_builders/hash.rb +3 -3
  39. data/lib/super_diff/{object_inspection → basic}/inspection_tree_builders/primitive.rb +3 -3
  40. data/lib/super_diff/{object_inspection → basic}/inspection_tree_builders/time_like.rb +3 -3
  41. data/lib/super_diff/basic/inspection_tree_builders.rb +20 -0
  42. data/lib/super_diff/basic/operation_tree_builders/array.rb +111 -0
  43. data/lib/super_diff/basic/operation_tree_builders/custom_object.rb +42 -0
  44. data/lib/super_diff/basic/operation_tree_builders/date_like.rb +17 -0
  45. data/lib/super_diff/basic/operation_tree_builders/default_object.rb +117 -0
  46. data/lib/super_diff/basic/operation_tree_builders/hash.rb +222 -0
  47. data/lib/super_diff/basic/operation_tree_builders/multiline_string.rb +90 -0
  48. data/lib/super_diff/basic/operation_tree_builders/time_like.rb +26 -0
  49. data/lib/super_diff/basic/operation_tree_builders.rb +34 -0
  50. data/lib/super_diff/basic/operation_tree_flatteners/array.rb +17 -0
  51. data/lib/super_diff/basic/operation_tree_flatteners/collection.rb +140 -0
  52. data/lib/super_diff/basic/operation_tree_flatteners/custom_object.rb +30 -0
  53. data/lib/super_diff/basic/operation_tree_flatteners/default_object.rb +32 -0
  54. data/lib/super_diff/basic/operation_tree_flatteners/hash.rb +35 -0
  55. data/lib/super_diff/basic/operation_tree_flatteners/multiline_string.rb +20 -0
  56. data/lib/super_diff/basic/operation_tree_flatteners.rb +24 -0
  57. data/lib/super_diff/basic/operation_trees/array.rb +17 -0
  58. data/lib/super_diff/basic/operation_trees/custom_object.rb +17 -0
  59. data/lib/super_diff/basic/operation_trees/default_object.rb +42 -0
  60. data/lib/super_diff/basic/operation_trees/hash.rb +17 -0
  61. data/lib/super_diff/basic/operation_trees/multiline_string.rb +17 -0
  62. data/lib/super_diff/basic/operation_trees.rb +25 -0
  63. data/lib/super_diff/basic.rb +48 -0
  64. data/lib/super_diff/{differs/base.rb → core/abstract_differ.rb} +2 -2
  65. data/lib/super_diff/core/abstract_inspection_tree_builder.rb +26 -0
  66. data/lib/super_diff/{operation_trees/base.rb → core/abstract_operation_tree.rb} +6 -2
  67. data/lib/super_diff/{operation_tree_builders/base.rb → core/abstract_operation_tree_builder.rb} +4 -8
  68. data/lib/super_diff/{operation_tree_flatteners/base.rb → core/abstract_operation_tree_flattener.rb} +2 -2
  69. data/lib/super_diff/{operations → core}/binary_operation.rb +1 -1
  70. data/lib/super_diff/core/colorized_document_extensions.rb +20 -0
  71. data/lib/super_diff/core/configuration.rb +192 -0
  72. data/lib/super_diff/core/differ_dispatcher.rb +31 -0
  73. data/lib/super_diff/core/gem_version.rb +47 -0
  74. data/lib/super_diff/core/helpers.rb +88 -0
  75. data/lib/super_diff/core/implementation_checks.rb +21 -0
  76. data/lib/super_diff/{object_inspection → core}/inspection_tree.rb +7 -6
  77. data/lib/super_diff/core/inspection_tree_builder_dispatcher.rb +23 -0
  78. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/as_lines_when_rendering_to_lines.rb +9 -3
  79. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/as_prefix_when_rendering_to_lines.rb +3 -3
  80. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/as_prelude_when_rendering_to_lines.rb +3 -3
  81. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/as_single_line.rb +3 -3
  82. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/base.rb +2 -2
  83. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/inspection.rb +7 -11
  84. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/nesting.rb +2 -2
  85. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/only_when.rb +2 -2
  86. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/text.rb +2 -2
  87. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/when_empty.rb +2 -2
  88. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/when_non_empty.rb +2 -2
  89. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/when_rendering_to_lines.rb +2 -2
  90. data/lib/super_diff/{object_inspection/nodes → core/inspection_tree_nodes}/when_rendering_to_string.rb +2 -2
  91. data/lib/super_diff/core/inspection_tree_nodes.rb +55 -0
  92. data/lib/super_diff/core/line.rb +85 -0
  93. data/lib/super_diff/{errors → core}/no_differ_available_error.rb +1 -1
  94. data/lib/super_diff/core/no_inspection_tree_builder_available_error.rb +21 -0
  95. data/lib/super_diff/core/no_operation_tree_available_error.rb +20 -0
  96. data/lib/super_diff/core/no_operation_tree_builder_available_error.rb +24 -0
  97. data/lib/super_diff/{operation_tree_builders/main.rb → core/operation_tree_builder_dispatcher.rb} +9 -13
  98. data/lib/super_diff/core/operation_tree_finder.rb +27 -0
  99. data/lib/super_diff/core/prefix_for_next_inspection_tree_node.rb +6 -0
  100. data/lib/super_diff/core/prelude_for_next_inspection_tree_node.rb +6 -0
  101. data/lib/super_diff/core/recursion_guard.rb +52 -0
  102. data/lib/super_diff/core/tiered_lines.rb +6 -0
  103. data/lib/super_diff/core/tiered_lines_elider.rb +472 -0
  104. data/lib/super_diff/core/tiered_lines_formatter.rb +77 -0
  105. data/lib/super_diff/{operations → core}/unary_operation.rb +1 -1
  106. data/lib/super_diff/core.rb +69 -0
  107. data/lib/super_diff/differs.rb +19 -12
  108. data/lib/super_diff/equality_matchers/array.rb +3 -3
  109. data/lib/super_diff/equality_matchers/default.rb +8 -3
  110. data/lib/super_diff/equality_matchers/hash.rb +3 -3
  111. data/lib/super_diff/equality_matchers/multiline_string.rb +3 -3
  112. data/lib/super_diff/equality_matchers/primitive.rb +3 -3
  113. data/lib/super_diff/equality_matchers/singleline_string.rb +2 -2
  114. data/lib/super_diff/errors.rb +12 -8
  115. data/lib/super_diff/object_inspection.rb +63 -14
  116. data/lib/super_diff/operation_tree_builders.rb +19 -15
  117. data/lib/super_diff/operation_tree_flatteners.rb +19 -16
  118. data/lib/super_diff/operation_trees.rb +19 -9
  119. data/lib/super_diff/operations.rb +12 -2
  120. data/lib/super_diff/rspec/augmented_matcher.rb +1 -1
  121. data/lib/super_diff/rspec/differ.rb +4 -5
  122. data/lib/super_diff/rspec/differs/collection_containing_exactly.rb +1 -1
  123. data/lib/super_diff/rspec/differs/collection_including.rb +1 -1
  124. data/lib/super_diff/rspec/differs/hash_including.rb +1 -1
  125. data/lib/super_diff/rspec/differs/object_having_attributes.rb +1 -1
  126. data/lib/super_diff/rspec/inspection_tree_builders/collection_containing_exactly.rb +34 -0
  127. data/lib/super_diff/rspec/inspection_tree_builders/collection_including.rb +40 -0
  128. data/lib/super_diff/rspec/inspection_tree_builders/double.rb +100 -0
  129. data/lib/super_diff/rspec/inspection_tree_builders/generic_describable_matcher.rb +17 -0
  130. data/lib/super_diff/rspec/inspection_tree_builders/hash_including.rb +40 -0
  131. data/lib/super_diff/rspec/inspection_tree_builders/instance_of.rb +25 -0
  132. data/lib/super_diff/rspec/inspection_tree_builders/kind_of.rb +25 -0
  133. data/lib/super_diff/rspec/inspection_tree_builders/object_having_attributes.rb +34 -0
  134. data/lib/super_diff/rspec/inspection_tree_builders/primitive.rb +9 -0
  135. data/lib/super_diff/rspec/inspection_tree_builders/value_within.rb +30 -0
  136. data/lib/super_diff/rspec/inspection_tree_builders.rb +40 -0
  137. data/lib/super_diff/rspec/object_inspection.rb +14 -4
  138. data/lib/super_diff/rspec/operation_tree_builders/collection_containing_exactly.rb +4 -4
  139. data/lib/super_diff/rspec/operation_tree_builders/collection_including.rb +1 -1
  140. data/lib/super_diff/rspec/operation_tree_builders/hash_including.rb +1 -1
  141. data/lib/super_diff/rspec/operation_tree_builders/object_having_attributes.rb +2 -2
  142. data/lib/super_diff/rspec.rb +20 -18
  143. data/lib/super_diff/version.rb +1 -1
  144. data/lib/super_diff.rb +69 -21
  145. data/spec/examples.txt +704 -543
  146. data/spec/support/integration/helpers.rb +4 -1
  147. data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +1 -1
  148. data/spec/support/models/active_record/person.rb +8 -1
  149. data/spec/support/shared_examples/active_record.rb +5 -5
  150. data/spec/support/unit/helpers.rb +12 -1
  151. data/spec/support/unit/matchers/be_deprecated_in_favor_of.rb +39 -0
  152. data/spec/unit/active_record/object_inspection_spec.rb +5 -5
  153. data/spec/unit/{operation_tree_flatteners → basic/operation_tree_flatteners}/array_spec.rb +8 -8
  154. data/spec/unit/{operation_tree_flatteners → basic/operation_tree_flatteners}/custom_object_spec.rb +9 -9
  155. data/spec/unit/{operation_tree_flatteners → basic/operation_tree_flatteners}/default_object_spec.rb +9 -9
  156. data/spec/unit/{operation_tree_flatteners → basic/operation_tree_flatteners}/hash_spec.rb +8 -8
  157. data/spec/unit/{operation_tree_flatteners → basic/operation_tree_flatteners}/multiline_string_spec.rb +4 -4
  158. data/spec/unit/{helpers_spec.rb → core/helpers_spec.rb} +2 -2
  159. data/spec/unit/{tiered_lines_elider_spec.rb → core/tiered_lines_elider_spec.rb} +2 -2
  160. data/spec/unit/{tiered_lines_formatter_spec.rb → core/tiered_lines_formatter_spec.rb} +1 -1
  161. data/spec/unit/deprecations_spec.rb +176 -0
  162. data/spec/unit/equality_matchers/main_spec.rb +5 -5
  163. data/super_diff.gemspec +6 -0
  164. metadata +127 -115
  165. data/lib/super_diff/active_record/object_inspection/inspection_tree_builders/active_record_model.rb +0 -51
  166. data/lib/super_diff/active_record/object_inspection/inspection_tree_builders/active_record_relation.rb +0 -36
  167. data/lib/super_diff/active_record/object_inspection/inspection_tree_builders.rb +0 -16
  168. data/lib/super_diff/active_support/object_inspection/inspection_tree_builders/hash_with_indifferent_access.rb +0 -46
  169. data/lib/super_diff/active_support/object_inspection/inspection_tree_builders/ordered_options.rb +0 -46
  170. data/lib/super_diff/active_support/object_inspection/inspection_tree_builders.rb +0 -16
  171. data/lib/super_diff/colorized_document_extensions.rb +0 -18
  172. data/lib/super_diff/configuration.rb +0 -149
  173. data/lib/super_diff/diff_formatters/collection.rb +0 -132
  174. data/lib/super_diff/diff_formatters/multiline_string.rb +0 -31
  175. data/lib/super_diff/differs/array.rb +0 -15
  176. data/lib/super_diff/differs/custom_object.rb +0 -17
  177. data/lib/super_diff/differs/date_like.rb +0 -15
  178. data/lib/super_diff/differs/default_object.rb +0 -19
  179. data/lib/super_diff/differs/defaults.rb +0 -13
  180. data/lib/super_diff/differs/empty.rb +0 -13
  181. data/lib/super_diff/differs/hash.rb +0 -15
  182. data/lib/super_diff/differs/main.rb +0 -31
  183. data/lib/super_diff/differs/multiline_string.rb +0 -16
  184. data/lib/super_diff/differs/time_like.rb +0 -15
  185. data/lib/super_diff/gem_version.rb +0 -45
  186. data/lib/super_diff/helpers.rb +0 -86
  187. data/lib/super_diff/implementation_checks.rb +0 -19
  188. data/lib/super_diff/line.rb +0 -83
  189. data/lib/super_diff/object_inspection/inspection_tree_builders/base.rb +0 -27
  190. data/lib/super_diff/object_inspection/inspection_tree_builders/defaults.rb +0 -15
  191. data/lib/super_diff/object_inspection/inspection_tree_builders/main.rb +0 -30
  192. data/lib/super_diff/object_inspection/inspection_tree_builders.rb +0 -48
  193. data/lib/super_diff/object_inspection/nodes.rb +0 -50
  194. data/lib/super_diff/object_inspection/prefix_for_next_node.rb +0 -6
  195. data/lib/super_diff/object_inspection/prelude_for_next_node.rb +0 -6
  196. data/lib/super_diff/operation_tree_builders/array.rb +0 -107
  197. data/lib/super_diff/operation_tree_builders/custom_object.rb +0 -40
  198. data/lib/super_diff/operation_tree_builders/date_like.rb +0 -15
  199. data/lib/super_diff/operation_tree_builders/default_object.rb +0 -119
  200. data/lib/super_diff/operation_tree_builders/defaults.rb +0 -5
  201. data/lib/super_diff/operation_tree_builders/hash.rb +0 -218
  202. data/lib/super_diff/operation_tree_builders/multiline_string.rb +0 -86
  203. data/lib/super_diff/operation_tree_builders/time_like.rb +0 -24
  204. data/lib/super_diff/operation_tree_flatteners/array.rb +0 -15
  205. data/lib/super_diff/operation_tree_flatteners/collection.rb +0 -136
  206. data/lib/super_diff/operation_tree_flatteners/custom_object.rb +0 -28
  207. data/lib/super_diff/operation_tree_flatteners/default_object.rb +0 -31
  208. data/lib/super_diff/operation_tree_flatteners/hash.rb +0 -33
  209. data/lib/super_diff/operation_tree_flatteners/multiline_string.rb +0 -18
  210. data/lib/super_diff/operation_trees/array.rb +0 -15
  211. data/lib/super_diff/operation_trees/custom_object.rb +0 -15
  212. data/lib/super_diff/operation_trees/default_object.rb +0 -40
  213. data/lib/super_diff/operation_trees/defaults.rb +0 -5
  214. data/lib/super_diff/operation_trees/hash.rb +0 -15
  215. data/lib/super_diff/operation_trees/main.rb +0 -35
  216. data/lib/super_diff/operation_trees/multiline_string.rb +0 -15
  217. data/lib/super_diff/recursion_guard.rb +0 -50
  218. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/collection_containing_exactly.rb +0 -36
  219. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/collection_including.rb +0 -42
  220. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/double.rb +0 -102
  221. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher.rb +0 -19
  222. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/hash_including.rb +0 -42
  223. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/instance_of.rb +0 -27
  224. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/kind_of.rb +0 -27
  225. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/object_having_attributes.rb +0 -36
  226. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/primitive.rb +0 -10
  227. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders/value_within.rb +0 -32
  228. data/lib/super_diff/rspec/object_inspection/inspection_tree_builders.rb +0 -48
  229. data/lib/super_diff/tiered_lines.rb +0 -4
  230. data/lib/super_diff/tiered_lines_elider.rb +0 -462
  231. data/lib/super_diff/tiered_lines_formatter.rb +0 -75
@@ -0,0 +1,42 @@
1
+ module SuperDiff
2
+ module Basic
3
+ module OperationTreeBuilders
4
+ class CustomObject < DefaultObject
5
+ def self.applies_to?(expected, actual)
6
+ expected.class == actual.class &&
7
+ expected.respond_to?(:attributes_for_super_diff) &&
8
+ actual.respond_to?(:attributes_for_super_diff)
9
+ end
10
+
11
+ protected
12
+
13
+ def build_operation_tree
14
+ # NOTE: It doesn't matter whether we use expected or actual here,
15
+ # because all we care about is the name of the class
16
+ OperationTrees::CustomObject.new([], underlying_object: actual)
17
+ end
18
+
19
+ def attribute_names
20
+ expected.attributes_for_super_diff.keys &
21
+ actual.attributes_for_super_diff.keys
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :expected_attributes, :actual_attributes
27
+
28
+ def establish_expected_and_actual_attributes
29
+ @expected_attributes =
30
+ attribute_names.reduce({}) do |hash, name|
31
+ hash.merge(name => expected.public_send(name))
32
+ end
33
+
34
+ @actual_attributes =
35
+ attribute_names.reduce({}) do |hash, name|
36
+ hash.merge(name => actual.public_send(name))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ module SuperDiff
2
+ module Basic
3
+ module OperationTreeBuilders
4
+ class DateLike < CustomObject
5
+ def self.applies_to?(expected, actual)
6
+ SuperDiff.date_like?(expected) && SuperDiff.date_like?(actual)
7
+ end
8
+
9
+ protected
10
+
11
+ def attribute_names
12
+ %w[year month day]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,117 @@
1
+ module SuperDiff
2
+ module Basic
3
+ module OperationTreeBuilders
4
+ class DefaultObject < Core::AbstractOperationTreeBuilder
5
+ def self.applies_to?(_expected, _actual)
6
+ true
7
+ end
8
+
9
+ def initialize(*args)
10
+ super(*args)
11
+
12
+ establish_expected_and_actual_attributes
13
+ end
14
+
15
+ protected
16
+
17
+ def unary_operations
18
+ attribute_names.reduce([]) do |operations, name|
19
+ possibly_add_noop_operation_to(operations, name)
20
+ possibly_add_delete_operation_to(operations, name)
21
+ possibly_add_insert_operation_to(operations, name)
22
+ operations
23
+ end
24
+ end
25
+
26
+ def build_operation_tree
27
+ # XXX This assumes that `expected` and `actual` are the same
28
+ # TODO: Does this need to find the operation tree matching `actual`?
29
+ OperationTrees::DefaultObject.new([], underlying_object: actual)
30
+ end
31
+
32
+ def attribute_names
33
+ (
34
+ expected.instance_variables.sort & actual.instance_variables.sort
35
+ ).map { |variable_name| variable_name[1..-1] }
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :expected_attributes, :actual_attributes
41
+
42
+ def establish_expected_and_actual_attributes
43
+ @expected_attributes =
44
+ attribute_names.reduce({}) do |hash, name|
45
+ hash.merge(name => expected.instance_variable_get("@#{name}"))
46
+ end
47
+
48
+ @actual_attributes =
49
+ attribute_names.reduce({}) do |hash, name|
50
+ hash.merge(name => actual.instance_variable_get("@#{name}"))
51
+ end
52
+ end
53
+
54
+ def possibly_add_noop_operation_to(operations, attribute_name)
55
+ if should_add_noop_operation?(attribute_name)
56
+ operations << Core::UnaryOperation.new(
57
+ name: :noop,
58
+ collection: actual_attributes,
59
+ key: attribute_name,
60
+ index: attribute_names.index(attribute_name),
61
+ value: actual_attributes[attribute_name]
62
+ )
63
+ end
64
+ end
65
+
66
+ def should_add_noop_operation?(attribute_name)
67
+ expected_attributes.include?(attribute_name) &&
68
+ actual_attributes.include?(attribute_name) &&
69
+ expected_attributes[attribute_name] ==
70
+ actual_attributes[attribute_name]
71
+ end
72
+
73
+ def possibly_add_delete_operation_to(operations, attribute_name)
74
+ if should_add_delete_operation?(attribute_name)
75
+ operations << Core::UnaryOperation.new(
76
+ name: :delete,
77
+ collection: expected_attributes,
78
+ key: attribute_name,
79
+ index: attribute_names.index(attribute_name),
80
+ value: expected_attributes[attribute_name]
81
+ )
82
+ end
83
+ end
84
+
85
+ def should_add_delete_operation?(attribute_name)
86
+ expected_attributes.include?(attribute_name) &&
87
+ (
88
+ !actual_attributes.include?(attribute_name) ||
89
+ expected_attributes[attribute_name] !=
90
+ actual_attributes[attribute_name]
91
+ )
92
+ end
93
+
94
+ def possibly_add_insert_operation_to(operations, attribute_name)
95
+ if should_add_insert_operation?(attribute_name)
96
+ operations << Core::UnaryOperation.new(
97
+ name: :insert,
98
+ collection: actual_attributes,
99
+ key: attribute_name,
100
+ index: attribute_names.index(attribute_name),
101
+ value: actual_attributes[attribute_name]
102
+ )
103
+ end
104
+ end
105
+
106
+ def should_add_insert_operation?(attribute_name)
107
+ !expected_attributes.include?(attribute_name) ||
108
+ (
109
+ actual_attributes.include?(attribute_name) &&
110
+ expected_attributes[attribute_name] !=
111
+ actual_attributes[attribute_name]
112
+ )
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,222 @@
1
+ module SuperDiff
2
+ module Basic
3
+ module OperationTreeBuilders
4
+ class Hash < Core::AbstractOperationTreeBuilder
5
+ def self.applies_to?(expected, actual)
6
+ expected.is_a?(::Hash) && actual.is_a?(::Hash)
7
+ end
8
+
9
+ protected
10
+
11
+ def unary_operations
12
+ unary_operations_using_variant_of_patience_algorithm
13
+ end
14
+
15
+ def build_operation_tree
16
+ OperationTrees::Hash.new([])
17
+ end
18
+
19
+ private
20
+
21
+ def unary_operations_using_variant_of_patience_algorithm
22
+ operations = []
23
+ aks, eks = actual.keys, expected.keys
24
+ previous_ei, ei = nil, 0
25
+ ai = 0
26
+
27
+ # When diffing a hash, we're more interested in the 'actual' version
28
+ # than the 'expected' version, because that's the ultimate truth.
29
+ # Therefore, the diff is presented from the perspective of the 'actual'
30
+ # hash, and we start off by looping over it.
31
+ while ai < aks.size
32
+ ak = aks[ai]
33
+ av, ev = actual[ak], expected[ak]
34
+ # While we iterate over 'actual' in order, we jump all over
35
+ # 'expected', trying to match up its keys with the keys in 'actual' as
36
+ # much as possible.
37
+ ei = eks.index(ak)
38
+
39
+ if should_add_noop_operation?(ak)
40
+ # (If we're here, it probably means that the key we're pointing to
41
+ # in the 'actual' and 'expected' hashes have the same value.)
42
+
43
+ if ei && previous_ei && (ei - previous_ei) > 1
44
+ # If we've jumped from one operation in the 'expected' hash to
45
+ # another operation later in 'expected' (due to the fact that the
46
+ # 'expected' hash is in a different order than 'actual'), collect
47
+ # any delete operations in between and add them to our operations
48
+ # array as deletes before adding the noop. If we don't do this
49
+ # now, then those deletes will disappear. (Again, we are mainly
50
+ # iterating over 'actual', so this is the only way to catch all of
51
+ # the keys in 'expected'.)
52
+ (previous_ei + 1).upto(ei - 1) do |ei2|
53
+ ek = eks[ei2]
54
+ ev2, av2 = expected[ek], actual[ek]
55
+
56
+ if (
57
+ (!actual.include?(ek) || ev2 != av2) &&
58
+ operations.none? do |operation|
59
+ %i[delete noop].include?(operation.name) &&
60
+ operation.key == ek
61
+ end
62
+ )
63
+ operations << Core::UnaryOperation.new(
64
+ name: :delete,
65
+ collection: expected,
66
+ key: ek,
67
+ value: ev2,
68
+ index: ei2
69
+ )
70
+ end
71
+ end
72
+ end
73
+
74
+ operations << Core::UnaryOperation.new(
75
+ name: :noop,
76
+ collection: actual,
77
+ key: ak,
78
+ value: av,
79
+ index: ai
80
+ )
81
+ else
82
+ # (If we're here, it probably means that the key in 'actual' isn't
83
+ # present in 'expected' or the values don't match.)
84
+
85
+ if (
86
+ (operations.empty? || operations.last.name == :noop) &&
87
+ (ai == 0 || eks.include?(aks[ai - 1]))
88
+ )
89
+ # If we go from a match in the last iteration to a missing or
90
+ # extra key in this one, or we're at the first key in 'actual' and
91
+ # it's missing or extra, look for deletes in the 'expected' hash
92
+ # and add them to our list of operations before we add the
93
+ # inserts. In most cases we will accomplish this by backtracking a
94
+ # bit to the key in 'expected' that matched the key in 'actual' we
95
+ # processed in the previous iteration (or just the first key in
96
+ # 'expected' if this is the first key in 'actual'), and then
97
+ # iterating from there through 'expected' until we reach the end
98
+ # or we hit some other condition (see below).
99
+
100
+ start_index =
101
+ if ai > 0
102
+ eks.index(aks[ai - 1]) + 1
103
+ else
104
+ 0
105
+ end
106
+
107
+ start_index.upto(eks.size - 1) do |ei2|
108
+ ek = eks[ei2]
109
+ ev, av2 = expected[ek], actual[ek]
110
+
111
+ if actual.include?(ek) && ev == av2
112
+ # If the key in 'expected' we've landed on happens to be a
113
+ # match in 'actual', then stop, because it's going to be
114
+ # handled in some future iteration of the 'actual' loop.
115
+ break
116
+ elsif (
117
+ aks[ai + 1..-1].any? do |k|
118
+ expected.include?(k) && expected[k] != actual[k]
119
+ end
120
+ )
121
+ # While we backtracked a bit to iterate over 'expected', we
122
+ # now have to look ahead. If we will end up encountering a
123
+ # insert that matches this delete later, stop and go back to
124
+ # iterating over 'actual'. This is because the delete we would
125
+ # have added now will be added later when we encounter the
126
+ # associated insert, so we don't want to add it twice.
127
+ break
128
+ else
129
+ operations << Core::UnaryOperation.new(
130
+ name: :delete,
131
+ collection: expected,
132
+ key: ek,
133
+ value: ev,
134
+ index: ei2
135
+ )
136
+ end
137
+
138
+ if ek == ak && ev != av
139
+ # If we're pointing to the same key in 'expected' as in
140
+ # 'actual', but with different values, go ahead and add an
141
+ # insert now to accompany the delete added above. That way
142
+ # they appear together, which will be easier to read.
143
+ operations << Core::UnaryOperation.new(
144
+ name: :insert,
145
+ collection: actual,
146
+ key: ak,
147
+ value: av,
148
+ index: ai
149
+ )
150
+ end
151
+ end
152
+ end
153
+
154
+ if (
155
+ expected.include?(ak) && ev != av &&
156
+ operations.none? do |op|
157
+ op.name == :delete && op.key == ak
158
+ end
159
+ )
160
+ # If we're here, it means that we didn't encounter any delete
161
+ # operations above for whatever reason and so we need to add a
162
+ # delete to represent the fact that the value for this key has
163
+ # changed.
164
+ operations << Core::UnaryOperation.new(
165
+ name: :delete,
166
+ collection: expected,
167
+ key: ak,
168
+ value: expected[ak],
169
+ index: ei
170
+ )
171
+ end
172
+
173
+ if operations.none? { |op| op.name == :insert && op.key == ak }
174
+ # If we're here, it means that we didn't encounter any insert
175
+ # operations above. Since we already handled delete, the only
176
+ # alternative is that this key must not exist in 'expected', so
177
+ # we need to add an insert.
178
+ operations << Core::UnaryOperation.new(
179
+ name: :insert,
180
+ collection: actual,
181
+ key: ak,
182
+ value: av,
183
+ index: ai
184
+ )
185
+ end
186
+ end
187
+
188
+ ai += 1
189
+ previous_ei = ei
190
+ end
191
+
192
+ # The last thing to do is this: if there are keys in 'expected' that
193
+ # aren't in 'actual', and they aren't associated with any inserts to
194
+ # where they would have been added above, tack those deletes onto the
195
+ # end of our operations array.
196
+ (eks - aks - operations.map(&:key)).each do |ek|
197
+ ei = eks.index(ek)
198
+ ev = expected[ek]
199
+
200
+ operations << Core::UnaryOperation.new(
201
+ name: :delete,
202
+ collection: expected,
203
+ key: ek,
204
+ value: ev,
205
+ index: ei
206
+ )
207
+ end
208
+
209
+ operations
210
+ end
211
+
212
+ def should_add_noop_operation?(key)
213
+ expected.include?(key) && expected[key] == actual[key]
214
+ end
215
+
216
+ def all_keys
217
+ actual.keys | expected.keys
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,90 @@
1
+ require "patience_diff"
2
+
3
+ module SuperDiff
4
+ module Basic
5
+ module OperationTreeBuilders
6
+ class MultilineString < Core::AbstractOperationTreeBuilder
7
+ def self.applies_to?(expected, actual)
8
+ expected.is_a?(::String) && actual.is_a?(::String) &&
9
+ (expected.include?("\n") || actual.include?("\n"))
10
+ end
11
+
12
+ def initialize(*args)
13
+ super(*args)
14
+
15
+ @original_expected = @expected
16
+ @original_actual = @actual
17
+ @expected = split_into_lines(@expected)
18
+ @actual = split_into_lines(@actual)
19
+ @sequence_matcher = PatienceDiff::SequenceMatcher.new
20
+ end
21
+
22
+ protected
23
+
24
+ def unary_operations
25
+ opcodes.flat_map do |code, a_start, a_end, b_start, b_end|
26
+ if code == :delete
27
+ add_delete_operations(a_start..a_end)
28
+ elsif code == :insert
29
+ add_insert_operations(b_start..b_end)
30
+ else
31
+ add_noop_operations(b_start..b_end)
32
+ end
33
+ end
34
+ end
35
+
36
+ def build_operation_tree
37
+ OperationTrees::MultilineString.new([])
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :sequence_matcher, :original_expected, :original_actual
43
+
44
+ def split_into_lines(string)
45
+ string.scan(/.+(?:\r|\n|\r\n|\Z)/)
46
+ end
47
+
48
+ def opcodes
49
+ sequence_matcher.diff_opcodes(expected, actual)
50
+ end
51
+
52
+ def add_delete_operations(indices)
53
+ indices.map do |index|
54
+ Core::UnaryOperation.new(
55
+ name: :delete,
56
+ collection: expected,
57
+ key: index,
58
+ index: index,
59
+ value: expected[index]
60
+ )
61
+ end
62
+ end
63
+
64
+ def add_insert_operations(indices)
65
+ indices.map do |index|
66
+ Core::UnaryOperation.new(
67
+ name: :insert,
68
+ collection: actual,
69
+ key: index,
70
+ index: index,
71
+ value: actual[index]
72
+ )
73
+ end
74
+ end
75
+
76
+ def add_noop_operations(indices)
77
+ indices.map do |index|
78
+ Core::UnaryOperation.new(
79
+ name: :noop,
80
+ collection: actual,
81
+ key: index,
82
+ index: index,
83
+ value: actual[index]
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,26 @@
1
+ module SuperDiff
2
+ module Basic
3
+ module OperationTreeBuilders
4
+ class TimeLike < CustomObject
5
+ def self.applies_to?(expected, actual)
6
+ SuperDiff.time_like?(expected) && SuperDiff.time_like?(actual)
7
+ end
8
+
9
+ protected
10
+
11
+ def attribute_names
12
+ base = %w[year month day hour min sec subsec zone utc_offset]
13
+
14
+ # If timezones are different, also show a normalized timestamp at the
15
+ # end of the diff to help visualize why they are different moments in
16
+ # time.
17
+ if actual.zone != expected.zone
18
+ base + ["utc"]
19
+ else
20
+ base
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ module SuperDiff
2
+ module Basic
3
+ module OperationTreeBuilders
4
+ autoload :Array, "super_diff/basic/operation_tree_builders/array"
5
+ autoload(
6
+ :CustomObject,
7
+ "super_diff/basic/operation_tree_builders/custom_object"
8
+ )
9
+ autoload(
10
+ :DefaultObject,
11
+ "super_diff/basic/operation_tree_builders/default_object"
12
+ )
13
+ autoload :Hash, "super_diff/basic/operation_tree_builders/hash"
14
+ # TODO: Where is this used?
15
+ autoload(
16
+ :MultilineString,
17
+ "super_diff/basic/operation_tree_builders/multiline_string"
18
+ )
19
+ autoload :TimeLike, "super_diff/basic/operation_tree_builders/time_like"
20
+ autoload :DateLike, "super_diff/basic/operation_tree_builders/date_like"
21
+
22
+ class Main
23
+ def self.call(*args)
24
+ warn <<~EOT
25
+ WARNING: SuperDiff::OperationTreeBuilders::Main.call(...) is deprecated and will be removed in the next major release.
26
+ Please use SuperDiff.build_operation_tree_for(...) instead.
27
+ #{caller_locations.join("\n")}
28
+ EOT
29
+ SuperDiff.build_operation_tree_for(*args)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ module SuperDiff
2
+ module Basic
3
+ module OperationTreeFlatteners
4
+ class Array < Collection
5
+ protected
6
+
7
+ def open_token
8
+ "["
9
+ end
10
+
11
+ def close_token
12
+ "]"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end