diff --git a/lib/super_diff.rb b/lib/super_diff.rb index a2118b25..349f9b52 100644 --- a/lib/super_diff.rb +++ b/lib/super_diff.rb @@ -1,47 +1,93 @@ require "attr_extras/explicit" -require "diff-lcs" -require "patience_diff" require "date" module SuperDiff - autoload( - :ColorizedDocumentExtensions, - "super_diff/colorized_document_extensions" - ) - autoload :OperationTreeFlatteners, "super_diff/operation_tree_flatteners" - autoload :Configuration, "super_diff/configuration" + autoload :Core, "super_diff/core" autoload :Csi, "super_diff/csi" - autoload :DiffFormatters, "super_diff/diff_formatters" autoload :Differs, "super_diff/differs" autoload :EqualityMatchers, "super_diff/equality_matchers" autoload :Errors, "super_diff/errors" - autoload :GemVersion, "super_diff/gem_version" - autoload :Helpers, "super_diff/helpers" - autoload :ImplementationChecks, "super_diff/implementation_checks" - autoload :Line, "super_diff/line" - autoload :TieredLines, "super_diff/tiered_lines" - autoload :TieredLinesElider, "super_diff/tiered_lines_elider" - autoload :TieredLinesFormatter, "super_diff/tiered_lines_formatter" autoload :ObjectInspection, "super_diff/object_inspection" - autoload :OperationTrees, "super_diff/operation_trees" autoload :OperationTreeBuilders, "super_diff/operation_tree_builders" + autoload :OperationTreeFlatteners, "super_diff/operation_tree_flatteners" + autoload :OperationTrees, "super_diff/operation_trees" autoload :Operations, "super_diff/operations" - autoload :RecursionGuard, "super_diff/recursion_guard" autoload :VERSION, "super_diff/version" + def self.const_missing(missing_const_name) + if Core.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Core.const_get(missing_const_name) + elsif Basic.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Basic::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Basic.const_get(missing_const_name) + else + super + end + end + def self.configure yield configuration configuration.updated end def self.configuration - @_configuration ||= Configuration.new + @_configuration ||= Core::Configuration.new + end + + def self.diff( + expected, + actual, + indent_level: 0, + raise_if_nothing_applies: true + ) + Core::DifferDispatcher.call( + expected, + actual, + available_classes: configuration.extra_differ_classes, + indent_level: indent_level, + raise_if_nothing_applies: raise_if_nothing_applies + ) + end + + def self.build_operation_tree_for( + expected, + actual, + extra_operation_tree_builder_classes: [], + raise_if_nothing_applies: false + ) + Core::OperationTreeBuilderDispatcher.call( + expected, + actual, + available_classes: + configuration.extra_operation_tree_builder_classes + + extra_operation_tree_builder_classes, + raise_if_nothing_applies: raise_if_nothing_applies + ) + end + + def self.find_operation_tree_for(value) + SuperDiff::Core::OperationTreeFinder.call( + value, + available_classes: configuration.extra_operation_tree_classes + ) end def self.inspect_object(object, as_lines:, **rest) - SuperDiff::RecursionGuard.guarding_recursion_of(object) do + Core::RecursionGuard.guarding_recursion_of(object) do inspection_tree = - ObjectInspection::InspectionTreeBuilders::Main.call(object) + Core::InspectionTreeBuilderDispatcher.call( + object, + available_classes: configuration.extra_inspection_tree_builder_classes + ) if as_lines inspection_tree.render_to_lines(object, **rest) @@ -87,3 +133,5 @@ def self.insert_singleton_overrides(target_module, mod = nil, &block) end end end + +require "super_diff/basic" diff --git a/lib/super_diff/active_record.rb b/lib/super_diff/active_record.rb index 5c0fb123..f092b798 100644 --- a/lib/super_diff/active_record.rb +++ b/lib/super_diff/active_record.rb @@ -1,28 +1,24 @@ require "super_diff/active_support" +require "super_diff/active_record/differs" +require "super_diff/active_record/inspection_tree_builders" +require "super_diff/active_record/operation_trees" +require "super_diff/active_record/operation_tree_builders" +require "super_diff/active_record/operation_tree_flatteners" + module SuperDiff module ActiveRecord - autoload :Differs, "super_diff/active_record/differs" - autoload(:ObjectInspection, "super_diff/active_record/object_inspection") - autoload(:OperationTrees, "super_diff/active_record/operation_trees") - autoload( - :OperationTreeBuilders, - "super_diff/active_record/operation_tree_builders" - ) - autoload( - :OperationTreeFlatteners, - "super_diff/active_record/operation_tree_flatteners" - ) + autoload :ObjectInspection, "super_diff/active_record/object_inspection" SuperDiff.configure do |config| - config.add_extra_differ_classes(Differs::ActiveRecordRelation) - config.add_extra_operation_tree_builder_classes( + config.prepend_extra_differ_classes(Differs::ActiveRecordRelation) + config.prepend_extra_operation_tree_builder_classes( OperationTreeBuilders::ActiveRecordModel, OperationTreeBuilders::ActiveRecordRelation ) - config.add_extra_inspection_tree_builder_classes( - ObjectInspection::InspectionTreeBuilders::ActiveRecordModel, - ObjectInspection::InspectionTreeBuilders::ActiveRecordRelation + config.prepend_extra_inspection_tree_builder_classes( + InspectionTreeBuilders::ActiveRecordModel, + InspectionTreeBuilders::ActiveRecordRelation ) end end diff --git a/lib/super_diff/active_record/differs/active_record_relation.rb b/lib/super_diff/active_record/differs/active_record_relation.rb index 0c1e5467..d8eb9170 100644 --- a/lib/super_diff/active_record/differs/active_record_relation.rb +++ b/lib/super_diff/active_record/differs/active_record_relation.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveRecord module Differs - class ActiveRecordRelation < SuperDiff::Differs::Base + class ActiveRecordRelation < Core::AbstractDiffer def self.applies_to?(expected, actual) expected.is_a?(::Array) && actual.is_a?(::ActiveRecord::Relation) end diff --git a/lib/super_diff/active_record/inspection_tree_builders.rb b/lib/super_diff/active_record/inspection_tree_builders.rb new file mode 100644 index 00000000..19673c24 --- /dev/null +++ b/lib/super_diff/active_record/inspection_tree_builders.rb @@ -0,0 +1,14 @@ +module SuperDiff + module ActiveRecord + module InspectionTreeBuilders + autoload( + :ActiveRecordModel, + "super_diff/active_record/inspection_tree_builders/active_record_model" + ) + autoload( + :ActiveRecordRelation, + "super_diff/active_record/inspection_tree_builders/active_record_relation" + ) + end + end +end diff --git a/lib/super_diff/active_record/inspection_tree_builders/active_record_model.rb b/lib/super_diff/active_record/inspection_tree_builders/active_record_model.rb new file mode 100644 index 00000000..25bf2a2c --- /dev/null +++ b/lib/super_diff/active_record/inspection_tree_builders/active_record_model.rb @@ -0,0 +1,49 @@ +module SuperDiff + module ActiveRecord + module InspectionTreeBuilders + class ActiveRecordModel < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + value.is_a?(::ActiveRecord::Base) + end + + def call + Core::InspectionTree.new do |t1| + t1.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t2| + t2.add_text "#<#{object.class} " + + # stree-ignore + t2.when_rendering_to_lines do |t3| + t3.add_text "{" + end + end + + t1.nested do |t2| + t2.insert_separated_list( + ["id"] + (object.attributes.keys.sort - ["id"]) + ) do |t3, name| + t3.as_prefix_when_rendering_to_lines do |t4| + t4.add_text "#{name}: " + end + + t3.add_inspection_of object.read_attribute(name) + end + end + + t1.as_lines_when_rendering_to_lines( + collection_bookend: :close + ) do |t2| + # stree-ignore + t2.when_rendering_to_lines do |t3| + t3.add_text "}" + end + + t2.add_text ">" + end + end + end + end + end + end +end diff --git a/lib/super_diff/active_record/inspection_tree_builders/active_record_relation.rb b/lib/super_diff/active_record/inspection_tree_builders/active_record_relation.rb new file mode 100644 index 00000000..5380b00c --- /dev/null +++ b/lib/super_diff/active_record/inspection_tree_builders/active_record_relation.rb @@ -0,0 +1,34 @@ +module SuperDiff + module ActiveRecord + module InspectionTreeBuilders + class ActiveRecordRelation < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + value.is_a?(::ActiveRecord::Relation) + end + + def call + Core::InspectionTree.new do |t1| + # stree-ignore + t1.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t2| + t2.add_text "#" + end + end + end + end + end + end +end diff --git a/lib/super_diff/active_record/monkey_patches.rb b/lib/super_diff/active_record/monkey_patches.rb index 05274a9c..b2ebce7e 100644 --- a/lib/super_diff/active_record/monkey_patches.rb +++ b/lib/super_diff/active_record/monkey_patches.rb @@ -1,5 +1,6 @@ # rubocop:disable Style/BracesAroundHashParameters, Style/ClassAndModuleChildren class ActiveRecord::Base + # TODO: Remove this monkey patch if possible def attributes_for_super_diff (attributes.keys.sort - ["id"]).reduce({ id: id }) do |hash, key| hash.merge(key.to_sym => attributes[key]) diff --git a/lib/super_diff/active_record/object_inspection.rb b/lib/super_diff/active_record/object_inspection.rb index 341938ea..6d300c47 100644 --- a/lib/super_diff/active_record/object_inspection.rb +++ b/lib/super_diff/active_record/object_inspection.rb @@ -1,10 +1,22 @@ module SuperDiff module ActiveRecord module ObjectInspection - autoload( - :InspectionTreeBuilders, - "super_diff/active_record/object_inspection/inspection_tree_builders" - ) + module InspectionTreeBuilders + def self.const_missing(missing_const_name) + if ActiveRecord::InspectionTreeBuilders.const_defined?( + missing_const_name + ) + warn <<~EOT + WARNING: SuperDiff::ActiveRecord::ObjectInspection::InspectionTreeBuilders::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::ActiveRecord::InspectionTreeBuilders::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + ActiveRecord::InspectionTreeBuilders.const_get(missing_const_name) + else + super + end + end + end end end end diff --git a/lib/super_diff/active_record/object_inspection/inspection_tree_builders.rb b/lib/super_diff/active_record/object_inspection/inspection_tree_builders.rb deleted file mode 100644 index 40ef6905..00000000 --- a/lib/super_diff/active_record/object_inspection/inspection_tree_builders.rb +++ /dev/null @@ -1,16 +0,0 @@ -module SuperDiff - module ActiveRecord - module ObjectInspection - module InspectionTreeBuilders - autoload( - :ActiveRecordModel, - "super_diff/active_record/object_inspection/inspection_tree_builders/active_record_model" - ) - autoload( - :ActiveRecordRelation, - "super_diff/active_record/object_inspection/inspection_tree_builders/active_record_relation" - ) - end - end - end -end diff --git a/lib/super_diff/active_record/object_inspection/inspection_tree_builders/active_record_model.rb b/lib/super_diff/active_record/object_inspection/inspection_tree_builders/active_record_model.rb deleted file mode 100644 index a3b79766..00000000 --- a/lib/super_diff/active_record/object_inspection/inspection_tree_builders/active_record_model.rb +++ /dev/null @@ -1,51 +0,0 @@ -module SuperDiff - module ActiveRecord - module ObjectInspection - module InspectionTreeBuilders - class ActiveRecordModel < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - value.is_a?(::ActiveRecord::Base) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - t1.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t2| - t2.add_text "#<#{object.class} " - - # stree-ignore - t2.when_rendering_to_lines do |t3| - t3.add_text "{" - end - end - - t1.nested do |t2| - t2.insert_separated_list( - ["id"] + (object.attributes.keys.sort - ["id"]) - ) do |t3, name| - t3.as_prefix_when_rendering_to_lines do |t4| - t4.add_text "#{name}: " - end - - t3.add_inspection_of object.read_attribute(name) - end - end - - t1.as_lines_when_rendering_to_lines( - collection_bookend: :close - ) do |t2| - # stree-ignore - t2.when_rendering_to_lines do |t3| - t3.add_text "}" - end - - t2.add_text ">" - end - end - end - end - end - end - end -end diff --git a/lib/super_diff/active_record/object_inspection/inspection_tree_builders/active_record_relation.rb b/lib/super_diff/active_record/object_inspection/inspection_tree_builders/active_record_relation.rb deleted file mode 100644 index d85167e7..00000000 --- a/lib/super_diff/active_record/object_inspection/inspection_tree_builders/active_record_relation.rb +++ /dev/null @@ -1,36 +0,0 @@ -module SuperDiff - module ActiveRecord - module ObjectInspection - module InspectionTreeBuilders - class ActiveRecordRelation < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - value.is_a?(::ActiveRecord::Relation) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - # stree-ignore - t1.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t2| - t2.add_text "#" - end - end - end - end - end - end - end -end diff --git a/lib/super_diff/active_record/operation_tree_builders/active_record_model.rb b/lib/super_diff/active_record/operation_tree_builders/active_record_model.rb index 6d22196c..72442b08 100644 --- a/lib/super_diff/active_record/operation_tree_builders/active_record_model.rb +++ b/lib/super_diff/active_record/operation_tree_builders/active_record_model.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveRecord module OperationTreeBuilders - class ActiveRecordModel < SuperDiff::OperationTreeBuilders::CustomObject + class ActiveRecordModel < Basic::OperationTreeBuilders::CustomObject def self.applies_to?(expected, actual) expected.is_a?(::ActiveRecord::Base) && actual.is_a?(::ActiveRecord::Base) && expected.class == actual.class diff --git a/lib/super_diff/active_record/operation_tree_builders/active_record_relation.rb b/lib/super_diff/active_record/operation_tree_builders/active_record_relation.rb index 1752966d..9a991e1c 100644 --- a/lib/super_diff/active_record/operation_tree_builders/active_record_relation.rb +++ b/lib/super_diff/active_record/operation_tree_builders/active_record_relation.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveRecord module OperationTreeBuilders - class ActiveRecordRelation < SuperDiff::OperationTreeBuilders::Array + class ActiveRecordRelation < Basic::OperationTreeBuilders::Array def self.applies_to?(expected, actual) expected.is_a?(::Array) && actual.is_a?(::ActiveRecord::Relation) end diff --git a/lib/super_diff/active_record/operation_tree_flatteners/active_record_relation.rb b/lib/super_diff/active_record/operation_tree_flatteners/active_record_relation.rb index fda4b6b0..29e4069d 100644 --- a/lib/super_diff/active_record/operation_tree_flatteners/active_record_relation.rb +++ b/lib/super_diff/active_record/operation_tree_flatteners/active_record_relation.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveRecord module OperationTreeFlatteners - class ActiveRecordRelation < SuperDiff::OperationTreeFlatteners::Collection + class ActiveRecordRelation < Basic::OperationTreeFlatteners::Collection protected def open_token diff --git a/lib/super_diff/active_record/operation_trees/active_record_relation.rb b/lib/super_diff/active_record/operation_trees/active_record_relation.rb index 53a9ae37..e5176426 100644 --- a/lib/super_diff/active_record/operation_trees/active_record_relation.rb +++ b/lib/super_diff/active_record/operation_trees/active_record_relation.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveRecord module OperationTrees - class ActiveRecordRelation < SuperDiff::OperationTrees::Array + class ActiveRecordRelation < Basic::OperationTrees::Array def self.applies_to?(value) value.is_a?(ActiveRecord::Relation) end diff --git a/lib/super_diff/active_support.rb b/lib/super_diff/active_support.rb index c907c1b2..5e69bb40 100644 --- a/lib/super_diff/active_support.rb +++ b/lib/super_diff/active_support.rb @@ -1,25 +1,21 @@ +require "super_diff/active_support/differs" +require "super_diff/active_support/inspection_tree_builders" +require "super_diff/active_support/operation_trees" +require "super_diff/active_support/operation_tree_builders" +require "super_diff/active_support/operation_tree_flatteners" + module SuperDiff module ActiveSupport - autoload :Differs, "super_diff/active_support/differs" autoload :ObjectInspection, "super_diff/active_support/object_inspection" - autoload(:OperationTrees, "super_diff/active_support/operation_trees") - autoload( - :OperationTreeBuilders, - "super_diff/active_support/operation_tree_builders" - ) - autoload( - :OperationTreeFlatteners, - "super_diff/active_support/operation_tree_flatteners" - ) SuperDiff.configure do |config| - config.add_extra_differ_classes(Differs::HashWithIndifferentAccess) - config.add_extra_operation_tree_builder_classes( + config.prepend_extra_differ_classes(Differs::HashWithIndifferentAccess) + config.prepend_extra_operation_tree_builder_classes( OperationTreeBuilders::HashWithIndifferentAccess ) - config.add_extra_inspection_tree_builder_classes( - ObjectInspection::InspectionTreeBuilders::HashWithIndifferentAccess, - ObjectInspection::InspectionTreeBuilders::OrderedOptions + config.prepend_extra_inspection_tree_builder_classes( + InspectionTreeBuilders::HashWithIndifferentAccess, + InspectionTreeBuilders::OrderedOptions ) end end diff --git a/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb b/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb index 2a9fc3a2..fc00551a 100644 --- a/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb +++ b/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveSupport module Differs - class HashWithIndifferentAccess < SuperDiff::Differs::Hash + class HashWithIndifferentAccess < Basic::Differs::Hash def self.applies_to?(expected, actual) ( expected.is_a?(::HashWithIndifferentAccess) && actual.is_a?(::Hash) diff --git a/lib/super_diff/active_support/inspection_tree_builders.rb b/lib/super_diff/active_support/inspection_tree_builders.rb new file mode 100644 index 00000000..7247f255 --- /dev/null +++ b/lib/super_diff/active_support/inspection_tree_builders.rb @@ -0,0 +1,14 @@ +module SuperDiff + module ActiveSupport + module InspectionTreeBuilders + autoload( + :HashWithIndifferentAccess, + "super_diff/active_support/inspection_tree_builders/hash_with_indifferent_access" + ) + autoload( + :OrderedOptions, + "super_diff/active_support/inspection_tree_builders/ordered_options" + ) + end + end +end diff --git a/lib/super_diff/active_support/inspection_tree_builders/hash_with_indifferent_access.rb b/lib/super_diff/active_support/inspection_tree_builders/hash_with_indifferent_access.rb new file mode 100644 index 00000000..0c7bf646 --- /dev/null +++ b/lib/super_diff/active_support/inspection_tree_builders/hash_with_indifferent_access.rb @@ -0,0 +1,44 @@ +module SuperDiff + module ActiveSupport + module InspectionTreeBuilders + class HashWithIndifferentAccess < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + value.is_a?(::HashWithIndifferentAccess) + end + + def call + Core::InspectionTree.new do |t1| + # stree-ignore + t1.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t2| + t2.add_text "#" + end + end + end + end + end + end +end diff --git a/lib/super_diff/active_support/inspection_tree_builders/ordered_options.rb b/lib/super_diff/active_support/inspection_tree_builders/ordered_options.rb new file mode 100644 index 00000000..83446115 --- /dev/null +++ b/lib/super_diff/active_support/inspection_tree_builders/ordered_options.rb @@ -0,0 +1,44 @@ +module SuperDiff + module ActiveSupport + module InspectionTreeBuilders + class OrderedOptions < Basic::InspectionTreeBuilders::Hash + def self.applies_to?(value) + value.is_a?(::ActiveSupport::OrderedOptions) + end + + def call + Core::InspectionTree.new do |t1| + # stree-ignore + t1.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t2| + t2.add_text "#" + end + end + end + end + end + end +end diff --git a/lib/super_diff/active_support/object_inspection.rb b/lib/super_diff/active_support/object_inspection.rb index 66e0e1f7..6deffb77 100644 --- a/lib/super_diff/active_support/object_inspection.rb +++ b/lib/super_diff/active_support/object_inspection.rb @@ -1,10 +1,22 @@ module SuperDiff module ActiveSupport module ObjectInspection - autoload( - :InspectionTreeBuilders, - "super_diff/active_support/object_inspection/inspection_tree_builders" - ) + module InspectionTreeBuilders + def self.const_missing(missing_const_name) + if ActiveSupport::InspectionTreeBuilders.const_defined?( + missing_const_name + ) + warn <<~EOT + WARNING: SuperDiff::ActiveSupport::ObjectInspection::InspectionTreeBuilders::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::ActiveSupport::InspectionTreeBuilders::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + ActiveSupport::InspectionTreeBuilders.const_get(missing_const_name) + else + super + end + end + end end end end diff --git a/lib/super_diff/active_support/object_inspection/inspection_tree_builders.rb b/lib/super_diff/active_support/object_inspection/inspection_tree_builders.rb deleted file mode 100644 index 412218e3..00000000 --- a/lib/super_diff/active_support/object_inspection/inspection_tree_builders.rb +++ /dev/null @@ -1,16 +0,0 @@ -module SuperDiff - module ActiveSupport - module ObjectInspection - module InspectionTreeBuilders - autoload( - :HashWithIndifferentAccess, - "super_diff/active_support/object_inspection/inspection_tree_builders/hash_with_indifferent_access" - ) - autoload( - :OrderedOptions, - "super_diff/active_support/object_inspection/inspection_tree_builders/ordered_options" - ) - end - end - end -end diff --git a/lib/super_diff/active_support/object_inspection/inspection_tree_builders/hash_with_indifferent_access.rb b/lib/super_diff/active_support/object_inspection/inspection_tree_builders/hash_with_indifferent_access.rb deleted file mode 100644 index 9af7448f..00000000 --- a/lib/super_diff/active_support/object_inspection/inspection_tree_builders/hash_with_indifferent_access.rb +++ /dev/null @@ -1,46 +0,0 @@ -module SuperDiff - module ActiveSupport - module ObjectInspection - module InspectionTreeBuilders - class HashWithIndifferentAccess < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - value.is_a?(::HashWithIndifferentAccess) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - # stree-ignore - t1.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t2| - t2.add_text "#" - end - end - end - end - end - end - end -end diff --git a/lib/super_diff/active_support/object_inspection/inspection_tree_builders/ordered_options.rb b/lib/super_diff/active_support/object_inspection/inspection_tree_builders/ordered_options.rb deleted file mode 100644 index 730d3817..00000000 --- a/lib/super_diff/active_support/object_inspection/inspection_tree_builders/ordered_options.rb +++ /dev/null @@ -1,46 +0,0 @@ -module SuperDiff - module ActiveSupport - module ObjectInspection - module InspectionTreeBuilders - class OrderedOptions < SuperDiff::ObjectInspection::InspectionTreeBuilders::Hash - def self.applies_to?(value) - value.is_a?(::ActiveSupport::OrderedOptions) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - # stree-ignore - t1.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t2| - t2.add_text "#" - end - end - end - end - end - end - end -end diff --git a/lib/super_diff/active_support/operation_tree_builders/hash_with_indifferent_access.rb b/lib/super_diff/active_support/operation_tree_builders/hash_with_indifferent_access.rb index 35e1d321..8231e5f1 100644 --- a/lib/super_diff/active_support/operation_tree_builders/hash_with_indifferent_access.rb +++ b/lib/super_diff/active_support/operation_tree_builders/hash_with_indifferent_access.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveSupport module OperationTreeBuilders - class HashWithIndifferentAccess < SuperDiff::OperationTreeBuilders::Hash + class HashWithIndifferentAccess < Basic::OperationTreeBuilders::Hash def self.applies_to?(expected, actual) ( expected.is_a?(::HashWithIndifferentAccess) && actual.is_a?(::Hash) diff --git a/lib/super_diff/active_support/operation_tree_flatteners/hash_with_indifferent_access.rb b/lib/super_diff/active_support/operation_tree_flatteners/hash_with_indifferent_access.rb index 2c7c0727..685968d4 100644 --- a/lib/super_diff/active_support/operation_tree_flatteners/hash_with_indifferent_access.rb +++ b/lib/super_diff/active_support/operation_tree_flatteners/hash_with_indifferent_access.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveSupport module OperationTreeFlatteners - class HashWithIndifferentAccess < SuperDiff::OperationTreeFlatteners::Hash + class HashWithIndifferentAccess < Basic::OperationTreeFlatteners::Hash protected def open_token diff --git a/lib/super_diff/active_support/operation_trees/hash_with_indifferent_access.rb b/lib/super_diff/active_support/operation_trees/hash_with_indifferent_access.rb index e06608e4..39e0c4f7 100644 --- a/lib/super_diff/active_support/operation_trees/hash_with_indifferent_access.rb +++ b/lib/super_diff/active_support/operation_trees/hash_with_indifferent_access.rb @@ -1,7 +1,7 @@ module SuperDiff module ActiveSupport module OperationTrees - class HashWithIndifferentAccess < SuperDiff::OperationTrees::Base + class HashWithIndifferentAccess < Core::AbstractOperationTree protected def operation_tree_flattener_class diff --git a/lib/super_diff/basic.rb b/lib/super_diff/basic.rb new file mode 100644 index 00000000..82c9dd14 --- /dev/null +++ b/lib/super_diff/basic.rb @@ -0,0 +1,48 @@ +require "super_diff/basic/differs" +require "super_diff/basic/inspection_tree_builders" +require "super_diff/basic/operation_tree_builders" +require "super_diff/basic/operation_tree_flatteners" +require "super_diff/basic/operation_trees" + +module SuperDiff + module Basic + autoload :DiffFormatters, "super_diff/basic/diff_formatters" + + SuperDiff.configuration.tap do |config| + config.add_extra_differ_classes( + Differs::Array, + Differs::Hash, + Differs::TimeLike, + Differs::DateLike, + Differs::MultilineString, + Differs::CustomObject, + Differs::DefaultObject + ) + + config.add_extra_inspection_tree_builder_classes( + InspectionTreeBuilders::CustomObject, + InspectionTreeBuilders::Array, + InspectionTreeBuilders::Hash, + InspectionTreeBuilders::Primitive, + InspectionTreeBuilders::TimeLike, + InspectionTreeBuilders::DateLike, + InspectionTreeBuilders::DefaultObject + ) + + config.add_extra_operation_tree_builder_classes( + OperationTreeBuilders::Array, + OperationTreeBuilders::Hash, + OperationTreeBuilders::TimeLike, + OperationTreeBuilders::DateLike, + OperationTreeBuilders::CustomObject + ) + + config.add_extra_operation_tree_classes( + OperationTrees::Array, + OperationTrees::Hash, + OperationTrees::CustomObject, + OperationTrees::DefaultObject + ) + end + end +end diff --git a/lib/super_diff/basic/diff_formatters.rb b/lib/super_diff/basic/diff_formatters.rb new file mode 100644 index 00000000..cac4893d --- /dev/null +++ b/lib/super_diff/basic/diff_formatters.rb @@ -0,0 +1,11 @@ +module SuperDiff + module Basic + module DiffFormatters + autoload :Collection, "super_diff/basic/diff_formatters/collection" + autoload( + :MultilineString, + "super_diff/basic/diff_formatters/multiline_string" + ) + end + end +end diff --git a/lib/super_diff/basic/diff_formatters/collection.rb b/lib/super_diff/basic/diff_formatters/collection.rb new file mode 100644 index 00000000..5cfbe31d --- /dev/null +++ b/lib/super_diff/basic/diff_formatters/collection.rb @@ -0,0 +1,135 @@ +module SuperDiff + module Basic + module DiffFormatters + # TODO: Remove + class Collection + extend AttrExtras.mixin + + ICONS = { delete: "-", insert: "+" }.freeze + STYLES = { insert: :actual, delete: :expected, noop: :plain }.freeze + + method_object( + %i[ + open_token! + close_token! + operation_tree! + indent_level! + add_comma! + collection_prefix! + build_item_prefix! + ] + ) + + def call + lines.join("\n") + end + + private + + attr_query :add_comma? + + def lines + [ + " #{indentation}#{collection_prefix}#{open_token}", + *contents, + " #{indentation}#{close_token}#{comma}" + ] + end + + def contents + operation_tree.map do |operation| + if operation.name == :change + handle_change_operation(operation) + else + handle_non_change_operation(operation) + end + end + end + + def handle_change_operation(operation) + SuperDiff::RecursionGuard.guarding_recursion_of( + operation.left_collection, + operation.right_collection + ) do |already_seen| + if already_seen + raise "Infinite recursion!" + else + operation.child_operations.to_diff( + indent_level: indent_level + 1, + collection_prefix: build_item_prefix.call(operation), + add_comma: operation.should_add_comma_after_displaying? + ) + end + end + end + + def handle_non_change_operation(operation) + icon = ICONS.fetch(operation.name, " ") + style_name = STYLES.fetch(operation.name, :normal) + chunk = + build_chunk_for( + operation, + prefix: build_item_prefix.call(operation), + icon: icon + ) + + chunk << "," if operation.should_add_comma_after_displaying? + + style_chunk(style_name, chunk) + end + + def build_chunk_for(operation, prefix:, icon:) + if operation.value.equal?(operation.collection) + build_chunk_from_string( + SuperDiff::RecursionGuard::PLACEHOLDER, + prefix: build_item_prefix.call(operation), + icon: icon + ) + else + build_chunk_by_inspecting( + operation.value, + prefix: build_item_prefix.call(operation), + icon: icon + ) + end + end + + def build_chunk_by_inspecting(value, prefix:, icon:) + inspection = SuperDiff.inspect_object(value, as_single_line: false) + build_chunk_from_string(inspection, prefix: prefix, icon: icon) + end + + def build_chunk_from_string(value, prefix:, icon:) + value + .split("\n") + .map + .with_index do |line, index| + [ + icon, + " ", + indentation(offset: 1), + (index == 0 ? prefix : ""), + line + ].join + end + .join("\n") + end + + def style_chunk(style_name, chunk) + chunk + .split("\n") + .map { |line| Helpers.style(style_name, line) } + .join("\n") + end + + def indentation(offset: 0) + " " * (indent_level + offset) + end + + def comma + add_comma? ? "," : "" + end + end + end + end +end diff --git a/lib/super_diff/basic/diff_formatters/multiline_string.rb b/lib/super_diff/basic/diff_formatters/multiline_string.rb new file mode 100644 index 00000000..a5a58dfb --- /dev/null +++ b/lib/super_diff/basic/diff_formatters/multiline_string.rb @@ -0,0 +1,34 @@ +module SuperDiff + module Core + module DiffFormatters + # TODO: Remove + class MultilineString < Base + def self.applies_to?(operation_tree) + operation_tree.is_a?(OperationTrees::MultilineString) + end + + def call + lines.join("\n") + end + + private + + def lines + operation_tree.reduce([]) do |array, operation| + case operation.name + when :change + array << Helpers.style(:expected, "- #{operation.left_value}") + array << Helpers.style(:actual, "+ #{operation.right_value}") + when :delete + array << Helpers.style(:expected, "- #{operation.value}") + when :insert + array << Helpers.style(:actual, "+ #{operation.value}") + else + array << Helpers.style(:plain, " #{operation.value}") + end + end + end + end + end + end +end diff --git a/lib/super_diff/basic/differs.rb b/lib/super_diff/basic/differs.rb new file mode 100644 index 00000000..1e154929 --- /dev/null +++ b/lib/super_diff/basic/differs.rb @@ -0,0 +1,24 @@ +module SuperDiff + module Basic + module Differs + autoload :Array, "super_diff/basic/differs/array" + autoload :CustomObject, "super_diff/basic/differs/custom_object" + autoload :DateLike, "super_diff/basic/differs/date_like" + autoload :DefaultObject, "super_diff/basic/differs/default_object" + autoload :Hash, "super_diff/basic/differs/hash" + autoload :MultilineString, "super_diff/basic/differs/multiline_string" + autoload :TimeLike, "super_diff/basic/differs/time_like" + + class Main + def self.call(*args) + warn <<~EOT + WARNING: SuperDiff::Differs::Main.call(...) is deprecated and will be removed in the next major release. + Please use SuperDiff.diff(...) instead. + #{caller_locations.join("\n")} + EOT + SuperDiff.diff(*args) + end + end + end + end +end diff --git a/lib/super_diff/basic/differs/array.rb b/lib/super_diff/basic/differs/array.rb new file mode 100644 index 00000000..601059b3 --- /dev/null +++ b/lib/super_diff/basic/differs/array.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module Differs + class Array < Core::AbstractDiffer + def self.applies_to?(expected, actual) + expected.is_a?(::Array) && actual.is_a?(::Array) + end + + protected + + def operation_tree_builder_class + OperationTreeBuilders::Array + end + end + end + end +end diff --git a/lib/super_diff/basic/differs/custom_object.rb b/lib/super_diff/basic/differs/custom_object.rb new file mode 100644 index 00000000..47ff00fb --- /dev/null +++ b/lib/super_diff/basic/differs/custom_object.rb @@ -0,0 +1,19 @@ +module SuperDiff + module Basic + module Differs + class CustomObject < Core::AbstractDiffer + def self.applies_to?(expected, actual) + expected.class == actual.class && + expected.respond_to?(:attributes_for_super_diff) && + actual.respond_to?(:attributes_for_super_diff) + end + + protected + + def operation_tree_builder_class + OperationTreeBuilders::CustomObject + end + end + end + end +end diff --git a/lib/super_diff/basic/differs/date_like.rb b/lib/super_diff/basic/differs/date_like.rb new file mode 100644 index 00000000..22219b60 --- /dev/null +++ b/lib/super_diff/basic/differs/date_like.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module Differs + class DateLike < Core::AbstractDiffer + def self.applies_to?(expected, actual) + SuperDiff.date_like?(expected) && SuperDiff.date_like?(actual) + end + + protected + + def operation_tree_builder_class + OperationTreeBuilders::DateLike + end + end + end + end +end diff --git a/lib/super_diff/basic/differs/default_object.rb b/lib/super_diff/basic/differs/default_object.rb new file mode 100644 index 00000000..0b0b7307 --- /dev/null +++ b/lib/super_diff/basic/differs/default_object.rb @@ -0,0 +1,24 @@ +module SuperDiff + module Basic + module Differs + class DefaultObject < Core::AbstractDiffer + def self.applies_to?(expected, actual) + expected.class == actual.class + end + + protected + + def operation_tree + SuperDiff.build_operation_tree_for( + expected, + actual, + extra_operation_tree_builder_classes: [ + SuperDiff::Basic::OperationTreeBuilders::DefaultObject + ], + raise_if_nothing_applies: true + ) + end + end + end + end +end diff --git a/lib/super_diff/basic/differs/hash.rb b/lib/super_diff/basic/differs/hash.rb new file mode 100644 index 00000000..17834446 --- /dev/null +++ b/lib/super_diff/basic/differs/hash.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module Differs + class Hash < Core::AbstractDiffer + def self.applies_to?(expected, actual) + expected.is_a?(::Hash) && actual.is_a?(::Hash) + end + + protected + + def operation_tree_builder_class + OperationTreeBuilders::Hash + end + end + end + end +end diff --git a/lib/super_diff/basic/differs/multiline_string.rb b/lib/super_diff/basic/differs/multiline_string.rb new file mode 100644 index 00000000..72fdf12e --- /dev/null +++ b/lib/super_diff/basic/differs/multiline_string.rb @@ -0,0 +1,18 @@ +module SuperDiff + module Basic + module Differs + class MultilineString < Core::AbstractDiffer + def self.applies_to?(expected, actual) + expected.is_a?(::String) && actual.is_a?(::String) && + (expected.include?("\n") || actual.include?("\n")) + end + + protected + + def operation_tree_builder_class + OperationTreeBuilders::MultilineString + end + end + end + end +end diff --git a/lib/super_diff/basic/differs/time_like.rb b/lib/super_diff/basic/differs/time_like.rb new file mode 100644 index 00000000..9837e350 --- /dev/null +++ b/lib/super_diff/basic/differs/time_like.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module Differs + class TimeLike < Core::AbstractDiffer + def self.applies_to?(expected, actual) + SuperDiff.time_like?(expected) && SuperDiff.time_like?(actual) + end + + protected + + def operation_tree_builder_class + OperationTreeBuilders::TimeLike + end + end + end + end +end diff --git a/lib/super_diff/basic/inspection_tree_builders.rb b/lib/super_diff/basic/inspection_tree_builders.rb new file mode 100644 index 00000000..e7101e1b --- /dev/null +++ b/lib/super_diff/basic/inspection_tree_builders.rb @@ -0,0 +1,20 @@ +module SuperDiff + module Basic + module InspectionTreeBuilders + autoload :Array, "super_diff/basic/inspection_tree_builders/array" + autoload( + :CustomObject, + "super_diff/basic/inspection_tree_builders/custom_object" + ) + autoload( + :DefaultObject, + "super_diff/basic/inspection_tree_builders/default_object" + ) + autoload :Hash, "super_diff/basic/inspection_tree_builders/hash" + autoload :Primitive, "super_diff/basic/inspection_tree_builders/primitive" + autoload :String, "super_diff/basic/inspection_tree_builders/string" + autoload :TimeLike, "super_diff/basic/inspection_tree_builders/time_like" + autoload :DateLike, "super_diff/basic/inspection_tree_builders/date_like" + end + end +end diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/array.rb b/lib/super_diff/basic/inspection_tree_builders/array.rb similarity index 90% rename from lib/super_diff/object_inspection/inspection_tree_builders/array.rb rename to lib/super_diff/basic/inspection_tree_builders/array.rb index 1a6bfb2b..f4105cbf 100644 --- a/lib/super_diff/object_inspection/inspection_tree_builders/array.rb +++ b/lib/super_diff/basic/inspection_tree_builders/array.rb @@ -1,13 +1,13 @@ module SuperDiff - module ObjectInspection + module Basic module InspectionTreeBuilders - class Array < Base + class Array < Core::AbstractInspectionTreeBuilder def self.applies_to?(value) value.is_a?(::Array) end def call - InspectionTree.new do |t1| + Core::InspectionTree.new do |t1| t1.only_when empty do |t2| # stree-ignore t2.as_lines_when_rendering_to_lines do |t3| diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/custom_object.rb b/lib/super_diff/basic/inspection_tree_builders/custom_object.rb similarity index 88% rename from lib/super_diff/object_inspection/inspection_tree_builders/custom_object.rb rename to lib/super_diff/basic/inspection_tree_builders/custom_object.rb index 741db24a..92016ae4 100644 --- a/lib/super_diff/object_inspection/inspection_tree_builders/custom_object.rb +++ b/lib/super_diff/basic/inspection_tree_builders/custom_object.rb @@ -1,13 +1,13 @@ module SuperDiff - module ObjectInspection + module Basic module InspectionTreeBuilders - class CustomObject < Base + class CustomObject < Core::AbstractInspectionTreeBuilder def self.applies_to?(value) value.respond_to?(:attributes_for_super_diff) end def call - InspectionTree.new do |t1| + Core::InspectionTree.new do |t1| t1.as_lines_when_rendering_to_lines( collection_bookend: :open ) do |t2| diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/date_like.rb b/lib/super_diff/basic/inspection_tree_builders/date_like.rb similarity index 91% rename from lib/super_diff/object_inspection/inspection_tree_builders/date_like.rb rename to lib/super_diff/basic/inspection_tree_builders/date_like.rb index a8dc6402..ced3ef24 100644 --- a/lib/super_diff/object_inspection/inspection_tree_builders/date_like.rb +++ b/lib/super_diff/basic/inspection_tree_builders/date_like.rb @@ -1,13 +1,13 @@ module SuperDiff - module ObjectInspection + module Basic module InspectionTreeBuilders - class DateLike < Base + class DateLike < Core::AbstractInspectionTreeBuilder def self.applies_to?(value) SuperDiff.date_like?(value) end def call - InspectionTree.new do |t1| + Core::InspectionTree.new do |t1| t1.as_lines_when_rendering_to_lines( collection_bookend: :open ) do |t2| diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/default_object.rb b/lib/super_diff/basic/inspection_tree_builders/default_object.rb similarity index 84% rename from lib/super_diff/object_inspection/inspection_tree_builders/default_object.rb rename to lib/super_diff/basic/inspection_tree_builders/default_object.rb index 42a54d58..c479224b 100644 --- a/lib/super_diff/object_inspection/inspection_tree_builders/default_object.rb +++ b/lib/super_diff/basic/inspection_tree_builders/default_object.rb @@ -1,18 +1,17 @@ module SuperDiff - module ObjectInspection + module Basic module InspectionTreeBuilders - class DefaultObject < Base + class DefaultObject < Core::AbstractInspectionTreeBuilder def self.applies_to?(_value) true end def call - InspectionTree.new do |t1| + Core::InspectionTree.new do |t1| t1.only_when empty do |t2| t2.as_lines_when_rendering_to_lines do |t3| t3.add_text( - "#<#{object.class.name}:" + - SuperDiff::Helpers.object_address_for(object) + ">" + "#<#{object.class.name}:" + object_address_for(object) + ">" ) end end @@ -22,8 +21,7 @@ def call collection_bookend: :open ) do |t3| t3.add_text( - "#<#{object.class.name}:" + - SuperDiff::Helpers.object_address_for(object) + "#<#{object.class.name}:" + object_address_for(object) ) # stree-ignore diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/hash.rb b/lib/super_diff/basic/inspection_tree_builders/hash.rb similarity index 92% rename from lib/super_diff/object_inspection/inspection_tree_builders/hash.rb rename to lib/super_diff/basic/inspection_tree_builders/hash.rb index ecce40bd..f784b51b 100644 --- a/lib/super_diff/object_inspection/inspection_tree_builders/hash.rb +++ b/lib/super_diff/basic/inspection_tree_builders/hash.rb @@ -1,13 +1,13 @@ module SuperDiff - module ObjectInspection + module Basic module InspectionTreeBuilders - class Hash < Base + class Hash < Core::AbstractInspectionTreeBuilder def self.applies_to?(value) value.is_a?(::Hash) end def call - InspectionTree.new do |t1| + Core::InspectionTree.new do |t1| t1.only_when empty do |t2| # stree-ignore t2.as_lines_when_rendering_to_lines do |t3| diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/primitive.rb b/lib/super_diff/basic/inspection_tree_builders/primitive.rb similarity index 74% rename from lib/super_diff/object_inspection/inspection_tree_builders/primitive.rb rename to lib/super_diff/basic/inspection_tree_builders/primitive.rb index 4a3b021e..a1c4602f 100644 --- a/lib/super_diff/object_inspection/inspection_tree_builders/primitive.rb +++ b/lib/super_diff/basic/inspection_tree_builders/primitive.rb @@ -1,13 +1,13 @@ module SuperDiff - module ObjectInspection + module Basic module InspectionTreeBuilders - class Primitive < Base + class Primitive < Core::AbstractInspectionTreeBuilder def self.applies_to?(value) SuperDiff.primitive?(value) || value.is_a?(::String) end def call - InspectionTree.new do |t1| + Core::InspectionTree.new do |t1| t1.as_lines_when_rendering_to_lines do |t2| t2.add_text object.inspect end diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/time_like.rb b/lib/super_diff/basic/inspection_tree_builders/time_like.rb similarity index 93% rename from lib/super_diff/object_inspection/inspection_tree_builders/time_like.rb rename to lib/super_diff/basic/inspection_tree_builders/time_like.rb index 0739a2d6..61908030 100644 --- a/lib/super_diff/object_inspection/inspection_tree_builders/time_like.rb +++ b/lib/super_diff/basic/inspection_tree_builders/time_like.rb @@ -1,13 +1,13 @@ module SuperDiff - module ObjectInspection + module Basic module InspectionTreeBuilders - class TimeLike < Base + class TimeLike < Core::AbstractInspectionTreeBuilder def self.applies_to?(value) SuperDiff.time_like?(value) end def call - InspectionTree.new do |t1| + Core::InspectionTree.new do |t1| t1.as_lines_when_rendering_to_lines( collection_bookend: :open ) do |t2| diff --git a/lib/super_diff/basic/operation_tree_builders.rb b/lib/super_diff/basic/operation_tree_builders.rb new file mode 100644 index 00000000..a5bf8d03 --- /dev/null +++ b/lib/super_diff/basic/operation_tree_builders.rb @@ -0,0 +1,34 @@ +module SuperDiff + module Basic + module OperationTreeBuilders + autoload :Array, "super_diff/basic/operation_tree_builders/array" + autoload( + :CustomObject, + "super_diff/basic/operation_tree_builders/custom_object" + ) + autoload( + :DefaultObject, + "super_diff/basic/operation_tree_builders/default_object" + ) + autoload :Hash, "super_diff/basic/operation_tree_builders/hash" + # TODO: Where is this used? + autoload( + :MultilineString, + "super_diff/basic/operation_tree_builders/multiline_string" + ) + autoload :TimeLike, "super_diff/basic/operation_tree_builders/time_like" + autoload :DateLike, "super_diff/basic/operation_tree_builders/date_like" + + class Main + def self.call(*args) + warn <<~EOT + WARNING: SuperDiff::OperationTreeBuilders::Main.call(...) is deprecated and will be removed in the next major release. + Please use SuperDiff.build_operation_tree_for(...) instead. + #{caller_locations.join("\n")} + EOT + SuperDiff.build_operation_tree_for(*args) + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_builders/array.rb b/lib/super_diff/basic/operation_tree_builders/array.rb new file mode 100644 index 00000000..8fa4e43b --- /dev/null +++ b/lib/super_diff/basic/operation_tree_builders/array.rb @@ -0,0 +1,111 @@ +require "diff-lcs" + +module SuperDiff + module Basic + module OperationTreeBuilders + class Array < Core::AbstractOperationTreeBuilder + def self.applies_to?(expected, actual) + expected.is_a?(::Array) && actual.is_a?(::Array) + end + + def call + Diff::LCS.traverse_balanced(expected, actual, lcs_callbacks) + operation_tree + end + + private + + def lcs_callbacks + @_lcs_callbacks ||= + LcsCallbacks.new( + operation_tree: operation_tree, + expected: expected, + actual: actual, + compare: method(:compare) + ) + end + + def operation_tree + @_operation_tree ||= OperationTrees::Array.new([]) + end + + class LcsCallbacks + extend AttrExtras.mixin + + pattr_initialize %i[operation_tree! expected! actual! compare!] + public :operation_tree + + def match(event) + add_noop_operation(event) + end + + def discard_a(event) + add_delete_operation(event) + end + + def discard_b(event) + add_insert_operation(event) + end + + def change(event) + children = compare.(event.old_element, event.new_element) + + if children + add_change_operation(event, children) + else + add_delete_operation(event) + add_insert_operation(event) + end + end + + private + + def add_delete_operation(event) + operation_tree << Core::UnaryOperation.new( + name: :delete, + collection: expected, + key: event.old_position, + value: event.old_element, + index: event.old_position + ) + end + + def add_insert_operation(event) + operation_tree << Core::UnaryOperation.new( + name: :insert, + collection: actual, + key: event.new_position, + value: event.new_element, + index: event.new_position + ) + end + + def add_noop_operation(event) + operation_tree << Core::UnaryOperation.new( + name: :noop, + collection: actual, + key: event.new_position, + value: event.new_element, + index: event.new_position + ) + end + + def add_change_operation(event, children) + operation_tree << Core::BinaryOperation.new( + name: :change, + left_collection: expected, + right_collection: actual, + left_key: event.old_position, + right_key: event.new_position, + left_value: event.old_element, + right_value: event.new_element, + left_index: event.old_position, + right_index: event.new_position, + children: children + ) + end + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_builders/custom_object.rb b/lib/super_diff/basic/operation_tree_builders/custom_object.rb new file mode 100644 index 00000000..14f0c543 --- /dev/null +++ b/lib/super_diff/basic/operation_tree_builders/custom_object.rb @@ -0,0 +1,42 @@ +module SuperDiff + module Basic + module OperationTreeBuilders + class CustomObject < DefaultObject + def self.applies_to?(expected, actual) + expected.class == actual.class && + expected.respond_to?(:attributes_for_super_diff) && + actual.respond_to?(:attributes_for_super_diff) + end + + protected + + def build_operation_tree + # NOTE: It doesn't matter whether we use expected or actual here, + # because all we care about is the name of the class + OperationTrees::CustomObject.new([], underlying_object: actual) + end + + def attribute_names + expected.attributes_for_super_diff.keys & + actual.attributes_for_super_diff.keys + end + + private + + attr_reader :expected_attributes, :actual_attributes + + def establish_expected_and_actual_attributes + @expected_attributes = + attribute_names.reduce({}) do |hash, name| + hash.merge(name => expected.public_send(name)) + end + + @actual_attributes = + attribute_names.reduce({}) do |hash, name| + hash.merge(name => actual.public_send(name)) + end + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_builders/date_like.rb b/lib/super_diff/basic/operation_tree_builders/date_like.rb new file mode 100644 index 00000000..2f8a9b4a --- /dev/null +++ b/lib/super_diff/basic/operation_tree_builders/date_like.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module OperationTreeBuilders + class DateLike < CustomObject + def self.applies_to?(expected, actual) + SuperDiff.date_like?(expected) && SuperDiff.date_like?(actual) + end + + protected + + def attribute_names + %w[year month day] + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_builders/default_object.rb b/lib/super_diff/basic/operation_tree_builders/default_object.rb new file mode 100644 index 00000000..f1ecbf4b --- /dev/null +++ b/lib/super_diff/basic/operation_tree_builders/default_object.rb @@ -0,0 +1,117 @@ +module SuperDiff + module Basic + module OperationTreeBuilders + class DefaultObject < Core::AbstractOperationTreeBuilder + def self.applies_to?(_expected, _actual) + true + end + + def initialize(*args) + super(*args) + + establish_expected_and_actual_attributes + end + + protected + + def unary_operations + attribute_names.reduce([]) do |operations, name| + possibly_add_noop_operation_to(operations, name) + possibly_add_delete_operation_to(operations, name) + possibly_add_insert_operation_to(operations, name) + operations + end + end + + def build_operation_tree + # XXX This assumes that `expected` and `actual` are the same + # TODO: Does this need to find the operation tree matching `actual`? + OperationTrees::DefaultObject.new([], underlying_object: actual) + end + + def attribute_names + ( + expected.instance_variables.sort & actual.instance_variables.sort + ).map { |variable_name| variable_name[1..-1] } + end + + private + + attr_reader :expected_attributes, :actual_attributes + + def establish_expected_and_actual_attributes + @expected_attributes = + attribute_names.reduce({}) do |hash, name| + hash.merge(name => expected.instance_variable_get("@#{name}")) + end + + @actual_attributes = + attribute_names.reduce({}) do |hash, name| + hash.merge(name => actual.instance_variable_get("@#{name}")) + end + end + + def possibly_add_noop_operation_to(operations, attribute_name) + if should_add_noop_operation?(attribute_name) + operations << Core::UnaryOperation.new( + name: :noop, + collection: actual_attributes, + key: attribute_name, + index: attribute_names.index(attribute_name), + value: actual_attributes[attribute_name] + ) + end + end + + def should_add_noop_operation?(attribute_name) + expected_attributes.include?(attribute_name) && + actual_attributes.include?(attribute_name) && + expected_attributes[attribute_name] == + actual_attributes[attribute_name] + end + + def possibly_add_delete_operation_to(operations, attribute_name) + if should_add_delete_operation?(attribute_name) + operations << Core::UnaryOperation.new( + name: :delete, + collection: expected_attributes, + key: attribute_name, + index: attribute_names.index(attribute_name), + value: expected_attributes[attribute_name] + ) + end + end + + def should_add_delete_operation?(attribute_name) + expected_attributes.include?(attribute_name) && + ( + !actual_attributes.include?(attribute_name) || + expected_attributes[attribute_name] != + actual_attributes[attribute_name] + ) + end + + def possibly_add_insert_operation_to(operations, attribute_name) + if should_add_insert_operation?(attribute_name) + operations << Core::UnaryOperation.new( + name: :insert, + collection: actual_attributes, + key: attribute_name, + index: attribute_names.index(attribute_name), + value: actual_attributes[attribute_name] + ) + end + end + + def should_add_insert_operation?(attribute_name) + !expected_attributes.include?(attribute_name) || + ( + actual_attributes.include?(attribute_name) && + expected_attributes[attribute_name] != + actual_attributes[attribute_name] + ) + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_builders/hash.rb b/lib/super_diff/basic/operation_tree_builders/hash.rb new file mode 100644 index 00000000..df09ceda --- /dev/null +++ b/lib/super_diff/basic/operation_tree_builders/hash.rb @@ -0,0 +1,222 @@ +module SuperDiff + module Basic + module OperationTreeBuilders + class Hash < Core::AbstractOperationTreeBuilder + def self.applies_to?(expected, actual) + expected.is_a?(::Hash) && actual.is_a?(::Hash) + end + + protected + + def unary_operations + unary_operations_using_variant_of_patience_algorithm + end + + def build_operation_tree + OperationTrees::Hash.new([]) + end + + private + + def unary_operations_using_variant_of_patience_algorithm + operations = [] + aks, eks = actual.keys, expected.keys + previous_ei, ei = nil, 0 + ai = 0 + + # When diffing a hash, we're more interested in the 'actual' version + # than the 'expected' version, because that's the ultimate truth. + # Therefore, the diff is presented from the perspective of the 'actual' + # hash, and we start off by looping over it. + while ai < aks.size + ak = aks[ai] + av, ev = actual[ak], expected[ak] + # While we iterate over 'actual' in order, we jump all over + # 'expected', trying to match up its keys with the keys in 'actual' as + # much as possible. + ei = eks.index(ak) + + if should_add_noop_operation?(ak) + # (If we're here, it probably means that the key we're pointing to + # in the 'actual' and 'expected' hashes have the same value.) + + if ei && previous_ei && (ei - previous_ei) > 1 + # If we've jumped from one operation in the 'expected' hash to + # another operation later in 'expected' (due to the fact that the + # 'expected' hash is in a different order than 'actual'), collect + # any delete operations in between and add them to our operations + # array as deletes before adding the noop. If we don't do this + # now, then those deletes will disappear. (Again, we are mainly + # iterating over 'actual', so this is the only way to catch all of + # the keys in 'expected'.) + (previous_ei + 1).upto(ei - 1) do |ei2| + ek = eks[ei2] + ev2, av2 = expected[ek], actual[ek] + + if ( + (!actual.include?(ek) || ev2 != av2) && + operations.none? do |operation| + %i[delete noop].include?(operation.name) && + operation.key == ek + end + ) + operations << Core::UnaryOperation.new( + name: :delete, + collection: expected, + key: ek, + value: ev2, + index: ei2 + ) + end + end + end + + operations << Core::UnaryOperation.new( + name: :noop, + collection: actual, + key: ak, + value: av, + index: ai + ) + else + # (If we're here, it probably means that the key in 'actual' isn't + # present in 'expected' or the values don't match.) + + if ( + (operations.empty? || operations.last.name == :noop) && + (ai == 0 || eks.include?(aks[ai - 1])) + ) + # If we go from a match in the last iteration to a missing or + # extra key in this one, or we're at the first key in 'actual' and + # it's missing or extra, look for deletes in the 'expected' hash + # and add them to our list of operations before we add the + # inserts. In most cases we will accomplish this by backtracking a + # bit to the key in 'expected' that matched the key in 'actual' we + # processed in the previous iteration (or just the first key in + # 'expected' if this is the first key in 'actual'), and then + # iterating from there through 'expected' until we reach the end + # or we hit some other condition (see below). + + start_index = + if ai > 0 + eks.index(aks[ai - 1]) + 1 + else + 0 + end + + start_index.upto(eks.size - 1) do |ei2| + ek = eks[ei2] + ev, av2 = expected[ek], actual[ek] + + if actual.include?(ek) && ev == av2 + # If the key in 'expected' we've landed on happens to be a + # match in 'actual', then stop, because it's going to be + # handled in some future iteration of the 'actual' loop. + break + elsif ( + aks[ai + 1..-1].any? do |k| + expected.include?(k) && expected[k] != actual[k] + end + ) + # While we backtracked a bit to iterate over 'expected', we + # now have to look ahead. If we will end up encountering a + # insert that matches this delete later, stop and go back to + # iterating over 'actual'. This is because the delete we would + # have added now will be added later when we encounter the + # associated insert, so we don't want to add it twice. + break + else + operations << Core::UnaryOperation.new( + name: :delete, + collection: expected, + key: ek, + value: ev, + index: ei2 + ) + end + + if ek == ak && ev != av + # If we're pointing to the same key in 'expected' as in + # 'actual', but with different values, go ahead and add an + # insert now to accompany the delete added above. That way + # they appear together, which will be easier to read. + operations << Core::UnaryOperation.new( + name: :insert, + collection: actual, + key: ak, + value: av, + index: ai + ) + end + end + end + + if ( + expected.include?(ak) && ev != av && + operations.none? do |op| + op.name == :delete && op.key == ak + end + ) + # If we're here, it means that we didn't encounter any delete + # operations above for whatever reason and so we need to add a + # delete to represent the fact that the value for this key has + # changed. + operations << Core::UnaryOperation.new( + name: :delete, + collection: expected, + key: ak, + value: expected[ak], + index: ei + ) + end + + if operations.none? { |op| op.name == :insert && op.key == ak } + # If we're here, it means that we didn't encounter any insert + # operations above. Since we already handled delete, the only + # alternative is that this key must not exist in 'expected', so + # we need to add an insert. + operations << Core::UnaryOperation.new( + name: :insert, + collection: actual, + key: ak, + value: av, + index: ai + ) + end + end + + ai += 1 + previous_ei = ei + end + + # The last thing to do is this: if there are keys in 'expected' that + # aren't in 'actual', and they aren't associated with any inserts to + # where they would have been added above, tack those deletes onto the + # end of our operations array. + (eks - aks - operations.map(&:key)).each do |ek| + ei = eks.index(ek) + ev = expected[ek] + + operations << Core::UnaryOperation.new( + name: :delete, + collection: expected, + key: ek, + value: ev, + index: ei + ) + end + + operations + end + + def should_add_noop_operation?(key) + expected.include?(key) && expected[key] == actual[key] + end + + def all_keys + actual.keys | expected.keys + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_builders/multiline_string.rb b/lib/super_diff/basic/operation_tree_builders/multiline_string.rb new file mode 100644 index 00000000..d6f1d9e2 --- /dev/null +++ b/lib/super_diff/basic/operation_tree_builders/multiline_string.rb @@ -0,0 +1,90 @@ +require "patience_diff" + +module SuperDiff + module Basic + module OperationTreeBuilders + class MultilineString < Core::AbstractOperationTreeBuilder + def self.applies_to?(expected, actual) + expected.is_a?(::String) && actual.is_a?(::String) && + (expected.include?("\n") || actual.include?("\n")) + end + + def initialize(*args) + super(*args) + + @original_expected = @expected + @original_actual = @actual + @expected = split_into_lines(@expected) + @actual = split_into_lines(@actual) + @sequence_matcher = PatienceDiff::SequenceMatcher.new + end + + protected + + def unary_operations + opcodes.flat_map do |code, a_start, a_end, b_start, b_end| + if code == :delete + add_delete_operations(a_start..a_end) + elsif code == :insert + add_insert_operations(b_start..b_end) + else + add_noop_operations(b_start..b_end) + end + end + end + + def build_operation_tree + OperationTrees::MultilineString.new([]) + end + + private + + attr_reader :sequence_matcher, :original_expected, :original_actual + + def split_into_lines(string) + string.scan(/.+(?:\r|\n|\r\n|\Z)/) + end + + def opcodes + sequence_matcher.diff_opcodes(expected, actual) + end + + def add_delete_operations(indices) + indices.map do |index| + Core::UnaryOperation.new( + name: :delete, + collection: expected, + key: index, + index: index, + value: expected[index] + ) + end + end + + def add_insert_operations(indices) + indices.map do |index| + Core::UnaryOperation.new( + name: :insert, + collection: actual, + key: index, + index: index, + value: actual[index] + ) + end + end + + def add_noop_operations(indices) + indices.map do |index| + Core::UnaryOperation.new( + name: :noop, + collection: actual, + key: index, + index: index, + value: actual[index] + ) + end + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_builders/time_like.rb b/lib/super_diff/basic/operation_tree_builders/time_like.rb new file mode 100644 index 00000000..4a1fcc4b --- /dev/null +++ b/lib/super_diff/basic/operation_tree_builders/time_like.rb @@ -0,0 +1,26 @@ +module SuperDiff + module Basic + module OperationTreeBuilders + class TimeLike < CustomObject + def self.applies_to?(expected, actual) + SuperDiff.time_like?(expected) && SuperDiff.time_like?(actual) + end + + protected + + def attribute_names + base = %w[year month day hour min sec subsec zone utc_offset] + + # If timezones are different, also show a normalized timestamp at the + # end of the diff to help visualize why they are different moments in + # time. + if actual.zone != expected.zone + base + ["utc"] + else + base + end + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_flatteners.rb b/lib/super_diff/basic/operation_tree_flatteners.rb new file mode 100644 index 00000000..e36e8008 --- /dev/null +++ b/lib/super_diff/basic/operation_tree_flatteners.rb @@ -0,0 +1,24 @@ +module SuperDiff + module Basic + module OperationTreeFlatteners + autoload :Array, "super_diff/basic/operation_tree_flatteners/array" + autoload( + :Collection, + "super_diff/basic/operation_tree_flatteners/collection" + ) + autoload( + :CustomObject, + "super_diff/basic/operation_tree_flatteners/custom_object" + ) + autoload( + :DefaultObject, + "super_diff/basic/operation_tree_flatteners/default_object" + ) + autoload :Hash, "super_diff/basic/operation_tree_flatteners/hash" + autoload( + :MultilineString, + "super_diff/basic/operation_tree_flatteners/multiline_string" + ) + end + end +end diff --git a/lib/super_diff/basic/operation_tree_flatteners/array.rb b/lib/super_diff/basic/operation_tree_flatteners/array.rb new file mode 100644 index 00000000..f7791ba2 --- /dev/null +++ b/lib/super_diff/basic/operation_tree_flatteners/array.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module OperationTreeFlatteners + class Array < Collection + protected + + def open_token + "[" + end + + def close_token + "]" + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_flatteners/collection.rb b/lib/super_diff/basic/operation_tree_flatteners/collection.rb new file mode 100644 index 00000000..93a68846 --- /dev/null +++ b/lib/super_diff/basic/operation_tree_flatteners/collection.rb @@ -0,0 +1,140 @@ +module SuperDiff + module Basic + module OperationTreeFlatteners + class Collection < Core::AbstractOperationTreeFlattener + protected + + def build_tiered_lines + [ + Core::Line.new( + type: :noop, + indentation_level: indentation_level, + value: open_token, + collection_bookend: :open + ), + *inner_lines, + Core::Line.new( + type: :noop, + indentation_level: indentation_level, + value: close_token, + collection_bookend: :close + ) + ] + end + + def inner_lines + @_inner_lines ||= + operation_tree.flat_map do |operation| + lines = + if operation.name == :change + build_lines_for_change_operation(operation) + else + build_lines_for_non_change_operation(operation) + end + + maybe_add_prefix_at_beginning_of_lines( + maybe_add_comma_at_end_of_lines(lines, operation), + operation + ) + end + end + + def maybe_add_prefix_at_beginning_of_lines(lines, operation) + if add_prefix_at_beginning_of_lines?(operation) + add_prefix_at_beginning_of_lines(lines, operation) + else + lines + end + end + + def add_prefix_at_beginning_of_lines?(operation) + !!item_prefix_for(operation) + end + + def add_prefix_at_beginning_of_lines(lines, operation) + [lines[0].prefixed_with(item_prefix_for(operation))] + lines[1..-1] + end + + def maybe_add_comma_at_end_of_lines(lines, operation) + if last_item_in_collection?(operation) + lines + else + add_comma_at_end_of_lines(lines) + end + end + + def last_item_in_collection?(operation) + if operation.name == :change + operation.left_index == operation.left_collection.size - 1 && + operation.right_index == operation.right_collection.size - 1 + else + operation.index == operation.collection.size - 1 + end + end + + def add_comma_at_end_of_lines(lines) + lines[0..-2] + [lines[-1].with_comma] + end + + def build_lines_for_change_operation(operation) + Core::RecursionGuard.guarding_recursion_of( + operation.left_collection, + operation.right_collection + ) do |already_seen| + if already_seen + raise InfiniteRecursionError + else + operation.children.flatten( + indentation_level: indentation_level + 1 + ) + end + end + end + + def build_lines_for_non_change_operation(operation) + indentation_level = @indentation_level + 1 + + if recursive_operation?(operation) + [ + Core::Line.new( + type: operation.name, + indentation_level: indentation_level, + value: Core::RecursionGuard::PLACEHOLDER + ) + ] + else + build_lines_from_inspection_of( + operation.value, + type: operation.name, + indentation_level: indentation_level + ) + end + end + + def recursive_operation?(operation) + operation.value.equal?(operation.collection) || + Core::RecursionGuard.already_seen?(operation.value) + end + + def item_prefix_for(_operation) + "" + end + + def build_lines_from_inspection_of(value, type:, indentation_level:) + SuperDiff.inspect_object( + value, + as_lines: true, + type: type, + indentation_level: indentation_level + ) + end + + class InfiniteRecursionError < StandardError + def initialize(_message = nil) + super("Unhandled recursive data structure encountered!") + end + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_flatteners/custom_object.rb b/lib/super_diff/basic/operation_tree_flatteners/custom_object.rb new file mode 100644 index 00000000..36dbb74b --- /dev/null +++ b/lib/super_diff/basic/operation_tree_flatteners/custom_object.rb @@ -0,0 +1,30 @@ +module SuperDiff + module Basic + module OperationTreeFlatteners + class CustomObject < Collection + protected + + def open_token + "#<%s {" % { class: operation_tree.underlying_object.class } + end + + def close_token + "}>" + end + + def item_prefix_for(operation) + key = + # Note: We could have used the right_key here too, they're both the + # same keys + if operation.respond_to?(:left_key) + operation.left_key + else + operation.key + end + + "#{key}: " + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_flatteners/default_object.rb b/lib/super_diff/basic/operation_tree_flatteners/default_object.rb new file mode 100644 index 00000000..4d994237 --- /dev/null +++ b/lib/super_diff/basic/operation_tree_flatteners/default_object.rb @@ -0,0 +1,32 @@ +module SuperDiff + module Basic + module OperationTreeFlatteners + class DefaultObject < Collection + protected + + def open_token + "#<#{operation_tree.underlying_object.class.name}:" + + Core::Helpers.object_address_for(operation_tree.underlying_object) + + " {" + end + + def close_token + "}>" + end + + def item_prefix_for(operation) + key = + # Note: We could have used the right_key here too, they're both the + # same keys + if operation.respond_to?(:left_key) + operation.left_key + else + operation.key + end + + "@#{key}=" + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_flatteners/hash.rb b/lib/super_diff/basic/operation_tree_flatteners/hash.rb new file mode 100644 index 00000000..1f35ee23 --- /dev/null +++ b/lib/super_diff/basic/operation_tree_flatteners/hash.rb @@ -0,0 +1,35 @@ +module SuperDiff + module Basic + module OperationTreeFlatteners + class Hash < Collection + protected + + def open_token + "{" + end + + def close_token + "}" + end + + def item_prefix_for(operation) + key = key_for(operation) + + format_keys_as_kwargs? ? "#{key}: " : "#{key.inspect} => " + end + + private + + def format_keys_as_kwargs? + operation_tree.all? { |operation| key_for(operation).is_a?(Symbol) } + end + + def key_for(operation) + # Note: We could have used the right_key here too, they're both the + # same keys + operation.respond_to?(:left_key) ? operation.left_key : operation.key + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_tree_flatteners/multiline_string.rb b/lib/super_diff/basic/operation_tree_flatteners/multiline_string.rb new file mode 100644 index 00000000..77048aca --- /dev/null +++ b/lib/super_diff/basic/operation_tree_flatteners/multiline_string.rb @@ -0,0 +1,20 @@ +module SuperDiff + module Basic + module OperationTreeFlatteners + class MultilineString < Core::AbstractOperationTreeFlattener + def build_tiered_lines + operation_tree.map do |operation| + Core::Line.new( + type: operation.name, + indentation_level: indentation_level, + # TODO: Test that quotes and things don't get escaped but escape + # characters do + value: + operation.value.inspect[1..-2].gsub(/\\"/, '"').gsub(/\\'/, "'") + ) + end + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_trees.rb b/lib/super_diff/basic/operation_trees.rb new file mode 100644 index 00000000..e505d0b1 --- /dev/null +++ b/lib/super_diff/basic/operation_trees.rb @@ -0,0 +1,25 @@ +module SuperDiff + module Basic + module OperationTrees + autoload :Array, "super_diff/basic/operation_trees/array" + autoload :CustomObject, "super_diff/basic/operation_trees/custom_object" + autoload :DefaultObject, "super_diff/basic/operation_trees/default_object" + autoload :Hash, "super_diff/basic/operation_trees/hash" + autoload( + :MultilineString, + "super_diff/basic/operation_trees/multiline_string" + ) + + class Main + def self.call(*args) + warn <<~EOT + WARNING: SuperDiff::OperationTrees::Main.call(...) is deprecated and will be removed in the next major release. + Please use SuperDiff.find_operation_tree_for(...) instead. + #{caller_locations.join("\n")} + EOT + SuperDiff.find_operation_tree_for(*args) + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_trees/array.rb b/lib/super_diff/basic/operation_trees/array.rb new file mode 100644 index 00000000..adc09c42 --- /dev/null +++ b/lib/super_diff/basic/operation_trees/array.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module OperationTrees + class Array < Core::AbstractOperationTree + def self.applies_to?(value) + value.is_a?(::Array) + end + + protected + + def operation_tree_flattener_class + OperationTreeFlatteners::Array + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_trees/custom_object.rb b/lib/super_diff/basic/operation_trees/custom_object.rb new file mode 100644 index 00000000..ce756caa --- /dev/null +++ b/lib/super_diff/basic/operation_trees/custom_object.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module OperationTrees + class CustomObject < DefaultObject + def self.applies_to?(value) + value.respond_to?(:attributes_for_super_diff) + end + + protected + + def operation_tree_flattener_class + OperationTreeFlatteners::CustomObject + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_trees/default_object.rb b/lib/super_diff/basic/operation_trees/default_object.rb new file mode 100644 index 00000000..57f40a2f --- /dev/null +++ b/lib/super_diff/basic/operation_trees/default_object.rb @@ -0,0 +1,42 @@ +module SuperDiff + module Basic + module OperationTrees + class DefaultObject < Core::AbstractOperationTree + def self.applies_to?(*) + true + end + + attr_reader :underlying_object + + def initialize(operations, underlying_object:) + super(operations) + @underlying_object = underlying_object + end + + def pretty_print(pp) + pp.text "#<#{self.class.name} " + pp.nest(1) do + pp.breakable + pp.text ":operations=>" + pp.group(1, "[", "]") do + pp.breakable + pp.seplist(self) { |value| pp.pp value } + end + pp.comma_breakable + pp.text ":underlying_object=>" + pp.object_address_group underlying_object do + # do nothing + end + end + pp.text ">" + end + + protected + + def operation_tree_flattener_class + OperationTreeFlatteners::DefaultObject + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_trees/hash.rb b/lib/super_diff/basic/operation_trees/hash.rb new file mode 100644 index 00000000..d6ced92a --- /dev/null +++ b/lib/super_diff/basic/operation_trees/hash.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module OperationTrees + class Hash < Core::AbstractOperationTree + def self.applies_to?(value) + value.is_a?(::Hash) + end + + protected + + def operation_tree_flattener_class + OperationTreeFlatteners::Hash + end + end + end + end +end diff --git a/lib/super_diff/basic/operation_trees/multiline_string.rb b/lib/super_diff/basic/operation_trees/multiline_string.rb new file mode 100644 index 00000000..9fd0bd25 --- /dev/null +++ b/lib/super_diff/basic/operation_trees/multiline_string.rb @@ -0,0 +1,17 @@ +module SuperDiff + module Basic + module OperationTrees + class MultilineString < Core::AbstractOperationTree + def self.applies_to?(value) + value.is_a?(::String) && value.is_a?(::String) + end + + protected + + def operation_tree_flattener_class + OperationTreeFlatteners::MultilineString + end + end + end + end +end diff --git a/lib/super_diff/colorized_document_extensions.rb b/lib/super_diff/colorized_document_extensions.rb deleted file mode 100644 index f8cbc1a5..00000000 --- a/lib/super_diff/colorized_document_extensions.rb +++ /dev/null @@ -1,18 +0,0 @@ -module SuperDiff - module ColorizedDocumentExtensions - def self.extended(extendee) - extendee.singleton_class.class_eval { alias_method :normal, :text } - end - - %i[actual border elision_marker expected header].each do |method_name| - define_method(method_name) do |*args, **opts, &block| - colorize( - *args, - **opts, - fg: SuperDiff.configuration.public_send("#{method_name}_color"), - &block - ) - end - end - end -end diff --git a/lib/super_diff/configuration.rb b/lib/super_diff/configuration.rb deleted file mode 100644 index 5fc1d47e..00000000 --- a/lib/super_diff/configuration.rb +++ /dev/null @@ -1,149 +0,0 @@ -module SuperDiff - class Configuration - attr_reader( - :extra_diff_formatter_classes, - :extra_differ_classes, - :extra_inspection_tree_builder_classes, - :extra_operation_tree_builder_classes, - :extra_operation_tree_classes - ) - attr_accessor( - :actual_color, - :border_color, - :color_enabled, - :diff_elision_enabled, - :diff_elision_maximum, - :elision_marker_color, - :expected_color, - :header_color, - :key_enabled - ) - - def initialize(options = {}) - @actual_color = :yellow - @border_color = :blue - @color_enabled = color_enabled_by_default? - @diff_elision_enabled = false - @diff_elision_maximum = 0 - @elision_marker_color = :cyan - @expected_color = :magenta - @extra_diff_formatter_classes = [].freeze - @extra_differ_classes = [].freeze - @extra_inspection_tree_builder_classes = [].freeze - @extra_operation_tree_builder_classes = [].freeze - @extra_operation_tree_classes = [].freeze - @header_color = :white - @key_enabled = true - - merge!(options) - end - - def initialize_dup(original) - super - @extra_differ_classes = original.extra_differ_classes.dup.freeze - @extra_operation_tree_builder_classes = - original.extra_operation_tree_builder_classes.dup.freeze - @extra_operation_tree_classes = - original.extra_operation_tree_classes.dup.freeze - @extra_inspection_tree_builder_classes = - original.extra_inspection_tree_builder_classes.dup.freeze - end - - def color_enabled? - @color_enabled - end - - def diff_elision_enabled? - @diff_elision_enabled - end - - def key_enabled? - @key_enabled - end - - def merge!(configuration_or_options) - options = - if configuration_or_options.is_a?(self.class) - configuration_or_options.to_h - else - configuration_or_options - end - - options.each { |key, value| instance_variable_set("@#{key}", value) } - - updated - end - - def updated - SuperDiff::Csi.color_enabled = color_enabled? - end - - def add_extra_diff_formatter_classes(*classes) - @extra_diff_formatter_classes = - (@extra_diff_formatter_classes + classes).freeze - end - alias_method( - :add_extra_diff_formatter_class, - :add_extra_diff_formatter_classes - ) - - def add_extra_differ_classes(*classes) - @extra_differ_classes = (@extra_differ_classes + classes).freeze - end - alias_method :add_extra_differ_class, :add_extra_differ_classes - - def add_extra_inspection_tree_builder_classes(*classes) - @extra_inspection_tree_builder_classes = - (@extra_inspection_tree_builder_classes + classes).freeze - end - alias_method( - :add_extra_inspection_tree_builder_class, - :add_extra_inspection_tree_builder_classes - ) - - def add_extra_operation_tree_builder_classes(*classes) - @extra_operation_tree_builder_classes = - (@extra_operation_tree_builder_classes + classes).freeze - end - alias_method( - :add_extra_operation_tree_builder_class, - :add_extra_operation_tree_builder_classes - ) - - def add_extra_operation_tree_classes(*classes) - @extra_operation_tree_classes = - (@extra_operation_tree_classes + classes).freeze - end - alias_method( - :add_extra_operation_tree_class, - :add_extra_operation_tree_classes - ) - - def to_h - { - actual_color: actual_color, - border_color: border_color, - color_enabled: color_enabled?, - diff_elision_enabled: diff_elision_enabled?, - diff_elision_maximum: diff_elision_maximum, - elision_marker_color: elision_marker_color, - expected_color: expected_color, - extra_diff_formatter_classes: extra_diff_formatter_classes.dup, - extra_differ_classes: extra_differ_classes.dup, - extra_inspection_tree_builder_classes: - extra_inspection_tree_builder_classes.dup, - extra_operation_tree_builder_classes: - extra_operation_tree_builder_classes.dup, - extra_operation_tree_classes: extra_operation_tree_classes.dup, - header_color: header_color, - key_enabled: key_enabled? - } - end - - private - - def color_enabled_by_default? - ENV["CI"] == "true" || $stdout.respond_to?(:tty?) && $stdout.tty? - end - end -end diff --git a/lib/super_diff/core.rb b/lib/super_diff/core.rb new file mode 100644 index 00000000..997dd91a --- /dev/null +++ b/lib/super_diff/core.rb @@ -0,0 +1,69 @@ +module SuperDiff + module Core + autoload :AbstractDiffer, "super_diff/core/abstract_differ" + autoload( + :AbstractInspectionTreeBuilder, + "super_diff/core/abstract_inspection_tree_builder" + ) + autoload :AbstractOperationTree, "super_diff/core/abstract_operation_tree" + autoload( + :AbstractOperationTreeBuilder, + "super_diff/core/abstract_operation_tree_builder" + ) + autoload( + :AbstractOperationTreeFlattener, + "super_diff/core/abstract_operation_tree_flattener" + ) + autoload :BinaryOperation, "super_diff/core/binary_operation" + autoload( + :ColorizedDocumentExtensions, + "super_diff/core/colorized_document_extensions" + ) + autoload :Configuration, "super_diff/core/configuration" + autoload :DifferDispatcher, "super_diff/core/differ_dispatcher" + autoload :GemVersion, "super_diff/core/gem_version" + autoload :Helpers, "super_diff/core/helpers" + autoload :ImplementationChecks, "super_diff/core/implementation_checks" + autoload :InspectionTree, "super_diff/core/inspection_tree" + autoload( + :InspectionTreeBuilderDispatcher, + "super_diff/core/inspection_tree_builder_dispatcher" + ) + autoload :InspectionTreeNodes, "super_diff/core/inspection_tree_nodes" + autoload :Line, "super_diff/core/line" + autoload( + :OperationTreeBuilderDispatcher, + "super_diff/core/operation_tree_builder_dispatcher" + ) + autoload( + :NoDifferAvailableError, + "super_diff/core/no_differ_available_error" + ) + autoload( + :NoInspectionTreeAvailableError, + "super_diff/core/no_inspection_tree_builder_available_error" + ) + autoload( + :NoOperationTreeBuilderAvailableError, + "super_diff/core/no_operation_tree_builder_available_error" + ) + autoload( + :NoOperationTreeAvailableError, + "super_diff/core/no_operation_tree_available_error" + ) + autoload :OperationTreeFinder, "super_diff/core/operation_tree_finder" + autoload( + :PrefixForNextInspectionTreeNode, + "super_diff/core/prefix_for_next_inspection_tree_node" + ) + autoload( + :PreludeForNextInspectionTreeNode, + "super_diff/core/prelude_for_next_inspection_tree_node" + ) + autoload :RecursionGuard, "super_diff/core/recursion_guard" + autoload :TieredLines, "super_diff/core/tiered_lines" + autoload :TieredLinesElider, "super_diff/core/tiered_lines_elider" + autoload :TieredLinesFormatter, "super_diff/core/tiered_lines_formatter" + autoload :UnaryOperation, "super_diff/core/unary_operation" + end +end diff --git a/lib/super_diff/differs/base.rb b/lib/super_diff/core/abstract_differ.rb similarity index 93% rename from lib/super_diff/differs/base.rb rename to lib/super_diff/core/abstract_differ.rb index 86eca7a1..2bc37eda 100644 --- a/lib/super_diff/differs/base.rb +++ b/lib/super_diff/core/abstract_differ.rb @@ -1,6 +1,6 @@ module SuperDiff - module Differs - class Base + module Core + class AbstractDiffer def self.applies_to?(_expected, _actual) raise NotImplementedError end diff --git a/lib/super_diff/core/abstract_inspection_tree_builder.rb b/lib/super_diff/core/abstract_inspection_tree_builder.rb new file mode 100644 index 00000000..acbd3f4c --- /dev/null +++ b/lib/super_diff/core/abstract_inspection_tree_builder.rb @@ -0,0 +1,26 @@ +module SuperDiff + module Core + class AbstractInspectionTreeBuilder + extend AttrExtras.mixin + extend ImplementationChecks + include ImplementationChecks + include Helpers + + def self.applies_to?(_value) + unimplemented_class_method! + end + + method_object :object + + def call + unimplemented_instance_method! + end + + protected + + def inspection_tree + unimplemented_instance_method! + end + end + end +end diff --git a/lib/super_diff/operation_trees/base.rb b/lib/super_diff/core/abstract_operation_tree.rb similarity index 89% rename from lib/super_diff/operation_trees/base.rb rename to lib/super_diff/core/abstract_operation_tree.rb index 2ff34683..f93b1b46 100644 --- a/lib/super_diff/operation_trees/base.rb +++ b/lib/super_diff/core/abstract_operation_tree.rb @@ -1,8 +1,8 @@ require "forwardable" module SuperDiff - module OperationTrees - class Base + module Core + class AbstractOperationTree def self.applies_to?(*) unimplemented_class_method! end @@ -46,6 +46,10 @@ def perhaps_elide(tiered_lines) end end + def ==(other) + other.is_a?(self.class) && other.to_a == to_a + end + private attr_reader :operations diff --git a/lib/super_diff/operation_tree_builders/base.rb b/lib/super_diff/core/abstract_operation_tree_builder.rb similarity index 91% rename from lib/super_diff/operation_tree_builders/base.rb rename to lib/super_diff/core/abstract_operation_tree_builder.rb index 24fa35ff..5b8af2ea 100644 --- a/lib/super_diff/operation_tree_builders/base.rb +++ b/lib/super_diff/core/abstract_operation_tree_builder.rb @@ -1,6 +1,6 @@ module SuperDiff - module OperationTreeBuilders - class Base + module Core + class AbstractOperationTreeBuilder def self.applies_to?(_expected, _actual) raise NotImplementedError end @@ -48,7 +48,7 @@ def operation_tree possible_comparison_of(delete_operation, insert_operation) ) operation_tree.delete(delete_operation) - operation_tree << Operations::BinaryOperation.new( + operation_tree << BinaryOperation.new( name: :change, left_collection: delete_operation.collection, right_collection: insert_operation.collection, @@ -89,11 +89,7 @@ def should_compare?(operation, next_operation) end def compare(expected, actual) - OperationTreeBuilders::Main.call( - expected: expected, - actual: actual, - all_or_nothing: false - ) + SuperDiff.build_operation_tree_for(expected, actual) end end end diff --git a/lib/super_diff/operation_tree_flatteners/base.rb b/lib/super_diff/core/abstract_operation_tree_flattener.rb similarity index 94% rename from lib/super_diff/operation_tree_flatteners/base.rb rename to lib/super_diff/core/abstract_operation_tree_flattener.rb index aaf12d48..f5b4e765 100644 --- a/lib/super_diff/operation_tree_flatteners/base.rb +++ b/lib/super_diff/core/abstract_operation_tree_flattener.rb @@ -1,6 +1,6 @@ module SuperDiff - module OperationTreeFlatteners - class Base + module Core + class AbstractOperationTreeFlattener include ImplementationChecks extend AttrExtras.mixin diff --git a/lib/super_diff/operations/binary_operation.rb b/lib/super_diff/core/binary_operation.rb similarity index 95% rename from lib/super_diff/operations/binary_operation.rb rename to lib/super_diff/core/binary_operation.rb index c31e6635..06bb357d 100644 --- a/lib/super_diff/operations/binary_operation.rb +++ b/lib/super_diff/core/binary_operation.rb @@ -1,5 +1,5 @@ module SuperDiff - module Operations + module Core class BinaryOperation extend AttrExtras.mixin diff --git a/lib/super_diff/core/colorized_document_extensions.rb b/lib/super_diff/core/colorized_document_extensions.rb new file mode 100644 index 00000000..1463157f --- /dev/null +++ b/lib/super_diff/core/colorized_document_extensions.rb @@ -0,0 +1,20 @@ +module SuperDiff + module Core + module ColorizedDocumentExtensions + def self.extended(extendee) + extendee.singleton_class.class_eval { alias_method :normal, :text } + end + + %i[actual border elision_marker expected header].each do |method_name| + define_method(method_name) do |*args, **opts, &block| + colorize( + *args, + **opts, + fg: SuperDiff.configuration.public_send("#{method_name}_color"), + &block + ) + end + end + end + end +end diff --git a/lib/super_diff/core/configuration.rb b/lib/super_diff/core/configuration.rb new file mode 100644 index 00000000..0f5a533d --- /dev/null +++ b/lib/super_diff/core/configuration.rb @@ -0,0 +1,192 @@ +module SuperDiff + module Core + class Configuration + attr_reader( + :extra_diff_formatter_classes, + :extra_differ_classes, + :extra_inspection_tree_builder_classes, + :extra_operation_tree_builder_classes, + :extra_operation_tree_classes + ) + attr_accessor( + :actual_color, + :border_color, + :color_enabled, + :diff_elision_enabled, + :diff_elision_maximum, + :elision_marker_color, + :expected_color, + :header_color, + :key_enabled + ) + + def initialize(options = {}) + @actual_color = :yellow + @border_color = :blue + @color_enabled = color_enabled_by_default? + @diff_elision_enabled = false + @diff_elision_maximum = 0 + @elision_marker_color = :cyan + @expected_color = :magenta + @extra_diff_formatter_classes = [].freeze + @extra_differ_classes = [].freeze + @extra_inspection_tree_builder_classes = [].freeze + @extra_operation_tree_builder_classes = [].freeze + @extra_operation_tree_classes = [].freeze + @header_color = :white + @key_enabled = true + + merge!(options) + end + + def initialize_dup(original) + super + @extra_differ_classes = original.extra_differ_classes.dup.freeze + @extra_operation_tree_builder_classes = + original.extra_operation_tree_builder_classes.dup.freeze + @extra_operation_tree_classes = + original.extra_operation_tree_classes.dup.freeze + @extra_inspection_tree_builder_classes = + original.extra_inspection_tree_builder_classes.dup.freeze + end + + def color_enabled? + @color_enabled + end + + def diff_elision_enabled? + @diff_elision_enabled + end + + def key_enabled? + @key_enabled + end + + def merge!(configuration_or_options) + options = + if configuration_or_options.is_a?(self.class) + configuration_or_options.to_h + else + configuration_or_options + end + + options.each { |key, value| instance_variable_set("@#{key}", value) } + + updated + end + + def updated + SuperDiff::Csi.color_enabled = color_enabled? + end + + def add_extra_diff_formatter_classes(*classes) + @extra_diff_formatter_classes = + (@extra_diff_formatter_classes + classes).freeze + end + alias_method( + :add_extra_diff_formatter_class, + :add_extra_diff_formatter_classes + ) + + def prepend_extra_diff_formatter_classes(*classes) + @extra_diff_formatter_classes = + (classes + @extra_diff_formatter_classes).freeze + end + alias_method( + :prepend_extra_diff_formatter_class, + :prepend_extra_diff_formatter_classes + ) + + def add_extra_differ_classes(*classes) + @extra_differ_classes = (@extra_differ_classes + classes).freeze + end + alias_method :add_extra_differ_class, :add_extra_differ_classes + + def prepend_extra_differ_classes(*classes) + @extra_differ_classes = (classes + @extra_differ_classes).freeze + end + alias_method :prepend_extra_differ_class, :prepend_extra_differ_classes + + def add_extra_inspection_tree_builder_classes(*classes) + @extra_inspection_tree_builder_classes = + (@extra_inspection_tree_builder_classes + classes).freeze + end + alias_method( + :add_extra_inspection_tree_builder_class, + :add_extra_inspection_tree_builder_classes + ) + + def prepend_extra_inspection_tree_builder_classes(*classes) + @extra_inspection_tree_builder_classes = + (classes + @extra_inspection_tree_builder_classes).freeze + end + alias_method( + :prepend_extra_inspection_tree_builder_class, + :prepend_extra_inspection_tree_builder_classes + ) + + def add_extra_operation_tree_builder_classes(*classes) + @extra_operation_tree_builder_classes = + (@extra_operation_tree_builder_classes + classes).freeze + end + alias_method( + :add_extra_operation_tree_builder_class, + :add_extra_operation_tree_builder_classes + ) + + def prepend_extra_operation_tree_builder_classes(*classes) + @extra_operation_tree_builder_classes = + (classes + @extra_operation_tree_builder_classes).freeze + end + alias_method( + :prepend_extra_operation_tree_builder_class, + :prepend_extra_operation_tree_builder_classes + ) + + def add_extra_operation_tree_classes(*classes) + @extra_operation_tree_classes = + (@extra_operation_tree_classes + classes).freeze + end + alias_method( + :add_extra_operation_tree_class, + :add_extra_operation_tree_classes + ) + + def prepend_extra_operation_tree_classes(*classes) + @extra_operation_tree_classes = + (classes + @extra_operation_tree_classes).freeze + end + alias_method( + :prepend_extra_operation_tree_class, + :prepend_extra_operation_tree_classes + ) + + def to_h + { + actual_color: actual_color, + border_color: border_color, + color_enabled: color_enabled?, + diff_elision_enabled: diff_elision_enabled?, + diff_elision_maximum: diff_elision_maximum, + elision_marker_color: elision_marker_color, + expected_color: expected_color, + extra_diff_formatter_classes: extra_diff_formatter_classes.dup, + extra_differ_classes: extra_differ_classes.dup, + extra_inspection_tree_builder_classes: + extra_inspection_tree_builder_classes.dup, + extra_operation_tree_builder_classes: + extra_operation_tree_builder_classes.dup, + extra_operation_tree_classes: extra_operation_tree_classes.dup, + header_color: header_color, + key_enabled: key_enabled? + } + end + + private + + def color_enabled_by_default? + ENV["CI"] == "true" || $stdout.respond_to?(:tty?) && $stdout.tty? + end + end + end +end diff --git a/lib/super_diff/core/differ_dispatcher.rb b/lib/super_diff/core/differ_dispatcher.rb new file mode 100644 index 00000000..5321ff49 --- /dev/null +++ b/lib/super_diff/core/differ_dispatcher.rb @@ -0,0 +1,33 @@ +module SuperDiff + module Core + class DifferDispatcher + extend AttrExtras.mixin + + method_object( + :expected, + :actual, + [:available_classes, indent_level: 0, raise_if_nothing_applies: true] + ) + + def call + pp available_classes: available_classes + + if resolved_class + resolved_class.call(expected, actual, indent_level: indent_level) + elsif raise_if_nothing_applies? + raise NoDifferAvailableError.create(expected, actual) + else + "" + end + end + + private + + attr_query :raise_if_nothing_applies? + + def resolved_class + available_classes.find { |klass| klass.applies_to?(expected, actual) } + end + end + end +end diff --git a/lib/super_diff/core/gem_version.rb b/lib/super_diff/core/gem_version.rb new file mode 100644 index 00000000..e75a54c4 --- /dev/null +++ b/lib/super_diff/core/gem_version.rb @@ -0,0 +1,47 @@ +module SuperDiff + module Core + class GemVersion + def initialize(version) + @version = Gem::Version.new(version.to_s) + end + + def <(other) + compare?(:<, other) + end + + def <=(other) + compare?(:<=, other) + end + + def ==(other) + compare?(:==, other) + end + + def >=(other) + compare?(:>=, other) + end + + def >(other) + compare?(:>, other) + end + + def =~(other) + Gem::Requirement.new(other).satisfied_by?(version) + end + + def to_s + version.to_s + end + + private + + attr_reader :version + + def compare?(operator, other_version) + Gem::Requirement.new("#{operator} #{other_version}").satisfied_by?( + version + ) + end + end + end +end diff --git a/lib/super_diff/core/helpers.rb b/lib/super_diff/core/helpers.rb new file mode 100644 index 00000000..35dcc979 --- /dev/null +++ b/lib/super_diff/core/helpers.rb @@ -0,0 +1,88 @@ +module SuperDiff + module Core + module Helpers + extend self + + # TODO: Simplify this + def style(*args, color_enabled: true, **opts, &block) + klass = + if color_enabled && Csi.color_enabled? + Csi::ColorizedDocument + else + Csi::UncolorizedDocument + end + + document = klass.new.extend(ColorizedDocumentExtensions) + + if block + document.__send__(:evaluate_block, &block) + else + document.colorize(*args, **opts) + end + + document + end + + def plural_type_for(value) + case value + when Numeric + "numbers" + when String + "strings" + when Symbol + "symbols" + else + "objects" + end + end + + def jruby? + defined?(JRUBY_VERSION) + end + + def ruby_version_matches?(version_string) + Gem::Requirement.new(version_string).satisfied_by?( + Gem::Version.new(RUBY_VERSION) + ) + end + + if jruby? + def object_address_for(object) + # Source: + "0x%x" % object.hash + end + elsif ruby_version_matches?(">= 2.7.0") + require "json" + require "objspace" + + def object_address_for(object) + # Sources: and + json = JSON.parse(ObjectSpace.dump(object)) + json.is_a?(Hash) ? "0x%016x" % Integer(json["address"], 16) : "" + end + else + def object_address_for(object) + "0x%016x" % (object.object_id * 2) + end + end + + def with_slice_of_array_replaced(array, range, replacement) + beginning = + if range.begin > 0 + array[Range.new(0, range.begin - 1)] + else + [] + end + + ending = + if range.end <= array.length - 1 + array[Range.new(range.end + 1, array.length - 1)] + else + [] + end + + beginning + [replacement] + ending + end + end + end +end diff --git a/lib/super_diff/core/implementation_checks.rb b/lib/super_diff/core/implementation_checks.rb new file mode 100644 index 00000000..5ebb371c --- /dev/null +++ b/lib/super_diff/core/implementation_checks.rb @@ -0,0 +1,21 @@ +module SuperDiff + module Core + module ImplementationChecks + protected def unimplemented_instance_method! + raise( + NotImplementedError, + "#{self.class} must implement ##{caller_locations(1, 1).first.label}", + caller(1) + ) + end + + protected def unimplemented_class_method! + raise( + NotImplementedError, + "#{self} must implement .#{caller_locations(1, 1).first.label}", + caller(1) + ) + end + end + end +end diff --git a/lib/super_diff/object_inspection/inspection_tree.rb b/lib/super_diff/core/inspection_tree.rb similarity index 94% rename from lib/super_diff/object_inspection/inspection_tree.rb rename to lib/super_diff/core/inspection_tree.rb index 2df63022..f49c75fc 100644 --- a/lib/super_diff/object_inspection/inspection_tree.rb +++ b/lib/super_diff/core/inspection_tree.rb @@ -1,5 +1,5 @@ module SuperDiff - module ObjectInspection + module Core class InspectionTree include Enumerable @@ -10,7 +10,7 @@ def initialize(disallowed_node_names: [], &block) evaluate_block(&block) if block end - Nodes.registry.each do |node_class| + InspectionTreeNodes.registry.each do |node_class| define_method(node_class.method_name) do |*args, **options, &block| add_node(node_class, *args, **options, &block) end @@ -59,7 +59,7 @@ def insert_array_inspection_of(array) insert_separated_list(array) do |t, value| # Have to do these shenanigans so that if value is a hash, Ruby # doesn't try to interpret it as keyword args - if SuperDiff::Helpers.ruby_version_matches?(">= 2.7.1") + if Helpers.ruby_version_matches?(">= 2.7.1") t.add_inspection_of(value, **{}) else t.add_inspection_of(*[value, {}]) @@ -86,7 +86,7 @@ def insert_hash_inspection_of(hash) # Have to do these shenanigans so that if hash[key] is a hash, Ruby # doesn't try to interpret it as keyword args - if SuperDiff::Helpers.ruby_version_matches?(">= 2.7.1") + if Helpers.ruby_version_matches?(">= 2.7.1") t1.add_inspection_of(hash[key], **{}) else t1.add_inspection_of(*[hash[key], {}]) @@ -148,11 +148,12 @@ class UpdateTieredLines def call if rendering.is_a?(Array) concat_with_lines - elsif rendering.is_a?(PrefixForNextNode) + elsif rendering.is_a?(PrefixForNextInspectionTreeNode) add_to_prefix elsif tiered_lines.any? add_to_last_line - elsif index < nodes.size - 1 || rendering.is_a?(PreludeForNextNode) + elsif index < nodes.size - 1 || + rendering.is_a?(PreludeForNextInspectionTreeNode) add_to_prelude else add_to_lines diff --git a/lib/super_diff/core/inspection_tree_builder_dispatcher.rb b/lib/super_diff/core/inspection_tree_builder_dispatcher.rb new file mode 100644 index 00000000..c357a400 --- /dev/null +++ b/lib/super_diff/core/inspection_tree_builder_dispatcher.rb @@ -0,0 +1,23 @@ +module SuperDiff + module Core + class InspectionTreeBuilderDispatcher + extend AttrExtras.mixin + + method_object :object, [:available_classes] + + def call + if resolved_class + resolved_class.call(object) + else + raise NoInspectionTreeBuilderAvailableError.create(object) + end + end + + private + + def resolved_class + available_classes.find { |klass| klass.applies_to?(object) } + end + end + end +end diff --git a/lib/super_diff/core/inspection_tree_nodes.rb b/lib/super_diff/core/inspection_tree_nodes.rb new file mode 100644 index 00000000..8c801579 --- /dev/null +++ b/lib/super_diff/core/inspection_tree_nodes.rb @@ -0,0 +1,55 @@ +module SuperDiff + module Core + module InspectionTreeNodes + autoload( + :AsLinesWhenRenderingToLines, + "super_diff/core/inspection_tree_nodes/as_lines_when_rendering_to_lines" + ) + autoload( + :AsPrefixWhenRenderingToLines, + "super_diff/core/inspection_tree_nodes/as_prefix_when_rendering_to_lines" + ) + autoload( + :AsPreludeWhenRenderingToLines, + "super_diff/core/inspection_tree_nodes/as_prelude_when_rendering_to_lines" + ) + autoload( + :AsSingleLine, + "super_diff/core/inspection_tree_nodes/as_single_line" + ) + autoload :Base, "super_diff/core/inspection_tree_nodes/base" + autoload :Inspection, "super_diff/core/inspection_tree_nodes/inspection" + autoload :Nesting, "super_diff/core/inspection_tree_nodes/nesting" + autoload :OnlyWhen, "super_diff/core/inspection_tree_nodes/only_when" + autoload :Text, "super_diff/core/inspection_tree_nodes/text" + autoload :WhenEmpty, "super_diff/core/inspection_tree_nodes/when_empty" + autoload( + :WhenNonEmpty, + "super_diff/core/inspection_tree_nodes/when_non_empty" + ) + autoload( + :WhenRenderingToLines, + "super_diff/core/inspection_tree_nodes/when_rendering_to_lines" + ) + autoload( + :WhenRenderingToString, + "super_diff/core/inspection_tree_nodes/when_rendering_to_string" + ) + + def self.registry + @_registry ||= [ + AsLinesWhenRenderingToLines, + AsPrefixWhenRenderingToLines, + AsPreludeWhenRenderingToLines, + AsSingleLine, + Inspection, + Nesting, + OnlyWhen, + Text, + WhenRenderingToLines, + WhenRenderingToString + ] + end + end + end +end diff --git a/lib/super_diff/object_inspection/nodes/as_lines_when_rendering_to_lines.rb b/lib/super_diff/core/inspection_tree_nodes/as_lines_when_rendering_to_lines.rb similarity index 90% rename from lib/super_diff/object_inspection/nodes/as_lines_when_rendering_to_lines.rb rename to lib/super_diff/core/inspection_tree_nodes/as_lines_when_rendering_to_lines.rb index f8a13158..26476929 100644 --- a/lib/super_diff/object_inspection/nodes/as_lines_when_rendering_to_lines.rb +++ b/lib/super_diff/core/inspection_tree_nodes/as_lines_when_rendering_to_lines.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class AsLinesWhenRenderingToLines < Base def self.node_name :as_lines_when_rendering_to_lines @@ -27,7 +27,13 @@ def initialize( def render_to_string(object) # TODO: This happens a lot, can we simplify this? string = - (block ? render_to_string_in_subtree(object) : immediate_value.to_s) + ( + if block + render_to_string_in_subtree(object) + else + immediate_value.to_s + end + ) add_comma? ? string + "," : string end diff --git a/lib/super_diff/object_inspection/nodes/as_prefix_when_rendering_to_lines.rb b/lib/super_diff/core/inspection_tree_nodes/as_prefix_when_rendering_to_lines.rb similarity index 78% rename from lib/super_diff/object_inspection/nodes/as_prefix_when_rendering_to_lines.rb rename to lib/super_diff/core/inspection_tree_nodes/as_prefix_when_rendering_to_lines.rb index 28262ecd..592ca9f8 100644 --- a/lib/super_diff/object_inspection/nodes/as_prefix_when_rendering_to_lines.rb +++ b/lib/super_diff/core/inspection_tree_nodes/as_prefix_when_rendering_to_lines.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class AsPrefixWhenRenderingToLines < Base def self.name :as_prefix_when_rendering_to_lines @@ -15,7 +15,7 @@ def render_to_string(object) end def render_to_lines(object, **) - ObjectInspection::PrefixForNextNode.new(render_to_string(object)) + PrefixForNextInspectionTreeNode.new(render_to_string(object)) end end end diff --git a/lib/super_diff/object_inspection/nodes/as_prelude_when_rendering_to_lines.rb b/lib/super_diff/core/inspection_tree_nodes/as_prelude_when_rendering_to_lines.rb similarity index 78% rename from lib/super_diff/object_inspection/nodes/as_prelude_when_rendering_to_lines.rb rename to lib/super_diff/core/inspection_tree_nodes/as_prelude_when_rendering_to_lines.rb index df5c8dde..8ca74a36 100644 --- a/lib/super_diff/object_inspection/nodes/as_prelude_when_rendering_to_lines.rb +++ b/lib/super_diff/core/inspection_tree_nodes/as_prelude_when_rendering_to_lines.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class AsPreludeWhenRenderingToLines < Base def self.name :as_prelude_when_rendering_to_lines @@ -15,7 +15,7 @@ def render_to_string(object) end def render_to_lines(object, **) - ObjectInspection::PreludeForNextNode.new(render_to_string(object)) + PreludeForNextInspectionTreeNode.new(render_to_string(object)) end end end diff --git a/lib/super_diff/object_inspection/nodes/as_single_line.rb b/lib/super_diff/core/inspection_tree_nodes/as_single_line.rb similarity index 88% rename from lib/super_diff/object_inspection/nodes/as_single_line.rb rename to lib/super_diff/core/inspection_tree_nodes/as_single_line.rb index 447818fa..6b4b9fcc 100644 --- a/lib/super_diff/object_inspection/nodes/as_single_line.rb +++ b/lib/super_diff/core/inspection_tree_nodes/as_single_line.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class AsSingleLine < Base def self.node_name :as_single_line @@ -16,7 +16,7 @@ def render_to_string(object) def render_to_lines(object, type:, indentation_level:) [ - SuperDiff::Line.new( + Line.new( type: type, indentation_level: indentation_level, value: render_to_string(object) diff --git a/lib/super_diff/object_inspection/nodes/base.rb b/lib/super_diff/core/inspection_tree_nodes/base.rb similarity index 98% rename from lib/super_diff/object_inspection/nodes/base.rb rename to lib/super_diff/core/inspection_tree_nodes/base.rb index 51618e50..71adbeeb 100644 --- a/lib/super_diff/object_inspection/nodes/base.rb +++ b/lib/super_diff/core/inspection_tree_nodes/base.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class Base def self.node_name unimplemented_class_method! diff --git a/lib/super_diff/object_inspection/nodes/inspection.rb b/lib/super_diff/core/inspection_tree_nodes/inspection.rb similarity index 71% rename from lib/super_diff/object_inspection/nodes/inspection.rb rename to lib/super_diff/core/inspection_tree_nodes/inspection.rb index e457737c..a8d5b5c2 100644 --- a/lib/super_diff/object_inspection/nodes/inspection.rb +++ b/lib/super_diff/core/inspection_tree_nodes/inspection.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class Inspection < Base def self.node_name :inspection @@ -13,11 +13,9 @@ def self.method_name def render_to_string(object) value = (block ? evaluate_block(object) : immediate_value) - SuperDiff::RecursionGuard.guarding_recursion_of( - value - ) do |already_seen| + RecursionGuard.guarding_recursion_of(value) do |already_seen| if already_seen - SuperDiff::RecursionGuard::PLACEHOLDER + RecursionGuard::PLACEHOLDER else SuperDiff.inspect_object(value, as_lines: false) end @@ -27,15 +25,13 @@ def render_to_string(object) def render_to_lines(object, type:, indentation_level:) value = (block ? evaluate_block(object) : immediate_value) - SuperDiff::RecursionGuard.guarding_recursion_of( - value - ) do |already_seen| + RecursionGuard.guarding_recursion_of(value) do |already_seen| if already_seen [ - SuperDiff::Line.new( + SuperDiff::Core::Line.new( type: type, indentation_level: indentation_level, - value: SuperDiff::RecursionGuard::PLACEHOLDER + value: RecursionGuard::PLACEHOLDER ) ] else diff --git a/lib/super_diff/object_inspection/nodes/nesting.rb b/lib/super_diff/core/inspection_tree_nodes/nesting.rb similarity index 91% rename from lib/super_diff/object_inspection/nodes/nesting.rb rename to lib/super_diff/core/inspection_tree_nodes/nesting.rb index c8908553..ef7875c2 100644 --- a/lib/super_diff/object_inspection/nodes/nesting.rb +++ b/lib/super_diff/core/inspection_tree_nodes/nesting.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class Nesting < Base def self.node_name :nesting diff --git a/lib/super_diff/object_inspection/nodes/only_when.rb b/lib/super_diff/core/inspection_tree_nodes/only_when.rb similarity index 95% rename from lib/super_diff/object_inspection/nodes/only_when.rb rename to lib/super_diff/core/inspection_tree_nodes/only_when.rb index 453e6e2b..953678a5 100644 --- a/lib/super_diff/object_inspection/nodes/only_when.rb +++ b/lib/super_diff/core/inspection_tree_nodes/only_when.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class OnlyWhen < Base def self.node_name :only_when diff --git a/lib/super_diff/object_inspection/nodes/text.rb b/lib/super_diff/core/inspection_tree_nodes/text.rb similarity index 92% rename from lib/super_diff/object_inspection/nodes/text.rb rename to lib/super_diff/core/inspection_tree_nodes/text.rb index e7317f07..30260de4 100644 --- a/lib/super_diff/object_inspection/nodes/text.rb +++ b/lib/super_diff/core/inspection_tree_nodes/text.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class Text < Base def self.node_name :text diff --git a/lib/super_diff/object_inspection/nodes/when_empty.rb b/lib/super_diff/core/inspection_tree_nodes/when_empty.rb similarity index 94% rename from lib/super_diff/object_inspection/nodes/when_empty.rb rename to lib/super_diff/core/inspection_tree_nodes/when_empty.rb index cebe591e..a620f36b 100644 --- a/lib/super_diff/object_inspection/nodes/when_empty.rb +++ b/lib/super_diff/core/inspection_tree_nodes/when_empty.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class WhenEmpty < Base def self.node_name :when_empty diff --git a/lib/super_diff/object_inspection/nodes/when_non_empty.rb b/lib/super_diff/core/inspection_tree_nodes/when_non_empty.rb similarity index 94% rename from lib/super_diff/object_inspection/nodes/when_non_empty.rb rename to lib/super_diff/core/inspection_tree_nodes/when_non_empty.rb index b3c875d1..c6685909 100644 --- a/lib/super_diff/object_inspection/nodes/when_non_empty.rb +++ b/lib/super_diff/core/inspection_tree_nodes/when_non_empty.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class WhenNonEmpty < Base def self.node_name :when_non_empty diff --git a/lib/super_diff/object_inspection/nodes/when_rendering_to_lines.rb b/lib/super_diff/core/inspection_tree_nodes/when_rendering_to_lines.rb similarity index 91% rename from lib/super_diff/object_inspection/nodes/when_rendering_to_lines.rb rename to lib/super_diff/core/inspection_tree_nodes/when_rendering_to_lines.rb index 1132c6bf..8cb8747f 100644 --- a/lib/super_diff/object_inspection/nodes/when_rendering_to_lines.rb +++ b/lib/super_diff/core/inspection_tree_nodes/when_rendering_to_lines.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class WhenRenderingToLines < Base def self.node_name :when_rendering_to_lines diff --git a/lib/super_diff/object_inspection/nodes/when_rendering_to_string.rb b/lib/super_diff/core/inspection_tree_nodes/when_rendering_to_string.rb similarity index 90% rename from lib/super_diff/object_inspection/nodes/when_rendering_to_string.rb rename to lib/super_diff/core/inspection_tree_nodes/when_rendering_to_string.rb index 8ee14d1d..b6bfc130 100644 --- a/lib/super_diff/object_inspection/nodes/when_rendering_to_string.rb +++ b/lib/super_diff/core/inspection_tree_nodes/when_rendering_to_string.rb @@ -1,6 +1,6 @@ module SuperDiff - module ObjectInspection - module Nodes + module Core + module InspectionTreeNodes class WhenRenderingToString < Base def self.node_name :when_rendering_to_string diff --git a/lib/super_diff/core/line.rb b/lib/super_diff/core/line.rb new file mode 100644 index 00000000..b88903cb --- /dev/null +++ b/lib/super_diff/core/line.rb @@ -0,0 +1,85 @@ +module SuperDiff + module Core + class Line + extend AttrExtras.mixin + + ICONS = { delete: "-", insert: "+", noop: " " }.freeze + COLORS = { insert: :actual, delete: :expected, noop: :plain }.freeze + + rattr_initialize( + [ + :type!, + :indentation_level!, + :value!, + prefix: "", + add_comma: false, + children: [], + elided: false, + collection_bookend: nil, + complete_bookend: nil + ] + ) + attr_query :add_comma? + attr_query :elided? + + def clone_with(overrides = {}) + self.class.new( + type: type, + indentation_level: indentation_level, + prefix: prefix, + value: value, + add_comma: add_comma?, + children: children, + elided: elided?, + collection_bookend: collection_bookend, + complete_bookend: complete_bookend, + **overrides + ) + end + + def icon + ICONS.fetch(type) + end + + def color + COLORS.fetch(type) + end + + def with_comma + clone_with(add_comma: true) + end + + def as_elided + clone_with(elided: true) + end + + def with_value_prepended(prelude) + clone_with(value: prelude + value) + end + + def with_value_appended(suffix) + clone_with(value: value + suffix) + end + + def prefixed_with(prefix) + clone_with(prefix: prefix + self.prefix) + end + + def with_complete_bookend(complete_bookend) + clone_with(complete_bookend: complete_bookend) + end + + def opens_collection? + collection_bookend == :open + end + + def closes_collection? + collection_bookend == :close + end + + def complete_bookend? + complete_bookend != nil + end + end + end +end diff --git a/lib/super_diff/errors/no_differ_available_error.rb b/lib/super_diff/core/no_differ_available_error.rb similarity index 97% rename from lib/super_diff/errors/no_differ_available_error.rb rename to lib/super_diff/core/no_differ_available_error.rb index 902271da..029bb03a 100644 --- a/lib/super_diff/errors/no_differ_available_error.rb +++ b/lib/super_diff/core/no_differ_available_error.rb @@ -1,5 +1,5 @@ module SuperDiff - module Errors + module Core class NoDifferAvailableError < StandardError def self.create(expected, actual) allocate.tap do |error| diff --git a/lib/super_diff/core/no_inspection_tree_builder_available_error.rb b/lib/super_diff/core/no_inspection_tree_builder_available_error.rb new file mode 100644 index 00000000..8cdaec33 --- /dev/null +++ b/lib/super_diff/core/no_inspection_tree_builder_available_error.rb @@ -0,0 +1,21 @@ +module SuperDiff + module Core + class NoInspectionTreeBuilderAvailableError < StandardError + def self.create(object) + allocate.tap do |error| + error.object = object + error.__send__(:initialize) + end + end + + attr_accessor :object + + def initialize + super(<<-MESSAGE) +There is no inspection tree builder available to handle a "value" of type +#{object.class}. + MESSAGE + end + end + end +end diff --git a/lib/super_diff/core/no_operation_tree_available_error.rb b/lib/super_diff/core/no_operation_tree_available_error.rb new file mode 100644 index 00000000..6adf7a9e --- /dev/null +++ b/lib/super_diff/core/no_operation_tree_available_error.rb @@ -0,0 +1,20 @@ +module SuperDiff + module Core + class NoOperationTreeAvailableError < StandardError + def self.create(value) + allocate.tap do |error| + error.value = value + error.__send__(:initialize) + end + end + + attr_accessor :value + + def initialize + super(<<-MESSAGE) +There is no operation tree available to handle a "value" of type #{value.class}. + MESSAGE + end + end + end +end diff --git a/lib/super_diff/core/no_operation_tree_builder_available_error.rb b/lib/super_diff/core/no_operation_tree_builder_available_error.rb new file mode 100644 index 00000000..aae6f042 --- /dev/null +++ b/lib/super_diff/core/no_operation_tree_builder_available_error.rb @@ -0,0 +1,24 @@ +module SuperDiff + module Core + class NoOperationTreeBuilderAvailableError < StandardError + def self.create(expected, actual) + allocate.tap do |error| + error.expected = expected + error.actual = actual + error.__send__(:initialize) + end + end + + attr_accessor :expected, :actual + + def initialize + super(<<-MESSAGE) +There is no operation tree builder available to handle an "expected" value of type +#{expected.class} +and an "actual" value of type +#{actual.class}. + MESSAGE + end + end + end +end diff --git a/lib/super_diff/operation_tree_builders/main.rb b/lib/super_diff/core/operation_tree_builder_dispatcher.rb similarity index 53% rename from lib/super_diff/operation_tree_builders/main.rb rename to lib/super_diff/core/operation_tree_builder_dispatcher.rb index 6a1a9b54..344b3b17 100644 --- a/lib/super_diff/operation_tree_builders/main.rb +++ b/lib/super_diff/core/operation_tree_builder_dispatcher.rb @@ -1,14 +1,18 @@ module SuperDiff - module OperationTreeBuilders - class Main + module Core + class OperationTreeBuilderDispatcher extend AttrExtras.mixin - method_object %i[expected! actual! all_or_nothing!] + method_object( + :expected, + :actual, + [:available_classes, raise_if_nothing_applies: true] + ) def call if resolved_class resolved_class.call(expected: expected, actual: actual) - elsif all_or_nothing? + elsif raise_if_nothing_applies? raise NoOperationTreeBuilderAvailableError.create(expected, actual) else nil @@ -17,19 +21,11 @@ def call private - attr_query :all_or_nothing? + attr_query :raise_if_nothing_applies? def resolved_class available_classes.find { |klass| klass.applies_to?(expected, actual) } end - - def available_classes - classes = - SuperDiff.configuration.extra_operation_tree_builder_classes + - DEFAULTS - - all_or_nothing? ? classes + [DefaultObject] : classes - end end end end diff --git a/lib/super_diff/core/operation_tree_finder.rb b/lib/super_diff/core/operation_tree_finder.rb new file mode 100644 index 00000000..6d981a06 --- /dev/null +++ b/lib/super_diff/core/operation_tree_finder.rb @@ -0,0 +1,27 @@ +module SuperDiff + module Core + class OperationTreeFinder + extend AttrExtras.mixin + + method_object :value, [:available_classes] + + def call + if resolved_class + begin + resolved_class.new([], underlying_object: value) + rescue ArgumentError + resolved_class.new([]) + end + else + raise NoOperationTreeAvailableError.create(value) + end + end + + private + + def resolved_class + available_classes.find { |klass| klass.applies_to?(value) } + end + end + end +end diff --git a/lib/super_diff/core/prefix_for_next_inspection_tree_node.rb b/lib/super_diff/core/prefix_for_next_inspection_tree_node.rb new file mode 100644 index 00000000..5f47499d --- /dev/null +++ b/lib/super_diff/core/prefix_for_next_inspection_tree_node.rb @@ -0,0 +1,6 @@ +module SuperDiff + module Core + class PrefixForNextInspectionTreeNode < String + end + end +end diff --git a/lib/super_diff/core/prelude_for_next_inspection_tree_node.rb b/lib/super_diff/core/prelude_for_next_inspection_tree_node.rb new file mode 100644 index 00000000..7e8215ea --- /dev/null +++ b/lib/super_diff/core/prelude_for_next_inspection_tree_node.rb @@ -0,0 +1,6 @@ +module SuperDiff + module Core + class PreludeForNextInspectionTreeNode < String + end + end +end diff --git a/lib/super_diff/core/recursion_guard.rb b/lib/super_diff/core/recursion_guard.rb new file mode 100644 index 00000000..68402985 --- /dev/null +++ b/lib/super_diff/core/recursion_guard.rb @@ -0,0 +1,52 @@ +require "set" + +module SuperDiff + module Core + module RecursionGuard + RECURSION_GUARD_KEY = "super_diff_recursion_guard_key".freeze + PLACEHOLDER = "∙∙∙".freeze + + def self.guarding_recursion_of(*objects, &block) + already_seen_objects, first_seen_objects = + objects.partition do |object| + !SuperDiff.primitive?(object) && already_seen?(object) + end + + first_seen_objects.each do |object| + already_seen_object_ids.add(object.object_id) + end + + result = + if block.arity > 0 + block.call(already_seen_objects.any?) + else + block.call + end + + first_seen_objects.each do |object| + already_seen_object_ids.delete(object.object_id) + end + + result + end + + def self.substituting_recursion_of(*objects) + guarding_recursion_of(*objects) do |already_seen| + if already_seen + PLACEHOLDER + else + yield + end + end + end + + def self.already_seen?(object) + already_seen_object_ids.include?(object.object_id) + end + + def self.already_seen_object_ids + Thread.current[RECURSION_GUARD_KEY] ||= Set.new + end + end + end +end diff --git a/lib/super_diff/core/tiered_lines.rb b/lib/super_diff/core/tiered_lines.rb new file mode 100644 index 00000000..e921f681 --- /dev/null +++ b/lib/super_diff/core/tiered_lines.rb @@ -0,0 +1,6 @@ +module SuperDiff + module Core + class TieredLines < Array + end + end +end diff --git a/lib/super_diff/core/tiered_lines_elider.rb b/lib/super_diff/core/tiered_lines_elider.rb new file mode 100644 index 00000000..8fcc280d --- /dev/null +++ b/lib/super_diff/core/tiered_lines_elider.rb @@ -0,0 +1,472 @@ +module SuperDiff + module Core + class TieredLinesElider + SIZE_OF_ELISION = 1 + + extend AttrExtras.mixin + include Helpers + + method_object :lines + + def call + all_lines_are_changed_or_unchanged? ? lines : elided_lines + end + + private + + def all_lines_are_changed_or_unchanged? + panes.size == 1 && panes.first.range == Range.new(0, lines.length - 1) + end + + def elided_lines + boxes_to_elide + .reverse + .reduce(lines) do |lines_with_elisions, box| + with_box_elided(box, lines_with_elisions) + end + end + + def boxes_to_elide + @_boxes_to_elide ||= + panes_to_consider_for_eliding.reduce([]) do |array, pane| + array + (find_boxes_to_elide_within(pane) || []) + end + end + + def panes_to_consider_for_eliding + panes.select { |pane| pane.type == :clean && pane.range.size > maximum } + end + + def panes + @_panes ||= + BuildPanes.call(dirty_panes: padded_dirty_panes, lines: lines) + end + + def padded_dirty_panes + @_padded_dirty_panes ||= + combine_congruent_panes( + dirty_panes + .map(&:padded) + .map { |pane| pane.capped_to(0, lines.size - 1) } + ) + end + + def dirty_panes + @_dirty_panes ||= + lines + .each_with_index + .select { |line, index| line.type != :noop } + .reduce([]) do |panes, (_, index)| + if !panes.empty? && panes.last.range.end == index - 1 + panes[0..-2] + [panes[-1].extended_to(index)] + else + panes + [Pane.new(type: :dirty, range: index..index)] + end + end + end + + def with_box_elided(box, lines) + box_at_start_of_lines = + if lines.first.complete_bookend? + box.range.begin == 1 + else + box.range.begin == 0 + end + + box_at_end_of_lines = + if lines.last.complete_bookend? + box.range.end == lines.size - 2 + else + box.range.end == lines.size - 1 + end + + if one_dimensional_line_tree? && outermost_box?(box) + if box_at_start_of_lines + with_start_of_box_elided(box, lines) + elsif box_at_end_of_lines + with_end_of_box_elided(box, lines) + else + with_middle_of_box_elided(box, lines) + end + else + with_subset_of_lines_elided( + lines, + range: box.range, + indentation_level: box.indentation_level + ) + end + end + + def outermost_box?(box) + box.indentation_level == all_indentation_levels.min + end + + def one_dimensional_line_tree? + all_indentation_levels.size == 1 + end + + def all_indentation_levels + lines + .map(&:indentation_level) + .select { |indentation_level| indentation_level > 0 } + .uniq + end + + def find_boxes_to_elide_within(pane) + set_of_boxes = + normalized_box_groups_at_decreasing_indentation_levels_within(pane) + + total_size_before_eliding = + lines[pane.range].reject(&:complete_bookend?).size + + if total_size_before_eliding > maximum + if maximum > 0 + set_of_boxes.find do |boxes| + total_size_after_eliding = + total_size_before_eliding - + boxes.sum { |box| box.range.size - SIZE_OF_ELISION } + total_size_after_eliding <= maximum + end + else + set_of_boxes[-1] + end + else + [] + end + end + + def normalized_box_groups_at_decreasing_indentation_levels_within(pane) + box_groups_at_decreasing_indentation_levels_within(pane).map( + &method(:filter_out_boxes_fully_contained_in_others) + ).map(&method(:combine_congruent_boxes)) + end + + def box_groups_at_decreasing_indentation_levels_within(pane) + boxes_within_pane = boxes.select { |box| box.fits_fully_within?(pane) } + + possible_indentation_levels = + boxes_within_pane + .map(&:indentation_level) + .select { |indentation_level| indentation_level > 0 } + .uniq + .sort + .reverse + + possible_indentation_levels.map do |indentation_level| + boxes_within_pane.select do |box| + box.indentation_level >= indentation_level + end + end + end + + def filter_out_boxes_fully_contained_in_others(boxes) + sorted_boxes = + boxes.sort_by do |box| + [box.indentation_level, box.range.begin, box.range.end] + end + + boxes.reject do |box2| + sorted_boxes.any? do |box1| + !box1.equal?(box2) && box1.fully_contains?(box2) + end + end + end + + def combine_congruent_boxes(boxes) + combine(boxes, on: :indentation_level) + end + + def combine_congruent_panes(panes) + combine(panes, on: :type) + end + + def combine(spannables, on:) + criterion = on + spannables.reduce([]) do |combined_spannables, spannable| + if ( + !combined_spannables.empty? && + spannable.range.begin <= + combined_spannables.last.range.end + 1 && + spannable.public_send(criterion) == + combined_spannables.last.public_send(criterion) + ) + combined_spannables[0..-2] + + [combined_spannables[-1].extended_to(spannable.range.end)] + else + combined_spannables + [spannable] + end + end + end + + def boxes + @_boxes ||= BuildBoxes.call(lines) + end + + def with_start_of_box_elided(box, lines) + amount_to_elide = + if maximum > 0 + box.range.size - maximum + SIZE_OF_ELISION + else + box.range.size + end + + with_subset_of_lines_elided( + lines, + range: + Range.new(box.range.begin, box.range.begin + amount_to_elide - 1), + indentation_level: box.indentation_level + ) + end + + def with_end_of_box_elided(box, lines) + amount_to_elide = + if maximum > 0 + box.range.size - maximum + SIZE_OF_ELISION + else + box.range.size + end + + range = + if amount_to_elide > 0 + Range.new(box.range.end - amount_to_elide + 1, box.range.end) + else + box.range + end + + with_subset_of_lines_elided( + lines, + range: range, + indentation_level: box.indentation_level + ) + end + + def with_middle_of_box_elided(box, lines) + half_of_maximum, remainder = + if maximum > 0 + (maximum - SIZE_OF_ELISION).divmod(2) + else + [0, 0] + end + + opening_length, closing_length = + half_of_maximum, + half_of_maximum + remainder + + with_subset_of_lines_elided( + lines, + range: + Range.new( + box.range.begin + opening_length, + box.range.end - closing_length + ), + indentation_level: box.indentation_level + ) + end + + def with_subset_of_lines_elided(lines, range:, indentation_level:) + with_slice_of_array_replaced( + lines, + range, + Elision.new( + indentation_level: indentation_level, + children: lines[range].map(&:as_elided) + ) + ) + end + + def maximum + SuperDiff.configuration.diff_elision_maximum || 0 + end + + class BuildPanes + extend AttrExtras.mixin + + method_object %i[dirty_panes! lines!] + + def call + beginning + middle + ending + end + + private + + def beginning + if (dirty_panes.empty? || dirty_panes.first.range.begin == 0) + [] + else + [ + Pane.new( + type: :clean, + range: Range.new(0, dirty_panes.first.range.begin - 1) + ) + ] + end + end + + def middle + if dirty_panes.size == 1 + dirty_panes + else + dirty_panes + .each_with_index + .each_cons(2) + .reduce([]) do |panes, ((pane1, _), (pane2, index2))| + panes + + [ + pane1, + Pane.new( + type: :clean, + range: + Range.new(pane1.range.end + 1, pane2.range.begin - 1) + ) + ] + (index2 == dirty_panes.size - 1 ? [pane2] : []) + end + end + end + + def ending + if ( + dirty_panes.empty? || + dirty_panes.last.range.end >= lines.size - 1 + ) + [] + else + [ + Pane.new( + type: :clean, + range: Range.new(dirty_panes.last.range.end + 1, lines.size - 1) + ) + ] + end + end + end + + class Pane + extend AttrExtras.mixin + + rattr_initialize %i[type! range!] + + def extended_to(new_end) + self.class.new(type: type, range: range.begin..new_end) + end + + def padded + self.class.new(type: type, range: Range.new(range.begin, range.end)) + end + + def capped_to(beginning, ending) + new_beginning = range.begin < beginning ? beginning : range.begin + new_ending = range.end > ending ? ending : range.end + self.class.new( + type: type, + range: Range.new(new_beginning, new_ending) + ) + end + end + + class BuildBoxes + def self.call(lines) + builder = new(lines) + builder.build + builder.final_boxes + end + + attr_reader :final_boxes + + def initialize(lines) + @lines = lines + + @open_collection_boxes = [] + @final_boxes = [] + end + + def build + lines.each_with_index do |line, index| + if line.opens_collection? + open_new_collection_box(line, index) + elsif line.closes_collection? + extend_working_collection_box(index) + close_working_collection_box + else + extend_working_collection_box(index) if open_collection_boxes.any? + record_item_box(line, index) + end + end + end + + private + + attr_reader :lines, :open_collection_boxes + + def extend_working_collection_box(index) + open_collection_boxes.last.extend_to(index) + end + + def close_working_collection_box + final_boxes << open_collection_boxes.pop + end + + def open_new_collection_box(line, index) + open_collection_boxes << Box.new( + indentation_level: line.indentation_level, + range: index..index + ) + end + + def record_item_box(line, index) + final_boxes << Box.new( + indentation_level: line.indentation_level, + range: index..index + ) + end + end + + class Box + extend AttrExtras.mixin + + rattr_initialize %i[indentation_level! range!] + + def fully_contains?(other) + range.begin <= other.range.begin && range.end >= other.range.end + end + + def fits_fully_within?(other) + other.range.begin <= range.begin && other.range.end >= range.end + end + + def extended_to(new_end) + dup.tap { |clone| clone.extend_to(new_end) } + end + + def extend_to(new_end) + @range = range.begin..new_end + end + end + + class Elision + extend AttrExtras.mixin + + rattr_initialize %i[indentation_level! children!] + + def type + :elision + end + + def prefix + "" + end + + def value + "# ..." + end + + def elided? + true + end + + def add_comma? + false + end + end + end + end +end diff --git a/lib/super_diff/core/tiered_lines_formatter.rb b/lib/super_diff/core/tiered_lines_formatter.rb new file mode 100644 index 00000000..bf944374 --- /dev/null +++ b/lib/super_diff/core/tiered_lines_formatter.rb @@ -0,0 +1,77 @@ +module SuperDiff + module Core + class TieredLinesFormatter + extend AttrExtras.mixin + + method_object :tiered_lines + + def call + colorized_document.to_s.chomp + end + + private + + def colorized_document + Helpers.style do |doc| + formattable_lines.each do |formattable_line| + doc.public_send( + "#{formattable_line.color}_line", + formattable_line.content + ) + end + end + end + + def formattable_lines + tiered_lines.map { |line| FormattableLine.new(line) } + end + + class FormattableLine + extend AttrExtras.mixin + + INDENTATION_UNIT = " ".freeze + ICONS = { delete: "-", insert: "+", elision: " ", noop: " " }.freeze + COLORS = { + delete: :expected, + insert: :actual, + elision: :elision_marker, + noop: :plain + }.freeze + + pattr_initialize :line + + def content + icon + " " + indentation + line.prefix + line.value + possible_comma + end + + def color + COLORS.fetch(line.type) do + raise( + KeyError, + "Couldn't find color for line type #{line.type.inspect}!" + ) + end + end + + private + + def icon + ICONS.fetch(line.type) do + raise( + KeyError, + "Couldn't find icon for line type #{line.type.inspect}!" + ) + end + end + + def indentation + INDENTATION_UNIT * line.indentation_level + end + + def possible_comma + line.add_comma? ? "," : "" + end + end + end + end +end diff --git a/lib/super_diff/operations/unary_operation.rb b/lib/super_diff/core/unary_operation.rb similarity index 88% rename from lib/super_diff/operations/unary_operation.rb rename to lib/super_diff/core/unary_operation.rb index ad886d53..f6fc19bb 100644 --- a/lib/super_diff/operations/unary_operation.rb +++ b/lib/super_diff/core/unary_operation.rb @@ -1,5 +1,5 @@ module SuperDiff - module Operations + module Core class UnaryOperation extend AttrExtras.mixin diff --git a/lib/super_diff/diff_formatters/collection.rb b/lib/super_diff/diff_formatters/collection.rb deleted file mode 100644 index e07b8949..00000000 --- a/lib/super_diff/diff_formatters/collection.rb +++ /dev/null @@ -1,132 +0,0 @@ -module SuperDiff - module DiffFormatters - class Collection - extend AttrExtras.mixin - - ICONS = { delete: "-", insert: "+" }.freeze - STYLES = { insert: :actual, delete: :expected, noop: :plain }.freeze - - method_object( - %i[ - open_token! - close_token! - operation_tree! - indent_level! - add_comma! - collection_prefix! - build_item_prefix! - ] - ) - - def call - lines.join("\n") - end - - private - - attr_query :add_comma? - - def lines - [ - " #{indentation}#{collection_prefix}#{open_token}", - *contents, - " #{indentation}#{close_token}#{comma}" - ] - end - - def contents - operation_tree.map do |operation| - if operation.name == :change - handle_change_operation(operation) - else - handle_non_change_operation(operation) - end - end - end - - def handle_change_operation(operation) - SuperDiff::RecursionGuard.guarding_recursion_of( - operation.left_collection, - operation.right_collection - ) do |already_seen| - if already_seen - raise "Infinite recursion!" - else - operation.child_operations.to_diff( - indent_level: indent_level + 1, - collection_prefix: build_item_prefix.call(operation), - add_comma: operation.should_add_comma_after_displaying? - ) - end - end - end - - def handle_non_change_operation(operation) - icon = ICONS.fetch(operation.name, " ") - style_name = STYLES.fetch(operation.name, :normal) - chunk = - build_chunk_for( - operation, - prefix: build_item_prefix.call(operation), - icon: icon - ) - - chunk << "," if operation.should_add_comma_after_displaying? - - style_chunk(style_name, chunk) - end - - def build_chunk_for(operation, prefix:, icon:) - if operation.value.equal?(operation.collection) - build_chunk_from_string( - SuperDiff::RecursionGuard::PLACEHOLDER, - prefix: build_item_prefix.call(operation), - icon: icon - ) - else - build_chunk_by_inspecting( - operation.value, - prefix: build_item_prefix.call(operation), - icon: icon - ) - end - end - - def build_chunk_by_inspecting(value, prefix:, icon:) - inspection = SuperDiff.inspect_object(value, as_single_line: false) - build_chunk_from_string(inspection, prefix: prefix, icon: icon) - end - - def build_chunk_from_string(value, prefix:, icon:) - value - .split("\n") - .map - .with_index do |line, index| - [ - icon, - " ", - indentation(offset: 1), - (index == 0 ? prefix : ""), - line - ].join - end - .join("\n") - end - - def style_chunk(style_name, chunk) - chunk - .split("\n") - .map { |line| Helpers.style(style_name, line) } - .join("\n") - end - - def indentation(offset: 0) - " " * (indent_level + offset) - end - - def comma - add_comma? ? "," : "" - end - end - end -end diff --git a/lib/super_diff/diff_formatters/multiline_string.rb b/lib/super_diff/diff_formatters/multiline_string.rb deleted file mode 100644 index b084bd9b..00000000 --- a/lib/super_diff/diff_formatters/multiline_string.rb +++ /dev/null @@ -1,31 +0,0 @@ -module SuperDiff - module DiffFormatters - class MultilineString < Base - def self.applies_to?(operation_tree) - operation_tree.is_a?(OperationTrees::MultilineString) - end - - def call - lines.join("\n") - end - - private - - def lines - operation_tree.reduce([]) do |array, operation| - case operation.name - when :change - array << Helpers.style(:expected, "- #{operation.left_value}") - array << Helpers.style(:actual, "+ #{operation.right_value}") - when :delete - array << Helpers.style(:expected, "- #{operation.value}") - when :insert - array << Helpers.style(:actual, "+ #{operation.value}") - else - array << Helpers.style(:plain, " #{operation.value}") - end - end - end - end - end -end diff --git a/lib/super_diff/differs.rb b/lib/super_diff/differs.rb index c2aba45f..d7a9f14e 100644 --- a/lib/super_diff/differs.rb +++ b/lib/super_diff/differs.rb @@ -1,16 +1,23 @@ module SuperDiff module Differs - autoload :Array, "super_diff/differs/array" - autoload :Base, "super_diff/differs/base" - autoload :CustomObject, "super_diff/differs/custom_object" - autoload :DefaultObject, "super_diff/differs/default_object" - autoload :Empty, "super_diff/differs/empty" - autoload :Hash, "super_diff/differs/hash" - autoload :Main, "super_diff/differs/main" - autoload :MultilineString, "super_diff/differs/multiline_string" - autoload :TimeLike, "super_diff/differs/time_like" - autoload :DateLike, "super_diff/differs/date_like" + def self.const_missing(missing_const_name) + if missing_const_name == :Base + warn <<~EOT + WARNING: SuperDiff::Differs::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::AbstractDiffer instead. + #{caller_locations.join("\n")} + EOT + Core::AbstractDiffer + elsif Basic::Differs.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::Differs::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Basic::Differs::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Basic::Differs.const_get(missing_const_name) + else + super + end + end end end - -require "super_diff/differs/defaults" diff --git a/lib/super_diff/differs/array.rb b/lib/super_diff/differs/array.rb deleted file mode 100644 index 8fc4181c..00000000 --- a/lib/super_diff/differs/array.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module Differs - class Array < Base - def self.applies_to?(expected, actual) - expected.is_a?(::Array) && actual.is_a?(::Array) - end - - protected - - def operation_tree_builder_class - OperationTreeBuilders::Array - end - end - end -end diff --git a/lib/super_diff/differs/custom_object.rb b/lib/super_diff/differs/custom_object.rb deleted file mode 100644 index 3d4a9c75..00000000 --- a/lib/super_diff/differs/custom_object.rb +++ /dev/null @@ -1,17 +0,0 @@ -module SuperDiff - module Differs - class CustomObject < Base - def self.applies_to?(expected, actual) - expected.class == actual.class && - expected.respond_to?(:attributes_for_super_diff) && - actual.respond_to?(:attributes_for_super_diff) - end - - protected - - def operation_tree_builder_class - OperationTreeBuilders::CustomObject - end - end - end -end diff --git a/lib/super_diff/differs/date_like.rb b/lib/super_diff/differs/date_like.rb deleted file mode 100644 index a30f0030..00000000 --- a/lib/super_diff/differs/date_like.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module Differs - class DateLike < Base - def self.applies_to?(expected, actual) - SuperDiff.date_like?(expected) && SuperDiff.date_like?(actual) - end - - protected - - def operation_tree_builder_class - OperationTreeBuilders::DateLike - end - end - end -end diff --git a/lib/super_diff/differs/default_object.rb b/lib/super_diff/differs/default_object.rb deleted file mode 100644 index 784a960b..00000000 --- a/lib/super_diff/differs/default_object.rb +++ /dev/null @@ -1,19 +0,0 @@ -module SuperDiff - module Differs - class DefaultObject < Base - def self.applies_to?(expected, actual) - expected.class == actual.class - end - - protected - - def operation_tree - OperationTreeBuilders::Main.call( - expected: expected, - actual: actual, - all_or_nothing: true - ) - end - end - end -end diff --git a/lib/super_diff/differs/defaults.rb b/lib/super_diff/differs/defaults.rb deleted file mode 100644 index d1e58113..00000000 --- a/lib/super_diff/differs/defaults.rb +++ /dev/null @@ -1,13 +0,0 @@ -module SuperDiff - module Differs - DEFAULTS = [ - Array, - Hash, - TimeLike, - DateLike, - MultilineString, - CustomObject, - DefaultObject - ].freeze - end -end diff --git a/lib/super_diff/differs/empty.rb b/lib/super_diff/differs/empty.rb deleted file mode 100644 index ae20d779..00000000 --- a/lib/super_diff/differs/empty.rb +++ /dev/null @@ -1,13 +0,0 @@ -module SuperDiff - module Differs - class Empty < Base - def self.applies_to?(_expected, _actual) - true - end - - def call - "" - end - end - end -end diff --git a/lib/super_diff/differs/hash.rb b/lib/super_diff/differs/hash.rb deleted file mode 100644 index 215b4139..00000000 --- a/lib/super_diff/differs/hash.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module Differs - class Hash < Base - def self.applies_to?(expected, actual) - expected.is_a?(::Hash) && actual.is_a?(::Hash) - end - - protected - - def operation_tree_builder_class - OperationTreeBuilders::Hash - end - end - end -end diff --git a/lib/super_diff/differs/main.rb b/lib/super_diff/differs/main.rb deleted file mode 100644 index 14a086e9..00000000 --- a/lib/super_diff/differs/main.rb +++ /dev/null @@ -1,31 +0,0 @@ -module SuperDiff - module Differs - class Main - extend AttrExtras.mixin - - method_object(:expected, :actual, [indent_level: 0, omit_empty: false]) - - def call - if resolved_class - resolved_class.call(expected, actual, indent_level: indent_level) - else - raise Errors::NoDifferAvailableError.create(expected, actual) - end - end - - private - - attr_query :omit_empty? - - def resolved_class - available_classes.find { |klass| klass.applies_to?(expected, actual) } - end - - def available_classes - classes = SuperDiff.configuration.extra_differ_classes + DEFAULTS - - omit_empty? ? classes : classes + [Empty] - end - end - end -end diff --git a/lib/super_diff/differs/multiline_string.rb b/lib/super_diff/differs/multiline_string.rb deleted file mode 100644 index b8b6c554..00000000 --- a/lib/super_diff/differs/multiline_string.rb +++ /dev/null @@ -1,16 +0,0 @@ -module SuperDiff - module Differs - class MultilineString < Base - def self.applies_to?(expected, actual) - expected.is_a?(::String) && actual.is_a?(::String) && - (expected.include?("\n") || actual.include?("\n")) - end - - protected - - def operation_tree_builder_class - OperationTreeBuilders::MultilineString - end - end - end -end diff --git a/lib/super_diff/differs/time_like.rb b/lib/super_diff/differs/time_like.rb deleted file mode 100644 index dded8cff..00000000 --- a/lib/super_diff/differs/time_like.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module Differs - class TimeLike < Base - def self.applies_to?(expected, actual) - SuperDiff.time_like?(expected) && SuperDiff.time_like?(actual) - end - - protected - - def operation_tree_builder_class - OperationTreeBuilders::TimeLike - end - end - end -end diff --git a/lib/super_diff/equality_matchers/array.rb b/lib/super_diff/equality_matchers/array.rb index 0af10073..f2012ef6 100644 --- a/lib/super_diff/equality_matchers/array.rb +++ b/lib/super_diff/equality_matchers/array.rb @@ -10,13 +10,13 @@ def fail Differing arrays. #{ - Helpers.style( + Core::Helpers.style( :expected, "Expected: " + SuperDiff.inspect_object(expected, as_lines: false) ) } #{ - Helpers.style( + Core::Helpers.style( :actual, " Actual: " + SuperDiff.inspect_object(actual, as_lines: false) ) @@ -31,7 +31,7 @@ def fail protected def diff - Differs::Array.call(expected, actual, indent_level: 0) + Basic::Differs::Array.call(expected, actual, indent_level: 0) end end end diff --git a/lib/super_diff/equality_matchers/default.rb b/lib/super_diff/equality_matchers/default.rb index d00ffce0..3fcabfb7 100644 --- a/lib/super_diff/equality_matchers/default.rb +++ b/lib/super_diff/equality_matchers/default.rb @@ -18,14 +18,14 @@ def fail protected def expected_line - Helpers.style( + Core::Helpers.style( :expected, "Expected: " + SuperDiff.inspect_object(expected, as_lines: false) ) end def actual_line - Helpers.style( + Core::Helpers.style( :actual, " Actual: " + SuperDiff.inspect_object(actual, as_lines: false) ) @@ -45,7 +45,12 @@ def diff_section end def diff - Differs::Main.call(expected, actual, indent_level: 0) + SuperDiff.diff( + expected, + actual, + indent_level: 0, + raise_if_nothing_applies: false + ) end end end diff --git a/lib/super_diff/equality_matchers/hash.rb b/lib/super_diff/equality_matchers/hash.rb index 5fbe5335..8cd57b1c 100644 --- a/lib/super_diff/equality_matchers/hash.rb +++ b/lib/super_diff/equality_matchers/hash.rb @@ -10,13 +10,13 @@ def fail Differing hashes. #{ - Helpers.style( + Core::Helpers.style( :expected, "Expected: " + SuperDiff.inspect_object(expected, as_lines: false) ) } #{ - Helpers.style( + Core::Helpers.style( :actual, " Actual: " + SuperDiff.inspect_object(actual, as_lines: false) ) @@ -31,7 +31,7 @@ def fail protected def diff - Differs::Hash.call(expected, actual, indent_level: 0) + Basic::Differs::Hash.call(expected, actual, indent_level: 0) end end end diff --git a/lib/super_diff/equality_matchers/multiline_string.rb b/lib/super_diff/equality_matchers/multiline_string.rb index b7d1da8d..5ed60689 100644 --- a/lib/super_diff/equality_matchers/multiline_string.rb +++ b/lib/super_diff/equality_matchers/multiline_string.rb @@ -11,13 +11,13 @@ def fail #{ # TODO: This whole thing should not be red or green, just the values - Helpers.style( + Core::Helpers.style( :expected, "Expected: " + SuperDiff.inspect_object(expected, as_lines: false) ) } #{ - Helpers.style( + Core::Helpers.style( :actual, " Actual: " + SuperDiff.inspect_object(actual, as_lines: false) ) @@ -32,7 +32,7 @@ def fail private def diff - Differs::MultilineString.call(expected, actual, indent_level: 0) + Basic::Differs::MultilineString.call(expected, actual, indent_level: 0) end end end diff --git a/lib/super_diff/equality_matchers/primitive.rb b/lib/super_diff/equality_matchers/primitive.rb index a0a1315b..c0ce6299 100644 --- a/lib/super_diff/equality_matchers/primitive.rb +++ b/lib/super_diff/equality_matchers/primitive.rb @@ -8,16 +8,16 @@ def self.applies_to?(value) def fail <<~OUTPUT.strip - Differing #{Helpers.plural_type_for(actual)}. + Differing #{Core::Helpers.plural_type_for(actual)}. #{ - Helpers.style( + Core::Helpers.style( :expected, "Expected: " + SuperDiff.inspect_object(expected, as_lines: false) ) } #{ - Helpers.style( + Core::Helpers.style( :actual, " Actual: " + SuperDiff.inspect_object(actual, as_lines: false) ) diff --git a/lib/super_diff/equality_matchers/singleline_string.rb b/lib/super_diff/equality_matchers/singleline_string.rb index d658781e..7df1bb25 100644 --- a/lib/super_diff/equality_matchers/singleline_string.rb +++ b/lib/super_diff/equality_matchers/singleline_string.rb @@ -10,13 +10,13 @@ def fail Differing strings. #{ - Helpers.style( + Core::Helpers.style( :expected, "Expected: " + SuperDiff.inspect_object(expected, as_lines: false) ) } #{ - Helpers.style( + Core::Helpers.style( :actual, " Actual: " + SuperDiff.inspect_object(actual, as_lines: false) ) diff --git a/lib/super_diff/errors.rb b/lib/super_diff/errors.rb index 1ff0759a..fe991e57 100644 --- a/lib/super_diff/errors.rb +++ b/lib/super_diff/errors.rb @@ -1,12 +1,16 @@ module SuperDiff module Errors - autoload( - :NoDifferAvailableError, - "super_diff/errors/no_differ_available_error" - ) - autoload( - :NoOperationTreeBuilderAvailableError, - "super_diff/errors/no_operation_tree_builder_available_error" - ) + def self.const_missing(missing_const_name) + if Core.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::Errors::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Core.const_get(missing_const_name) + else + super + end + end end end diff --git a/lib/super_diff/gem_version.rb b/lib/super_diff/gem_version.rb deleted file mode 100644 index e14a5672..00000000 --- a/lib/super_diff/gem_version.rb +++ /dev/null @@ -1,45 +0,0 @@ -module SuperDiff - class GemVersion - def initialize(version) - @version = Gem::Version.new(version.to_s) - end - - def <(other) - compare?(:<, other) - end - - def <=(other) - compare?(:<=, other) - end - - def ==(other) - compare?(:==, other) - end - - def >=(other) - compare?(:>=, other) - end - - def >(other) - compare?(:>, other) - end - - def =~(other) - Gem::Requirement.new(other).satisfied_by?(version) - end - - def to_s - version.to_s - end - - private - - attr_reader :version - - def compare?(operator, other_version) - Gem::Requirement.new("#{operator} #{other_version}").satisfied_by?( - version - ) - end - end -end diff --git a/lib/super_diff/helpers.rb b/lib/super_diff/helpers.rb deleted file mode 100644 index da07c6d4..00000000 --- a/lib/super_diff/helpers.rb +++ /dev/null @@ -1,86 +0,0 @@ -module SuperDiff - module Helpers - extend self - - # TODO: Simplify this - def style(*args, color_enabled: true, **opts, &block) - klass = - if color_enabled && Csi.color_enabled? - Csi::ColorizedDocument - else - Csi::UncolorizedDocument - end - - document = klass.new.extend(ColorizedDocumentExtensions) - - if block - document.__send__(:evaluate_block, &block) - else - document.colorize(*args, **opts) - end - - document - end - - def plural_type_for(value) - case value - when Numeric - "numbers" - when String - "strings" - when Symbol - "symbols" - else - "objects" - end - end - - def jruby? - defined?(JRUBY_VERSION) - end - - def ruby_version_matches?(version_string) - Gem::Requirement.new(version_string).satisfied_by?( - Gem::Version.new(RUBY_VERSION) - ) - end - - if jruby? - def object_address_for(object) - # Source: - "0x%x" % object.hash - end - elsif ruby_version_matches?(">= 2.7.0") - require "json" - require "objspace" - - def object_address_for(object) - # Sources: and - json = JSON.parse(ObjectSpace.dump(object)) - json.is_a?(Hash) ? "0x%016x" % Integer(json["address"], 16) : "" - end - else - def object_address_for(object) - "0x%016x" % (object.object_id * 2) - end - end - - def with_slice_of_array_replaced(array, range, replacement) - beginning = - if range.begin > 0 - array[Range.new(0, range.begin - 1)] - else - [] - end - - ending = - if range.end <= array.length - 1 - array[Range.new(range.end + 1, array.length - 1)] - else - [] - end - - beginning + [replacement] + ending - end - end -end diff --git a/lib/super_diff/implementation_checks.rb b/lib/super_diff/implementation_checks.rb deleted file mode 100644 index e4992227..00000000 --- a/lib/super_diff/implementation_checks.rb +++ /dev/null @@ -1,19 +0,0 @@ -module SuperDiff - module ImplementationChecks - protected def unimplemented_instance_method! - raise( - NotImplementedError, - "#{self.class} must implement ##{caller_locations(1, 1).first.label}", - caller(1) - ) - end - - protected def unimplemented_class_method! - raise( - NotImplementedError, - "#{self} must implement .#{caller_locations(1, 1).first.label}", - caller(1) - ) - end - end -end diff --git a/lib/super_diff/line.rb b/lib/super_diff/line.rb deleted file mode 100644 index de6d6eae..00000000 --- a/lib/super_diff/line.rb +++ /dev/null @@ -1,83 +0,0 @@ -module SuperDiff - class Line - extend AttrExtras.mixin - - ICONS = { delete: "-", insert: "+", noop: " " }.freeze - COLORS = { insert: :actual, delete: :expected, noop: :plain }.freeze - - rattr_initialize( - [ - :type!, - :indentation_level!, - :value!, - prefix: "", - add_comma: false, - children: [], - elided: false, - collection_bookend: nil, - complete_bookend: nil - ] - ) - attr_query :add_comma? - attr_query :elided? - - def clone_with(overrides = {}) - self.class.new( - type: type, - indentation_level: indentation_level, - prefix: prefix, - value: value, - add_comma: add_comma?, - children: children, - elided: elided?, - collection_bookend: collection_bookend, - complete_bookend: complete_bookend, - **overrides - ) - end - - def icon - ICONS.fetch(type) - end - - def color - COLORS.fetch(type) - end - - def with_comma - clone_with(add_comma: true) - end - - def as_elided - clone_with(elided: true) - end - - def with_value_prepended(prelude) - clone_with(value: prelude + value) - end - - def with_value_appended(suffix) - clone_with(value: value + suffix) - end - - def prefixed_with(prefix) - clone_with(prefix: prefix + self.prefix) - end - - def with_complete_bookend(complete_bookend) - clone_with(complete_bookend: complete_bookend) - end - - def opens_collection? - collection_bookend == :open - end - - def closes_collection? - collection_bookend == :close - end - - def complete_bookend? - complete_bookend != nil - end - end -end diff --git a/lib/super_diff/object_inspection.rb b/lib/super_diff/object_inspection.rb index 961a9faf..cccdfee5 100644 --- a/lib/super_diff/object_inspection.rb +++ b/lib/super_diff/object_inspection.rb @@ -1,18 +1,67 @@ module SuperDiff module ObjectInspection - autoload :InspectionTree, "super_diff/object_inspection/inspection_tree" - autoload( - :InspectionTreeBuilders, - "super_diff/object_inspection/inspection_tree_builders" - ) - autoload :Nodes, "super_diff/object_inspection/nodes" - autoload( - :PrefixForNextNode, - "super_diff/object_inspection/prefix_for_next_node" - ) - autoload( - :PreludeForNextNode, - "super_diff/object_inspection/prelude_for_next_node" - ) + module InspectionTreeBuilders + def self.const_missing(missing_const_name) + if missing_const_name == :Base + warn <<~EOT + WARNING: SuperDiff::ObjectInspection::InspectionTreeBuilders::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::AbstractInspectionTreeBuilder instead. + #{caller_locations.join("\n")} + EOT + Core::AbstractInspectionTreeBuilder + elsif Basic::InspectionTreeBuilders.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::ObjectInspection::InspectionTreeBuilders::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Basic::InspectionTreeBuilders::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Basic::InspectionTreeBuilders.const_get(missing_const_name) + else + super + end + end + end + + module Nodes + def self.const_missing(missing_const_name) + if Core::InspectionTreeNodes.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::ObjectInspection::Nodes::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::InspectionTreeNodes::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Core::InspectionTreeNodes.const_get(missing_const_name) + else + super + end + end + end + + def self.const_missing(missing_const_name) + if missing_const_name == :PrefixForNextNode + warn <<~EOT + WARNING: SuperDiff::ObjectInspection::PrefixForNextNode is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::PrefixForNextInspectionTreeNode instead. + #{caller_locations.join("\n")} + EOT + Core::PrefixForNextInspectionTreeNode + elsif missing_const_name == :PreludeForNextNode + warn <<~EOT + WARNING: SuperDiff::ObjectInspection::PreludeForNextNode is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::PreludeForNextInspectionTreeNode instead. + #{caller_locations.join("\n")} + EOT + Core::PreludeForNextInspectionTreeNode + elsif Core.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::ObjectInspection::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Core.const_get(missing_const_name) + else + super + end + end end end diff --git a/lib/super_diff/object_inspection/inspection_tree_builders.rb b/lib/super_diff/object_inspection/inspection_tree_builders.rb deleted file mode 100644 index 5da89908..00000000 --- a/lib/super_diff/object_inspection/inspection_tree_builders.rb +++ /dev/null @@ -1,48 +0,0 @@ -module SuperDiff - module ObjectInspection - module InspectionTreeBuilders - autoload( - :Base, - "super_diff/object_inspection/inspection_tree_builders/base" - ) - autoload( - :Array, - "super_diff/object_inspection/inspection_tree_builders/array" - ) - autoload( - :CustomObject, - "super_diff/object_inspection/inspection_tree_builders/custom_object" - ) - autoload( - :DefaultObject, - "super_diff/object_inspection/inspection_tree_builders/default_object" - ) - autoload( - :Hash, - "super_diff/object_inspection/inspection_tree_builders/hash" - ) - autoload( - :Main, - "super_diff/object_inspection/inspection_tree_builders/main" - ) - autoload( - :Primitive, - "super_diff/object_inspection/inspection_tree_builders/primitive" - ) - autoload( - :String, - "super_diff/object_inspection/inspection_tree_builders/string" - ) - autoload( - :TimeLike, - "super_diff/object_inspection/inspection_tree_builders/time_like" - ) - autoload( - :DateLike, - "super_diff/object_inspection/inspection_tree_builders/date_like" - ) - end - end -end - -require "super_diff/object_inspection/inspection_tree_builders/defaults" diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/base.rb b/lib/super_diff/object_inspection/inspection_tree_builders/base.rb deleted file mode 100644 index aaf40d9a..00000000 --- a/lib/super_diff/object_inspection/inspection_tree_builders/base.rb +++ /dev/null @@ -1,27 +0,0 @@ -module SuperDiff - module ObjectInspection - module InspectionTreeBuilders - class Base - extend AttrExtras.mixin - extend ImplementationChecks - include ImplementationChecks - - def self.applies_to?(_value) - unimplemented_class_method! - end - - method_object :object - - def call - unimplemented_instance_method! - end - - protected - - def inspection_tree - unimplemented_instance_method! - end - end - end - end -end diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/defaults.rb b/lib/super_diff/object_inspection/inspection_tree_builders/defaults.rb deleted file mode 100644 index 53226565..00000000 --- a/lib/super_diff/object_inspection/inspection_tree_builders/defaults.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module ObjectInspection - module InspectionTreeBuilders - DEFAULTS = [ - CustomObject, - Array, - Hash, - Primitive, - TimeLike, - DateLike, - DefaultObject - ].freeze - end - end -end diff --git a/lib/super_diff/object_inspection/inspection_tree_builders/main.rb b/lib/super_diff/object_inspection/inspection_tree_builders/main.rb deleted file mode 100644 index cc395adf..00000000 --- a/lib/super_diff/object_inspection/inspection_tree_builders/main.rb +++ /dev/null @@ -1,30 +0,0 @@ -module SuperDiff - module ObjectInspection - module InspectionTreeBuilders - class Main - extend AttrExtras.mixin - - method_object :object - - def call - if resolved_class - resolved_class.call(object) - else - raise NoInspectorAvailableError.create(object) - end - end - - private - - def resolved_class - available_classes.find { |klass| klass.applies_to?(object) } - end - - def available_classes - SuperDiff.configuration.extra_inspection_tree_builder_classes + - DEFAULTS - end - end - end - end -end diff --git a/lib/super_diff/object_inspection/nodes.rb b/lib/super_diff/object_inspection/nodes.rb deleted file mode 100644 index 8d737b9a..00000000 --- a/lib/super_diff/object_inspection/nodes.rb +++ /dev/null @@ -1,50 +0,0 @@ -module SuperDiff - module ObjectInspection - module Nodes - autoload( - :AsLinesWhenRenderingToLines, - "super_diff/object_inspection/nodes/as_lines_when_rendering_to_lines" - ) - autoload( - :AsPrefixWhenRenderingToLines, - "super_diff/object_inspection/nodes/as_prefix_when_rendering_to_lines" - ) - autoload( - :AsPreludeWhenRenderingToLines, - "super_diff/object_inspection/nodes/as_prelude_when_rendering_to_lines" - ) - autoload( - :AsSingleLine, - "super_diff/object_inspection/nodes/as_single_line" - ) - autoload :Base, "super_diff/object_inspection/nodes/base" - autoload :Inspection, "super_diff/object_inspection/nodes/inspection" - autoload :Nesting, "super_diff/object_inspection/nodes/nesting" - autoload :OnlyWhen, "super_diff/object_inspection/nodes/only_when" - autoload :Text, "super_diff/object_inspection/nodes/text" - autoload( - :WhenRenderingToLines, - "super_diff/object_inspection/nodes/when_rendering_to_lines" - ) - autoload( - :WhenRenderingToString, - "super_diff/object_inspection/nodes/when_rendering_to_string" - ) - - def self.registry - @_registry ||= [ - AsLinesWhenRenderingToLines, - AsPrefixWhenRenderingToLines, - AsPreludeWhenRenderingToLines, - AsSingleLine, - Inspection, - Nesting, - OnlyWhen, - Text, - WhenRenderingToLines, - WhenRenderingToString - ] - end - end - end -end diff --git a/lib/super_diff/object_inspection/prefix_for_next_node.rb b/lib/super_diff/object_inspection/prefix_for_next_node.rb deleted file mode 100644 index baddf1ba..00000000 --- a/lib/super_diff/object_inspection/prefix_for_next_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module SuperDiff - module ObjectInspection - class PrefixForNextNode < String - end - end -end diff --git a/lib/super_diff/object_inspection/prelude_for_next_node.rb b/lib/super_diff/object_inspection/prelude_for_next_node.rb deleted file mode 100644 index 037a04a7..00000000 --- a/lib/super_diff/object_inspection/prelude_for_next_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module SuperDiff - module ObjectInspection - class PreludeForNextNode < String - end - end -end diff --git a/lib/super_diff/operation_tree_builders.rb b/lib/super_diff/operation_tree_builders.rb index af8b0c56..5fd1bd03 100644 --- a/lib/super_diff/operation_tree_builders.rb +++ b/lib/super_diff/operation_tree_builders.rb @@ -1,19 +1,23 @@ module SuperDiff module OperationTreeBuilders - autoload :Array, "super_diff/operation_tree_builders/array" - autoload :Base, "super_diff/operation_tree_builders/base" - autoload :CustomObject, "super_diff/operation_tree_builders/custom_object" - autoload :DefaultObject, "super_diff/operation_tree_builders/default_object" - autoload :Hash, "super_diff/operation_tree_builders/hash" - autoload :Main, "super_diff/operation_tree_builders/main" - # TODO: Where is this used? - autoload( - :MultilineString, - "super_diff/operation_tree_builders/multiline_string" - ) - autoload :TimeLike, "super_diff/operation_tree_builders/time_like" - autoload :DateLike, "super_diff/operation_tree_builders/date_like" + def self.const_missing(missing_const_name) + if missing_const_name == :Base + warn <<~EOT + WARNING: SuperDiff::OperationTreeBuilders::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::AbstractOperationTreeBuilder instead. + #{caller_locations.join("\n")} + EOT + Core::AbstractOperationTreeBuilder + elsif Basic::OperationTreeBuilders.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::OperationTreeBuilders::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Basic::OperationTreeBuilders::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Basic::OperationTreeBuilders.const_get(missing_const_name) + else + super + end + end end end - -require "super_diff/operation_tree_builders/defaults" diff --git a/lib/super_diff/operation_tree_builders/array.rb b/lib/super_diff/operation_tree_builders/array.rb deleted file mode 100644 index 3c46e374..00000000 --- a/lib/super_diff/operation_tree_builders/array.rb +++ /dev/null @@ -1,107 +0,0 @@ -module SuperDiff - module OperationTreeBuilders - class Array < Base - def self.applies_to?(expected, actual) - expected.is_a?(::Array) && actual.is_a?(::Array) - end - - def call - Diff::LCS.traverse_balanced(expected, actual, lcs_callbacks) - operation_tree - end - - private - - def lcs_callbacks - @_lcs_callbacks ||= - LcsCallbacks.new( - operation_tree: operation_tree, - expected: expected, - actual: actual, - compare: method(:compare) - ) - end - - def operation_tree - @_operation_tree ||= OperationTrees::Array.new([]) - end - - class LcsCallbacks - extend AttrExtras.mixin - - pattr_initialize %i[operation_tree! expected! actual! compare!] - public :operation_tree - - def match(event) - add_noop_operation(event) - end - - def discard_a(event) - add_delete_operation(event) - end - - def discard_b(event) - add_insert_operation(event) - end - - def change(event) - children = compare.(event.old_element, event.new_element) - - if children - add_change_operation(event, children) - else - add_delete_operation(event) - add_insert_operation(event) - end - end - - private - - def add_delete_operation(event) - operation_tree << Operations::UnaryOperation.new( - name: :delete, - collection: expected, - key: event.old_position, - value: event.old_element, - index: event.old_position - ) - end - - def add_insert_operation(event) - operation_tree << Operations::UnaryOperation.new( - name: :insert, - collection: actual, - key: event.new_position, - value: event.new_element, - index: event.new_position - ) - end - - def add_noop_operation(event) - operation_tree << Operations::UnaryOperation.new( - name: :noop, - collection: actual, - key: event.new_position, - value: event.new_element, - index: event.new_position - ) - end - - def add_change_operation(event, children) - operation_tree << Operations::BinaryOperation.new( - name: :change, - left_collection: expected, - right_collection: actual, - left_key: event.old_position, - right_key: event.new_position, - left_value: event.old_element, - right_value: event.new_element, - left_index: event.old_position, - right_index: event.new_position, - children: children - ) - end - end - end - end -end diff --git a/lib/super_diff/operation_tree_builders/custom_object.rb b/lib/super_diff/operation_tree_builders/custom_object.rb deleted file mode 100644 index 6f70a701..00000000 --- a/lib/super_diff/operation_tree_builders/custom_object.rb +++ /dev/null @@ -1,40 +0,0 @@ -module SuperDiff - module OperationTreeBuilders - class CustomObject < DefaultObject - def self.applies_to?(expected, actual) - expected.class == actual.class && - expected.respond_to?(:attributes_for_super_diff) && - actual.respond_to?(:attributes_for_super_diff) - end - - protected - - def build_operation_tree - # NOTE: It doesn't matter whether we use expected or actual here, - # because all we care about is the name of the class - OperationTrees::CustomObject.new([], underlying_object: actual) - end - - def attribute_names - expected.attributes_for_super_diff.keys & - actual.attributes_for_super_diff.keys - end - - private - - attr_reader :expected_attributes, :actual_attributes - - def establish_expected_and_actual_attributes - @expected_attributes = - attribute_names.reduce({}) do |hash, name| - hash.merge(name => expected.public_send(name)) - end - - @actual_attributes = - attribute_names.reduce({}) do |hash, name| - hash.merge(name => actual.public_send(name)) - end - end - end - end -end diff --git a/lib/super_diff/operation_tree_builders/date_like.rb b/lib/super_diff/operation_tree_builders/date_like.rb deleted file mode 100644 index 3dc091e1..00000000 --- a/lib/super_diff/operation_tree_builders/date_like.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module OperationTreeBuilders - class DateLike < CustomObject - def self.applies_to?(expected, actual) - SuperDiff.date_like?(expected) && SuperDiff.date_like?(actual) - end - - protected - - def attribute_names - %w[year month day] - end - end - end -end diff --git a/lib/super_diff/operation_tree_builders/default_object.rb b/lib/super_diff/operation_tree_builders/default_object.rb deleted file mode 100644 index 326ec73b..00000000 --- a/lib/super_diff/operation_tree_builders/default_object.rb +++ /dev/null @@ -1,119 +0,0 @@ -module SuperDiff - module OperationTreeBuilders - class DefaultObject < Base - def self.applies_to?(_expected, _actual) - true - end - - def initialize(*args) - super(*args) - - establish_expected_and_actual_attributes - end - - protected - - def unary_operations - attribute_names.reduce([]) do |operations, name| - possibly_add_noop_operation_to(operations, name) - possibly_add_delete_operation_to(operations, name) - possibly_add_insert_operation_to(operations, name) - operations - end - end - - def build_operation_tree - # XXX This assumes that `expected` and `actual` are the same - # TODO: Does this need to be find_operation_tree_for? - OperationTrees::DefaultObject.new([], underlying_object: actual) - end - - def find_operation_tree_for(value) - OperationTrees::Main.call(value) - end - - def attribute_names - ( - expected.instance_variables.sort & actual.instance_variables.sort - ).map { |variable_name| variable_name[1..-1] } - end - - private - - attr_reader :expected_attributes, :actual_attributes - - def establish_expected_and_actual_attributes - @expected_attributes = - attribute_names.reduce({}) do |hash, name| - hash.merge(name => expected.instance_variable_get("@#{name}")) - end - - @actual_attributes = - attribute_names.reduce({}) do |hash, name| - hash.merge(name => actual.instance_variable_get("@#{name}")) - end - end - - def possibly_add_noop_operation_to(operations, attribute_name) - if should_add_noop_operation?(attribute_name) - operations << Operations::UnaryOperation.new( - name: :noop, - collection: actual_attributes, - key: attribute_name, - index: attribute_names.index(attribute_name), - value: actual_attributes[attribute_name] - ) - end - end - - def should_add_noop_operation?(attribute_name) - expected_attributes.include?(attribute_name) && - actual_attributes.include?(attribute_name) && - expected_attributes[attribute_name] == - actual_attributes[attribute_name] - end - - def possibly_add_delete_operation_to(operations, attribute_name) - if should_add_delete_operation?(attribute_name) - operations << Operations::UnaryOperation.new( - name: :delete, - collection: expected_attributes, - key: attribute_name, - index: attribute_names.index(attribute_name), - value: expected_attributes[attribute_name] - ) - end - end - - def should_add_delete_operation?(attribute_name) - expected_attributes.include?(attribute_name) && - ( - !actual_attributes.include?(attribute_name) || - expected_attributes[attribute_name] != - actual_attributes[attribute_name] - ) - end - - def possibly_add_insert_operation_to(operations, attribute_name) - if should_add_insert_operation?(attribute_name) - operations << Operations::UnaryOperation.new( - name: :insert, - collection: actual_attributes, - key: attribute_name, - index: attribute_names.index(attribute_name), - value: actual_attributes[attribute_name] - ) - end - end - - def should_add_insert_operation?(attribute_name) - !expected_attributes.include?(attribute_name) || - ( - actual_attributes.include?(attribute_name) && - expected_attributes[attribute_name] != - actual_attributes[attribute_name] - ) - end - end - end -end diff --git a/lib/super_diff/operation_tree_builders/defaults.rb b/lib/super_diff/operation_tree_builders/defaults.rb deleted file mode 100644 index e3c3d797..00000000 --- a/lib/super_diff/operation_tree_builders/defaults.rb +++ /dev/null @@ -1,5 +0,0 @@ -module SuperDiff - module OperationTreeBuilders - DEFAULTS = [Array, Hash, TimeLike, DateLike, CustomObject].freeze - end -end diff --git a/lib/super_diff/operation_tree_builders/hash.rb b/lib/super_diff/operation_tree_builders/hash.rb deleted file mode 100644 index 56d665b0..00000000 --- a/lib/super_diff/operation_tree_builders/hash.rb +++ /dev/null @@ -1,218 +0,0 @@ -module SuperDiff - module OperationTreeBuilders - class Hash < Base - def self.applies_to?(expected, actual) - expected.is_a?(::Hash) && actual.is_a?(::Hash) - end - - protected - - def unary_operations - unary_operations_using_variant_of_patience_algorithm - end - - def build_operation_tree - OperationTrees::Hash.new([]) - end - - private - - def unary_operations_using_variant_of_patience_algorithm - operations = [] - aks, eks = actual.keys, expected.keys - previous_ei, ei = nil, 0 - ai = 0 - - # When diffing a hash, we're more interested in the 'actual' version - # than the 'expected' version, because that's the ultimate truth. - # Therefore, the diff is presented from the perspective of the 'actual' - # hash, and we start off by looping over it. - while ai < aks.size - ak = aks[ai] - av, ev = actual[ak], expected[ak] - # While we iterate over 'actual' in order, we jump all over - # 'expected', trying to match up its keys with the keys in 'actual' as - # much as possible. - ei = eks.index(ak) - - if should_add_noop_operation?(ak) - # (If we're here, it probably means that the key we're pointing to - # in the 'actual' and 'expected' hashes have the same value.) - - if ei && previous_ei && (ei - previous_ei) > 1 - # If we've jumped from one operation in the 'expected' hash to - # another operation later in 'expected' (due to the fact that the - # 'expected' hash is in a different order than 'actual'), collect - # any delete operations in between and add them to our operations - # array as deletes before adding the noop. If we don't do this - # now, then those deletes will disappear. (Again, we are mainly - # iterating over 'actual', so this is the only way to catch all of - # the keys in 'expected'.) - (previous_ei + 1).upto(ei - 1) do |ei2| - ek = eks[ei2] - ev2, av2 = expected[ek], actual[ek] - - if ( - (!actual.include?(ek) || ev2 != av2) && - operations.none? do |operation| - %i[delete noop].include?(operation.name) && - operation.key == ek - end - ) - operations << Operations::UnaryOperation.new( - name: :delete, - collection: expected, - key: ek, - value: ev2, - index: ei2 - ) - end - end - end - - operations << Operations::UnaryOperation.new( - name: :noop, - collection: actual, - key: ak, - value: av, - index: ai - ) - else - # (If we're here, it probably means that the key in 'actual' isn't - # present in 'expected' or the values don't match.) - - if ( - (operations.empty? || operations.last.name == :noop) && - (ai == 0 || eks.include?(aks[ai - 1])) - ) - # If we go from a match in the last iteration to a missing or - # extra key in this one, or we're at the first key in 'actual' and - # it's missing or extra, look for deletes in the 'expected' hash - # and add them to our list of operations before we add the - # inserts. In most cases we will accomplish this by backtracking a - # bit to the key in 'expected' that matched the key in 'actual' we - # processed in the previous iteration (or just the first key in - # 'expected' if this is the first key in 'actual'), and then - # iterating from there through 'expected' until we reach the end - # or we hit some other condition (see below). - - start_index = - if ai > 0 - eks.index(aks[ai - 1]) + 1 - else - 0 - end - - start_index.upto(eks.size - 1) do |ei2| - ek = eks[ei2] - ev, av2 = expected[ek], actual[ek] - - if actual.include?(ek) && ev == av2 - # If the key in 'expected' we've landed on happens to be a - # match in 'actual', then stop, because it's going to be - # handled in some future iteration of the 'actual' loop. - break - elsif ( - aks[ai + 1..-1].any? do |k| - expected.include?(k) && expected[k] != actual[k] - end - ) - # While we backtracked a bit to iterate over 'expected', we - # now have to look ahead. If we will end up encountering a - # insert that matches this delete later, stop and go back to - # iterating over 'actual'. This is because the delete we would - # have added now will be added later when we encounter the - # associated insert, so we don't want to add it twice. - break - else - operations << Operations::UnaryOperation.new( - name: :delete, - collection: expected, - key: ek, - value: ev, - index: ei2 - ) - end - - if ek == ak && ev != av - # If we're pointing to the same key in 'expected' as in - # 'actual', but with different values, go ahead and add an - # insert now to accompany the delete added above. That way - # they appear together, which will be easier to read. - operations << Operations::UnaryOperation.new( - name: :insert, - collection: actual, - key: ak, - value: av, - index: ai - ) - end - end - end - - if ( - expected.include?(ak) && ev != av && - operations.none? { |op| op.name == :delete && op.key == ak } - ) - # If we're here, it means that we didn't encounter any delete - # operations above for whatever reason and so we need to add a - # delete to represent the fact that the value for this key has - # changed. - operations << Operations::UnaryOperation.new( - name: :delete, - collection: expected, - key: ak, - value: expected[ak], - index: ei - ) - end - - if operations.none? { |op| op.name == :insert && op.key == ak } - # If we're here, it means that we didn't encounter any insert - # operations above. Since we already handled delete, the only - # alternative is that this key must not exist in 'expected', so - # we need to add an insert. - operations << Operations::UnaryOperation.new( - name: :insert, - collection: actual, - key: ak, - value: av, - index: ai - ) - end - end - - ai += 1 - previous_ei = ei - end - - # The last thing to do is this: if there are keys in 'expected' that - # aren't in 'actual', and they aren't associated with any inserts to - # where they would have been added above, tack those deletes onto the - # end of our operations array. - (eks - aks - operations.map(&:key)).each do |ek| - ei = eks.index(ek) - ev = expected[ek] - - operations << Operations::UnaryOperation.new( - name: :delete, - collection: expected, - key: ek, - value: ev, - index: ei - ) - end - - operations - end - - def should_add_noop_operation?(key) - expected.include?(key) && expected[key] == actual[key] - end - - def all_keys - actual.keys | expected.keys - end - end - end -end diff --git a/lib/super_diff/operation_tree_builders/multiline_string.rb b/lib/super_diff/operation_tree_builders/multiline_string.rb deleted file mode 100644 index fdda3487..00000000 --- a/lib/super_diff/operation_tree_builders/multiline_string.rb +++ /dev/null @@ -1,86 +0,0 @@ -module SuperDiff - module OperationTreeBuilders - class MultilineString < Base - def self.applies_to?(expected, actual) - expected.is_a?(::String) && actual.is_a?(::String) && - (expected.include?("\n") || actual.include?("\n")) - end - - def initialize(*args) - super(*args) - - @original_expected = @expected - @original_actual = @actual - @expected = split_into_lines(@expected) - @actual = split_into_lines(@actual) - @sequence_matcher = PatienceDiff::SequenceMatcher.new - end - - protected - - def unary_operations - opcodes.flat_map do |code, a_start, a_end, b_start, b_end| - if code == :delete - add_delete_operations(a_start..a_end) - elsif code == :insert - add_insert_operations(b_start..b_end) - else - add_noop_operations(b_start..b_end) - end - end - end - - def build_operation_tree - OperationTrees::MultilineString.new([]) - end - - private - - attr_reader :sequence_matcher, :original_expected, :original_actual - - def split_into_lines(string) - string.scan(/.+(?:\r|\n|\r\n|\Z)/) - end - - def opcodes - sequence_matcher.diff_opcodes(expected, actual) - end - - def add_delete_operations(indices) - indices.map do |index| - Operations::UnaryOperation.new( - name: :delete, - collection: expected, - key: index, - index: index, - value: expected[index] - ) - end - end - - def add_insert_operations(indices) - indices.map do |index| - Operations::UnaryOperation.new( - name: :insert, - collection: actual, - key: index, - index: index, - value: actual[index] - ) - end - end - - def add_noop_operations(indices) - indices.map do |index| - Operations::UnaryOperation.new( - name: :noop, - collection: actual, - key: index, - index: index, - value: actual[index] - ) - end - end - end - end -end diff --git a/lib/super_diff/operation_tree_builders/time_like.rb b/lib/super_diff/operation_tree_builders/time_like.rb deleted file mode 100644 index 18151f47..00000000 --- a/lib/super_diff/operation_tree_builders/time_like.rb +++ /dev/null @@ -1,24 +0,0 @@ -module SuperDiff - module OperationTreeBuilders - class TimeLike < CustomObject - def self.applies_to?(expected, actual) - SuperDiff.time_like?(expected) && SuperDiff.time_like?(actual) - end - - protected - - def attribute_names - base = %w[year month day hour min sec subsec zone utc_offset] - - # If timezones are different, also show a normalized timestamp at the - # end of the diff to help visualize why they are different moments in - # time. - if actual.zone != expected.zone - base + ["utc"] - else - base - end - end - end - end -end diff --git a/lib/super_diff/operation_tree_flatteners.rb b/lib/super_diff/operation_tree_flatteners.rb index 5e1df2a9..f732f0d9 100644 --- a/lib/super_diff/operation_tree_flatteners.rb +++ b/lib/super_diff/operation_tree_flatteners.rb @@ -1,20 +1,23 @@ module SuperDiff module OperationTreeFlatteners - autoload :Array, "super_diff/operation_tree_flatteners/array" - autoload :Base, "super_diff/operation_tree_flatteners/base" - autoload :Collection, "super_diff/operation_tree_flatteners/collection" - autoload( - :CustomObject, - "super_diff/operation_tree_flatteners/custom_object" - ) - autoload( - :DefaultObject, - "super_diff/operation_tree_flatteners/default_object" - ) - autoload :Hash, "super_diff/operation_tree_flatteners/hash" - autoload( - :MultilineString, - "super_diff/operation_tree_flatteners/multiline_string" - ) + def self.const_missing(missing_const_name) + if missing_const_name == :Base + warn <<~EOT + WARNING: SuperDiff::OperationTreeFlatteners::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::AbstractOperationTreeFlattener instead. + #{caller_locations.join("\n")} + EOT + Core::AbstractOperationTreeFlattener + elsif Basic::OperationTreeFlatteners.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::OperationTreeFlatteners::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Basic::OperationTreeFlatteners::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Basic::OperationTreeFlatteners.const_get(missing_const_name) + else + super + end + end end end diff --git a/lib/super_diff/operation_tree_flatteners/array.rb b/lib/super_diff/operation_tree_flatteners/array.rb deleted file mode 100644 index 56b8dc40..00000000 --- a/lib/super_diff/operation_tree_flatteners/array.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module OperationTreeFlatteners - class Array < Collection - protected - - def open_token - "[" - end - - def close_token - "]" - end - end - end -end diff --git a/lib/super_diff/operation_tree_flatteners/collection.rb b/lib/super_diff/operation_tree_flatteners/collection.rb deleted file mode 100644 index 0fc65117..00000000 --- a/lib/super_diff/operation_tree_flatteners/collection.rb +++ /dev/null @@ -1,136 +0,0 @@ -module SuperDiff - module OperationTreeFlatteners - class Collection < Base - protected - - def build_tiered_lines - [ - Line.new( - type: :noop, - indentation_level: indentation_level, - value: open_token, - collection_bookend: :open - ), - *inner_lines, - Line.new( - type: :noop, - indentation_level: indentation_level, - value: close_token, - collection_bookend: :close - ) - ] - end - - def inner_lines - @_inner_lines ||= - operation_tree.flat_map do |operation| - lines = - if operation.name == :change - build_lines_for_change_operation(operation) - else - build_lines_for_non_change_operation(operation) - end - - maybe_add_prefix_at_beginning_of_lines( - maybe_add_comma_at_end_of_lines(lines, operation), - operation - ) - end - end - - def maybe_add_prefix_at_beginning_of_lines(lines, operation) - if add_prefix_at_beginning_of_lines?(operation) - add_prefix_at_beginning_of_lines(lines, operation) - else - lines - end - end - - def add_prefix_at_beginning_of_lines?(operation) - !!item_prefix_for(operation) - end - - def add_prefix_at_beginning_of_lines(lines, operation) - [lines[0].prefixed_with(item_prefix_for(operation))] + lines[1..-1] - end - - def maybe_add_comma_at_end_of_lines(lines, operation) - if last_item_in_collection?(operation) - lines - else - add_comma_at_end_of_lines(lines) - end - end - - def last_item_in_collection?(operation) - if operation.name == :change - operation.left_index == operation.left_collection.size - 1 && - operation.right_index == operation.right_collection.size - 1 - else - operation.index == operation.collection.size - 1 - end - end - - def add_comma_at_end_of_lines(lines) - lines[0..-2] + [lines[-1].with_comma] - end - - def build_lines_for_change_operation(operation) - SuperDiff::RecursionGuard.guarding_recursion_of( - operation.left_collection, - operation.right_collection - ) do |already_seen| - if already_seen - raise InfiniteRecursionError - else - operation.children.flatten(indentation_level: indentation_level + 1) - end - end - end - - def build_lines_for_non_change_operation(operation) - indentation_level = @indentation_level + 1 - - if recursive_operation?(operation) - [ - Line.new( - type: operation.name, - indentation_level: indentation_level, - value: SuperDiff::RecursionGuard::PLACEHOLDER - ) - ] - else - build_lines_from_inspection_of( - operation.value, - type: operation.name, - indentation_level: indentation_level - ) - end - end - - def recursive_operation?(operation) - operation.value.equal?(operation.collection) || - SuperDiff::RecursionGuard.already_seen?(operation.value) - end - - def item_prefix_for(_operation) - "" - end - - def build_lines_from_inspection_of(value, type:, indentation_level:) - SuperDiff.inspect_object( - value, - as_lines: true, - type: type, - indentation_level: indentation_level - ) - end - - class InfiniteRecursionError < StandardError - def initialize(_message = nil) - super("Unhandled recursive data structure encountered!") - end - end - end - end -end diff --git a/lib/super_diff/operation_tree_flatteners/custom_object.rb b/lib/super_diff/operation_tree_flatteners/custom_object.rb deleted file mode 100644 index 38e08bdd..00000000 --- a/lib/super_diff/operation_tree_flatteners/custom_object.rb +++ /dev/null @@ -1,28 +0,0 @@ -module SuperDiff - module OperationTreeFlatteners - class CustomObject < Collection - protected - - def open_token - "#<%s {" % { class: operation_tree.underlying_object.class } - end - - def close_token - "}>" - end - - def item_prefix_for(operation) - key = - # Note: We could have used the right_key here too, they're both the - # same keys - if operation.respond_to?(:left_key) - operation.left_key - else - operation.key - end - - "#{key}: " - end - end - end -end diff --git a/lib/super_diff/operation_tree_flatteners/default_object.rb b/lib/super_diff/operation_tree_flatteners/default_object.rb deleted file mode 100644 index 8966557e..00000000 --- a/lib/super_diff/operation_tree_flatteners/default_object.rb +++ /dev/null @@ -1,31 +0,0 @@ -module SuperDiff - module OperationTreeFlatteners - class DefaultObject < Collection - protected - - def open_token - "#<#{operation_tree.underlying_object.class.name}:" + - SuperDiff::Helpers.object_address_for( - operation_tree.underlying_object - ) + " {" - end - - def close_token - "}>" - end - - def item_prefix_for(operation) - key = - # Note: We could have used the right_key here too, they're both the - # same keys - if operation.respond_to?(:left_key) - operation.left_key - else - operation.key - end - - "@#{key}=" - end - end - end -end diff --git a/lib/super_diff/operation_tree_flatteners/hash.rb b/lib/super_diff/operation_tree_flatteners/hash.rb deleted file mode 100644 index 853d1f08..00000000 --- a/lib/super_diff/operation_tree_flatteners/hash.rb +++ /dev/null @@ -1,33 +0,0 @@ -module SuperDiff - module OperationTreeFlatteners - class Hash < Collection - protected - - def open_token - "{" - end - - def close_token - "}" - end - - def item_prefix_for(operation) - key = key_for(operation) - - format_keys_as_kwargs? ? "#{key}: " : "#{key.inspect} => " - end - - private - - def format_keys_as_kwargs? - operation_tree.all? { |operation| key_for(operation).is_a?(Symbol) } - end - - def key_for(operation) - # Note: We could have used the right_key here too, they're both the - # same keys - operation.respond_to?(:left_key) ? operation.left_key : operation.key - end - end - end -end diff --git a/lib/super_diff/operation_tree_flatteners/multiline_string.rb b/lib/super_diff/operation_tree_flatteners/multiline_string.rb deleted file mode 100644 index 9f4fa83d..00000000 --- a/lib/super_diff/operation_tree_flatteners/multiline_string.rb +++ /dev/null @@ -1,18 +0,0 @@ -module SuperDiff - module OperationTreeFlatteners - class MultilineString < Base - def build_tiered_lines - operation_tree.map do |operation| - Line.new( - type: operation.name, - indentation_level: indentation_level, - # TODO: Test that quotes and things don't get escaped but escape - # characters do - value: - operation.value.inspect[1..-2].gsub(/\\"/, '"').gsub(/\\'/, "'") - ) - end - end - end - end -end diff --git a/lib/super_diff/operation_trees.rb b/lib/super_diff/operation_trees.rb index 2075ef77..3dc3e257 100644 --- a/lib/super_diff/operation_trees.rb +++ b/lib/super_diff/operation_trees.rb @@ -1,13 +1,23 @@ module SuperDiff module OperationTrees - autoload :Array, "super_diff/operation_trees/array" - autoload :Base, "super_diff/operation_trees/base" - autoload :CustomObject, "super_diff/operation_trees/custom_object" - autoload :DefaultObject, "super_diff/operation_trees/default_object" - autoload :Hash, "super_diff/operation_trees/hash" - autoload :Main, "super_diff/operation_trees/main" - autoload :MultilineString, "super_diff/operation_trees/multiline_string" + def self.const_missing(missing_const_name) + if missing_const_name == :Base + warn <<~EOT + WARNING: SuperDiff::OperationTrees::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::AbstractOperationTree instead. + #{caller_locations.join("\n")} + EOT + Core::AbstractOperationTree + elsif Basic::OperationTrees.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::OperationTrees::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Basic::OperationTrees::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Basic::OperationTrees.const_get(missing_const_name) + else + super + end + end end end - -require "super_diff/operation_trees/defaults" diff --git a/lib/super_diff/operation_trees/array.rb b/lib/super_diff/operation_trees/array.rb deleted file mode 100644 index 63458e12..00000000 --- a/lib/super_diff/operation_trees/array.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module OperationTrees - class Array < Base - def self.applies_to?(value) - value.is_a?(::Array) - end - - protected - - def operation_tree_flattener_class - OperationTreeFlatteners::Array - end - end - end -end diff --git a/lib/super_diff/operation_trees/custom_object.rb b/lib/super_diff/operation_trees/custom_object.rb deleted file mode 100644 index 7e75467e..00000000 --- a/lib/super_diff/operation_trees/custom_object.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module OperationTrees - class CustomObject < DefaultObject - def self.applies_to?(value) - value.respond_to?(:attributes_for_super_diff) - end - - protected - - def operation_tree_flattener_class - OperationTreeFlatteners::CustomObject - end - end - end -end diff --git a/lib/super_diff/operation_trees/default_object.rb b/lib/super_diff/operation_trees/default_object.rb deleted file mode 100644 index 4ccfb6b0..00000000 --- a/lib/super_diff/operation_trees/default_object.rb +++ /dev/null @@ -1,40 +0,0 @@ -module SuperDiff - module OperationTrees - class DefaultObject < Base - def self.applies_to?(*) - true - end - - attr_reader :underlying_object - - def initialize(operations, underlying_object:) - super(operations) - @underlying_object = underlying_object - end - - def pretty_print(pp) - pp.text "#<#{self.class.name} " - pp.nest(1) do - pp.breakable - pp.text ":operations=>" - pp.group(1, "[", "]") do - pp.breakable - pp.seplist(self) { |value| pp.pp value } - end - pp.comma_breakable - pp.text ":underlying_object=>" - pp.object_address_group underlying_object do - # do nothing - end - end - pp.text ">" - end - - protected - - def operation_tree_flattener_class - OperationTreeFlatteners::DefaultObject - end - end - end -end diff --git a/lib/super_diff/operation_trees/defaults.rb b/lib/super_diff/operation_trees/defaults.rb deleted file mode 100644 index dd3dad60..00000000 --- a/lib/super_diff/operation_trees/defaults.rb +++ /dev/null @@ -1,5 +0,0 @@ -module SuperDiff - module OperationTrees - DEFAULTS = [Array, Hash, CustomObject, DefaultObject].freeze - end -end diff --git a/lib/super_diff/operation_trees/hash.rb b/lib/super_diff/operation_trees/hash.rb deleted file mode 100644 index 9f3f64ff..00000000 --- a/lib/super_diff/operation_trees/hash.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module OperationTrees - class Hash < Base - def self.applies_to?(value) - value.is_a?(::Hash) - end - - protected - - def operation_tree_flattener_class - OperationTreeFlatteners::Hash - end - end - end -end diff --git a/lib/super_diff/operation_trees/main.rb b/lib/super_diff/operation_trees/main.rb deleted file mode 100644 index 584d5ccb..00000000 --- a/lib/super_diff/operation_trees/main.rb +++ /dev/null @@ -1,35 +0,0 @@ -module SuperDiff - module OperationTrees - class Main - extend AttrExtras.mixin - - method_object :value - - def call - if resolved_class - begin - resolved_class.new([], underlying_object: value) - rescue ArgumentError - resolved_class.new([]) - end - else - raise Errors::NoOperationalSequenceAvailableError.create(value) - end - end - - private - - def resolved_class - if value.respond_to?(:attributes_for_super_diff) - CustomObject - else - available_classes.find { |klass| klass.applies_to?(value) } - end - end - - def available_classes - SuperDiff.configuration.extra_operation_tree_classes + DEFAULTS - end - end - end -end diff --git a/lib/super_diff/operation_trees/multiline_string.rb b/lib/super_diff/operation_trees/multiline_string.rb deleted file mode 100644 index 95d3e6c4..00000000 --- a/lib/super_diff/operation_trees/multiline_string.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SuperDiff - module OperationTrees - class MultilineString < Base - def self.applies_to?(value) - value.is_a?(::String) && value.is_a?(::String) - end - - protected - - def operation_tree_flattener_class - OperationTreeFlatteners::MultilineString - end - end - end -end diff --git a/lib/super_diff/operations.rb b/lib/super_diff/operations.rb index 9b587b35..dc40c224 100644 --- a/lib/super_diff/operations.rb +++ b/lib/super_diff/operations.rb @@ -1,6 +1,16 @@ module SuperDiff module Operations - autoload :BinaryOperation, "super_diff/operations/binary_operation" - autoload :UnaryOperation, "super_diff/operations/unary_operation" + def self.const_missing(missing_const_name) + if Core.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::Operations::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::Core::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + Core.const_get(missing_const_name) + else + super + end + end end end diff --git a/lib/super_diff/recursion_guard.rb b/lib/super_diff/recursion_guard.rb deleted file mode 100644 index 1e8763eb..00000000 --- a/lib/super_diff/recursion_guard.rb +++ /dev/null @@ -1,50 +0,0 @@ -require "set" - -module SuperDiff - module RecursionGuard - RECURSION_GUARD_KEY = "super_diff_recursion_guard_key".freeze - PLACEHOLDER = "∙∙∙".freeze - - def self.guarding_recursion_of(*objects, &block) - already_seen_objects, first_seen_objects = - objects.partition do |object| - !SuperDiff.primitive?(object) && already_seen?(object) - end - - first_seen_objects.each do |object| - already_seen_object_ids.add(object.object_id) - end - - result = - if block.arity > 0 - block.call(already_seen_objects.any?) - else - block.call - end - - first_seen_objects.each do |object| - already_seen_object_ids.delete(object.object_id) - end - - result - end - - def self.substituting_recursion_of(*objects) - guarding_recursion_of(*objects) do |already_seen| - if already_seen - PLACEHOLDER - else - yield - end - end - end - - def self.already_seen?(object) - already_seen_object_ids.include?(object.object_id) - end - - def self.already_seen_object_ids - Thread.current[RECURSION_GUARD_KEY] ||= Set.new - end - end -end diff --git a/lib/super_diff/rspec.rb b/lib/super_diff/rspec.rb index e9a09692..70bc5325 100644 --- a/lib/super_diff/rspec.rb +++ b/lib/super_diff/rspec.rb @@ -1,15 +1,17 @@ require "super_diff" +require "super_diff/rspec/differs" +require "super_diff/rspec/inspection_tree_builders" +require "super_diff/rspec/operation_tree_builders" + module SuperDiff module RSpec autoload :AugmentedMatcher, "super_diff/rspec/augmented_matcher" autoload :Configuration, "super_diff/rspec/configuration" autoload :Differ, "super_diff/rspec/differ" - autoload :Differs, "super_diff/rspec/differs" autoload :MatcherTextBuilders, "super_diff/rspec/matcher_text_builders" autoload :MatcherTextTemplate, "super_diff/rspec/matcher_text_template" autoload :ObjectInspection, "super_diff/rspec/object_inspection" - autoload :OperationTreeBuilders, "super_diff/rspec/operation_tree_builders" def self.configure(&block) SuperDiff.configure(&block) @@ -84,37 +86,37 @@ def self.rspec_version @_rspec_version ||= begin require "rspec/core/version" - GemVersion.new(::RSpec::Core::Version::STRING) + Core::GemVersion.new(::RSpec::Core::Version::STRING) end end SuperDiff.configuration.tap do |config| - config.add_extra_differ_classes( + config.prepend_extra_differ_classes( Differs::CollectionContainingExactly, Differs::CollectionIncluding, Differs::HashIncluding, Differs::ObjectHavingAttributes ) - config.add_extra_operation_tree_builder_classes( + config.prepend_extra_inspection_tree_builder_classes( + InspectionTreeBuilders::Double, + InspectionTreeBuilders::CollectionContainingExactly, + InspectionTreeBuilders::CollectionIncluding, + InspectionTreeBuilders::HashIncluding, + InspectionTreeBuilders::InstanceOf, + InspectionTreeBuilders::KindOf, + InspectionTreeBuilders::ObjectHavingAttributes, + # ObjectInspection::InspectionTreeBuilders::Primitive, + InspectionTreeBuilders::ValueWithin, + InspectionTreeBuilders::GenericDescribableMatcher + ) + + config.prepend_extra_operation_tree_builder_classes( OperationTreeBuilders::CollectionContainingExactly, OperationTreeBuilders::CollectionIncluding, OperationTreeBuilders::HashIncluding, OperationTreeBuilders::ObjectHavingAttributes ) - - config.add_extra_inspection_tree_builder_classes( - ObjectInspection::InspectionTreeBuilders::Double, - ObjectInspection::InspectionTreeBuilders::CollectionContainingExactly, - ObjectInspection::InspectionTreeBuilders::CollectionIncluding, - ObjectInspection::InspectionTreeBuilders::HashIncluding, - ObjectInspection::InspectionTreeBuilders::InstanceOf, - ObjectInspection::InspectionTreeBuilders::KindOf, - ObjectInspection::InspectionTreeBuilders::ObjectHavingAttributes, - # ObjectInspection::InspectionTreeBuilders::Primitive, - ObjectInspection::InspectionTreeBuilders::ValueWithin, - ObjectInspection::InspectionTreeBuilders::GenericDescribableMatcher - ) end end end diff --git a/lib/super_diff/rspec/augmented_matcher.rb b/lib/super_diff/rspec/augmented_matcher.rb index 80a30468..54dd8521 100644 --- a/lib/super_diff/rspec/augmented_matcher.rb +++ b/lib/super_diff/rspec/augmented_matcher.rb @@ -35,7 +35,7 @@ def matcher_text_builder end def matcher_text_builder_class - RSpec::MatcherTextBuilders::Base + MatcherTextBuilders::Base end def matcher_text_builder_args diff --git a/lib/super_diff/rspec/differ.rb b/lib/super_diff/rspec/differ.rb index 2f41b0f5..538dccd1 100644 --- a/lib/super_diff/rspec/differ.rb +++ b/lib/super_diff/rspec/differ.rb @@ -7,13 +7,12 @@ class Differ def diff if worth_diffing? - diff = - SuperDiff::Differs::Main.call(expected, actual, omit_empty: true) + diff = SuperDiff.diff(expected, actual) "\n\n" + diff else "" end - rescue SuperDiff::Errors::NoDifferAvailableError + rescue Core::NoDifferAvailableError "" end @@ -39,10 +38,10 @@ def comparing_singleline_strings? end def helpers - @_helpers ||= Helpers.new + @_helpers ||= RSpecHelpers.new end - class Helpers + class RSpecHelpers include ::RSpec::Matchers::Composable public :values_match? diff --git a/lib/super_diff/rspec/differs/collection_containing_exactly.rb b/lib/super_diff/rspec/differs/collection_containing_exactly.rb index 8061c66f..89ba4e6d 100644 --- a/lib/super_diff/rspec/differs/collection_containing_exactly.rb +++ b/lib/super_diff/rspec/differs/collection_containing_exactly.rb @@ -1,7 +1,7 @@ module SuperDiff module RSpec module Differs - class CollectionContainingExactly < SuperDiff::Differs::Array + class CollectionContainingExactly < Basic::Differs::Array def self.applies_to?(expected, actual) SuperDiff::RSpec.a_collection_containing_exactly_something?( expected diff --git a/lib/super_diff/rspec/differs/collection_including.rb b/lib/super_diff/rspec/differs/collection_including.rb index d1cd7c2e..83e08994 100644 --- a/lib/super_diff/rspec/differs/collection_including.rb +++ b/lib/super_diff/rspec/differs/collection_including.rb @@ -1,7 +1,7 @@ module SuperDiff module RSpec module Differs - class CollectionIncluding < SuperDiff::Differs::Array + class CollectionIncluding < Basic::Differs::Array def self.applies_to?(expected, actual) ( SuperDiff::RSpec.a_collection_including_something?(expected) || diff --git a/lib/super_diff/rspec/differs/hash_including.rb b/lib/super_diff/rspec/differs/hash_including.rb index 5df1cb9c..91255d5d 100644 --- a/lib/super_diff/rspec/differs/hash_including.rb +++ b/lib/super_diff/rspec/differs/hash_including.rb @@ -1,7 +1,7 @@ module SuperDiff module RSpec module Differs - class HashIncluding < SuperDiff::Differs::Hash + class HashIncluding < Basic::Differs::Hash def self.applies_to?(expected, actual) ( SuperDiff::RSpec.a_hash_including_something?(expected) || diff --git a/lib/super_diff/rspec/differs/object_having_attributes.rb b/lib/super_diff/rspec/differs/object_having_attributes.rb index e096b3dd..d40309f9 100644 --- a/lib/super_diff/rspec/differs/object_having_attributes.rb +++ b/lib/super_diff/rspec/differs/object_having_attributes.rb @@ -1,7 +1,7 @@ module SuperDiff module RSpec module Differs - class ObjectHavingAttributes < SuperDiff::Differs::DefaultObject + class ObjectHavingAttributes < Basic::Differs::DefaultObject def self.applies_to?(expected, _actual) SuperDiff::RSpec.an_object_having_some_attributes?(expected) end diff --git a/lib/super_diff/rspec/inspection_tree_builders.rb b/lib/super_diff/rspec/inspection_tree_builders.rb new file mode 100644 index 00000000..52936c35 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders.rb @@ -0,0 +1,40 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + autoload( + :CollectionContainingExactly, + "super_diff/rspec/inspection_tree_builders/collection_containing_exactly" + ) + autoload( + :CollectionIncluding, + "super_diff/rspec/inspection_tree_builders/collection_including" + ) + autoload :Double, "super_diff/rspec/inspection_tree_builders/double" + autoload( + :GenericDescribableMatcher, + "super_diff/rspec/inspection_tree_builders/generic_describable_matcher" + ) + autoload( + :HashIncluding, + "super_diff/rspec/inspection_tree_builders/hash_including" + ) + autoload( + :InstanceOf, + "super_diff/rspec/inspection_tree_builders/instance_of" + ) + autoload :KindOf, "super_diff/rspec/inspection_tree_builders/kind_of" + autoload( + :ObjectHavingAttributes, + "super_diff/rspec/inspection_tree_builders/object_having_attributes" + ) + autoload( + :Primitive, + "super_diff/rspec/inspection_tree_builders/primitive" + ) + autoload( + :ValueWithin, + "super_diff/rspec/inspection_tree_builders/value_within" + ) + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/collection_containing_exactly.rb b/lib/super_diff/rspec/inspection_tree_builders/collection_containing_exactly.rb new file mode 100644 index 00000000..db167561 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/collection_containing_exactly.rb @@ -0,0 +1,34 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class CollectionContainingExactly < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + SuperDiff::RSpec.a_collection_containing_exactly_something?(value) + end + + def call + Core::InspectionTree.new do |t1| + # stree-ignore + t1.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t2| + t2.add_text "#" + end + end + end + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/collection_including.rb b/lib/super_diff/rspec/inspection_tree_builders/collection_including.rb new file mode 100644 index 00000000..ff426a16 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/collection_including.rb @@ -0,0 +1,40 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class CollectionIncluding < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + SuperDiff::RSpec.a_collection_including_something?(value) || + SuperDiff::RSpec.array_including_something?(value) + end + + def call + Core::InspectionTree.new do |t1| + # stree-ignore + t1.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t2| + t2.add_text "#" + end + end + end + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/double.rb b/lib/super_diff/rspec/inspection_tree_builders/double.rb new file mode 100644 index 00000000..4eb42775 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/double.rb @@ -0,0 +1,100 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class Double < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + value.is_a?(::RSpec::Mocks::Double) + end + + def call + Core::InspectionTree.new do |t1| + t1.only_when method(:empty?) do |t2| + t2.as_lines_when_rendering_to_lines do |t3| + t3.add_text("#<#{inspected_class} #{inspected_name}>") + end + end + + t1.only_when method(:nonempty?) do |t2| + t2.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t3| + t3.add_text("#<#{inspected_class} #{inspected_name}") + + # stree-ignore + t3.when_rendering_to_lines do |t4| + t4.add_text " {" + end + end + + # stree-ignore + t2.when_rendering_to_string do |t3| + t3.add_text " " + end + + # stree-ignore + t2.nested do |t3| + t3.insert_hash_inspection_of doubled_methods + end + + t2.as_lines_when_rendering_to_lines( + collection_bookend: :close + ) do |t3| + # stree-ignore + t3.when_rendering_to_lines do |t4| + t4.add_text "}" + end + + t3.add_text ">" + end + end + end + end + + private + + def empty? + doubled_methods.empty? + end + + def nonempty? + !empty? + end + + def inspected_class + case object + when ::RSpec::Mocks::InstanceVerifyingDouble + "InstanceDouble" + when ::RSpec::Mocks::ClassVerifyingDouble + "ClassDouble" + when ::RSpec::Mocks::ObjectVerifyingDouble + "ObjectDouble" + else + "Double" + end + end + + def inspected_name + if object.instance_variable_get("@name") + object.instance_variable_get("@name").inspect + else + "(anonymous)" + end + end + + def doubled_methods + @_doubled_methods ||= + doubled_method_names.reduce({}) do |hash, key| + hash.merge(key => object.public_send(key)) + end + end + + def doubled_method_names + object + .__send__(:__mock_proxy) + .instance_variable_get("@method_doubles") + .keys + end + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/generic_describable_matcher.rb b/lib/super_diff/rspec/inspection_tree_builders/generic_describable_matcher.rb new file mode 100644 index 00000000..309fc01f --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/generic_describable_matcher.rb @@ -0,0 +1,17 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class GenericDescribableMatcher < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + ::RSpec::Matchers.is_a_describable_matcher?(value) + end + + def call + Core::InspectionTree.new do |t1| + t1.add_text "#<#{object.description}>" + end + end + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/hash_including.rb b/lib/super_diff/rspec/inspection_tree_builders/hash_including.rb new file mode 100644 index 00000000..a9936d07 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/hash_including.rb @@ -0,0 +1,40 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class HashIncluding < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + SuperDiff::RSpec.a_hash_including_something?(value) || + SuperDiff::RSpec.hash_including_something?(value) + end + + def call + Core::InspectionTree.new do |t1| + # stree-ignore + t1.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t2| + t2.add_text "#" + end + end + end + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/instance_of.rb b/lib/super_diff/rspec/inspection_tree_builders/instance_of.rb new file mode 100644 index 00000000..55802150 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/instance_of.rb @@ -0,0 +1,25 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class InstanceOf < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + SuperDiff::RSpec.an_instance_of_something?(value) || + SuperDiff::RSpec.instance_of_something?(value) + end + + def call + Core::InspectionTree.new do |t1| + klass = + if SuperDiff::RSpec.an_instance_of_something?(object) + object.expected + else + object.instance_variable_get(:@klass) + end + + t1.add_text "#" + end + end + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/kind_of.rb b/lib/super_diff/rspec/inspection_tree_builders/kind_of.rb new file mode 100644 index 00000000..c79bd63a --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/kind_of.rb @@ -0,0 +1,25 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class KindOf < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + SuperDiff::RSpec.a_kind_of_something?(value) || + SuperDiff::RSpec.kind_of_something?(value) + end + + def call + Core::InspectionTree.new do |t1| + klass = + if SuperDiff::RSpec.a_kind_of_something?(object) + object.expected + else + object.instance_variable_get(:@klass) + end + + t1.add_text "#" + end + end + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/object_having_attributes.rb b/lib/super_diff/rspec/inspection_tree_builders/object_having_attributes.rb new file mode 100644 index 00000000..bfddd486 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/object_having_attributes.rb @@ -0,0 +1,34 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class ObjectHavingAttributes < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + SuperDiff::RSpec.an_object_having_some_attributes?(value) + end + + def call + Core::InspectionTree.new do |t1| + # stree-ignore + t1.as_lines_when_rendering_to_lines( + collection_bookend: :open + ) do |t2| + t2.add_text "#" + end + end + end + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/primitive.rb b/lib/super_diff/rspec/inspection_tree_builders/primitive.rb new file mode 100644 index 00000000..929894d9 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/primitive.rb @@ -0,0 +1,9 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + # TODO: Is this needed? + class Primitive < Basic::InspectionTreeBuilders::Primitive + end + end + end +end diff --git a/lib/super_diff/rspec/inspection_tree_builders/value_within.rb b/lib/super_diff/rspec/inspection_tree_builders/value_within.rb new file mode 100644 index 00000000..0b611b93 --- /dev/null +++ b/lib/super_diff/rspec/inspection_tree_builders/value_within.rb @@ -0,0 +1,30 @@ +module SuperDiff + module RSpec + module InspectionTreeBuilders + class ValueWithin < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + SuperDiff::RSpec.a_value_within_something?(value) + end + + def call + Core::InspectionTree.new do |t1| + t1.as_prelude_when_rendering_to_lines do |t2| + t2.add_text "#" + end + end + end + end + end +end diff --git a/lib/super_diff/rspec/object_inspection.rb b/lib/super_diff/rspec/object_inspection.rb index 9de683af..d45e21cd 100644 --- a/lib/super_diff/rspec/object_inspection.rb +++ b/lib/super_diff/rspec/object_inspection.rb @@ -1,10 +1,20 @@ module SuperDiff module RSpec module ObjectInspection - autoload( - :InspectionTreeBuilders, - "super_diff/rspec/object_inspection/inspection_tree_builders" - ) + module InspectionTreeBuilders + def self.const_missing(missing_const_name) + if RSpec::InspectionTreeBuilders.const_defined?(missing_const_name) + warn <<~EOT + WARNING: SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::#{missing_const_name} is deprecated and will be removed in the next major release. + Please use SuperDiff::RSpec::InspectionTreeBuilders::#{missing_const_name} instead. + #{caller_locations.join("\n")} + EOT + RSpec::InspectionTreeBuilders.const_get(missing_const_name) + else + super + end + end + end end end end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders.rb deleted file mode 100644 index d14650cf..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders.rb +++ /dev/null @@ -1,48 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - autoload( - :CollectionContainingExactly, - "super_diff/rspec/object_inspection/inspection_tree_builders/collection_containing_exactly" - ) - autoload( - :CollectionIncluding, - "super_diff/rspec/object_inspection/inspection_tree_builders/collection_including" - ) - autoload( - :Double, - "super_diff/rspec/object_inspection/inspection_tree_builders/double" - ) - autoload( - :GenericDescribableMatcher, - "super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher" - ) - autoload( - :HashIncluding, - "super_diff/rspec/object_inspection/inspection_tree_builders/hash_including" - ) - autoload( - :InstanceOf, - "super_diff/rspec/object_inspection/inspection_tree_builders/instance_of" - ) - autoload( - :KindOf, - "super_diff/rspec/object_inspection/inspection_tree_builders/kind_of" - ) - autoload( - :ObjectHavingAttributes, - "super_diff/rspec/object_inspection/inspection_tree_builders/object_having_attributes" - ) - autoload( - :Primitive, - "super_diff/rspec/object_inspection/inspection_tree_builders/primitive" - ) - autoload( - :ValueWithin, - "super_diff/rspec/object_inspection/inspection_tree_builders/value_within" - ) - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/collection_containing_exactly.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/collection_containing_exactly.rb deleted file mode 100644 index b64325f1..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/collection_containing_exactly.rb +++ /dev/null @@ -1,36 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class CollectionContainingExactly < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - SuperDiff::RSpec.a_collection_containing_exactly_something?(value) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - # stree-ignore - t1.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t2| - t2.add_text "#" - end - end - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/collection_including.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/collection_including.rb deleted file mode 100644 index cb4b6887..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/collection_including.rb +++ /dev/null @@ -1,42 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class CollectionIncluding < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - SuperDiff::RSpec.a_collection_including_something?(value) || - SuperDiff::RSpec.array_including_something?(value) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - # stree-ignore - t1.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t2| - t2.add_text "#" - end - end - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/double.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/double.rb deleted file mode 100644 index 8278db45..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/double.rb +++ /dev/null @@ -1,102 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class Double < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - value.is_a?(::RSpec::Mocks::Double) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - t1.only_when method(:empty?) do |t2| - t2.as_lines_when_rendering_to_lines do |t3| - t3.add_text("#<#{inspected_class} #{inspected_name}>") - end - end - - t1.only_when method(:nonempty?) do |t2| - t2.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t3| - t3.add_text("#<#{inspected_class} #{inspected_name}") - - # stree-ignore - t3.when_rendering_to_lines do |t4| - t4.add_text " {" - end - end - - # stree-ignore - t2.when_rendering_to_string do |t3| - t3.add_text " " - end - - # stree-ignore - t2.nested do |t3| - t3.insert_hash_inspection_of doubled_methods - end - - t2.as_lines_when_rendering_to_lines( - collection_bookend: :close - ) do |t3| - # stree-ignore - t3.when_rendering_to_lines do |t4| - t4.add_text "}" - end - - t3.add_text ">" - end - end - end - end - - private - - def empty? - doubled_methods.empty? - end - - def nonempty? - !empty? - end - - def inspected_class - case object - when ::RSpec::Mocks::InstanceVerifyingDouble - "InstanceDouble" - when ::RSpec::Mocks::ClassVerifyingDouble - "ClassDouble" - when ::RSpec::Mocks::ObjectVerifyingDouble - "ObjectDouble" - else - "Double" - end - end - - def inspected_name - if object.instance_variable_get("@name") - object.instance_variable_get("@name").inspect - else - "(anonymous)" - end - end - - def doubled_methods - @_doubled_methods ||= - doubled_method_names.reduce({}) do |hash, key| - hash.merge(key => object.public_send(key)) - end - end - - def doubled_method_names - object - .__send__(:__mock_proxy) - .instance_variable_get("@method_doubles") - .keys - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher.rb deleted file mode 100644 index 1a983ca9..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher.rb +++ /dev/null @@ -1,19 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class GenericDescribableMatcher < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - ::RSpec::Matchers.is_a_describable_matcher?(value) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - t1.add_text "#<#{object.description}>" - end - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/hash_including.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/hash_including.rb deleted file mode 100644 index 9cb977bf..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/hash_including.rb +++ /dev/null @@ -1,42 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class HashIncluding < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - SuperDiff::RSpec.a_hash_including_something?(value) || - SuperDiff::RSpec.hash_including_something?(value) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - # stree-ignore - t1.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t2| - t2.add_text "#" - end - end - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/instance_of.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/instance_of.rb deleted file mode 100644 index e666f5f6..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/instance_of.rb +++ /dev/null @@ -1,27 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class InstanceOf < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - SuperDiff::RSpec.an_instance_of_something?(value) || - SuperDiff::RSpec.instance_of_something?(value) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - klass = - if SuperDiff::RSpec.an_instance_of_something?(object) - object.expected - else - object.instance_variable_get(:@klass) - end - - t1.add_text "#" - end - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/kind_of.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/kind_of.rb deleted file mode 100644 index 00ec4168..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/kind_of.rb +++ /dev/null @@ -1,27 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class KindOf < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - SuperDiff::RSpec.a_kind_of_something?(value) || - SuperDiff::RSpec.kind_of_something?(value) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - klass = - if SuperDiff::RSpec.a_kind_of_something?(object) - object.expected - else - object.instance_variable_get(:@klass) - end - - t1.add_text "#" - end - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/object_having_attributes.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/object_having_attributes.rb deleted file mode 100644 index 8696f07b..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/object_having_attributes.rb +++ /dev/null @@ -1,36 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class ObjectHavingAttributes < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - SuperDiff::RSpec.an_object_having_some_attributes?(value) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - # stree-ignore - t1.as_lines_when_rendering_to_lines( - collection_bookend: :open - ) do |t2| - t2.add_text "#" - end - end - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/primitive.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/primitive.rb deleted file mode 100644 index d38fa296..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/primitive.rb +++ /dev/null @@ -1,10 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class Primitive < SuperDiff::ObjectInspection::InspectionTreeBuilders::Primitive - end - end - end - end -end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/value_within.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/value_within.rb deleted file mode 100644 index c9fb9854..00000000 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/value_within.rb +++ /dev/null @@ -1,32 +0,0 @@ -module SuperDiff - module RSpec - module ObjectInspection - module InspectionTreeBuilders - class ValueWithin < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base - def self.applies_to?(value) - SuperDiff::RSpec.a_value_within_something?(value) - end - - def call - SuperDiff::ObjectInspection::InspectionTree.new do |t1| - t1.as_prelude_when_rendering_to_lines do |t2| - t2.add_text "#" - end - end - end - end - end - end -end diff --git a/lib/super_diff/rspec/operation_tree_builders/collection_containing_exactly.rb b/lib/super_diff/rspec/operation_tree_builders/collection_containing_exactly.rb index a6272b90..ba95b2d8 100644 --- a/lib/super_diff/rspec/operation_tree_builders/collection_containing_exactly.rb +++ b/lib/super_diff/rspec/operation_tree_builders/collection_containing_exactly.rb @@ -1,7 +1,7 @@ module SuperDiff module RSpec module OperationTreeBuilders - class CollectionContainingExactly < SuperDiff::OperationTreeBuilders::Base + class CollectionContainingExactly < Core::AbstractOperationTreeBuilder def self.applies_to?(expected, actual) SuperDiff::RSpec.a_collection_containing_exactly_something?( expected @@ -47,7 +47,7 @@ def populate_pairings_maximizer_in_expected_with(actual) def add_noop_to(operations, index) value = actual[index] - operations << ::SuperDiff::Operations::UnaryOperation.new( + operations << Core::UnaryOperation.new( name: :noop, collection: collection, key: index, @@ -58,7 +58,7 @@ def add_noop_to(operations, index) def add_delete_to(operations, index) value = expected.expected[index] - operations << ::SuperDiff::Operations::UnaryOperation.new( + operations << Core::UnaryOperation.new( name: :delete, collection: collection, key: index, @@ -69,7 +69,7 @@ def add_delete_to(operations, index) def add_insert_to(operations, index) value = actual[index] - operations << ::SuperDiff::Operations::UnaryOperation.new( + operations << Core::UnaryOperation.new( name: :insert, collection: collection, key: index, diff --git a/lib/super_diff/rspec/operation_tree_builders/collection_including.rb b/lib/super_diff/rspec/operation_tree_builders/collection_including.rb index a1bc6465..244e388a 100644 --- a/lib/super_diff/rspec/operation_tree_builders/collection_including.rb +++ b/lib/super_diff/rspec/operation_tree_builders/collection_including.rb @@ -1,7 +1,7 @@ module SuperDiff module RSpec module OperationTreeBuilders - class CollectionIncluding < SuperDiff::OperationTreeBuilders::Array + class CollectionIncluding < Basic::OperationTreeBuilders::Array def self.applies_to?(expected, actual) ( SuperDiff::RSpec.a_collection_including_something?(expected) || diff --git a/lib/super_diff/rspec/operation_tree_builders/hash_including.rb b/lib/super_diff/rspec/operation_tree_builders/hash_including.rb index b9271163..14711331 100644 --- a/lib/super_diff/rspec/operation_tree_builders/hash_including.rb +++ b/lib/super_diff/rspec/operation_tree_builders/hash_including.rb @@ -1,7 +1,7 @@ module SuperDiff module RSpec module OperationTreeBuilders - class HashIncluding < SuperDiff::OperationTreeBuilders::Hash + class HashIncluding < Basic::OperationTreeBuilders::Hash include ::RSpec::Matchers::Composable def self.applies_to?(expected, actual) diff --git a/lib/super_diff/rspec/operation_tree_builders/object_having_attributes.rb b/lib/super_diff/rspec/operation_tree_builders/object_having_attributes.rb index 4b17d341..d017f8d2 100644 --- a/lib/super_diff/rspec/operation_tree_builders/object_having_attributes.rb +++ b/lib/super_diff/rspec/operation_tree_builders/object_having_attributes.rb @@ -1,7 +1,7 @@ module SuperDiff module RSpec module OperationTreeBuilders - class ObjectHavingAttributes < SuperDiff::OperationTreeBuilders::DefaultObject + class ObjectHavingAttributes < Basic::OperationTreeBuilders::DefaultObject def self.applies_to?(expected, _actual) SuperDiff::RSpec.an_object_having_some_attributes?(expected) end @@ -9,7 +9,7 @@ def self.applies_to?(expected, _actual) protected def build_operation_tree - find_operation_tree_for(actual) + SuperDiff.find_operation_tree_for(actual) end def attribute_names diff --git a/lib/super_diff/tiered_lines.rb b/lib/super_diff/tiered_lines.rb deleted file mode 100644 index d868d6f3..00000000 --- a/lib/super_diff/tiered_lines.rb +++ /dev/null @@ -1,4 +0,0 @@ -module SuperDiff - class TieredLines < Array - end -end diff --git a/lib/super_diff/tiered_lines_elider.rb b/lib/super_diff/tiered_lines_elider.rb deleted file mode 100644 index c691acf5..00000000 --- a/lib/super_diff/tiered_lines_elider.rb +++ /dev/null @@ -1,462 +0,0 @@ -module SuperDiff - class TieredLinesElider - SIZE_OF_ELISION = 1 - - extend AttrExtras.mixin - include Helpers - - method_object :lines - - def call - all_lines_are_changed_or_unchanged? ? lines : elided_lines - end - - private - - def all_lines_are_changed_or_unchanged? - panes.size == 1 && panes.first.range == Range.new(0, lines.length - 1) - end - - def elided_lines - boxes_to_elide - .reverse - .reduce(lines) do |lines_with_elisions, box| - with_box_elided(box, lines_with_elisions) - end - end - - def boxes_to_elide - @_boxes_to_elide ||= - panes_to_consider_for_eliding.reduce([]) do |array, pane| - array + (find_boxes_to_elide_within(pane) || []) - end - end - - def panes_to_consider_for_eliding - panes.select { |pane| pane.type == :clean && pane.range.size > maximum } - end - - def panes - @_panes ||= BuildPanes.call(dirty_panes: padded_dirty_panes, lines: lines) - end - - def padded_dirty_panes - @_padded_dirty_panes ||= - combine_congruent_panes( - dirty_panes - .map(&:padded) - .map { |pane| pane.capped_to(0, lines.size - 1) } - ) - end - - def dirty_panes - @_dirty_panes ||= - lines - .each_with_index - .select { |line, index| line.type != :noop } - .reduce([]) do |panes, (_, index)| - if !panes.empty? && panes.last.range.end == index - 1 - panes[0..-2] + [panes[-1].extended_to(index)] - else - panes + [Pane.new(type: :dirty, range: index..index)] - end - end - end - - def with_box_elided(box, lines) - box_at_start_of_lines = - if lines.first.complete_bookend? - box.range.begin == 1 - else - box.range.begin == 0 - end - - box_at_end_of_lines = - if lines.last.complete_bookend? - box.range.end == lines.size - 2 - else - box.range.end == lines.size - 1 - end - - if one_dimensional_line_tree? && outermost_box?(box) - if box_at_start_of_lines - with_start_of_box_elided(box, lines) - elsif box_at_end_of_lines - with_end_of_box_elided(box, lines) - else - with_middle_of_box_elided(box, lines) - end - else - with_subset_of_lines_elided( - lines, - range: box.range, - indentation_level: box.indentation_level - ) - end - end - - def outermost_box?(box) - box.indentation_level == all_indentation_levels.min - end - - def one_dimensional_line_tree? - all_indentation_levels.size == 1 - end - - def all_indentation_levels - lines - .map(&:indentation_level) - .select { |indentation_level| indentation_level > 0 } - .uniq - end - - def find_boxes_to_elide_within(pane) - set_of_boxes = - normalized_box_groups_at_decreasing_indentation_levels_within(pane) - - total_size_before_eliding = - lines[pane.range].reject(&:complete_bookend?).size - - if total_size_before_eliding > maximum - if maximum > 0 - set_of_boxes.find do |boxes| - total_size_after_eliding = - total_size_before_eliding - - boxes.sum { |box| box.range.size - SIZE_OF_ELISION } - total_size_after_eliding <= maximum - end - else - set_of_boxes[-1] - end - else - [] - end - end - - def normalized_box_groups_at_decreasing_indentation_levels_within(pane) - box_groups_at_decreasing_indentation_levels_within(pane).map( - &method(:filter_out_boxes_fully_contained_in_others) - ).map(&method(:combine_congruent_boxes)) - end - - def box_groups_at_decreasing_indentation_levels_within(pane) - boxes_within_pane = boxes.select { |box| box.fits_fully_within?(pane) } - - possible_indentation_levels = - boxes_within_pane - .map(&:indentation_level) - .select { |indentation_level| indentation_level > 0 } - .uniq - .sort - .reverse - - possible_indentation_levels.map do |indentation_level| - boxes_within_pane.select do |box| - box.indentation_level >= indentation_level - end - end - end - - def filter_out_boxes_fully_contained_in_others(boxes) - sorted_boxes = - boxes.sort_by do |box| - [box.indentation_level, box.range.begin, box.range.end] - end - - boxes.reject do |box2| - sorted_boxes.any? do |box1| - !box1.equal?(box2) && box1.fully_contains?(box2) - end - end - end - - def combine_congruent_boxes(boxes) - combine(boxes, on: :indentation_level) - end - - def combine_congruent_panes(panes) - combine(panes, on: :type) - end - - def combine(spannables, on:) - criterion = on - spannables.reduce([]) do |combined_spannables, spannable| - if ( - !combined_spannables.empty? && - spannable.range.begin <= - combined_spannables.last.range.end + 1 && - spannable.public_send(criterion) == - combined_spannables.last.public_send(criterion) - ) - combined_spannables[0..-2] + - [combined_spannables[-1].extended_to(spannable.range.end)] - else - combined_spannables + [spannable] - end - end - end - - def boxes - @_boxes ||= BuildBoxes.call(lines) - end - - def with_start_of_box_elided(box, lines) - amount_to_elide = - if maximum > 0 - box.range.size - maximum + SIZE_OF_ELISION - else - box.range.size - end - - with_subset_of_lines_elided( - lines, - range: - Range.new(box.range.begin, box.range.begin + amount_to_elide - 1), - indentation_level: box.indentation_level - ) - end - - def with_end_of_box_elided(box, lines) - amount_to_elide = - if maximum > 0 - box.range.size - maximum + SIZE_OF_ELISION - else - box.range.size - end - - range = - if amount_to_elide > 0 - Range.new(box.range.end - amount_to_elide + 1, box.range.end) - else - box.range - end - - with_subset_of_lines_elided( - lines, - range: range, - indentation_level: box.indentation_level - ) - end - - def with_middle_of_box_elided(box, lines) - half_of_maximum, remainder = - if maximum > 0 - (maximum - SIZE_OF_ELISION).divmod(2) - else - [0, 0] - end - - opening_length, closing_length = - half_of_maximum, - half_of_maximum + remainder - - with_subset_of_lines_elided( - lines, - range: - Range.new( - box.range.begin + opening_length, - box.range.end - closing_length - ), - indentation_level: box.indentation_level - ) - end - - def with_subset_of_lines_elided(lines, range:, indentation_level:) - with_slice_of_array_replaced( - lines, - range, - Elision.new( - indentation_level: indentation_level, - children: lines[range].map(&:as_elided) - ) - ) - end - - def maximum - SuperDiff.configuration.diff_elision_maximum || 0 - end - - class BuildPanes - extend AttrExtras.mixin - - method_object %i[dirty_panes! lines!] - - def call - beginning + middle + ending - end - - private - - def beginning - if (dirty_panes.empty? || dirty_panes.first.range.begin == 0) - [] - else - [ - Pane.new( - type: :clean, - range: Range.new(0, dirty_panes.first.range.begin - 1) - ) - ] - end - end - - def middle - if dirty_panes.size == 1 - dirty_panes - else - dirty_panes - .each_with_index - .each_cons(2) - .reduce([]) do |panes, ((pane1, _), (pane2, index2))| - panes + - [ - pane1, - Pane.new( - type: :clean, - range: Range.new(pane1.range.end + 1, pane2.range.begin - 1) - ) - ] + (index2 == dirty_panes.size - 1 ? [pane2] : []) - end - end - end - - def ending - if (dirty_panes.empty? || dirty_panes.last.range.end >= lines.size - 1) - [] - else - [ - Pane.new( - type: :clean, - range: Range.new(dirty_panes.last.range.end + 1, lines.size - 1) - ) - ] - end - end - end - - class Pane - extend AttrExtras.mixin - - rattr_initialize %i[type! range!] - - def extended_to(new_end) - self.class.new(type: type, range: range.begin..new_end) - end - - def padded - self.class.new(type: type, range: Range.new(range.begin, range.end)) - end - - def capped_to(beginning, ending) - new_beginning = range.begin < beginning ? beginning : range.begin - new_ending = range.end > ending ? ending : range.end - self.class.new(type: type, range: Range.new(new_beginning, new_ending)) - end - end - - class BuildBoxes - def self.call(lines) - builder = new(lines) - builder.build - builder.final_boxes - end - - attr_reader :final_boxes - - def initialize(lines) - @lines = lines - - @open_collection_boxes = [] - @final_boxes = [] - end - - def build - lines.each_with_index do |line, index| - if line.opens_collection? - open_new_collection_box(line, index) - elsif line.closes_collection? - extend_working_collection_box(index) - close_working_collection_box - else - extend_working_collection_box(index) if open_collection_boxes.any? - record_item_box(line, index) - end - end - end - - private - - attr_reader :lines, :open_collection_boxes - - def extend_working_collection_box(index) - open_collection_boxes.last.extend_to(index) - end - - def close_working_collection_box - final_boxes << open_collection_boxes.pop - end - - def open_new_collection_box(line, index) - open_collection_boxes << Box.new( - indentation_level: line.indentation_level, - range: index..index - ) - end - - def record_item_box(line, index) - final_boxes << Box.new( - indentation_level: line.indentation_level, - range: index..index - ) - end - end - - class Box - extend AttrExtras.mixin - - rattr_initialize %i[indentation_level! range!] - - def fully_contains?(other) - range.begin <= other.range.begin && range.end >= other.range.end - end - - def fits_fully_within?(other) - other.range.begin <= range.begin && other.range.end >= range.end - end - - def extended_to(new_end) - dup.tap { |clone| clone.extend_to(new_end) } - end - - def extend_to(new_end) - @range = range.begin..new_end - end - end - - class Elision - extend AttrExtras.mixin - - rattr_initialize %i[indentation_level! children!] - - def type - :elision - end - - def prefix - "" - end - - def value - "# ..." - end - - def elided? - true - end - - def add_comma? - false - end - end - end -end diff --git a/lib/super_diff/tiered_lines_formatter.rb b/lib/super_diff/tiered_lines_formatter.rb deleted file mode 100644 index 9a21a013..00000000 --- a/lib/super_diff/tiered_lines_formatter.rb +++ /dev/null @@ -1,75 +0,0 @@ -module SuperDiff - class TieredLinesFormatter - extend AttrExtras.mixin - - method_object :tiered_lines - - def call - colorized_document.to_s.chomp - end - - private - - def colorized_document - SuperDiff::Helpers.style do |doc| - formattable_lines.each do |formattable_line| - doc.public_send( - "#{formattable_line.color}_line", - formattable_line.content - ) - end - end - end - - def formattable_lines - tiered_lines.map { |line| FormattableLine.new(line) } - end - - class FormattableLine - extend AttrExtras.mixin - - INDENTATION_UNIT = " ".freeze - ICONS = { delete: "-", insert: "+", elision: " ", noop: " " }.freeze - COLORS = { - delete: :expected, - insert: :actual, - elision: :elision_marker, - noop: :plain - }.freeze - - pattr_initialize :line - - def content - icon + " " + indentation + line.prefix + line.value + possible_comma - end - - def color - COLORS.fetch(line.type) do - raise( - KeyError, - "Couldn't find color for line type #{line.type.inspect}!" - ) - end - end - - private - - def icon - ICONS.fetch(line.type) do - raise( - KeyError, - "Couldn't find icon for line type #{line.type.inspect}!" - ) - end - end - - def indentation - INDENTATION_UNIT * line.indentation_level - end - - def possible_comma - line.add_comma? ? "," : "" - end - end - end -end diff --git a/spec/support/integration/helpers.rb b/spec/support/integration/helpers.rb index d498224d..33d18cb6 100644 --- a/spec/support/integration/helpers.rb +++ b/spec/support/integration/helpers.rb @@ -115,7 +115,10 @@ def build_expected_output( end def colored(color_enabled: true, &block) - SuperDiff::Helpers.style(color_enabled: color_enabled, &block).to_s.chomp + SuperDiff::Core::Helpers + .style(color_enabled: color_enabled, &block) + .to_s + .chomp end end end diff --git a/spec/support/unit/helpers.rb b/spec/support/unit/helpers.rb index 5b1901d1..580dcea0 100644 --- a/spec/support/unit/helpers.rb +++ b/spec/support/unit/helpers.rb @@ -1,5 +1,7 @@ module SuperDiff module UnitTests + extend self + def with_configuration(configuration) old_configuration = SuperDiff.configuration.dup SuperDiff.configuration.merge!(configuration) @@ -7,7 +9,16 @@ def with_configuration(configuration) end def colored(*args, **opts, &block) - SuperDiff::Helpers.style(*args, **opts, &block).to_s.chomp + SuperDiff::Core::Helpers.style(*args, **opts, &block).to_s.chomp + end + + def capture_warnings + fake_stderr = StringIO.new + original_stderr = $stderr + $stderr = fake_stderr + yield + $stderr = original_stderr + fake_stderr.string end end end diff --git a/spec/support/unit/matchers/be_deprecated_in_favor_of.rb b/spec/support/unit/matchers/be_deprecated_in_favor_of.rb new file mode 100644 index 00000000..8d5773bb --- /dev/null +++ b/spec/support/unit/matchers/be_deprecated_in_favor_of.rb @@ -0,0 +1,39 @@ +module SuperDiff + module UnitTests + def be_deprecated_in_favor_of(new_constant_name) + BeDeprecatedInFavorOfMatcher.new(new_constant_name) + end + + class BeDeprecatedInFavorOfMatcher + extend AttrExtras.mixin + + pattr_initialize :new_constant_name + attr_private :actual_output, :old_constant_name + + def matches?(old_constant_name) + @old_constant_name = old_constant_name + @actual_output = + SuperDiff::UnitTests.capture_warnings do + Object.const_get(old_constant_name) + end + @actual_output.start_with?(expected_prefix) + end + + def failure_message + "Expected stderr to start with:\n\n" + + SuperDiff::Test::OutputHelpers.bookended(expected_prefix) + "\n" + + "Actual output:\n\n" + + SuperDiff::Test::OutputHelpers.bookended(actual_output) + end + + private + + def expected_prefix + <<~EOT.rstrip + WARNING: #{old_constant_name} is deprecated and will be removed in the next major release. + Please use #{new_constant_name} instead. + EOT + end + end + end +end diff --git a/spec/unit/operation_tree_flatteners/array_spec.rb b/spec/unit/basic/operation_tree_flatteners/array_spec.rb similarity index 97% rename from spec/unit/operation_tree_flatteners/array_spec.rb rename to spec/unit/basic/operation_tree_flatteners/array_spec.rb index 0a239ebe..1b43f6b6 100644 --- a/spec/unit/operation_tree_flatteners/array_spec.rb +++ b/spec/unit/basic/operation_tree_flatteners/array_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -RSpec.describe SuperDiff::OperationTreeFlatteners::Array do +RSpec.describe SuperDiff::Basic::OperationTreeFlatteners::Array do context "given an empty tree" do it "returns a set of lines which are simply the open token and close token" do expect(described_class.call([])).to match( @@ -30,7 +30,7 @@ it "returns a series of lines from inspecting each value, creating multiple lines upon encountering inner data structures" do collection = Array.new(3) { :some_value } operation_tree = - SuperDiff::OperationTrees::Array.new( + SuperDiff::Basic::OperationTrees::Array.new( [ double( :operation, @@ -134,7 +134,7 @@ expected = Array.new(3) { :some_value } actual = Array.new(4) { :some_value } operation_tree = - SuperDiff::OperationTrees::Array.new( + SuperDiff::Basic::OperationTrees::Array.new( [ double( :operation, @@ -266,7 +266,7 @@ collection = Array.new(3) { :some_value } subcollection = Array.new(2) { :some_value } operation_tree = - SuperDiff::OperationTrees::Array.new( + SuperDiff::Basic::OperationTrees::Array.new( [ double( :operation, @@ -283,7 +283,7 @@ right_collection: collection, right_index: 1, children: - SuperDiff::OperationTrees::Array.new( + SuperDiff::Basic::OperationTrees::Array.new( [ double( :operation, @@ -406,7 +406,7 @@ right_collection << right_collection operation_tree = - SuperDiff::OperationTrees::Array.new( + SuperDiff::Basic::OperationTrees::Array.new( [ double( :operation, @@ -502,7 +502,7 @@ right_subcollection << right_subcollection operation_tree = - SuperDiff::OperationTrees::Array.new( + SuperDiff::Basic::OperationTrees::Array.new( [ double( :operation, @@ -519,7 +519,7 @@ right_collection: collection, right_index: 1, children: - SuperDiff::OperationTrees::Array.new( + SuperDiff::Basic::OperationTrees::Array.new( [ double( :operation, diff --git a/spec/unit/operation_tree_flatteners/custom_object_spec.rb b/spec/unit/basic/operation_tree_flatteners/custom_object_spec.rb similarity index 97% rename from spec/unit/operation_tree_flatteners/custom_object_spec.rb rename to spec/unit/basic/operation_tree_flatteners/custom_object_spec.rb index 9dbcfa8e..0c8a7d19 100644 --- a/spec/unit/operation_tree_flatteners/custom_object_spec.rb +++ b/spec/unit/basic/operation_tree_flatteners/custom_object_spec.rb @@ -1,10 +1,10 @@ require "spec_helper" -RSpec.describe SuperDiff::OperationTreeFlatteners::CustomObject do +RSpec.describe SuperDiff::Basic::OperationTreeFlatteners::CustomObject do context "given an empty tree" do it "returns a set of lines which are simply the open token and close token" do operation_tree = - SuperDiff::OperationTrees::CustomObject.new( + SuperDiff::Basic::OperationTrees::CustomObject.new( [], underlying_object: underlying_object ) @@ -38,7 +38,7 @@ it "returns a series of lines from inspecting each value, creating multiple lines upon encountering inner data structures" do collection = Array.new(3) { :some_value } operation_tree = - SuperDiff::OperationTrees::CustomObject.new( + SuperDiff::Basic::OperationTrees::CustomObject.new( [ double( :operation, @@ -146,7 +146,7 @@ expected = Array.new(3) { :some_value } actual = Array.new(4) { :some_value } operation_tree = - SuperDiff::OperationTrees::CustomObject.new( + SuperDiff::Basic::OperationTrees::CustomObject.new( [ double( :operation, @@ -284,7 +284,7 @@ collection = Array.new(3) { :some_value } subcollection = Array.new(2) { :some_value } operation_tree = - SuperDiff::OperationTrees::CustomObject.new( + SuperDiff::Basic::OperationTrees::CustomObject.new( [ double( :operation, @@ -304,7 +304,7 @@ right_key: :baz, right_index: 1, children: - SuperDiff::OperationTrees::CustomObject.new( + SuperDiff::Basic::OperationTrees::CustomObject.new( [ double( :operation, @@ -435,7 +435,7 @@ .tap { |collection| collection << right_collection } operation_tree = - SuperDiff::OperationTrees::CustomObject.new( + SuperDiff::Basic::OperationTrees::CustomObject.new( [ double( :operation, @@ -536,7 +536,7 @@ Array.new(1) { :some_value }.tap { |coll| coll << right_subcollection } operation_tree = - SuperDiff::OperationTrees::CustomObject.new( + SuperDiff::Basic::OperationTrees::CustomObject.new( [ double( :operation, @@ -556,7 +556,7 @@ right_key: :baz, right_index: 1, children: - SuperDiff::OperationTrees::CustomObject.new( + SuperDiff::Basic::OperationTrees::CustomObject.new( [ double( :operation, diff --git a/spec/unit/operation_tree_flatteners/default_object_spec.rb b/spec/unit/basic/operation_tree_flatteners/default_object_spec.rb similarity index 97% rename from spec/unit/operation_tree_flatteners/default_object_spec.rb rename to spec/unit/basic/operation_tree_flatteners/default_object_spec.rb index df396459..43e082fa 100644 --- a/spec/unit/operation_tree_flatteners/default_object_spec.rb +++ b/spec/unit/basic/operation_tree_flatteners/default_object_spec.rb @@ -1,10 +1,10 @@ require "spec_helper" -RSpec.describe SuperDiff::OperationTreeFlatteners::DefaultObject do +RSpec.describe SuperDiff::Basic::OperationTreeFlatteners::DefaultObject do context "given an empty tree" do it "returns a set of lines which are simply the open token and close token" do operation_tree = - SuperDiff::OperationTrees::DefaultObject.new( + SuperDiff::Basic::OperationTrees::DefaultObject.new( [], underlying_object: underlying_object ) @@ -38,7 +38,7 @@ it "returns a series of lines from inspecting each value, creating multiple lines upon encountering inner data structures" do collection = Array.new(3) { :some_value } operation_tree = - SuperDiff::OperationTrees::DefaultObject.new( + SuperDiff::Basic::OperationTrees::DefaultObject.new( [ double( :operation, @@ -146,7 +146,7 @@ expected = Array.new(3) { :some_value } actual = Array.new(4) { :some_value } operation_tree = - SuperDiff::OperationTrees::DefaultObject.new( + SuperDiff::Basic::OperationTrees::DefaultObject.new( [ double( :operation, @@ -284,7 +284,7 @@ collection = Array.new(3) { :some_value } subcollection = Array.new(2) { :some_value } operation_tree = - SuperDiff::OperationTrees::DefaultObject.new( + SuperDiff::Basic::OperationTrees::DefaultObject.new( [ double( :operation, @@ -304,7 +304,7 @@ right_key: :baz, right_index: 1, children: - SuperDiff::OperationTrees::DefaultObject.new( + SuperDiff::Basic::OperationTrees::DefaultObject.new( [ double( :operation, @@ -435,7 +435,7 @@ .tap { |collection| collection << right_collection } operation_tree = - SuperDiff::OperationTrees::DefaultObject.new( + SuperDiff::Basic::OperationTrees::DefaultObject.new( [ double( :operation, @@ -536,7 +536,7 @@ Array.new(1) { :some_value }.tap { |coll| coll << right_subcollection } operation_tree = - SuperDiff::OperationTrees::DefaultObject.new( + SuperDiff::Basic::OperationTrees::DefaultObject.new( [ double( :operation, @@ -556,7 +556,7 @@ right_key: :baz, right_index: 1, children: - SuperDiff::OperationTrees::DefaultObject.new( + SuperDiff::Basic::OperationTrees::DefaultObject.new( [ double( :operation, diff --git a/spec/unit/operation_tree_flatteners/hash_spec.rb b/spec/unit/basic/operation_tree_flatteners/hash_spec.rb similarity index 97% rename from spec/unit/operation_tree_flatteners/hash_spec.rb rename to spec/unit/basic/operation_tree_flatteners/hash_spec.rb index d8eaf075..6db948a7 100644 --- a/spec/unit/operation_tree_flatteners/hash_spec.rb +++ b/spec/unit/basic/operation_tree_flatteners/hash_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -RSpec.describe SuperDiff::OperationTreeFlatteners::Hash do +RSpec.describe SuperDiff::Basic::OperationTreeFlatteners::Hash do context "given an empty tree" do it "returns a set of lines which are simply the open token and close token" do expect(described_class.call([])).to match( @@ -30,7 +30,7 @@ it "returns a series of lines from inspecting each value, creating multiple lines upon encountering inner data structures" do collection = Array.new(3) { :some_value } operation_tree = - SuperDiff::OperationTrees::Hash.new( + SuperDiff::Basic::OperationTrees::Hash.new( [ double( :operation, @@ -140,7 +140,7 @@ expected = Array.new(3) { :some_value } actual = Array.new(4) { :some_value } operation_tree = - SuperDiff::OperationTrees::Hash.new( + SuperDiff::Basic::OperationTrees::Hash.new( [ double( :operation, @@ -280,7 +280,7 @@ collection = Array.new(3) { :some_value } subcollection = Array.new(2) { :some_value } operation_tree = - SuperDiff::OperationTrees::Hash.new( + SuperDiff::Basic::OperationTrees::Hash.new( [ double( :operation, @@ -300,7 +300,7 @@ right_key: :baz, right_index: 1, children: - SuperDiff::OperationTrees::Hash.new( + SuperDiff::Basic::OperationTrees::Hash.new( [ double( :operation, @@ -429,7 +429,7 @@ .tap { |collection| collection << right_collection } operation_tree = - SuperDiff::OperationTrees::Hash.new( + SuperDiff::Basic::OperationTrees::Hash.new( [ double( :operation, @@ -529,7 +529,7 @@ Array.new(1) { :some_value }.tap { |coll| coll << right_subcollection } operation_tree = - SuperDiff::OperationTrees::Hash.new( + SuperDiff::Basic::OperationTrees::Hash.new( [ double( :operation, @@ -549,7 +549,7 @@ right_key: :baz, right_index: 1, children: - SuperDiff::OperationTrees::Hash.new( + SuperDiff::Basic::OperationTrees::Hash.new( [ double( :operation, diff --git a/spec/unit/operation_tree_flatteners/multiline_string_spec.rb b/spec/unit/basic/operation_tree_flatteners/multiline_string_spec.rb similarity index 92% rename from spec/unit/operation_tree_flatteners/multiline_string_spec.rb rename to spec/unit/basic/operation_tree_flatteners/multiline_string_spec.rb index ab904940..2eeae0d3 100644 --- a/spec/unit/operation_tree_flatteners/multiline_string_spec.rb +++ b/spec/unit/basic/operation_tree_flatteners/multiline_string_spec.rb @@ -1,9 +1,9 @@ require "spec_helper" -RSpec.describe SuperDiff::OperationTreeFlatteners::MultilineString do +RSpec.describe SuperDiff::Basic::OperationTreeFlatteners::MultilineString do context "given an empty tree" do it "returns an empty set of lines" do - operation_tree = SuperDiff::OperationTrees::MultilineString.new([]) + operation_tree = SuperDiff::Basic::OperationTrees::MultilineString.new([]) flattened_operation_tree = described_class.call(operation_tree) expect(flattened_operation_tree).to eq([]) end @@ -13,7 +13,7 @@ it "returns a series of lines from printing each value" do collection = Array.new(3) { :some_value } operation_tree = - SuperDiff::OperationTrees::MultilineString.new( + SuperDiff::Basic::OperationTrees::MultilineString.new( [ double( :operation, @@ -73,7 +73,7 @@ it "returns a series of lines from printing each value" do collection = Array.new(3) { :some_value } operation_tree = - SuperDiff::OperationTrees::MultilineString.new( + SuperDiff::Basic::OperationTrees::MultilineString.new( [ double( :operation, diff --git a/spec/unit/helpers_spec.rb b/spec/unit/core/helpers_spec.rb similarity index 94% rename from spec/unit/helpers_spec.rb rename to spec/unit/core/helpers_spec.rb index 20dc52d6..18a0d479 100644 --- a/spec/unit/helpers_spec.rb +++ b/spec/unit/core/helpers_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -RSpec.describe SuperDiff::Helpers do +RSpec.describe SuperDiff::Core::Helpers do shared_examples "helper tests" do describe "with_slice_of_array_replaced" do context "if the given range covers the whole array" do @@ -32,7 +32,7 @@ end describe "object_address_for" do - if SuperDiff::Helpers.ruby_version_matches?(">= 2.7.0") + if SuperDiff::Core::Helpers.ruby_version_matches?(">= 2.7.0") it "returns an empty string for Floats" do expect(helper.object_address_for(1.0)).to eq("") end diff --git a/spec/unit/tiered_lines_elider_spec.rb b/spec/unit/core/tiered_lines_elider_spec.rb similarity index 99% rename from spec/unit/tiered_lines_elider_spec.rb rename to spec/unit/core/tiered_lines_elider_spec.rb index f61e7352..8cfa9be0 100644 --- a/spec/unit/tiered_lines_elider_spec.rb +++ b/spec/unit/core/tiered_lines_elider_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -RSpec.describe SuperDiff::TieredLinesElider, type: :unit do +RSpec.describe SuperDiff::Core::TieredLinesElider, type: :unit do context "and the gem is configured with :diff_elision_maximum" do context "and :diff_elision_maximum is more than 0" do context "and the line tree contains a section of noops that does not span more than the maximum" do @@ -6299,7 +6299,7 @@ def an_actual_line(**args) add_comma = args.delete(:add_comma?) { false } - SuperDiff::Line.new(**args, add_comma: add_comma) + SuperDiff::Core::Line.new(**args, add_comma: add_comma) end def an_expected_line(type:, indentation_level:, value:, children: [], **rest) diff --git a/spec/unit/tiered_lines_formatter_spec.rb b/spec/unit/core/tiered_lines_formatter_spec.rb similarity index 98% rename from spec/unit/tiered_lines_formatter_spec.rb rename to spec/unit/core/tiered_lines_formatter_spec.rb index e7d39641..f9496e18 100644 --- a/spec/unit/tiered_lines_formatter_spec.rb +++ b/spec/unit/core/tiered_lines_formatter_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -RSpec.describe SuperDiff::TieredLinesFormatter, type: :unit do +RSpec.describe SuperDiff::Core::TieredLinesFormatter, type: :unit do it "formats the given lines as an array of strings with appropriate colors and indentation" do tiered_lines = [ line(type: :noop, indentation_level: 0, value: "["), diff --git a/spec/unit/deprecations_spec.rb b/spec/unit/deprecations_spec.rb new file mode 100644 index 00000000..5bb43f71 --- /dev/null +++ b/spec/unit/deprecations_spec.rb @@ -0,0 +1,176 @@ +require "spec_helper" + +# stree-ignore +common_constant_remappings = { + "SuperDiff::ColorizedDocumentExtensions" => "SuperDiff::Core::ColorizedDocumentExtensions", + "SuperDiff::Configuration" => "SuperDiff::Core::Configuration", + # TODO: Add back? + # "SuperDiff::DiffFormatters::Collection" => "SuperDiff::Basic::DiffFormatters::Collection", + # "SuperDiff::DiffFormatters::MultilineString" => "SuperDiff::Basic::DiffFormatters::MultilineString", + "SuperDiff::Differs::Array" => "SuperDiff::Basic::Differs::Array", + "SuperDiff::Differs::Base" => "SuperDiff::Core::AbstractDiffer", + "SuperDiff::Differs::CustomObject" => "SuperDiff::Basic::Differs::CustomObject", + "SuperDiff::Differs::DateLike" => "SuperDiff::Basic::Differs::DateLike", + "SuperDiff::Differs::DefaultObject" => "SuperDiff::Basic::Differs::DefaultObject", + "SuperDiff::Differs::Hash" => "SuperDiff::Basic::Differs::Hash", + "SuperDiff::Differs::MultilineString" => "SuperDiff::Basic::Differs::MultilineString", + "SuperDiff::Differs::TimeLike" => "SuperDiff::Basic::Differs::TimeLike", + "SuperDiff::Errors::NoDifferAvailableError" => "SuperDiff::Core::NoDifferAvailableError", + "SuperDiff::GemVersion" => "SuperDiff::Core::GemVersion", + "SuperDiff::Helpers" => "SuperDiff::Core::Helpers", + "SuperDiff::ImplementationChecks" => "SuperDiff::Core::ImplementationChecks", + "SuperDiff::Line" => "SuperDiff::Core::Line", + "SuperDiff::ObjectInspection::InspectionTree" => "SuperDiff::Core::InspectionTree", + "SuperDiff::ObjectInspection::InspectionTreeBuilders::Array" => "SuperDiff::Basic::InspectionTreeBuilders::Array", + "SuperDiff::ObjectInspection::InspectionTreeBuilders::Base" => "SuperDiff::Core::AbstractInspectionTreeBuilder", + "SuperDiff::ObjectInspection::InspectionTreeBuilders::CustomObject" => "SuperDiff::Basic::InspectionTreeBuilders::CustomObject", + "SuperDiff::ObjectInspection::InspectionTreeBuilders::DateLike" => "SuperDiff::Basic::InspectionTreeBuilders::DateLike", + "SuperDiff::ObjectInspection::InspectionTreeBuilders::DefaultObject" => "SuperDiff::Basic::InspectionTreeBuilders::DefaultObject", + "SuperDiff::ObjectInspection::InspectionTreeBuilders::Hash" => "SuperDiff::Basic::InspectionTreeBuilders::Hash", + "SuperDiff::ObjectInspection::InspectionTreeBuilders::Primitive" => "SuperDiff::Basic::InspectionTreeBuilders::Primitive", + "SuperDiff::ObjectInspection::InspectionTreeBuilders::TimeLike" => "SuperDiff::Basic::InspectionTreeBuilders::TimeLike", + "SuperDiff::ObjectInspection::Nodes::AsLinesWhenRenderingToLines" => "SuperDiff::Core::InspectionTreeNodes::AsLinesWhenRenderingToLines", + "SuperDiff::ObjectInspection::Nodes::AsPrefixWhenRenderingToLines" => "SuperDiff::Core::InspectionTreeNodes::AsPrefixWhenRenderingToLines", + "SuperDiff::ObjectInspection::Nodes::AsPreludeWhenRenderingToLines" => "SuperDiff::Core::InspectionTreeNodes::AsPreludeWhenRenderingToLines", + "SuperDiff::ObjectInspection::Nodes::AsSingleLine" => "SuperDiff::Core::InspectionTreeNodes::AsSingleLine", + "SuperDiff::ObjectInspection::Nodes::Base" => "SuperDiff::Core::InspectionTreeNodes::Base", + "SuperDiff::ObjectInspection::Nodes::Inspection" => "SuperDiff::Core::InspectionTreeNodes::Inspection", + "SuperDiff::ObjectInspection::Nodes::Nesting" => "SuperDiff::Core::InspectionTreeNodes::Nesting", + "SuperDiff::ObjectInspection::Nodes::OnlyWhen" => "SuperDiff::Core::InspectionTreeNodes::OnlyWhen", + "SuperDiff::ObjectInspection::Nodes::Text" => "SuperDiff::Core::InspectionTreeNodes::Text", + "SuperDiff::ObjectInspection::Nodes::WhenEmpty" => "SuperDiff::Core::InspectionTreeNodes::WhenEmpty", + "SuperDiff::ObjectInspection::Nodes::WhenNonEmpty" => "SuperDiff::Core::InspectionTreeNodes::WhenNonEmpty", + "SuperDiff::ObjectInspection::Nodes::WhenRenderingToLines" => "SuperDiff::Core::InspectionTreeNodes::WhenRenderingToLines", + "SuperDiff::ObjectInspection::Nodes::WhenRenderingToString" => "SuperDiff::Core::InspectionTreeNodes::WhenRenderingToString", + "SuperDiff::ObjectInspection::PrefixForNextNode" => "SuperDiff::Core::PrefixForNextInspectionTreeNode", + "SuperDiff::ObjectInspection::PreludeForNextNode" => "SuperDiff::Core::PreludeForNextInspectionTreeNode", + "SuperDiff::OperationTreeBuilders::Array" => "SuperDiff::Basic::OperationTreeBuilders::Array", + "SuperDiff::OperationTreeBuilders::Base" => "SuperDiff::Core::AbstractOperationTreeBuilder", + "SuperDiff::OperationTreeBuilders::CustomObject" => "SuperDiff::Basic::OperationTreeBuilders::CustomObject", + "SuperDiff::OperationTreeBuilders::DateLike" => "SuperDiff::Basic::OperationTreeBuilders::DateLike", + "SuperDiff::OperationTreeBuilders::DefaultObject" => "SuperDiff::Basic::OperationTreeBuilders::DefaultObject", + "SuperDiff::OperationTreeBuilders::Hash" => "SuperDiff::Basic::OperationTreeBuilders::Hash", + "SuperDiff::OperationTreeBuilders::MultilineString" => "SuperDiff::Basic::OperationTreeBuilders::MultilineString", + "SuperDiff::OperationTreeBuilders::TimeLike" => "SuperDiff::Basic::OperationTreeBuilders::TimeLike", + "SuperDiff::OperationTreeFlatteners::Array" => "SuperDiff::Basic::OperationTreeFlatteners::Array", + "SuperDiff::OperationTreeFlatteners::Base" => "SuperDiff::Core::AbstractOperationTreeFlattener", + "SuperDiff::OperationTreeFlatteners::Collection" => "SuperDiff::Basic::OperationTreeFlatteners::Collection", + "SuperDiff::OperationTreeFlatteners::CustomObject" => "SuperDiff::Basic::OperationTreeFlatteners::CustomObject", + "SuperDiff::OperationTreeFlatteners::DefaultObject" => "SuperDiff::Basic::OperationTreeFlatteners::DefaultObject", + "SuperDiff::OperationTreeFlatteners::Hash" => "SuperDiff::Basic::OperationTreeFlatteners::Hash", + "SuperDiff::OperationTreeFlatteners::MultilineString" => "SuperDiff::Basic::OperationTreeFlatteners::MultilineString", + "SuperDiff::OperationTrees::Array" => "SuperDiff::Basic::OperationTrees::Array", + "SuperDiff::OperationTrees::Base" => "SuperDiff::Core::AbstractOperationTree", + "SuperDiff::OperationTrees::CustomObject" => "SuperDiff::Basic::OperationTrees::CustomObject", + "SuperDiff::OperationTrees::DefaultObject" => "SuperDiff::Basic::OperationTrees::DefaultObject", + "SuperDiff::OperationTrees::Hash" => "SuperDiff::Basic::OperationTrees::Hash", + "SuperDiff::OperationTrees::MultilineString" => "SuperDiff::Basic::OperationTrees::MultilineString", + "SuperDiff::Operations::BinaryOperation" => "SuperDiff::Core::BinaryOperation", + "SuperDiff::Operations::UnaryOperation" => "SuperDiff::Core::UnaryOperation", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::CollectionContainingExactly" => "SuperDiff::RSpec::InspectionTreeBuilders::CollectionContainingExactly", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::CollectionIncluding" => "SuperDiff::RSpec::InspectionTreeBuilders::CollectionIncluding", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::Double" => "SuperDiff::RSpec::InspectionTreeBuilders::Double", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::GenericDescribableMatcher" => "SuperDiff::RSpec::InspectionTreeBuilders::GenericDescribableMatcher", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::HashIncluding" => "SuperDiff::RSpec::InspectionTreeBuilders::HashIncluding", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::InstanceOf" => "SuperDiff::RSpec::InspectionTreeBuilders::InstanceOf", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::KindOf" => "SuperDiff::RSpec::InspectionTreeBuilders::KindOf", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::ObjectHavingAttributes" => "SuperDiff::RSpec::InspectionTreeBuilders::ObjectHavingAttributes", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::Primitive" => "SuperDiff::RSpec::InspectionTreeBuilders::Primitive", + "SuperDiff::RSpec::ObjectInspection::InspectionTreeBuilders::ValueWithin" => "SuperDiff::RSpec::InspectionTreeBuilders::ValueWithin", + "SuperDiff::RecursionGuard" => "SuperDiff::Core::RecursionGuard", + "SuperDiff::TieredLines" => "SuperDiff::Core::TieredLines", + "SuperDiff::TieredLinesElider" => "SuperDiff::Core::TieredLinesElider", + "SuperDiff::TieredLinesFormatter" => "SuperDiff::Core::TieredLinesFormatter", +} + +# stree-ignore +active_record_constant_remappings = { + "SuperDiff::ActiveRecord::ObjectInspection::InspectionTreeBuilders::ActiveRecordModel" => "SuperDiff::ActiveRecord::InspectionTreeBuilders::ActiveRecordModel", + "SuperDiff::ActiveRecord::ObjectInspection::InspectionTreeBuilders::ActiveRecordRelation" => "SuperDiff::ActiveRecord::InspectionTreeBuilders::ActiveRecordRelation", +} + +# stree-ignore +active_support_constant_remappings = { + "SuperDiff::ActiveSupport::ObjectInspection::InspectionTreeBuilders::HashWithIndifferentAccess" => "SuperDiff::ActiveSupport::InspectionTreeBuilders::HashWithIndifferentAccess", + "SuperDiff::ActiveSupport::ObjectInspection::InspectionTreeBuilders::OrderedOptions" => "SuperDiff::ActiveSupport::InspectionTreeBuilders::OrderedOptions", +} + +common_constant_remappings.each do |old_constant_name, new_constant_name| + RSpec.describe old_constant_name, type: :unit do + it "maps to #{new_constant_name}" do + capture_warnings do + expect(Object.const_get(old_constant_name)).to be( + Object.const_get(new_constant_name) + ) + end + end + + it "points users to #{new_constant_name} instead" do + expect(old_constant_name).to be_deprecated_in_favor_of(new_constant_name) + end + end +end + +active_record_constant_remappings.each do |old_constant_name, new_constant_name| + RSpec.describe old_constant_name, type: :unit, active_record: true do + it "maps to #{new_constant_name}" do + capture_warnings do + expect(Object.const_get(old_constant_name)).to be( + Object.const_get(new_constant_name) + ) + end + end + + it "points users to #{new_constant_name} instead" do + expect(old_constant_name).to be_deprecated_in_favor_of(new_constant_name) + end + end +end + +active_support_constant_remappings.each do |old_constant_name, new_constant_name| + RSpec.describe old_constant_name, type: :unit, active_support: true do + it "maps to #{new_constant_name}" do + capture_warnings do + expect(Object.const_get(old_constant_name)).to be( + Object.const_get(new_constant_name) + ) + end + end + + it "points users to #{new_constant_name} instead" do + expect(old_constant_name).to be_deprecated_in_favor_of(new_constant_name) + end + end +end + +RSpec.describe "SuperDiff::Differs::Main.call", type: :unit do + it "maps to SuperDiff.diff" do + capture_warnings do + diff_before = SuperDiff::Differs::Main.call(1, 2) + diff_after = SuperDiff.diff(1, 2) + expect(diff_before).to eq(diff_after) + end + end +end + +RSpec.describe "SuperDiff::OperationTreeBuilders::Main.call", type: :unit do + it "maps to SuperDiff.build_operation_tree_for" do + capture_warnings do + operation_tree_builder_before = + SuperDiff::OperationTreeBuilders::Main.call(1, 2) + operation_tree_builder_after = SuperDiff.build_operation_tree_for(1, 2) + expect(operation_tree_builder_before).to eq(operation_tree_builder_after) + end + end +end + +RSpec.describe "SuperDiff::OperationTrees::Main.call", type: :unit do + it "maps to SuperDiff.find_operation_tree_for" do + capture_warnings do + operation_tree_before = + SuperDiff::OperationTrees::Main.call(%i[foo bar baz]) + operation_tree_after = SuperDiff.find_operation_tree_for(%i[foo bar baz]) + expect(operation_tree_before).to eq(operation_tree_after) + end + end +end diff --git a/spec/unit/equality_matchers/main_spec.rb b/spec/unit/equality_matchers/main_spec.rb index 51ec3d30..975149e5 100644 --- a/spec/unit/equality_matchers/main_spec.rb +++ b/spec/unit/equality_matchers/main_spec.rb @@ -1598,8 +1598,8 @@ #{ colored do - expected_line %(Expected: #) - actual_line %( Actual: #) + expected_line %(Expected: #) + actual_line %( Actual: #) end } @@ -1607,7 +1607,7 @@ #{ colored do - plain_line %( #) - actual_line %( Actual: #) + expected_line %(Expected: #) + actual_line %( Actual: #) end } STR