diff -Nru ruby-hashdiff-0.2.3/changelog.md ruby-hashdiff-1.0.0/changelog.md --- ruby-hashdiff-0.2.3/changelog.md 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/changelog.md 2019-08-06 18:41:09.000000000 +0000 @@ -1,5 +1,58 @@ # Change Log +## v1.0.0 2019-06-06 + +* Fix typo in readme (#72 @koic) +* Fix Rubocop offence (#73 @koic) +* Bumps version to v1.0.0 (#74 @jfelchner) + +## v1.0.0.beta1 2019-06-06 + +* fix warnings in ci (#69 @y-yagi) +* drop warnings of the constant change (#70 @jfelchner) + +## v0.4.0 2019-05-28 + +* refactoring (#56 #57 #59 #61 krzysiek1507) +* fix typo in README (#64 @pboling) +* change HashDiff to Hashdiff (#65 @jfelchner) + +## v0.3.9 2019-04-22 + +* Performance tweak (thanks @krzysiek1507: #51 #52 #53) + +## v0.3.8 2018-12-30 + +* Add Rubocop and drops Ruby 1.9 support #47 + +## v0.3.7 2017-10-08 + +* remove 1.8.7 support from gemspec #39 + +## v0.3.6 2017-08-22 + +* add option `use_lcs` #35 + +## v0.3.5 2017-08-06 + +* add option `array_path` #34 + +## v0.3.4 2017-05-01 + +* performance improvement of `#similar?` #31 + +## v0.3.2 2016-12-27 + +* replace `Fixnum` by `Integer` #28 + +## v0.3.1 2016-11-24 + +* fix an error when a hash has mixed types #26 + +## v0.3.0 2016-2-11 + +* support `:case_insensitive` option + ## v0.2.3 2015-11-5 * improve performance of LCS algorithm #12 @@ -28,7 +81,7 @@ ## v0.0.5 2012-7-1 * fix a bug in judging whehter two objects are similiar. -* add more spec test for HashDiff.best_diff +* add more spec test for `.best_diff` ## v0.0.4 2012-6-24 @@ -41,4 +94,3 @@ instead of following: [['-', 'c[0]', 4], ['-', 'c[1]', 5], ['-', 'c', []]] - diff -Nru ruby-hashdiff-0.2.3/debian/changelog ruby-hashdiff-1.0.0/debian/changelog --- ruby-hashdiff-0.2.3/debian/changelog 2016-01-22 14:20:28.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/changelog 2019-09-16 21:18:47.000000000 +0000 @@ -1,3 +1,26 @@ +ruby-hashdiff (1.0.0-1) unstable; urgency=medium + + [ Antonio Terceiro ] + * Remove myself from Uploaders: + + [ Utkarsh Gupta ] + * Add salsa-ci.yml + + [ Cédric Boutillier ] + * New upstream version 1.0.0 + * Add myself as an uploader + * Trim trailing whitespace. + * Use secure copyright file specification URI. + * Move debian/watch to gemwatch.debian.net + * Use salsa.debian.org in Vcs-* fields + * Bump Standards-Version to 4.4.0 (no changes needed) + * Drop compat file, rely on debhelper-compat and bump compat level to 12 + * Add upstream/metadata + * Drop patches, solved upstream + * Breaks older ruby-webmock + + -- Cédric Boutillier Mon, 16 Sep 2019 23:18:47 +0200 + ruby-hashdiff (0.2.3-1) unstable; urgency=medium * Initial release diff -Nru ruby-hashdiff-0.2.3/debian/compat ruby-hashdiff-1.0.0/debian/compat --- ruby-hashdiff-0.2.3/debian/compat 2016-01-22 14:20:28.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -9 diff -Nru ruby-hashdiff-0.2.3/debian/control ruby-hashdiff-1.0.0/debian/control --- ruby-hashdiff-0.2.3/debian/control 2016-01-22 14:20:28.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/control 2019-09-16 21:18:47.000000000 +0000 @@ -2,14 +2,14 @@ Section: ruby Priority: optional Maintainer: Debian Ruby Extras Maintainers -Uploaders: Antonio Terceiro -Build-Depends: debhelper (>= 9~), +Uploaders: Cédric Boutillier +Build-Depends: debhelper-compat (= 12), gem2deb, rake, ruby-rspec -Standards-Version: 3.9.6 -Vcs-Git: https://anonscm.debian.org/git/pkg-ruby-extras/ruby-hashdiff.git -Vcs-Browser: https://anonscm.debian.org/cgit/pkg-ruby-extras/ruby-hashdiff.git +Standards-Version: 4.4.0 +Vcs-Git: https://salsa.debian.org/ruby-team/ruby-hashdiff.git +Vcs-Browser: https://salsa.debian.org/ruby-team/ruby-hashdiff Homepage: https://github.com/liufengyun/hashdiff Testsuite: autopkgtest-pkg-ruby XS-Ruby-Versions: all @@ -20,6 +20,7 @@ Depends: ruby | ruby-interpreter, ${misc:Depends}, ${shlibs:Depends} +Breaks: ruby-webmock (<< 3.6~) Description: library for computing the smallest difference between two hashes Given two Hashes A and B, HashDiff will calculate the smallest modification that can be made to change A into B. diff -Nru ruby-hashdiff-0.2.3/debian/copyright ruby-hashdiff-1.0.0/debian/copyright --- ruby-hashdiff-0.2.3/debian/copyright 2016-01-22 14:20:28.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/copyright 2019-09-16 21:18:47.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: hashdiff Source: https://github.com/liufengyun/hashdiff diff -Nru ruby-hashdiff-0.2.3/debian/patches/0001-Port-tests-to-rspec-3.patch ruby-hashdiff-1.0.0/debian/patches/0001-Port-tests-to-rspec-3.patch --- ruby-hashdiff-0.2.3/debian/patches/0001-Port-tests-to-rspec-3.patch 2016-01-22 14:20:28.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/patches/0001-Port-tests-to-rspec-3.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -From: Antonio Terceiro -Date: Fri, 22 Jan 2016 12:14:40 -0200 -Subject: Port tests to rspec 3 - ---- - spec/hashdiff/util_spec.rb | 34 +++++++++++++++++----------------- - 1 file changed, 17 insertions(+), 17 deletions(-) - -diff --git a/spec/hashdiff/util_spec.rb b/spec/hashdiff/util_spec.rb -index bce861c..9130686 100644 ---- a/spec/hashdiff/util_spec.rb -+++ b/spec/hashdiff/util_spec.rb -@@ -14,59 +14,59 @@ describe HashDiff do - it "should be able to tell similiar hash" do - a = {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5} - b = {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5} -- HashDiff.similar?(a, b).should be_true -- HashDiff.similar?(a, b, :similarity => 1).should be_false -+ HashDiff.similar?(a, b).should be true -+ HashDiff.similar?(a, b, :similarity => 1).should be false - end - - it "should be able to tell similiar hash with values within tolerance" do - a = {'a' => 1.5, 'b' => 2.25, 'c' => 3, 'd' => 4, 'e' => 5} - b = {'a' => 1.503, 'b' => 2.22, 'c' => 3, 'e' => 5} -- HashDiff.similar?(a, b, :numeric_tolerance => 0.05).should be_true -- HashDiff.similar?(a, b).should be_false -+ HashDiff.similar?(a, b, :numeric_tolerance => 0.05).should be true -+ HashDiff.similar?(a, b).should be false - end - - it "should be able to tell numbers and strings" do -- HashDiff.similar?(1, 2).should be_false -- HashDiff.similar?("a", "b").should be_false -- HashDiff.similar?("a", [1, 2, 3]).should be_false -- HashDiff.similar?(1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}).should be_false -+ HashDiff.similar?(1, 2).should be false -+ HashDiff.similar?("a", "b").should be false -+ HashDiff.similar?("a", [1, 2, 3]).should be false -+ HashDiff.similar?(1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}).should be false - end - - it "should be able to tell true when similarity == 0.5" do - a = {"value" => "New1", "onclick" => "CreateNewDoc()"} - b = {"value" => "New", "onclick" => "CreateNewDoc()"} - -- HashDiff.similar?(a, b, :similarity => 0.5).should be_true -+ HashDiff.similar?(a, b, :similarity => 0.5).should be true - end - - it "should be able to tell false when similarity == 0.5" do - a = {"value" => "New1", "onclick" => "open()"} - b = {"value" => "New", "onclick" => "CreateNewDoc()"} - -- HashDiff.similar?(a, b, :similarity => 0.5).should be_false -+ HashDiff.similar?(a, b, :similarity => 0.5).should be false - end - - describe '.compare_values' do - it "should compare numeric values exactly when no tolerance" do -- expect(HashDiff.compare_values(10.004, 10.003)).to be_false -+ expect(HashDiff.compare_values(10.004, 10.003)).to be false - end - - it "should allow tolerance with numeric values" do -- expect(HashDiff.compare_values(10.004, 10.003, :numeric_tolerance => 0.01)).to be_true -+ expect(HashDiff.compare_values(10.004, 10.003, :numeric_tolerance => 0.01)).to be true - end - - it "should compare other objects with or without tolerance" do -- expect(HashDiff.compare_values('hats', 'ninjas')).to be_false -- expect(HashDiff.compare_values('hats', 'ninjas', :numeric_tolerance => 0.01)).to be_false -- expect(HashDiff.compare_values('horse', 'horse')).to be_true -+ expect(HashDiff.compare_values('hats', 'ninjas')).to be false -+ expect(HashDiff.compare_values('hats', 'ninjas', :numeric_tolerance => 0.01)).to be false -+ expect(HashDiff.compare_values('horse', 'horse')).to be true - end - - it 'should compare strings exactly by default' do -- expect(HashDiff.compare_values(' horse', 'horse')).to be_false -+ expect(HashDiff.compare_values(' horse', 'horse')).to be false - end - - it 'should strip strings before comparing when requested' do -- expect(HashDiff.compare_values(' horse', 'horse', :strip => true)).to be_true -+ expect(HashDiff.compare_values(' horse', 'horse', :strip => true)).to be true - end - end - end diff -Nru ruby-hashdiff-0.2.3/debian/patches/series ruby-hashdiff-1.0.0/debian/patches/series --- ruby-hashdiff-0.2.3/debian/patches/series 2016-01-22 14:20:28.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -0001-Port-tests-to-rspec-3.patch diff -Nru ruby-hashdiff-0.2.3/debian/salsa-ci.yml ruby-hashdiff-1.0.0/debian/salsa-ci.yml --- ruby-hashdiff-0.2.3/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/salsa-ci.yml 2019-09-16 21:18:47.000000000 +0000 @@ -0,0 +1,4 @@ +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff -Nru ruby-hashdiff-0.2.3/debian/upstream/metadata ruby-hashdiff-1.0.0/debian/upstream/metadata --- ruby-hashdiff-0.2.3/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/upstream/metadata 2019-09-16 21:18:47.000000000 +0000 @@ -0,0 +1,5 @@ +--- +Archive: GitHub +Bug-Database: https://github.com/liufengyun/hashdiff/issues +Repository: https://github.com/liufengyun/hashdiff.git +Repository-Browse: https://github.com/liufengyun/hashdiff diff -Nru ruby-hashdiff-0.2.3/debian/watch ruby-hashdiff-1.0.0/debian/watch --- ruby-hashdiff-0.2.3/debian/watch 2016-01-22 14:20:28.000000000 +0000 +++ ruby-hashdiff-1.0.0/debian/watch 2019-09-16 21:18:47.000000000 +0000 @@ -1,2 +1,2 @@ version=3 -http://pkg-ruby-extras.alioth.debian.org/cgi-bin/gemwatch/hashdiff .*/hashdiff-(.*).tar.gz +https://gemwatch.debian.net/hashdiff .*/hashdiff-(.*).tar.gz diff -Nru ruby-hashdiff-0.2.3/Gemfile ruby-hashdiff-1.0.0/Gemfile --- ruby-hashdiff-0.2.3/Gemfile 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/Gemfile 2019-08-06 18:41:09.000000000 +0000 @@ -1,6 +1,8 @@ -source "http://rubygems.org" +# frozen_string_literal: true + +source 'http://rubygems.org' gemspec group :test do - gem 'rake' + gem 'rake', '< 11' end diff -Nru ruby-hashdiff-0.2.3/hashdiff.gemspec ruby-hashdiff-1.0.0/hashdiff.gemspec --- ruby-hashdiff-0.2.3/hashdiff.gemspec 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/hashdiff.gemspec 2019-08-06 18:41:09.000000000 +0000 @@ -1,25 +1,39 @@ -$LOAD_PATH << File.expand_path("../lib", __FILE__) +# frozen_string_literal: true + +$LOAD_PATH << File.expand_path('lib', __dir__) require 'hashdiff/version' Gem::Specification.new do |s| - s.name = %q{hashdiff} - s.version = HashDiff::VERSION + s.name = 'hashdiff' + s.version = Hashdiff::VERSION s.license = 'MIT' - s.summary = %q{ HashDiff is a diff lib to compute the smallest difference between two hashes. } - s.description = %q{ HashDiff is a diff lib to compute the smallest difference between two hashes. } + s.summary = ' Hashdiff is a diff lib to compute the smallest difference between two hashes. ' + s.description = ' Hashdiff is a diff lib to compute the smallest difference between two hashes. ' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- Appraisals {spec}/*`.split("\n") s.require_paths = ['lib'] - s.required_ruby_version = Gem::Requirement.new(">= 1.8.7") + s.required_ruby_version = Gem::Requirement.new('>= 2.0.0') - s.authors = ["Liu Fengyun"] - s.email = ["liufengyunchina@gmail.com"] + s.authors = ['Liu Fengyun'] + s.email = ['liufengyunchina@gmail.com'] - s.homepage = "https://github.com/liufengyun/hashdiff" + s.homepage = 'https://github.com/liufengyun/hashdiff' - s.add_development_dependency("rspec", "~> 2.0") - s.add_development_dependency("yard") - s.add_development_dependency("bluecloth") + s.add_development_dependency('bluecloth') + s.add_development_dependency('rspec', '~> 2.0') + s.add_development_dependency('rubocop') + s.add_development_dependency('rubocop-rspec') + s.add_development_dependency('yard') + + if s.respond_to?(:metadata) + s.metadata = { + 'bug_tracker_uri' => 'https://github.com/liufengyun/hashdiff/issues', + 'changelog_uri' => 'https://github.com/liufengyun/hashdiff/blob/master/changelog.md', + 'documentation_uri' => 'https://www.rubydoc.info/gems/hashdiff', + 'homepage_uri' => 'https://github.com/liufengyun/hashdiff', + 'source_code_uri' => 'https://github.com/liufengyun/hashdiff' + } + end end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff/compare_hashes.rb ruby-hashdiff-1.0.0/lib/hashdiff/compare_hashes.rb --- ruby-hashdiff-0.2.3/lib/hashdiff/compare_hashes.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff/compare_hashes.rb 2019-08-06 18:41:09.000000000 +0000 @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Hashdiff + # @private + # Used to compare hashes + class CompareHashes + class << self + def call(obj1, obj2, opts = {}) + return [] if obj1.empty? && obj2.empty? + + obj1_keys = obj1.keys + obj2_keys = obj2.keys + + added_keys = (obj2_keys - obj1_keys).sort_by(&:to_s) + common_keys = (obj1_keys & obj2_keys).sort_by(&:to_s) + deleted_keys = (obj1_keys - obj2_keys).sort_by(&:to_s) + + result = [] + + # add deleted properties + deleted_keys.each do |k| + change_key = Hashdiff.prefix_append_key(opts[:prefix], k, opts) + custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, obj1[k], nil) + + if custom_result + result.concat(custom_result) + else + result << ['-', change_key, obj1[k]] + end + end + + # recursive comparison for common keys + common_keys.each do |k| + prefix = Hashdiff.prefix_append_key(opts[:prefix], k, opts) + + result.concat(Hashdiff.diff(obj1[k], obj2[k], opts.merge(prefix: prefix))) + end + + # added properties + added_keys.each do |k| + change_key = Hashdiff.prefix_append_key(opts[:prefix], k, opts) + + custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, nil, obj2[k]) + + if custom_result + result.concat(custom_result) + else + result << ['+', change_key, obj2[k]] + end + end + + result + end + end + end +end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff/diff.rb ruby-hashdiff-1.0.0/lib/hashdiff/diff.rb --- ruby-hashdiff-0.2.3/lib/hashdiff/diff.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff/diff.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,16 +1,19 @@ -module HashDiff +# frozen_string_literal: true +module Hashdiff # Best diff two objects, which tries to generate the smallest change set using different similarity values. # - # HashDiff.best_diff is useful in case of comparing two objects which include similar hashes in arrays. + # Hashdiff.best_diff is useful in case of comparing two objects which include similar hashes in arrays. # # @param [Array, Hash] obj1 # @param [Array, Hash] obj2 # @param [Hash] options the options to use when comparing - # * :strict (Boolean) [true] whether numeric values will be compared on type as well as value. Set to false to allow comparing Fixnum, Float, BigDecimal to each other + # * :strict (Boolean) [true] whether numeric values will be compared on type as well as value. Set to false to allow comparing Integer, Float, BigDecimal to each other # * :delimiter (String) ['.'] the delimiter used when returning nested key references # * :numeric_tolerance (Numeric) [0] should be a positive numeric value. Value by which numeric differences must be greater than. By default, numeric values are compared exactly; with the :tolerance option, the difference between numeric values must be greater than the given value. # * :strip (Boolean) [false] whether or not to call #strip on strings before comparing + # * :array_path (Boolean) [false] whether to return the path references for nested values in an array, can be used for patch compatibility with non string keys. + # * :use_lcs (Boolean) [true] whether or not to use an implementation of the Longest common subsequence algorithm for comparing arrays, produces better diffs but is slower. # # @yield [path, value1, value2] Optional block is used to compare each value, instead of default #==. If the block returns value other than true of false, then other specified comparison options will be used to do the comparison. # @@ -20,27 +23,27 @@ # @example # a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]} # b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] } - # diff = HashDiff.best_diff(a, b) + # diff = Hashdiff.best_diff(a, b) # diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1].y', 3], ['-', 'x[1]', {}]] # # @since 0.0.1 def self.best_diff(obj1, obj2, options = {}, &block) options[:comparison] = block if block_given? - opts = { :similarity => 0.3 }.merge!(options) - diffs_1 = diff(obj1, obj2, opts) - count_1 = count_diff diffs_1 - - opts = { :similarity => 0.5 }.merge!(options) - diffs_2 = diff(obj1, obj2, opts) - count_2 = count_diff diffs_2 - - opts = { :similarity => 0.8 }.merge!(options) - diffs_3 = diff(obj1, obj2, opts) - count_3 = count_diff diffs_3 + opts = { similarity: 0.3 }.merge!(options) + diffs1 = diff(obj1, obj2, opts) + count1 = count_diff diffs1 + + opts = { similarity: 0.5 }.merge!(options) + diffs2 = diff(obj1, obj2, opts) + count2 = count_diff diffs2 + + opts = { similarity: 0.8 }.merge!(options) + diffs3 = diff(obj1, obj2, opts) + count3 = count_diff diffs3 - count, diffs = count_1 < count_2 ? [count_1, diffs_1] : [count_2, diffs_2] - diffs = count < count_3 ? diffs : diffs_3 + count, diffs = count1 < count2 ? [count1, diffs1] : [count2, diffs2] + count < count3 ? diffs : diffs3 end # Compute the diff of two hashes or arrays @@ -48,11 +51,14 @@ # @param [Array, Hash] obj1 # @param [Array, Hash] obj2 # @param [Hash] options the options to use when comparing - # * :strict (Boolean) [true] whether numeric values will be compared on type as well as value. Set to false to allow comparing Fixnum, Float, BigDecimal to each other + # * :strict (Boolean) [true] whether numeric values will be compared on type as well as value. Set to false to allow comparing Integer, Float, BigDecimal to each other # * :similarity (Numeric) [0.8] should be between (0, 1]. Meaningful if there are similar hashes in arrays. See {best_diff}. # * :delimiter (String) ['.'] the delimiter used when returning nested key references # * :numeric_tolerance (Numeric) [0] should be a positive numeric value. Value by which numeric differences must be greater than. By default, numeric values are compared exactly; with the :tolerance option, the difference between numeric values must be greater than the given value. # * :strip (Boolean) [false] whether or not to call #strip on strings before comparing + # * :array_path (Boolean) [false] whether to return the path references for nested values in an array, can be used for patch compatibility with non string keys. + # * :use_lcs (Boolean) [true] whether or not to use an implementation of the Longest common subsequence algorithm for comparing arrays, produces better diffs but is slower. + # # # @yield [path, value1, value2] Optional block is used to compare each value, instead of default #==. If the block returns value other than true of false, then other specified comparison options will be used to do the comparison. # @@ -63,136 +69,85 @@ # a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} # b = {"a" => 1, "b" => {}} # - # diff = HashDiff.diff(a, b) + # diff = Hashdiff.diff(a, b) # diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]] # # @since 0.0.1 def self.diff(obj1, obj2, options = {}, &block) opts = { - :prefix => '', - :similarity => 0.8, - :delimiter => '.', - :strict => true, - :strip => false, - :numeric_tolerance => 0 + prefix: '', + similarity: 0.8, + delimiter: '.', + strict: true, + strip: false, + numeric_tolerance: 0, + array_path: false, + use_lcs: true }.merge!(options) + opts[:prefix] = [] if opts[:array_path] && opts[:prefix] == '' + opts[:comparison] = block if block_given? # prefer to compare with provided block result = custom_compare(opts[:comparison], opts[:prefix], obj1, obj2) return result if result - if obj1.nil? and obj2.nil? - return [] - end + return [] if obj1.nil? && obj2.nil? - if obj1.nil? - return [['~', opts[:prefix], nil, obj2]] - end + return [['~', opts[:prefix], obj1, obj2]] if obj1.nil? || obj2.nil? - if obj2.nil? - return [['~', opts[:prefix], obj1, nil]] - end + return [['~', opts[:prefix], obj1, obj2]] unless comparable?(obj1, obj2, opts[:strict]) - unless comparable?(obj1, obj2, opts[:strict]) - return [['~', opts[:prefix], obj1, obj2]] - end + return LcsCompareArrays.call(obj1, obj2, opts) if obj1.is_a?(Array) && opts[:use_lcs] - result = [] - if obj1.is_a?(Array) - changeset = diff_array(obj1, obj2, opts) do |lcs| - # use a's index for similarity - lcs.each do |pair| - result.concat(diff(obj1[pair[0]], obj2[pair[1]], opts.merge(:prefix => "#{opts[:prefix]}[#{pair[0]}]"))) - end - end + return LinearCompareArray.call(obj1, obj2, opts) if obj1.is_a?(Array) && !opts[:use_lcs] - changeset.each do |change| - if change[0] == '-' - result << ['-', "#{opts[:prefix]}[#{change[1]}]", change[2]] - elsif change[0] == '+' - result << ['+', "#{opts[:prefix]}[#{change[1]}]", change[2]] - end - end - elsif obj1.is_a?(Hash) - if opts[:prefix].empty? - prefix = "" - else - prefix = "#{opts[:prefix]}#{opts[:delimiter]}" - end - - deleted_keys = obj1.keys - obj2.keys - common_keys = obj1.keys & obj2.keys - added_keys = obj2.keys - obj1.keys - - # add deleted properties - deleted_keys.sort.each do |k| - custom_result = custom_compare(opts[:comparison], "#{prefix}#{k}", obj1[k], nil) - - if custom_result - result.concat(custom_result) - else - result << ['-', "#{prefix}#{k}", obj1[k]] - end - end + return CompareHashes.call(obj1, obj2, opts) if obj1.is_a?(Hash) - # recursive comparison for common keys - common_keys.sort.each {|k| result.concat(diff(obj1[k], obj2[k], opts.merge(:prefix => "#{prefix}#{k}"))) } + return [] if compare_values(obj1, obj2, opts) - # added properties - added_keys.sort.each do |k| - unless obj1.key?(k) - custom_result = custom_compare(opts[:comparison], "#{prefix}#{k}", nil, obj2[k]) - - if custom_result - result.concat(custom_result) - else - result << ['+', "#{prefix}#{k}", obj2[k]] - end - end - end - else - return [] if compare_values(obj1, obj2, opts) - return [['~', opts[:prefix], obj1, obj2]] - end - - result + [['~', opts[:prefix], obj1, obj2]] end # @private # # diff array using LCS algorithm - def self.diff_array(a, b, options = {}) - opts = { - :prefix => '', - :similarity => 0.8, - :delimiter => '.' - }.merge!(options) + def self.diff_array_lcs(arraya, arrayb, options = {}) + return [] if arraya.empty? && arrayb.empty? change_set = [] - if a.size == 0 and b.size == 0 - return [] - elsif a.size == 0 - b.each_index do |index| - change_set << ['+', index, b[index]] + + if arraya.empty? + arrayb.each_index do |index| + change_set << ['+', index, arrayb[index]] end + return change_set - elsif b.size == 0 - a.each_index do |index| - i = a.size - index - 1 - change_set << ['-', i, a[i]] + end + + if arrayb.empty? + arraya.each_index do |index| + i = arraya.size - index - 1 + change_set << ['-', i, arraya[i]] end + return change_set end - links = lcs(a, b, opts) + opts = { + prefix: '', + similarity: 0.8, + delimiter: '.' + }.merge!(options) + + links = lcs(arraya, arrayb, opts) # yield common yield links if block_given? # padding the end - links << [a.size, b.size] + links << [arraya.size, arrayb.size] last_x = -1 last_y = -1 @@ -200,13 +155,13 @@ x, y = pair # remove from a, beginning from the end - (x > last_x + 1) and (x - last_x - 2).downto(0).each do |i| - change_set << ['-', last_y + i + 1, a[i + last_x + 1]] + (x > last_x + 1) && (x - last_x - 2).downto(0).each do |i| + change_set << ['-', last_y + i + 1, arraya[i + last_x + 1]] end # add from b, beginning from the head - (y > last_y + 1) and 0.upto(y - last_y - 2).each do |i| - change_set << ['+', last_y + i + 1, b[i + last_y + 1]] + (y > last_y + 1) && 0.upto(y - last_y - 2).each do |i| + change_set << ['+', last_y + i + 1, arrayb[i + last_y + 1]] end # update flags @@ -216,5 +171,4 @@ change_set end - end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff/lcs_compare_arrays.rb ruby-hashdiff-1.0.0/lib/hashdiff/lcs_compare_arrays.rb --- ruby-hashdiff-0.2.3/lib/hashdiff/lcs_compare_arrays.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff/lcs_compare_arrays.rb 2019-08-06 18:41:09.000000000 +0000 @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Hashdiff + # @private + # Used to compare arrays using the lcs algorithm + class LcsCompareArrays + class << self + def call(obj1, obj2, opts = {}) + result = [] + + changeset = Hashdiff.diff_array_lcs(obj1, obj2, opts) do |lcs| + # use a's index for similarity + lcs.each do |pair| + prefix = Hashdiff.prefix_append_array_index(opts[:prefix], pair[0], opts) + + result.concat(Hashdiff.diff(obj1[pair[0]], obj2[pair[1]], opts.merge(prefix: prefix))) + end + end + + changeset.each do |change| + next if change[0] != '-' && change[0] != '+' + + change_key = Hashdiff.prefix_append_array_index(opts[:prefix], change[1], opts) + + result << [change[0], change_key, change[2]] + end + + result + end + end + end +end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff/lcs.rb ruby-hashdiff-1.0.0/lib/hashdiff/lcs.rb --- ruby-hashdiff-0.2.3/lib/hashdiff/lcs.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff/lcs.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,46 +1,44 @@ -module HashDiff +# frozen_string_literal: true + +module Hashdiff # @private # # caculate array difference using LCS algorithm # http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - def self.lcs(a, b, options = {}) - opts = { :similarity => 0.8 }.merge!(options) + def self.lcs(arraya, arrayb, options = {}) + return [] if arraya.empty? || arrayb.empty? - opts[:prefix] = "#{opts[:prefix]}[*]" + opts = { similarity: 0.8 }.merge!(options) - return [] if a.size == 0 or b.size == 0 + opts[:prefix] = prefix_append_array_index(opts[:prefix], '*', opts) a_start = b_start = 0 - a_finish = a.size - 1 - b_finish = b.size - 1 + a_finish = arraya.size - 1 + b_finish = arrayb.size - 1 vector = [] lcs = [] (b_start..b_finish).each do |bi| - lcs[bi] = [] + lcs[bi] = [] (a_start..a_finish).each do |ai| - if similar?(a[ai], b[bi], opts) - topleft = (ai > 0 and bi > 0)? lcs[bi-1][ai-1][1] : 0 + if similar?(arraya[ai], arrayb[bi], opts) + topleft = (ai > 0) && (bi > 0) ? lcs[bi - 1][ai - 1][1] : 0 lcs[bi][ai] = [:topleft, topleft + 1] - elsif - top = (bi > 0)? lcs[bi-1][ai][1] : 0 - left = (ai > 0)? lcs[bi][ai-1][1] : 0 - count = (top > left) ? top : left - - direction = :both - if top > left - direction = :top - elsif top < left - direction = :left - else - if bi == 0 - direction = :top - elsif ai == 0 - direction = :left - else - direction = :both - end - end + elsif (top = bi > 0 ? lcs[bi - 1][ai][1] : 0) + left = ai > 0 ? lcs[bi][ai - 1][1] : 0 + count = top > left ? top : left + + direction = if top > left + :top + elsif top < left + :left + elsif bi.zero? + :top + elsif ai.zero? + :left + else + :both + end lcs[bi][ai] = [direction, count] end @@ -49,7 +47,7 @@ x = a_finish y = b_finish - while x >= 0 and y >= 0 and lcs[y][x][1] > 0 + while (x >= 0) && (y >= 0) && (lcs[y][x][1] > 0) if lcs[y][x][0] == :both x -= 1 elsif lcs[y][x][0] == :topleft @@ -65,5 +63,4 @@ vector end - end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff/linear_compare_array.rb ruby-hashdiff-1.0.0/lib/hashdiff/linear_compare_array.rb --- ruby-hashdiff-0.2.3/lib/hashdiff/linear_compare_array.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff/linear_compare_array.rb 2019-08-06 18:41:09.000000000 +0000 @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +module Hashdiff + # @private + # + # Used to compare arrays in a linear complexity, which produces longer diffs + # than using the lcs algorithm but is considerably faster + class LinearCompareArray + def self.call(old_array, new_array, options = {}) + instance = new(old_array, new_array, options) + instance.call + end + + def call + return [] if old_array.empty? && new_array.empty? + + self.old_index = 0 + self.new_index = 0 + # by comparing the array lengths we can expect that a number of items + # are either added or removed + self.expected_additions = new_array.length - old_array.length + + loop do + if extra_items_in_old_array? + append_deletion(old_array[old_index], old_index) + elsif extra_items_in_new_array? + append_addition(new_array[new_index], new_index) + else + compare_at_index + end + + self.old_index = old_index + 1 + self.new_index = new_index + 1 + break if iterated_through_both_arrays? + end + + changes + end + + private + + attr_reader :old_array, :new_array, :options, :additions, :deletions, :differences + attr_accessor :old_index, :new_index, :expected_additions + + def initialize(old_array, new_array, options) + @old_array = old_array + @new_array = new_array + @options = { prefix: '' }.merge!(options) + + @additions = [] + @deletions = [] + @differences = [] + end + + def extra_items_in_old_array? + old_index < old_array.length && new_index >= new_array.length + end + + def extra_items_in_new_array? + new_index < new_array.length && old_index >= old_array.length + end + + def iterated_through_both_arrays? + old_index >= old_array.length && new_index >= new_array.length + end + + def compare_at_index + difference = item_difference(old_array[old_index], new_array[new_index], old_index) + return if difference.empty? + + index_after_additions = index_of_match_after_additions + append_addititions_before_match(index_after_additions) + + index_after_deletions = index_of_match_after_deletions + append_deletions_before_match(index_after_deletions) + + match = index_after_additions || index_after_deletions + + append_differences(difference) unless match + end + + def item_difference(old_item, new_item, item_index) + prefix = Hashdiff.prefix_append_array_index(options[:prefix], item_index, options) + Hashdiff.diff(old_item, new_item, options.merge(prefix: prefix)) + end + + # look ahead in the new array to see if the current item appears later + # thereby having new items added + def index_of_match_after_additions + return unless expected_additions > 0 + + (1..expected_additions).each do |i| + next_difference = item_difference( + old_array[old_index], + new_array[new_index + i], + old_index + ) + + return new_index + i if next_difference.empty? + end + + nil + end + + # look ahead in the old array to see if the current item appears later + # thereby having items removed + def index_of_match_after_deletions + return unless expected_additions < 0 + + (1..(expected_additions.abs)).each do |i| + next_difference = item_difference( + old_array[old_index + i], + new_array[new_index], + old_index + ) + + return old_index + i if next_difference.empty? + end + + nil + end + + def append_addititions_before_match(match_index) + return unless match_index + + (new_index...match_index).each { |i| append_addition(new_array[i], i) } + self.expected_additions = expected_additions - (match_index - new_index) + self.new_index = match_index + end + + def append_deletions_before_match(match_index) + return unless match_index + + (old_index...match_index).each { |i| append_deletion(old_array[i], i) } + self.expected_additions = expected_additions + (match_index - new_index) + self.old_index = match_index + end + + def append_addition(item, index) + key = Hashdiff.prefix_append_array_index(options[:prefix], index, options) + additions << ['+', key, item] + end + + def append_deletion(item, index) + key = Hashdiff.prefix_append_array_index(options[:prefix], index, options) + deletions << ['-', key, item] + end + + def append_differences(difference) + differences.concat(difference) + end + + def changes + # this algorithm only allows there to be additions or deletions + # deletions are reverse so they don't change the index of earlier items + differences + additions + deletions.reverse + end + end +end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff/patch.rb ruby-hashdiff-1.0.0/lib/hashdiff/patch.rb --- ruby-hashdiff-0.2.3/lib/hashdiff/patch.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff/patch.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,8 +1,9 @@ -# +# frozen_string_literal: true + +# # This module provides methods to diff two hash, patch and unpatch hash # -module HashDiff - +module Hashdiff # Apply patch to object # # @param [Hash, Array] obj the object to be patched, can be an Array or a Hash @@ -17,19 +18,21 @@ delimiter = options[:delimiter] || '.' changes.each do |change| - parts = decode_property_path(change[1], delimiter) + parts = change[1] + parts = decode_property_path(parts, delimiter) unless parts.is_a?(Array) + last_part = parts.last - parent_node = node(obj, parts[0, parts.size-1]) + parent_node = node(obj, parts[0, parts.size - 1]) if change[0] == '+' - if last_part.is_a?(Fixnum) + if parent_node.is_a?(Array) parent_node.insert(last_part, change[2]) else parent_node[last_part] = change[2] end elsif change[0] == '-' - if last_part.is_a?(Fixnum) + if parent_node.is_a?(Array) parent_node.delete_at(last_part) else parent_node.delete(last_part) @@ -56,19 +59,21 @@ delimiter = options[:delimiter] || '.' changes.reverse_each do |change| - parts = decode_property_path(change[1], delimiter) + parts = change[1] + parts = decode_property_path(parts, delimiter) unless parts.is_a?(Array) + last_part = parts.last - parent_node = node(obj, parts[0, parts.size-1]) + parent_node = node(obj, parts[0, parts.size - 1]) if change[0] == '+' - if last_part.is_a?(Fixnum) + if parent_node.is_a?(Array) parent_node.delete_at(last_part) else parent_node.delete(last_part) end elsif change[0] == '-' - if last_part.is_a?(Fixnum) + if parent_node.is_a?(Array) parent_node.insert(last_part, change[2]) else parent_node[last_part] = change[2] @@ -80,5 +85,4 @@ obj end - end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff/util.rb ruby-hashdiff-1.0.0/lib/hashdiff/util.rb --- ruby-hashdiff-0.2.3/lib/hashdiff/util.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff/util.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,20 +1,22 @@ -module HashDiff +# frozen_string_literal: true +module Hashdiff # @private # # judge whether two objects are similar - def self.similar?(a, b, options = {}) - opts = { :similarity => 0.8 }.merge(options) + def self.similar?(obja, objb, options = {}) + return compare_values(obja, objb, options) if !options[:comparison] && !any_hash_or_array?(obja, objb) - count_a = count_nodes(a) - count_b = count_nodes(b) - diffs = count_diff diff(a, b, opts) + count_a = count_nodes(obja) + count_b = count_nodes(objb) - if count_a + count_b == 0 - return true - else - (1 - diffs.to_f/(count_a + count_b).to_f) >= opts[:similarity] - end + return true if (count_a + count_b).zero? + + opts = { similarity: 0.8 }.merge!(options) + + diffs = count_diff diff(obja, objb, opts) + + (1 - diffs.to_f / (count_a + count_b).to_f) >= opts[:similarity] end # @private @@ -24,7 +26,7 @@ diffs.inject(0) do |sum, item| old_change_count = count_nodes(item[2]) new_change_count = count_nodes(item[3]) - sum += (old_change_count + new_change_count) + sum + (old_change_count + new_change_count) end end @@ -36,9 +38,9 @@ count = 0 if obj.is_a?(Array) - obj.each {|e| count += count_nodes(e) } + obj.each { |e| count += count_nodes(e) } elsif obj.is_a?(Hash) - obj.each {|k, v| count += count_nodes(v) } + obj.each_value { |v| count += count_nodes(v) } else return 1 end @@ -53,20 +55,18 @@ # @param [String] delimiter Property-string delimiter # # e.g. "a.b[3].c" => ['a', 'b', 3, 'c'] - def self.decode_property_path(path, delimiter='.') - parts = path.split(delimiter).collect do |part| - if part =~ /^(\w*)\[(\d+)\]$/ - if $1.size > 0 - [$1, $2.to_i] + def self.decode_property_path(path, delimiter = '.') + path.split(delimiter).inject([]) do |memo, part| + if part =~ /^(.*)\[(\d+)\]$/ + if !Regexp.last_match(1).empty? + memo + [Regexp.last_match(1), Regexp.last_match(2).to_i] else - $2.to_i + memo + [Regexp.last_match(2).to_i] end else - part + memo + [part] end end - - parts.flatten end # @private @@ -84,15 +84,19 @@ # # check for equality or "closeness" within given tolerance def self.compare_values(obj1, obj2, options = {}) - if (options[:numeric_tolerance].is_a? Numeric) && - [obj1, obj2].all? { |v| v.is_a? Numeric } + if options[:numeric_tolerance].is_a?(Numeric) && + obj1.is_a?(Numeric) && obj2.is_a?(Numeric) return (obj1 - obj2).abs <= options[:numeric_tolerance] end if options[:strip] == true - first = obj1.strip if obj1.respond_to?(:strip) - second = obj2.strip if obj2.respond_to?(:strip) - return first == second + obj1 = obj1.strip if obj1.respond_to?(:strip) + obj2 = obj2.strip if obj2.respond_to?(:strip) + end + + if options[:case_insensitive] == true + obj1 = obj1.downcase if obj1.respond_to?(:downcase) + obj2 = obj2.downcase if obj2.respond_to?(:downcase) end obj1 == obj2 @@ -102,10 +106,9 @@ # # check if objects are comparable def self.comparable?(obj1, obj2, strict = true) - [Array, Hash].each do |type| - return true if obj1.is_a?(type) && obj2.is_a?(type) - end + return true if (obj1.is_a?(Array) || obj1.is_a?(Hash)) && obj2.is_a?(obj1.class) return true if !strict && obj1.is_a?(Numeric) && obj2.is_a?(Numeric) + obj1.is_a?(obj2.class) && obj2.is_a?(obj1.class) end @@ -113,15 +116,39 @@ # # try custom comparison def self.custom_compare(method, key, obj1, obj2) - if method - res = method.call(key, obj1, obj2) + return unless method - # nil != false here - if res == false - return [['~', key, obj1, obj2]] - elsif res == true - return [] - end + res = method.call(key, obj1, obj2) + + # nil != false here + return [['~', key, obj1, obj2]] if res == false + return [] if res == true + end + + def self.prefix_append_key(prefix, key, opts) + if opts[:array_path] + prefix + [key] + else + prefix.empty? ? key.to_s : "#{prefix}#{opts[:delimiter]}#{key}" + end + end + + def self.prefix_append_array_index(prefix, array_index, opts) + if opts[:array_path] + prefix + [array_index] + else + "#{prefix}[#{array_index}]" + end + end + + class << self + private + + # @private + # + # checks if both objects are Arrays or Hashes + def any_hash_or_array?(obja, objb) + obja.is_a?(Array) || obja.is_a?(Hash) || objb.is_a?(Array) || objb.is_a?(Hash) end end end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff/version.rb ruby-hashdiff-1.0.0/lib/hashdiff/version.rb --- ruby-hashdiff-0.2.3/lib/hashdiff/version.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff/version.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,3 +1,5 @@ -module HashDiff - VERSION = '0.2.3' +# frozen_string_literal: true + +module Hashdiff + VERSION = '1.0.0'.freeze end diff -Nru ruby-hashdiff-0.2.3/lib/hashdiff.rb ruby-hashdiff-1.0.0/lib/hashdiff.rb --- ruby-hashdiff-0.2.3/lib/hashdiff.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/lib/hashdiff.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,5 +1,10 @@ +# frozen_string_literal: true + require 'hashdiff/util' +require 'hashdiff/compare_hashes' require 'hashdiff/lcs' +require 'hashdiff/lcs_compare_arrays' +require 'hashdiff/linear_compare_array' require 'hashdiff/diff' require 'hashdiff/patch' require 'hashdiff/version' diff -Nru ruby-hashdiff-0.2.3/metadata.yml ruby-hashdiff-1.0.0/metadata.yml --- ruby-hashdiff-0.2.3/metadata.yml 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/metadata.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,110 +0,0 @@ ---- !ruby/object:Gem::Specification -name: hashdiff -version: !ruby/object:Gem::Version - version: 0.2.3 -platform: ruby -authors: -- Liu Fengyun -autorequire: -bindir: bin -cert_chain: [] -date: 2015-11-05 00:00:00.000000000 Z -dependencies: -- !ruby/object:Gem::Dependency - name: rspec - requirement: !ruby/object:Gem::Requirement - requirements: - - - ~> - - !ruby/object:Gem::Version - version: '2.0' - type: :development - prerelease: false - version_requirements: !ruby/object:Gem::Requirement - requirements: - - - ~> - - !ruby/object:Gem::Version - version: '2.0' -- !ruby/object:Gem::Dependency - name: yard - requirement: !ruby/object:Gem::Requirement - requirements: - - - '>=' - - !ruby/object:Gem::Version - version: '0' - type: :development - prerelease: false - version_requirements: !ruby/object:Gem::Requirement - requirements: - - - '>=' - - !ruby/object:Gem::Version - version: '0' -- !ruby/object:Gem::Dependency - name: bluecloth - requirement: !ruby/object:Gem::Requirement - requirements: - - - '>=' - - !ruby/object:Gem::Version - version: '0' - type: :development - prerelease: false - version_requirements: !ruby/object:Gem::Requirement - requirements: - - - '>=' - - !ruby/object:Gem::Version - version: '0' -description: ' HashDiff is a diff lib to compute the smallest difference between two - hashes. ' -email: -- liufengyunchina@gmail.com -executables: [] -extensions: [] -extra_rdoc_files: [] -files: -- .gitignore -- .rspec -- .travis.yml -- .yardopts -- Gemfile -- LICENSE -- README.md -- Rakefile -- changelog.md -- hashdiff.gemspec -- lib/hashdiff.rb -- lib/hashdiff/diff.rb -- lib/hashdiff/lcs.rb -- lib/hashdiff/patch.rb -- lib/hashdiff/util.rb -- lib/hashdiff/version.rb -- spec/hashdiff/best_diff_spec.rb -- spec/hashdiff/diff_array_spec.rb -- spec/hashdiff/diff_spec.rb -- spec/hashdiff/lcs_spec.rb -- spec/hashdiff/patch_spec.rb -- spec/hashdiff/util_spec.rb -- spec/spec_helper.rb -homepage: https://github.com/liufengyun/hashdiff -licenses: -- MIT -metadata: {} -post_install_message: -rdoc_options: [] -require_paths: -- lib -required_ruby_version: !ruby/object:Gem::Requirement - requirements: - - - '>=' - - !ruby/object:Gem::Version - version: 1.8.7 -required_rubygems_version: !ruby/object:Gem::Requirement - requirements: - - - '>=' - - !ruby/object:Gem::Version - version: '0' -requirements: [] -rubyforge_project: -rubygems_version: 2.0.14 -signing_key: -specification_version: 4 -summary: HashDiff is a diff lib to compute the smallest difference between two hashes. -test_files: [] diff -Nru ruby-hashdiff-0.2.3/Rakefile ruby-hashdiff-1.0.0/Rakefile --- ruby-hashdiff-0.2.3/Rakefile 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/Rakefile 2019-08-06 18:41:09.000000000 +0000 @@ -1,13 +1,18 @@ -$:.push File.expand_path("../lib", __FILE__) +# frozen_string_literal: true + +$LOAD_PATH.push File.expand_path('lib', __dir__) + +require 'rubocop/rake_task' require 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' -task :default => :spec +RuboCop::RakeTask.new + +task default: %w[spec rubocop] RSpec::Core::RakeTask.new(:spec) do |spec| - spec.pattern = "./spec/**/*_spec.rb" + spec.pattern = './spec/**/*_spec.rb' end - diff -Nru ruby-hashdiff-0.2.3/README.md ruby-hashdiff-1.0.0/README.md --- ruby-hashdiff-0.2.3/README.md 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/README.md 2019-08-06 18:41:09.000000000 +0000 @@ -1,10 +1,17 @@ -# HashDiff [![Build Status](https://secure.travis-ci.org/liufengyun/hashdiff.png)](http://travis-ci.org/liufengyun/hashdiff) [![Gem Version](https://badge.fury.io/rb/hashdiff.png)](http://badge.fury.io/rb/hashdiff) +# Hashdiff [![Build Status](https://secure.travis-ci.org/liufengyun/hashdiff.svg)](http://travis-ci.org/liufengyun/hashdiff) [![Gem Version](https://badge.fury.io/rb/hashdiff.svg)](http://badge.fury.io/rb/hashdiff) -HashDiff is a ruby library to compute the smallest difference between two hashes. +Hashdiff is a ruby library to compute the smallest difference between two hashes. + +It also supports comparing two arrays. + +Hashdiff does not monkey-patch any existing class. All features are contained inside the `Hashdiff` module. **Docs**: [Documentation](http://rubydoc.info/gems/hashdiff) -## Why HashDiff? + +__WARNING__: Don't use the library for comparing large arrays, say ~10K (see #49). + +## Why Hashdiff? Given two Hashes A and B, sometimes you face the question: what's the smallest modification that can be made to change A into B? @@ -14,7 +21,7 @@ * Compute recursively -- Arrays and Hashes may be nested arbitrarily in A or B. * Compute the smallest change -- it should recognize similar child Hashes or child Arrays between A and B. -HashDiff answers the question above using an opinionated approach: +Hashdiff answers the question above using an opinionated approach: * Hash can be represented as a list of (dot-syntax-path, value) pairs. For example, `{a:[{c:2}]}` can be represented as `["a[0].c", 2]`. * The change set can be represented using the dot-syntax representation. For example, `[['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]`. @@ -39,7 +46,7 @@ a = {a:3, b:2} b = {} -diff = HashDiff.diff(a, b) +diff = Hashdiff.diff(a, b) diff.should == [['-', 'a', 3], ['-', 'b', 2]] ``` @@ -49,7 +56,7 @@ a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}} b = {a:{y:3}, b:{y:3, z:30}} -diff = HashDiff.diff(a, b) +diff = Hashdiff.diff(a, b) diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]] ``` @@ -59,7 +66,7 @@ a = {a:[{x:2, y:3, z:4}, {x:11, y:22, z:33}], b:{x:3, z:45}} b = {a:[{y:3}, {x:11, z:33}], b:{y:22}} -diff = HashDiff.best_diff(a, b) +diff = Hashdiff.best_diff(a, b) diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]] ``` @@ -68,26 +75,28 @@ patch example: ```ruby -a = {a: 3} -b = {a: {a1: 1, a2: 2}} +a = {'a' => 3} +b = {'a' => {'a1' => 1, 'a2' => 2}} -diff = HashDiff.diff(a, b) -HashDiff.patch!(a, diff).should == b +diff = Hashdiff.diff(a, b) +Hashdiff.patch!(a, diff).should == b ``` unpatch example: ```ruby -a = [{a: 1, b: 2, c: 3, d: 4, e: 5}, {x: 5, y: 6, z: 3}, 1] -b = [1, {a: 1, b: 2, c: 3, e: 5}] +a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1] +b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}] -diff = HashDiff.diff(a, b) # diff two array is OK -HashDiff.unpatch!(b, diff).should == a +diff = Hashdiff.diff(a, b) # diff two array is OK +Hashdiff.unpatch!(b, diff).should == a ``` ### Options -There are five options available: `:delimiter`, `:similarity`, `:strict`, `:numeric_tolerance` and `:strip`. +There are eight options available: `:delimiter`, `:similarity`, +`:strict`, `:numeric_tolerance`, `:strip`, `:case_insensitive`, `:array_path` +and `:use_lcs` #### `:delimiter` @@ -97,7 +106,7 @@ a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}} b = {a:{y:3}, b:{y:3, z:30}} -diff = HashDiff.diff(a, b, :delimiter => '\t') +diff = Hashdiff.diff(a, b, :delimiter => '\t') diff.should == [['-', 'a\tx', 2], ['-', 'a\tz', 4], ['-', 'b\tx', 3], ['~', 'b\tz', 45, 30], ['+', 'b\ty', 3]] ``` @@ -107,7 +116,7 @@ #### `:strict` -The `:strict` option, which defaults to `true`, specifies whether numeric types are compared on type as well as value. By default, a Fixnum will never be equal to a Float (e.g. 4 != 4.0). Setting `:strict` to false makes the comparison looser (e.g. 4 == 4.0). +The `:strict` option, which defaults to `true`, specifies whether numeric types are compared on type as well as value. By default, an Integer will never be equal to a Float (e.g. 4 != 4.0). Setting `:strict` to false makes the comparison looser (e.g. 4 == 4.0). #### `:numeric_tolerance` @@ -117,7 +126,7 @@ a = {x:5, y:3.75, z:7} b = {x:6, y:3.76, z:7} -diff = HashDiff.diff(a, b, :numeric_tolerance => 0.1) +diff = Hashdiff.diff(a, b, :numeric_tolerance => 0.1) diff.should == [["~", "x", 5, 6]] ``` @@ -129,10 +138,77 @@ a = {x:5, s:'foo '} b = {x:6, s:'foo'} -diff = HashDiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :strip => true }) +diff = Hashdiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :strip => true }) +diff.should == [["~", "x", 5, 6]] +``` + +#### `:case_insensitive` + +The :case_insensitive option makes string comparisons ignore case. + +```ruby +a = {x:5, s:'FooBar'} +b = {x:6, s:'foobar'} + +diff = Hashdiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :case_insensitive => true }) diff.should == [["~", "x", 5, 6]] ``` +#### `:array_path` + +The :array_path option represents the path of the diff in an array rather than +a string. This can be used to show differences in between hash key types and +is useful for `patch!` when used on hashes without string keys. + +```ruby +a = {x:5} +b = {'x'=>6} + +diff = Hashdiff.diff(a, b, :array_path => true) +diff.should == [['-', [:x], 5], ['+', ['x'], 6]] +``` + +For cases where there are arrays in paths their index will be added to the path. +```ruby +a = {x:[0,1]} +b = {x:[0,2]} + +diff = Hashdiff.diff(a, b, :array_path => true) +diff.should == [["-", [:x, 1], 1], ["+", [:x, 1], 2]] +``` + +This shouldn't cause problems if you are comparing an array with a hash: + +```ruby +a = {x:{0=>1}} +b = {x:[1]} + +diff = Hashdiff.diff(a, b, :array_path => true) +diff.should == [["~", [:x], {0=>1}, [1]]] +``` + +#### `:use_lcs` + +The :use_lcs option is used to specify whether a +[Longest common subsequence](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem) +(LCS) algorithm is used to determine differences in arrays. This defaults to +`true` but can be changed to `false` for significantly faster array comparisons +(O(n) complexity rather than O(n2) for LCS). + +When :use_lcs is false the results of array comparisons have a tendency to +show changes at indexes rather than additions and subtractions when :use_lcs is +true. + +Note, currently the :similarity option has no effect when :use_lcs is false. + +```ruby +a = {x: [0, 1, 2]} +b = {x: [0, 2, 2, 3]} + +diff = Hashdiff.diff(a, b, :use_lcs => false) +diff.should == [["~", "x[1]", 1, 2], ["+", "x[3]", 3]] +``` + #### Specifying a custom comparison method It's possible to specify how the values of a key should be compared. @@ -141,7 +217,7 @@ a = {a:'car', b:'boat', c:'plane'} b = {a:'bus', b:'truck', c:' plan'} -diff = HashDiff.diff(a, b) do |path, obj1, obj2| +diff = Hashdiff.diff(a, b) do |path, obj1, obj2| case path when /a|b|c/ obj1.length == obj2.length @@ -157,7 +233,7 @@ a = {a:'car', b:['boat', 'plane'] } b = {a:'bus', b:['truck', ' plan'] } -diff = HashDiff.diff(a, b) do |path, obj1, obj2| +diff = Hashdiff.diff(a, b) do |path, obj1, obj2| case path when 'b[*]' obj1.length == obj2.length @@ -169,6 +245,8 @@ When a comparison block is given, it'll be given priority over other specified options. If the block returns value other than `true` or `false`, then the two values will be compared with other specified options. +When used in conjunction with the `array_path` option, the path passed in as an argument will be an array. When determining the ordering of an array a key of `"*"` will be used in place of the `key[*]` field. It is possible, if you have hashes with integer or `"*"` keys, to have problems distinguishing between arrays and hashes - although this shouldn't be an issue unless your data is very difficult to predict and/or your custom rules are very specific. + #### Sorting arrays before comparison An order difference alone between two arrays can create too many diffs to be useful. Consider sorting them prior to diffing. @@ -177,31 +255,18 @@ a = {a:'car', b:['boat', 'plane'] } b = {a:'car', b:['plane', 'boat'] } -HashDiff.diff(a, b) => [["+", "b[0]", "plane"], ["-", "b[2]", "plane"]] +Hashdiff.diff(a, b) => [["+", "b[0]", "plane"], ["-", "b[2]", "plane"]] b[:b].sort! -HashDiff.diff(a, b) => [] +Hashdiff.diff(a, b) => [] ``` -### Special use cases - -#### Using HashDiff on JSON API results +## Maintainers -```ruby -require 'uri' -require 'net/http' -require 'json' - -uri = URI('http://time.jsontest.com/') -json_resp = ->(uri) { JSON.parse(Net::HTTP.get_response(uri).body) } -a = json_resp.call(uri) -b = json_resp.call(uri) - -HashDiff.diff(a,b) => [["~", "milliseconds_since_epoch", 1410542545874, 1410542545985]] -``` +- Krzysztof Rybka ([@krzysiek1507](https://github.com/krzysiek1507)) +- Fengyun Liu ([@liufengyun](https://github.com/liufengyun)) ## License -HashDiff is distributed under the MIT-LICENSE. - +Hashdiff is distributed under the MIT-LICENSE. diff -Nru ruby-hashdiff-0.2.3/.rubocop.yml ruby-hashdiff-1.0.0/.rubocop.yml --- ruby-hashdiff-0.2.3/.rubocop.yml 1970-01-01 00:00:00.000000000 +0000 +++ ruby-hashdiff-1.0.0/.rubocop.yml 2019-08-06 18:41:09.000000000 +0000 @@ -0,0 +1,28 @@ +require: rubocop-rspec +Metrics/PerceivedComplexity: + Enabled: false +Metrics/CyclomaticComplexity: + Enabled: false +Metrics/MethodLength: + Enabled: false +Metrics/AbcSize: + Enabled: false +Metrics/LineLength: + Enabled: false +Metrics/ClassLength: + Enabled: false +Metrics/BlockLength: + Enabled: false +Metrics/ModuleLength: + Enabled: false +Style/Documentation: + Enabled: false +Style/FrozenStringLiteralComment: + Enabled: true + EnforcedStyle: always +Style/NumericPredicate: + Enabled: false +Style/RedundantFreeze: + Enabled: false +RSpec/ExampleLength: + Enabled: false diff -Nru ruby-hashdiff-0.2.3/spec/hashdiff/best_diff_spec.rb ruby-hashdiff-1.0.0/spec/hashdiff/best_diff_spec.rb --- ruby-hashdiff-0.2.3/spec/hashdiff/best_diff_spec.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/spec/hashdiff/best_diff_spec.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,65 +1,75 @@ +# frozen_string_literal: true + require 'spec_helper' -describe HashDiff do - it "should be able to best diff" do - a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]} - b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] } +describe Hashdiff do + it 'is able to best diff' do + a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] } + b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] } - diff = HashDiff.best_diff(a, b) - diff.should == [["-", "x[0].c", 3], ["+", "x[0].b", 2], ["-", "x[1]", {"y"=>3}]] + diff = described_class.best_diff(a, b) + diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1]', { 'y' => 3 }]] end - it "should use custom delimiter when provided" do - a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]} - b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] } + it 'uses custom delimiter when provided' do + a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] } + b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] } - diff = HashDiff.best_diff(a, b, :delimiter => "\t") - diff.should == [["-", "x[0]\tc", 3], ["+", "x[0]\tb", 2], ["-", "x[1]", {"y"=>3}]] + diff = described_class.best_diff(a, b, delimiter: "\t") + diff.should == [['-', "x[0]\tc", 3], ['+', "x[0]\tb", 2], ['-', 'x[1]', { 'y' => 3 }]] end - it "should use custom comparison when provided" do - a = {'x' => [{'a' => 'foo', 'c' => 'goat', 'e' => 'snake'}, {'y' => 'baz'}]} - b = {'x' => [{'a' => 'bar', 'b' => 'cow', 'e' => 'puppy'}] } + it 'uses custom comparison when provided' do + a = { 'x' => [{ 'a' => 'foo', 'c' => 'goat', 'e' => 'snake' }, { 'y' => 'baz' }] } + b = { 'x' => [{ 'a' => 'bar', 'b' => 'cow', 'e' => 'puppy' }] } - diff = HashDiff.best_diff(a, b) do |path, obj1, obj2| + diff = described_class.best_diff(a, b) do |path, obj1, obj2| case path when /^x\[.\]\..$/ - obj1.length == obj2.length if obj1 and obj2 + obj1.length == obj2.length if obj1 && obj2 end end - diff.should == [["-", "x[0].c", 'goat'], ["+", "x[0].b", 'cow'], ["-", "x[1]", {"y"=>'baz'}]] + diff.should == [['-', 'x[0].c', 'goat'], ['+', 'x[0].b', 'cow'], ['-', 'x[1]', { 'y' => 'baz' }]] end - it "should be able to best diff array in hash" do - a = {"menu" => { - "id" => "file", - "value" => "File", - "popup" => { - "menuitem" => [ - {"value" => "New", "onclick" => "CreateNewDoc()"}, - {"value" => "Close", "onclick" => "CloseDoc()"} + it 'is able to best diff array in hash' do + a = { 'menu' => { + 'id' => 'file', + 'value' => 'File', + 'popup' => { + 'menuitem' => [ + { 'value' => 'New', 'onclick' => 'CreateNewDoc()' }, + { 'value' => 'Close', 'onclick' => 'CloseDoc()' } ] } - }} + } } - b = {"menu" => { - "id" => "file 2", - "value" => "File", - "popup" => { - "menuitem" => [ - {"value" => "New1", "onclick" => "CreateNewDoc()"}, - {"value" => "Open", "onclick" => "OpenDoc()"}, - {"value" => "Close", "onclick" => "CloseDoc()"} + b = { 'menu' => { + 'id' => 'file 2', + 'value' => 'File', + 'popup' => { + 'menuitem' => [ + { 'value' => 'New1', 'onclick' => 'CreateNewDoc()' }, + { 'value' => 'Open', 'onclick' => 'OpenDoc()' }, + { 'value' => 'Close', 'onclick' => 'CloseDoc()' } ] } - }} + } } - diff = HashDiff.best_diff(a, b) + diff = described_class.best_diff(a, b) diff.should == [ ['~', 'menu.id', 'file', 'file 2'], ['~', 'menu.popup.menuitem[0].value', 'New', 'New1'], - ['+', 'menu.popup.menuitem[1]', {"value" => "Open", "onclick" => "OpenDoc()"}] + ['+', 'menu.popup.menuitem[1]', { 'value' => 'Open', 'onclick' => 'OpenDoc()' }] ] end + + it 'is able to have an array_path specified' do + a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] } + b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] } + + diff = described_class.best_diff(a, b, array_path: true) + diff.should == [['-', ['x', 0, 'c'], 3], ['+', ['x', 0, 'b'], 2], ['-', ['x', 1], { 'y' => 3 }]] + end end diff -Nru ruby-hashdiff-0.2.3/spec/hashdiff/diff_array_spec.rb ruby-hashdiff-1.0.0/spec/hashdiff/diff_array_spec.rb --- ruby-hashdiff-0.2.3/spec/hashdiff/diff_array_spec.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/spec/hashdiff/diff_array_spec.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,60 +1,60 @@ +# frozen_string_literal: true + require 'spec_helper' -describe HashDiff do - it "should be able to diff two equal array" do +describe Hashdiff do + it 'is able to diff two equal array' do a = [1, 2, 3] b = [1, 2, 3] - diff = HashDiff.diff_array(a, b) + diff = described_class.diff_array_lcs(a, b) diff.should == [] end - it "should be able to diff two arrays with one element in common" do + it 'is able to diff two arrays with one element in common' do a = [1, 2, 3] b = [1, 8, 7] - diff = HashDiff.diff_array(a, b) + diff = described_class.diff_array_lcs(a, b) diff.should == [['-', 2, 3], ['-', 1, 2], ['+', 1, 8], ['+', 2, 7]] end - it "should be able to diff two arrays with nothing in common" do + it 'is able to diff two arrays with nothing in common' do a = [1, 2] b = [] - diff = HashDiff.diff_array(a, b) + diff = described_class.diff_array_lcs(a, b) diff.should == [['-', 1, 2], ['-', 0, 1]] end - it "should be able to diff an empty array with an non-empty array" do + it 'is able to diff an empty array with an non-empty array' do a = [] b = [1, 2] - diff = HashDiff.diff_array(a, b) + diff = described_class.diff_array_lcs(a, b) diff.should == [['+', 0, 1], ['+', 1, 2]] end - it "should be able to diff two arrays with two elements in common" do + it 'is able to diff two arrays with two elements in common' do a = [1, 3, 5, 7] b = [2, 3, 7, 5] - diff = HashDiff.diff_array(a, b) + diff = described_class.diff_array_lcs(a, b) diff.should == [['-', 0, 1], ['+', 0, 2], ['+', 2, 7], ['-', 4, 7]] end - it "should be able to test two arrays with two common elements in different order" do + it 'is able to test two arrays with two common elements in different order' do a = [1, 3, 4, 7] b = [2, 3, 7, 5] - diff = HashDiff.diff_array(a, b) + diff = described_class.diff_array_lcs(a, b) diff.should == [['-', 0, 1], ['+', 0, 2], ['-', 2, 4], ['+', 3, 5]] end - it "should be able to diff two arrays with similar elements" do - a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3] - b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}] - diff = HashDiff.diff_array(a, b) + it 'is able to diff two arrays with similar elements' do + a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, 3] + b = [1, { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }] + diff = described_class.diff_array_lcs(a, b) diff.should == [['+', 0, 1], ['-', 2, 3]] end - end - diff -Nru ruby-hashdiff-0.2.3/spec/hashdiff/diff_spec.rb ruby-hashdiff-1.0.0/spec/hashdiff/diff_spec.rb --- ruby-hashdiff-0.2.3/spec/hashdiff/diff_spec.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/spec/hashdiff/diff_spec.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,203 +1,250 @@ +# frozen_string_literal: true + require 'spec_helper' -describe HashDiff do - it "should be able to diff two empty hashes" do - diff = HashDiff.diff({}, {}) +describe Hashdiff do + it 'is able to diff two empty hashes' do + diff = described_class.diff({}, {}) diff.should == [] end - it "should be able to diff an hash with an empty hash" do + it 'is able to diff an hash with an empty hash' do a = { 'a' => 3, 'b' => 2 } b = {} - diff = HashDiff.diff(a, b) - diff.should == [['-', 'a', 3], ['-', 'b', 2]] + diff = described_class.diff(a, b) + expect(diff).to eq([['-', 'a', 3], ['-', 'b', 2]]) - diff = HashDiff.diff(b, a) + diff = described_class.diff(b, a) diff.should == [['+', 'a', 3], ['+', 'b', 2]] end - it "should be able to diff two equal hashes" do - diff = HashDiff.diff({ 'a' => 2, 'b' => 2}, { 'a' => 2, 'b' => 2 }) + it 'is able to diff two equal hashes' do + diff = described_class.diff({ 'a' => 2, 'b' => 2 }, 'a' => 2, 'b' => 2) + diff.should == [] + end + + it 'is able to diff two equal hashes with mixed key types' do + a = { 'a' => 1, :b => 1 } + diff = described_class.diff(a, a) diff.should == [] end - it "should be able to diff two hashes with equivalent numerics, when strict is false" do - diff = HashDiff.diff({ 'a' => 2.0, 'b' => 2 }, { 'a' => 2, 'b' => 2.0 }, :strict => false) + it 'is able to diff if mixed key types are removed' do + a = { 'a' => 1, :b => 1 } + b = {} + diff = described_class.diff(a, b) + diff.should == [['-', 'a', 1], ['-', 'b', 1]] + end + + it 'is able to diff if mixed key types are added' do + a = { 'a' => 1, :b => 1 } + b = {} + diff = described_class.diff(b, a) + diff.should == [['+', 'a', 1], ['+', 'b', 1]] + end + + it 'is able to diff two hashes with equivalent numerics, when strict is false' do + diff = described_class.diff({ 'a' => 2.0, 'b' => 2 }, { 'a' => 2, 'b' => 2.0 }, strict: false) diff.should == [] end - it "should be able to diff changes in hash value" do - diff = HashDiff.diff({ 'a' => 2, 'b' => 3, 'c' => " hello" }, { 'a' => 2, 'b' => 4, 'c' => "hello" }) - diff.should == [['~', 'b', 3, 4], ['~', 'c', " hello", "hello"]] + it 'is able to diff changes in hash value' do + diff = described_class.diff({ 'a' => 2, 'b' => 3, 'c' => ' hello' }, 'a' => 2, 'b' => 4, 'c' => 'hello') + diff.should == [['~', 'b', 3, 4], ['~', 'c', ' hello', 'hello']] end - it "should be able to diff changes in hash value which is array" do - diff = HashDiff.diff({ 'a' => 2, 'b' => [1, 2, 3] }, { 'a' => 2, 'b' => [1, 3, 4]}) + it 'is able to diff changes in hash value which is array' do + diff = described_class.diff({ 'a' => 2, 'b' => [1, 2, 3] }, 'a' => 2, 'b' => [1, 3, 4]) diff.should == [['-', 'b[1]', 2], ['+', 'b[2]', 4]] end - it "should be able to diff changes in hash value which is hash" do - diff = HashDiff.diff({ 'a' => { 'x' => 2, 'y' => 3, 'z' => 4 }, 'b' => { 'x' => 3, 'z' => 45 } }, - { 'a' => { 'y' => 3 }, 'b' => { 'y' => 3, 'z' => 30 } }) + it 'is able to diff changes in hash value which is hash' do + diff = described_class.diff({ 'a' => { 'x' => 2, 'y' => 3, 'z' => 4 }, 'b' => { 'x' => 3, 'z' => 45 } }, + 'a' => { 'y' => 3 }, 'b' => { 'y' => 3, 'z' => 30 }) diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]] end - it "should be able to diff similar objects in array" do - diff = HashDiff.best_diff({ 'a' => [{ 'x' => 2, 'y' => 3, 'z' => 4 }, { 'x' => 11, 'y' => 22, 'z' => 33 }], 'b' => { 'x' => 3, 'z' => 45 } }, - { 'a' => [{ 'y' => 3 }, { 'x' => 11, 'z' => 33 }], 'b' => { 'y' => 22 } }) + it 'is able to best diff similar objects in array' do + diff = described_class.best_diff({ 'a' => [{ 'x' => 2, 'y' => 3, 'z' => 4 }, { 'x' => 11, 'y' => 22, 'z' => 33 }], 'b' => { 'x' => 3, 'z' => 45 } }, + 'a' => [{ 'y' => 3 }, { 'x' => 11, 'z' => 33 }], 'b' => { 'y' => 22 }) diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]] end - it 'should be able to diff addition of key value pair' do - a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200} - b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300} + it 'is able to diff addition of key value pair' do + a = { 'a' => 3, 'c' => 11, 'd' => 45, 'e' => 100, 'f' => 200 } + b = { 'a' => 3, 'c' => 11, 'd' => 45, 'e' => 100, 'f' => 200, 'g' => 300 } - diff = HashDiff.diff(a, b) - diff.should == [['+', 'g', 300]] + diff = described_class.diff(a, b) + expect(diff).to eq([['+', 'g', 300]]) - diff = HashDiff.diff(b, a) + diff = described_class.diff(b, a) diff.should == [['-', 'g', 300]] end - it 'should be able to diff value type changes' do - a = {"a" => 3} - b = {"a" => {"a1" => 1, "a2" => 2}} + it 'is able to diff value type changes' do + a = { 'a' => 3 } + b = { 'a' => { 'a1' => 1, 'a2' => 2 } } - diff = HashDiff.diff(a, b) - diff.should == [['~', 'a', 3, {"a1" => 1, "a2" => 2}]] + diff = described_class.diff(a, b) + expect(diff).to eq([['~', 'a', 3, { 'a1' => 1, 'a2' => 2 }]]) - diff = HashDiff.diff(b, a) - diff.should == [['~', 'a', {"a1" => 1, "a2" => 2}, 3]] + diff = described_class.diff(b, a) + diff.should == [['~', 'a', { 'a1' => 1, 'a2' => 2 }, 3]] end - it "should be able to diff value changes: array <=> []" do - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1, "b" => []} + it 'is able to diff value changes: array <=> []' do + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1, 'b' => [] } - diff = HashDiff.diff(a, b) + diff = described_class.diff(a, b) diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1]] end - it "should be able to diff value changes: array <=> nil" do - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1, "b" => nil} + it 'is able to diff value changes: array <=> nil' do + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1, 'b' => nil } - diff = HashDiff.diff(a, b) - diff.should == [["~", "b", [1, 2], nil]] + diff = described_class.diff(a, b) + diff.should == [['~', 'b', [1, 2], nil]] end - it "should be able to diff value chagnes: remove array completely" do - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1} + it 'is able to diff value chagnes: remove array completely' do + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1 } - diff = HashDiff.diff(a, b) - diff.should == [["-", "b", [1, 2]]] + diff = described_class.diff(a, b) + diff.should == [['-', 'b', [1, 2]]] end - it "should be able to diff value changes: remove whole hash" do - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1} + it 'is able to diff value changes: remove whole hash' do + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1 } - diff = HashDiff.diff(a, b) - diff.should == [["-", "b", {"b1"=>1, "b2"=>2}]] + diff = described_class.diff(a, b) + diff.should == [['-', 'b', { 'b1' => 1, 'b2' => 2 }]] end - it "should be able to diff value changes: hash <=> {}" do - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1, "b" => {}} + it 'is able to diff value changes: hash <=> {}' do + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1, 'b' => {} } - diff = HashDiff.diff(a, b) + diff = described_class.diff(a, b) diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]] end - it "should be able to diff value changes: hash <=> nil" do - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1, "b" => nil} + it 'is able to diff value changes: hash <=> nil' do + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1, 'b' => nil } - diff = HashDiff.diff(a, b) - diff.should == [["~", "b", {"b1"=>1, "b2"=>2}, nil]] + diff = described_class.diff(a, b) + diff.should == [['~', 'b', { 'b1' => 1, 'b2' => 2 }, nil]] end - it "should be able to diff similar objects in array" do - a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3] - b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}] + it 'is able to diff similar objects in array' do + a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, 3] + b = [1, { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }] - diff = HashDiff.diff(a, b) + diff = described_class.diff(a, b) diff.should == [['-', '[0].d', 4], ['+', '[0]', 1], ['-', '[2]', 3]] end - it "should be able to diff similar & equal objects in array" do - a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 3] - b = [{'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}, 3] + it 'is able to diff similar & equal objects in array' do + a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, { 'x' => 5, 'y' => 6, 'z' => 3 }, 3] + b = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }, 3] - diff = HashDiff.diff(a, b) - diff.should == [["-", "[0].d", 4], ["-", "[1]", {"x"=>5, "y"=>6, "z"=>3}]] + diff = described_class.diff(a, b) + diff.should == [['-', '[0].d', 4], ['-', '[1]', { 'x' => 5, 'y' => 6, 'z' => 3 }]] end - it "should use custom delimiter when provided" do - a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 3] - b = [{'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}, 3] + it 'uses custom delimiter when provided' do + a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, { 'x' => 5, 'y' => 6, 'z' => 3 }, 3] + b = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }, 3] - diff = HashDiff.diff(a, b, :similarity => 0.8, :delimiter => "\t") - diff.should == [["-", "[0]\td", 4], ["-", "[1]", {"x"=>5, "y"=>6, "z"=>3}]] + diff = described_class.diff(a, b, similarity: 0.8, delimiter: "\t") + diff.should == [['-', "[0]\td", 4], ['-', '[1]', { 'x' => 5, 'y' => 6, 'z' => 3 }]] end context 'when :numeric_tolerance requested' do - it "should be able to diff changes in hash value" do - a = {'a' => 0.558, 'b' => 0.0, 'c' => 0.65, 'd' => 'fin'} - b = {'a' => 0.557, 'b' => 'hats', 'c' => 0.67, 'd' => 'fin'} + it 'is able to diff changes in hash value' do + a = { 'a' => 0.558, 'b' => 0.0, 'c' => 0.65, 'd' => 'fin' } + b = { 'a' => 0.557, 'b' => 'hats', 'c' => 0.67, 'd' => 'fin' } - diff = HashDiff.diff(a, b, :numeric_tolerance => 0.01) - diff.should == [["~", "b", 0.0, 'hats'], ["~", "c", 0.65, 0.67]] + diff = described_class.diff(a, b, numeric_tolerance: 0.01) + expect(diff).to eq([['~', 'b', 0.0, 'hats'], ['~', 'c', 0.65, 0.67]]) - diff = HashDiff.diff(b, a, :numeric_tolerance => 0.01) - diff.should == [["~", "b", 'hats', 0.0], ["~", "c", 0.67, 0.65]] + diff = described_class.diff(b, a, numeric_tolerance: 0.01) + diff.should == [['~', 'b', 'hats', 0.0], ['~', 'c', 0.67, 0.65]] end - it "should be able to diff changes in nested values" do - a = {'a' => {'x' => 0.4, 'y' => 0.338}, 'b' => [13, 68.03]} - b = {'a' => {'x' => 0.6, 'y' => 0.341}, 'b' => [14, 68.025]} + it 'is able to diff changes in nested values' do + a = { 'a' => { 'x' => 0.4, 'y' => 0.338 }, 'b' => [13, 68.03] } + b = { 'a' => { 'x' => 0.6, 'y' => 0.341 }, 'b' => [14, 68.025] } - diff = HashDiff.diff(a, b, :numeric_tolerance => 0.01) - diff.should == [["~", "a.x", 0.4, 0.6], ["-", "b[0]", 13], ["+", "b[0]", 14]] + diff = described_class.diff(a, b, numeric_tolerance: 0.01) + expect(diff).to eq([['~', 'a.x', 0.4, 0.6], ['-', 'b[0]', 13], ['+', 'b[0]', 14]]) - diff = HashDiff.diff(b, a, :numeric_tolerance => 0.01) - diff.should == [["~", "a.x", 0.6, 0.4], ["-", "b[0]", 14], ["+", "b[0]", 13]] + diff = described_class.diff(b, a, numeric_tolerance: 0.01) + diff.should == [['~', 'a.x', 0.6, 0.4], ['-', 'b[0]', 14], ['+', 'b[0]', 13]] end end context 'when :strip requested' do - it "should strip strings before comparing" do - a = { 'a' => " foo", 'b' => "fizz buzz"} - b = { 'a' => "foo", 'b' => "fizzbuzz"} - diff = HashDiff.diff(a, b, :strip => true) - diff.should == [['~', 'b', "fizz buzz", "fizzbuzz"]] + it 'strips strings before comparing' do + a = { 'a' => ' foo', 'b' => 'fizz buzz' } + b = { 'a' => 'foo', 'b' => 'fizzbuzz' } + diff = described_class.diff(a, b, strip: true) + diff.should == [['~', 'b', 'fizz buzz', 'fizzbuzz']] + end + + it 'strips nested strings before comparing' do + a = { 'a' => { 'x' => ' foo' }, 'b' => ['fizz buzz', 'nerf'] } + b = { 'a' => { 'x' => 'foo' }, 'b' => %w[fizzbuzz nerf] } + diff = described_class.diff(a, b, strip: true) + diff.should == [['-', 'b[0]', 'fizz buzz'], ['+', 'b[0]', 'fizzbuzz']] + end + end + + context 'when :case_insensitive requested' do + it 'strips strings before comparing' do + a = { 'a' => 'Foo', 'b' => 'fizz buzz' } + b = { 'a' => 'foo', 'b' => 'fizzBuzz' } + diff = described_class.diff(a, b, case_insensitive: true) + diff.should == [['~', 'b', 'fizz buzz', 'fizzBuzz']] end - it "should strip nested strings before comparing" do - a = { 'a' => { 'x' => " foo" }, 'b' => ["fizz buzz", "nerf"] } - b = { 'a' => { 'x' => "foo" }, 'b' => ["fizzbuzz", "nerf"] } - diff = HashDiff.diff(a, b, :strip => true) - diff.should == [['-', 'b[0]', "fizz buzz"], ['+', 'b[0]', "fizzbuzz"]] + it 'ignores case on nested strings before comparing' do + a = { 'a' => { 'x' => 'Foo' }, 'b' => ['fizz buzz', 'nerf'] } + b = { 'a' => { 'x' => 'foo' }, 'b' => %w[fizzbuzz nerf] } + diff = described_class.diff(a, b, case_insensitive: true) + diff.should == [['-', 'b[0]', 'fizz buzz'], ['+', 'b[0]', 'fizzbuzz']] end end context 'when both :strip and :numeric_tolerance requested' do - it 'should apply filters to proper object types' do - a = { 'a' => " foo", 'b' => 35, 'c' => 'bar', 'd' => 'baz' } - b = { 'a' => "foo", 'b' => 35.005, 'c' => 'bar', 'd' => 18.5} - diff = HashDiff.diff(a, b, :strict => false, :numeric_tolerance => 0.01, :strip => true) - diff.should == [['~', 'd', "baz", 18.5]] + it 'applies filters to proper object types' do + a = { 'a' => ' foo', 'b' => 35, 'c' => 'bar', 'd' => 'baz' } + b = { 'a' => 'foo', 'b' => 35.005, 'c' => 'bar', 'd' => 18.5 } + diff = described_class.diff(a, b, strict: false, numeric_tolerance: 0.01, strip: true) + diff.should == [['~', 'd', 'baz', 18.5]] + end + end + + context 'when both :strip and :case_insensitive requested' do + it 'applies both filters to strings' do + a = { 'a' => ' Foo', 'b' => 'fizz buzz' } + b = { 'a' => 'foo', 'b' => 'fizzBuzz' } + diff = described_class.diff(a, b, case_insensitive: true, strip: true) + diff.should == [['~', 'b', 'fizz buzz', 'fizzBuzz']] end end context 'with custom comparison' do - let(:a) { { 'a' => 'car', 'b' => 'boat', 'c' => 'plane'} } - let(:b) { { 'a' => 'bus', 'b' => 'truck', 'c' => ' plan'} } + let(:a) { { 'a' => 'car', 'b' => 'boat', 'c' => 'plane' } } + let(:b) { { 'a' => 'bus', 'b' => 'truck', 'c' => ' plan' } } - it 'should compare using proc specified in block' do - diff = HashDiff.diff(a, b) do |prefix, obj1, obj2| + it 'compares using proc specified in block' do + diff = described_class.diff(a, b) do |prefix, obj1, obj2| case prefix when /a|b|c/ obj1.length == obj2.length @@ -206,11 +253,11 @@ diff.should == [['~', 'b', 'boat', 'truck']] end - it 'should yield added keys' do - x = { 'a' => 'car', 'b' => 'boat'} + it 'yields added keys' do + x = { 'a' => 'car', 'b' => 'boat' } y = { 'a' => 'car' } - diff = HashDiff.diff(x, y) do |prefix, obj1, obj2| + diff = described_class.diff(x, y) do |prefix, _obj1, _obj2| case prefix when /b/ true @@ -219,8 +266,8 @@ diff.should == [] end - it 'should compare with both proc and :strip when both provided' do - diff = HashDiff.diff(a, b, :strip => true) do |prefix, obj1, obj2| + it 'compares with both proc and :strip when both provided' do + diff = described_class.diff(a, b, strip: true) do |prefix, obj1, obj2| case prefix when 'a' obj1.length == obj2.length @@ -228,5 +275,81 @@ end diff.should == [['~', 'b', 'boat', 'truck'], ['~', 'c', 'plane', ' plan']] end + + it 'compares nested arrays using proc specified in block' do + a = { a: 'car', b: %w[boat plane] } + b = { a: 'bus', b: ['truck', ' plan'] } + + diff = described_class.diff(a, b) do |path, obj1, obj2| + case path + when 'b[*]' + obj1.length == obj2.length + end + end + + expect(diff).to eq [['~', 'a', 'car', 'bus'], ['~', 'b[1]', 'plane', ' plan'], ['-', 'b[0]', 'boat'], ['+', 'b[0]', 'truck']] + end + end + + context 'when :array_path is true' do + it 'returns the diff path in an array rather than a string' do + x = { 'a' => 'foo' } + y = { 'a' => 'bar' } + diff = described_class.diff(x, y, array_path: true) + + diff.should == [['~', ['a'], 'foo', 'bar']] + end + + it 'shows array indexes in paths' do + x = { 'a' => [0, 1, 2] } + y = { 'a' => [0, 1, 2, 3] } + + diff = described_class.diff(x, y, array_path: true) + + diff.should == [['+', ['a', 3], 3]] + end + + it 'shows differences with string and symbol keys' do + x = { 'a' => 'foo' } + y = { a: 'bar' } + + diff = described_class.diff(x, y, array_path: true) + diff.should == [['-', ['a'], 'foo'], ['+', [:a], 'bar']] + end + + it 'supports other key types' do + time = Time.now + x = { time => 'foo' } + y = { 0 => 'bar' } + + diff = described_class.diff(x, y, array_path: true) + diff.should == [['-', [time], 'foo'], ['+', [0], 'bar']] + end + end + + context 'when :use_lcs is false' do + it 'shows items in an array as changed' do + x = %i[a b] + y = %i[c d] + diff = described_class.diff(x, y, use_lcs: false) + + diff.should == [['~', '[0]', :a, :c], ['~', '[1]', :b, :d]] + end + + it 'shows additions to arrays' do + x = { a: [0] } + y = { a: [0, 1] } + diff = described_class.diff(x, y, use_lcs: false) + + diff.should == [['+', 'a[1]', 1]] + end + + it 'shows changes to nested arrays' do + x = { a: [[0, 1]] } + y = { a: [[1, 2]] } + diff = described_class.diff(x, y, use_lcs: false) + + diff.should == [['~', 'a[0][0]', 0, 1], ['~', 'a[0][1]', 1, 2]] + end end end diff -Nru ruby-hashdiff-0.2.3/spec/hashdiff/lcs_spec.rb ruby-hashdiff-1.0.0/spec/hashdiff/lcs_spec.rb --- ruby-hashdiff-0.2.3/spec/hashdiff/lcs_spec.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/spec/hashdiff/lcs_spec.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,75 +1,76 @@ +# frozen_string_literal: true + require 'spec_helper' -describe HashDiff do - it "should be able to find LCS between two equal array" do +describe Hashdiff do + it 'is able to find LCS between two equal array' do a = [1, 2, 3] b = [1, 2, 3] - lcs = HashDiff.lcs(a, b) + lcs = described_class.lcs(a, b) lcs.should == [[0, 0], [1, 1], [2, 2]] end - it "should be able to find LCS between two close arrays" do + it 'is able to find LCS between two close arrays' do a = [1.05, 2, 3.25] b = [1.06, 2, 3.24] - lcs = HashDiff.lcs(a, b, :numeric_tolerance => 0.1) + lcs = described_class.lcs(a, b, numeric_tolerance: 0.1) lcs.should == [[0, 0], [1, 1], [2, 2]] end - it "should strip strings when finding LCS if requested" do - a = ['foo', 'bar', 'baz'] + it 'strips strings when finding LCS if requested' do + a = %w[foo bar baz] b = [' foo', 'bar', 'zab'] - lcs = HashDiff.lcs(a, b, :strip => true) + lcs = described_class.lcs(a, b, strip: true) lcs.should == [[0, 0], [1, 1]] end - it "should be able to find LCS with one common elements" do + it 'is able to find LCS with one common elements' do a = [1, 2, 3] b = [1, 8, 7] - lcs = HashDiff.lcs(a, b) + lcs = described_class.lcs(a, b) lcs.should == [[0, 0]] end - it "should be able to find LCS with two common elements" do + it 'is able to find LCS with two common elements' do a = [1, 3, 5, 7] b = [2, 3, 7, 5] - lcs = HashDiff.lcs(a, b) + lcs = described_class.lcs(a, b) lcs.should == [[1, 1], [2, 3]] end - it "should be able to find LCS with two close elements" do + it 'is able to find LCS with two close elements' do a = [1, 3.05, 5, 7] b = [2, 3.06, 7, 5] - lcs = HashDiff.lcs(a, b, :numeric_tolerance => 0.1) + lcs = described_class.lcs(a, b, numeric_tolerance: 0.1) lcs.should == [[1, 1], [2, 3]] end - it "should be able to find LCS with two common elements in different ordering" do + it 'is able to find LCS with two common elements in different ordering' do a = [1, 3, 4, 7] b = [2, 3, 7, 5] - lcs = HashDiff.lcs(a, b) + lcs = described_class.lcs(a, b) lcs.should == [[1, 1], [3, 2]] end - it "should be able to find LCS with a similarity value" do + it 'is able to find LCS with a similarity value' do a = [ - {"value" => "New", "onclick" => "CreateNewDoc()"}, - {"value" => "Close", "onclick" => "CloseDoc()"} - ] + { 'value' => 'New', 'onclick' => 'CreateNewDoc()' }, + { 'value' => 'Close', 'onclick' => 'CloseDoc()' } + ] b = [ - {"value" => "New1", "onclick" => "CreateNewDoc()"}, - {"value" => "Open", "onclick" => "OpenDoc()"}, - {"value" => "Close", "onclick" => "CloseDoc()"} - ] + { 'value' => 'New1', 'onclick' => 'CreateNewDoc()' }, + { 'value' => 'Open', 'onclick' => 'OpenDoc()' }, + { 'value' => 'Close', 'onclick' => 'CloseDoc()' } + ] - lcs = HashDiff.lcs(a, b, :similarity => 0.5) + lcs = described_class.lcs(a, b, similarity: 0.5) lcs.should == [[0, 0], [1, 2]] end end - diff -Nru ruby-hashdiff-0.2.3/spec/hashdiff/linear_compare_array_spec.rb ruby-hashdiff-1.0.0/spec/hashdiff/linear_compare_array_spec.rb --- ruby-hashdiff-0.2.3/spec/hashdiff/linear_compare_array_spec.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-hashdiff-1.0.0/spec/hashdiff/linear_compare_array_spec.rb 2019-08-06 18:41:09.000000000 +0000 @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Hashdiff::LinearCompareArray do + it 'finds no differences between two empty arrays' do + difference = described_class.call([], []) + difference.should == [] + end + + it 'finds added items when the old array is empty' do + difference = described_class.call([], %i[a b]) + difference.should == [['+', '[0]', :a], ['+', '[1]', :b]] + end + + it 'finds removed items when the new array is empty' do + difference = described_class.call(%i[a b], []) + difference.should == [['-', '[1]', :b], ['-', '[0]', :a]] + end + + it 'finds no differences between identical arrays' do + difference = described_class.call(%i[a b], %i[a b]) + difference.should == [] + end + + it 'finds added items in an array' do + difference = described_class.call(%i[a d], %i[a b c d]) + difference.should == [['+', '[1]', :b], ['+', '[2]', :c]] + end + + it 'finds removed items in an array' do + difference = described_class.call(%i[a b c d e f], %i[a d f]) + difference.should == [['-', '[4]', :e], ['-', '[2]', :c], ['-', '[1]', :b]] + end + + it 'shows additions and deletions as changed items' do + difference = described_class.call(%i[a b c], %i[c b a]) + difference.should == [['~', '[0]', :a, :c], ['~', '[2]', :c, :a]] + end + + it 'shows changed items in a hash' do + difference = described_class.call([{ a: :b }], [{ a: :c }]) + difference.should == [['~', '[0].a', :b, :c]] + end + + it 'shows changed items and added items' do + difference = described_class.call([{ a: 1, b: 2 }], [{ a: 2, b: 2 }, :item]) + difference.should == [['~', '[0].a', 1, 2], ['+', '[1]', :item]] + end +end diff -Nru ruby-hashdiff-0.2.3/spec/hashdiff/patch_spec.rb ruby-hashdiff-1.0.0/spec/hashdiff/patch_spec.rb --- ruby-hashdiff-0.2.3/spec/hashdiff/patch_spec.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/spec/hashdiff/patch_spec.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,136 +1,185 @@ +# frozen_string_literal: true + require 'spec_helper' -describe HashDiff do - it "it should be able to patch key addition" do - a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200} - b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300} - diff = HashDiff.diff(a, b) +describe Hashdiff do + it 'is able to patch key addition' do + a = { 'a' => 3, 'c' => 11, 'd' => 45, 'e' => 100, 'f' => 200 } + b = { 'a' => 3, 'c' => 11, 'd' => 45, 'e' => 100, 'f' => 200, 'g' => 300 } + diff = described_class.diff(a, b) + + expect(described_class.patch!(a, diff)).to eq(b) + + a = { 'a' => 3, 'c' => 11, 'd' => 45, 'e' => 100, 'f' => 200 } + b = { 'a' => 3, 'c' => 11, 'd' => 45, 'e' => 100, 'f' => 200, 'g' => 300 } + described_class.unpatch!(b, diff).should == a + end + + it 'is able to patch value type changes' do + a = { 'a' => 3 } + b = { 'a' => { 'a1' => 1, 'a2' => 2 } } + diff = described_class.diff(a, b) + + expect(described_class.patch!(a, diff)).to eq(b) + + a = { 'a' => 3 } + b = { 'a' => { 'a1' => 1, 'a2' => 2 } } + described_class.unpatch!(b, diff).should == a + end + + it 'is able to patch value array <=> []' do + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1, 'b' => [] } + diff = described_class.diff(a, b) + + expect(described_class.patch!(a, diff)).to eq(b) + + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1, 'b' => [] } + described_class.unpatch!(b, diff).should == a + end + + it 'is able to patch value array <=> nil' do + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1, 'b' => nil } + diff = described_class.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200} - b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300} - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1, 'b' => nil } + described_class.unpatch!(b, diff).should == a end - it "should be able to patch value type changes" do - a = {"a" => 3} - b = {"a" => {"a1" => 1, "a2" => 2}} - diff = HashDiff.diff(a, b) + it 'is able to patch array value removal' do + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1 } + diff = described_class.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a" => 3} - b = {"a" => {"a1" => 1, "a2" => 2}} - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b' => [1, 2] } + b = { 'a' => 1 } + described_class.unpatch!(b, diff).should == a end - it "should be able to patch value array <=> []" do - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1, "b" => []} - diff = HashDiff.diff(a, b) + it 'is able to patch array under hash key with non-word characters' do + a = { 'a' => 1, 'b-b' => [1, 2] } + b = { 'a' => 1, 'b-b' => [2, 1] } + diff = described_class.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1, "b" => []} - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b-b' => [1, 2] } + b = { 'a' => 1, 'b-b' => [2, 1] } + described_class.unpatch!(b, diff).should == a end - it "should be able to patch value array <=> nil" do - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1, "b" => nil} - diff = HashDiff.diff(a, b) + it 'is able to patch hash value removal' do + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1 } + diff = described_class.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1, "b" => nil} - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1 } + described_class.unpatch!(b, diff).should == a end - it "should be able to patch array value removal" do - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1} - diff = HashDiff.diff(a, b) + it 'is able to patch value hash <=> {}' do + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1, 'b' => {} } + diff = described_class.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a" => 1, "b" => [1, 2]} - b = {"a" => 1} - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1, 'b' => {} } + described_class.unpatch!(b, diff).should == a end - it "should be able to patch hash value removal" do - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1} - diff = HashDiff.diff(a, b) + it 'is able to patch value hash <=> nil' do + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1, 'b' => nil } + diff = described_class.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1} - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1, 'b' => nil } + described_class.unpatch!(b, diff).should == a end - it "should be able to patch value hash <=> {}" do - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1, "b" => {}} - diff = HashDiff.diff(a, b) + it 'is able to patch value nil removal' do + a = { 'a' => 1, 'b' => nil } + b = { 'a' => 1 } + diff = described_class.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1, "b" => {}} - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b' => nil } + b = { 'a' => 1 } + described_class.unpatch!(b, diff).should == a end - it "should be able to patch value hash <=> nil" do - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1, "b" => nil} - diff = HashDiff.diff(a, b) + it 'is able to patch similar objects between arrays' do + a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, 3] + b = [1, { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }] - HashDiff.patch!(a, diff).should == b + diff = described_class.diff(a, b) + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} - b = {"a" => 1, "b" => nil} - HashDiff.unpatch!(b, diff).should == a + a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, 3] + b = [1, { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }] + described_class.unpatch!(b, diff).should == a end - it "should be able to patch value nil removal" do - a = {"a" => 1, "b" => nil} - b = {"a" => 1} - diff = HashDiff.diff(a, b) + it 'is able to patch similar & equal objects between arrays' do + a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, { 'x' => 5, 'y' => 6, 'z' => 3 }, 1] + b = [1, { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }] - HashDiff.patch!(a, diff).should == b + diff = described_class.diff(a, b) + expect(described_class.patch!(a, diff)).to eq(b) - a = {"a" => 1, "b" => nil} - b = {"a" => 1} - HashDiff.unpatch!(b, diff).should == a + a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, { 'x' => 5, 'y' => 6, 'z' => 3 }, 1] + b = [1, { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }] + described_class.unpatch!(b, diff).should == a end - it "should be able to patch similar objects between arrays" do - a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3] - b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}] + it 'is able to patch hash value removal with custom delimiter' do + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1, 'b' => { 'b1' => 3 } } + diff = described_class.diff(a, b, delimiter: "\n") - diff = HashDiff.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff, delimiter: "\n")).to eq(b) - a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3] - b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}] - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b' => { 'b1' => 1, 'b2' => 2 } } + b = { 'a' => 1, 'b' => { 'b1' => 3 } } + described_class.unpatch!(b, diff, delimiter: "\n").should == a end - it "should be able to patch similar & equal objects between arrays" do - a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1] - b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}] + it 'is able to patch when the diff is generated with an array_path' do + a = { 'a' => 1, 'b' => 1 } + b = { 'a' => 1, 'b' => 2 } + diff = described_class.diff(a, b, array_path: true) - diff = HashDiff.diff(a, b) - HashDiff.patch!(a, diff).should == b + expect(described_class.patch!(a, diff)).to eq(b) - a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1] - b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}] - HashDiff.unpatch!(b, diff).should == a + a = { 'a' => 1, 'b' => 1 } + b = { 'a' => 1, 'b' => 2 } + described_class.unpatch!(b, diff).should == a end + it 'is able to use non string keys when diff is generated with an array_path' do + a = { 'a' => 1, :a => 2, 0 => 3 } + b = { 'a' => 5, :a => 6, 0 => 7 } + diff = described_class.diff(a, b, array_path: true) + + expect(described_class.patch!(a, diff)).to eq(b) + + a = { 'a' => 1, :a => 2, 0 => 3 } + b = { 'a' => 5, :a => 6, 0 => 7 } + described_class.unpatch!(b, diff).should == a + end end diff -Nru ruby-hashdiff-0.2.3/spec/hashdiff/util_spec.rb ruby-hashdiff-1.0.0/spec/hashdiff/util_spec.rb --- ruby-hashdiff-0.2.3/spec/hashdiff/util_spec.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/spec/hashdiff/util_spec.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,73 +1,95 @@ +# frozen_string_literal: true + require 'spec_helper' -describe HashDiff do - it "should be able to decode property path" do - decoded = HashDiff.send(:decode_property_path, "a.b[0].c.city[5]") +describe Hashdiff do + it 'is able to decode property path' do + decoded = described_class.send(:decode_property_path, 'a.b[0].c.city[5]') decoded.should == ['a', 'b', 0, 'c', 'city', 5] end - it "should be able to decode property path with custom delimiter" do - decoded = HashDiff.send(:decode_property_path, "a\tb[0]\tc\tcity[5]", "\t") + it 'is able to decode property path with custom delimiter' do + decoded = described_class.send(:decode_property_path, "a\tb[0]\tc\tcity[5]", "\t") decoded.should == ['a', 'b', 0, 'c', 'city', 5] end - it "should be able to tell similiar hash" do - a = {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5} - b = {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5} - HashDiff.similar?(a, b).should be_true - HashDiff.similar?(a, b, :similarity => 1).should be_false + it 'is able to tell similiar hash' do + a = { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 } + b = { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 } + described_class.similar?(a, b).should be true + described_class.similar?(a, b, similarity: 1).should be false end - it "should be able to tell similiar hash with values within tolerance" do - a = {'a' => 1.5, 'b' => 2.25, 'c' => 3, 'd' => 4, 'e' => 5} - b = {'a' => 1.503, 'b' => 2.22, 'c' => 3, 'e' => 5} - HashDiff.similar?(a, b, :numeric_tolerance => 0.05).should be_true - HashDiff.similar?(a, b).should be_false + it 'is able to tell similiar empty hash' do + described_class.similar?({}, {}, similarity: 1).should be true end - it "should be able to tell numbers and strings" do - HashDiff.similar?(1, 2).should be_false - HashDiff.similar?("a", "b").should be_false - HashDiff.similar?("a", [1, 2, 3]).should be_false - HashDiff.similar?(1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}).should be_false + it 'is able to tell similiar empty array' do + described_class.similar?([], [], similarity: 1).should be true end - it "should be able to tell true when similarity == 0.5" do - a = {"value" => "New1", "onclick" => "CreateNewDoc()"} - b = {"value" => "New", "onclick" => "CreateNewDoc()"} + it 'is able to tell similiar hash with values within tolerance' do + a = { 'a' => 1.5, 'b' => 2.25, 'c' => 3, 'd' => 4, 'e' => 5 } + b = { 'a' => 1.503, 'b' => 2.22, 'c' => 3, 'e' => 5 } + described_class.similar?(a, b, numeric_tolerance: 0.05).should be true + described_class.similar?(a, b).should be false + end - HashDiff.similar?(a, b, :similarity => 0.5).should be_true + it 'is able to tell numbers and strings' do + described_class.similar?(1, 2).should be false + described_class.similar?('a', 'b').should be false + described_class.similar?('a', [1, 2, 3]).should be false + described_class.similar?(1, 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5).should be false end - it "should be able to tell false when similarity == 0.5" do - a = {"value" => "New1", "onclick" => "open()"} - b = {"value" => "New", "onclick" => "CreateNewDoc()"} + it 'is able to tell true when similarity == 0.5' do + a = { 'value' => 'New1', 'onclick' => 'CreateNewDoc()' } + b = { 'value' => 'New', 'onclick' => 'CreateNewDoc()' } - HashDiff.similar?(a, b, :similarity => 0.5).should be_false + described_class.similar?(a, b, similarity: 0.5).should be true + end + + it 'is able to tell false when similarity == 0.5' do + a = { 'value' => 'New1', 'onclick' => 'open()' } + b = { 'value' => 'New', 'onclick' => 'CreateNewDoc()' } + + described_class.similar?(a, b, similarity: 0.5).should be false end describe '.compare_values' do - it "should compare numeric values exactly when no tolerance" do - expect(HashDiff.compare_values(10.004, 10.003)).to be_false + it 'compares numeric values exactly when no tolerance' do + expect(described_class.compare_values(10.004, 10.003)).to be false end - it "should allow tolerance with numeric values" do - expect(HashDiff.compare_values(10.004, 10.003, :numeric_tolerance => 0.01)).to be_true + it 'allows tolerance with numeric values' do + expect(described_class.compare_values(10.004, 10.003, numeric_tolerance: 0.01)).to be true end - it "should compare other objects with or without tolerance" do - expect(HashDiff.compare_values('hats', 'ninjas')).to be_false - expect(HashDiff.compare_values('hats', 'ninjas', :numeric_tolerance => 0.01)).to be_false - expect(HashDiff.compare_values('horse', 'horse')).to be_true + it 'compares different objects without tolerance' do + expect(described_class.compare_values('hats', 'ninjas')).to be false + end + it 'compares other objects with tolerance' do + expect(described_class.compare_values('hats', 'ninjas', numeric_tolerance: 0.01)).to be false end - it 'should compare strings exactly by default' do - expect(HashDiff.compare_values(' horse', 'horse')).to be_false + it 'compares same objects without tolerance' do + expect(described_class.compare_values('horse', 'horse')).to be true end - it 'should strip strings before comparing when requested' do - expect(HashDiff.compare_values(' horse', 'horse', :strip => true)).to be_true + it 'compares strings for spaces exactly by default' do + expect(described_class.compare_values(' horse', 'horse')).to be false + end + + it 'compares strings for capitalization exactly by default' do + expect(described_class.compare_values('horse', 'Horse')).to be false + end + + it 'strips strings before comparing when requested' do + expect(described_class.compare_values(' horse', 'horse', strip: true)).to be true + end + + it 'ignores string case when requested' do + expect(described_class.compare_values('horse', 'Horse', case_insensitive: true)).to be true end end end - diff -Nru ruby-hashdiff-0.2.3/spec/spec_helper.rb ruby-hashdiff-1.0.0/spec/spec_helper.rb --- ruby-hashdiff-0.2.3/spec/spec_helper.rb 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/spec/spec_helper.rb 2019-08-06 18:41:09.000000000 +0000 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib') require 'rubygems' diff -Nru ruby-hashdiff-0.2.3/.travis.yml ruby-hashdiff-1.0.0/.travis.yml --- ruby-hashdiff-0.2.3/.travis.yml 2016-01-22 14:12:31.000000000 +0000 +++ ruby-hashdiff-1.0.0/.travis.yml 2019-08-06 18:41:09.000000000 +0000 @@ -1,7 +1,11 @@ +sudo: false language: ruby +cache: bundler rvm: - - 1.8.7 - - 1.9.3 - 2.0.0 - - 2.1.1 -script: "bundle exec rake spec" + - 2.1.10 + - 2.2.8 + - 2.3.4 + - 2.4.2 + - 2.5.3 + - 2.6.0