hashdiff-0.2.3/0000755000175000017500000000000012650434317013507 5ustar terceiroterceirohashdiff-0.2.3/metadata.yml0000644000175000017500000000502112650434317016010 0ustar terceiroterceiro--- !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: [] hashdiff-0.2.3/changelog.md0000644000175000017500000000166412650434317015767 0ustar terceiroterceiro# Change Log ## v0.2.3 2015-11-5 * improve performance of LCS algorithm #12 ## v0.2.2 2014-10-6 * make library 1.8.7 compatible ## v0.2.1 2014-7-13 * yield added/deleted keys for custom comparison ## v0.2.0 2014-3-29 * support custom comparison blocks * support `:strip`, `:numeric_tolerance` and `:strict` options ## v0.1.0 2013-8-25 * use options for parameters `:delimiter` and `:similarity` in interfaces ## v0.0.6 2013-3-2 * Add parameter for custom property-path delimiter. ## v0.0.5 2012-7-1 * fix a bug in judging whehter two objects are similiar. * add more spec test for HashDiff.best_diff ## v0.0.4 2012-6-24 Main changes in this version is to output the whole object in addition & deletion, instead of recursely add/deletes the object. For example, `diff({a:2, c:[4, 5]}, {a:2}) will generate following output: [['-', 'c', [4, 5]]] instead of following: [['-', 'c[0]', 4], ['-', 'c[1]', 5], ['-', 'c', []]] hashdiff-0.2.3/LICENSE0000644000175000017500000000203712650434317014516 0ustar terceiroterceiroCopyright (c) 2012 Liu Fengyun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hashdiff-0.2.3/.travis.yml0000644000175000017500000000013412650434317015616 0ustar terceiroterceirolanguage: ruby rvm: - 1.8.7 - 1.9.3 - 2.0.0 - 2.1.1 script: "bundle exec rake spec" hashdiff-0.2.3/.rspec0000644000175000017500000000001012650434317014613 0ustar terceiroterceiro--color hashdiff-0.2.3/lib/0000755000175000017500000000000012650434317014255 5ustar terceiroterceirohashdiff-0.2.3/lib/hashdiff.rb0000644000175000017500000000017312650434317016357 0ustar terceiroterceirorequire 'hashdiff/util' require 'hashdiff/lcs' require 'hashdiff/diff' require 'hashdiff/patch' require 'hashdiff/version' hashdiff-0.2.3/lib/hashdiff/0000755000175000017500000000000012650434317016031 5ustar terceiroterceirohashdiff-0.2.3/lib/hashdiff/diff.rb0000644000175000017500000001664112650434317017276 0ustar terceiroterceiromodule 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. # # @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 # * :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 # # @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. # # @return [Array] an array of changes. # e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']] # # @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.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 count, diffs = count_1 < count_2 ? [count_1, diffs_1] : [count_2, diffs_2] diffs = count < count_3 ? diffs : diffs_3 end # Compute the diff of two hashes or 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 # * :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 # # @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. # # @return [Array] an array of changes. # e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']] # # @example # a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} # b = {"a" => 1, "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 }.merge!(options) 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 if obj1.nil? return [['~', opts[:prefix], nil, obj2]] end if obj2.nil? return [['~', opts[:prefix], obj1, nil]] end unless comparable?(obj1, obj2, opts[:strict]) return [['~', opts[:prefix], obj1, obj2]] end 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 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 # recursive comparison for common keys common_keys.sort.each {|k| result.concat(diff(obj1[k], obj2[k], opts.merge(:prefix => "#{prefix}#{k}"))) } # 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 end # @private # # diff array using LCS algorithm def self.diff_array(a, b, options = {}) opts = { :prefix => '', :similarity => 0.8, :delimiter => '.' }.merge!(options) 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]] end return change_set elsif b.size == 0 a.each_index do |index| i = a.size - index - 1 change_set << ['-', i, a[i]] end return change_set end links = lcs(a, b, opts) # yield common yield links if block_given? # padding the end links << [a.size, b.size] last_x = -1 last_y = -1 links.each do |pair| 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]] 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]] end # update flags last_x = x last_y = y end change_set end end hashdiff-0.2.3/lib/hashdiff/lcs.rb0000644000175000017500000000312512650434317017140 0ustar terceiroterceiromodule 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) opts[:prefix] = "#{opts[:prefix]}[*]" return [] if a.size == 0 or b.size == 0 a_start = b_start = 0 a_finish = a.size - 1 b_finish = b.size - 1 vector = [] lcs = [] (b_start..b_finish).each do |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 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 lcs[bi][ai] = [direction, count] end end end x = a_finish y = b_finish while x >= 0 and y >= 0 and lcs[y][x][1] > 0 if lcs[y][x][0] == :both x -= 1 elsif lcs[y][x][0] == :topleft vector.insert(0, [x, y]) x -= 1 y -= 1 elsif lcs[y][x][0] == :top y -= 1 elsif lcs[y][x][0] == :left x -= 1 end end vector end end hashdiff-0.2.3/lib/hashdiff/version.rb0000644000175000017500000000005012650434317020036 0ustar terceiroterceiromodule HashDiff VERSION = '0.2.3' end hashdiff-0.2.3/lib/hashdiff/util.rb0000644000175000017500000000537512650434317017345 0ustar terceiroterceiromodule HashDiff # @private # # judge whether two objects are similar def self.similar?(a, b, options = {}) opts = { :similarity => 0.8 }.merge(options) count_a = count_nodes(a) count_b = count_nodes(b) diffs = count_diff diff(a, b, opts) if count_a + count_b == 0 return true else (1 - diffs.to_f/(count_a + count_b).to_f) >= opts[:similarity] end end # @private # # count node differences def self.count_diff(diffs) 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) end end # @private # # count total nodes for an object def self.count_nodes(obj) return 0 unless obj count = 0 if obj.is_a?(Array) obj.each {|e| count += count_nodes(e) } elsif obj.is_a?(Hash) obj.each {|k, v| count += count_nodes(v) } else return 1 end count end # @private # # decode property path into an array # @param [String] path Property-string # @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] else $2.to_i end else part end end parts.flatten end # @private # # get the node of hash by given path parts def self.node(hash, parts) temp = hash parts.each do |part| temp = temp[part] end temp end # @private # # 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 } 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 end obj1 == obj2 end # @private # # 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 !strict && obj1.is_a?(Numeric) && obj2.is_a?(Numeric) obj1.is_a?(obj2.class) && obj2.is_a?(obj1.class) end # @private # # try custom comparison def self.custom_compare(method, key, obj1, obj2) if method res = method.call(key, obj1, obj2) # nil != false here if res == false return [['~', key, obj1, obj2]] elsif res == true return [] end end end end hashdiff-0.2.3/lib/hashdiff/patch.rb0000644000175000017500000000453212650434317017461 0ustar terceiroterceiro# # This module provides methods to diff two hash, patch and unpatch hash # module HashDiff # Apply patch to object # # @param [Hash, Array] obj the object to be patched, can be an Array or a Hash # @param [Array] changes e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']] # @param [Hash] options supports following keys: # * :delimiter (String) ['.'] delimiter string for representing nested keys in changes array # # @return the object after patch # # @since 0.0.1 def self.patch!(obj, changes, options = {}) delimiter = options[:delimiter] || '.' changes.each do |change| parts = decode_property_path(change[1], delimiter) last_part = parts.last parent_node = node(obj, parts[0, parts.size-1]) if change[0] == '+' if last_part.is_a?(Fixnum) parent_node.insert(last_part, change[2]) else parent_node[last_part] = change[2] end elsif change[0] == '-' if last_part.is_a?(Fixnum) parent_node.delete_at(last_part) else parent_node.delete(last_part) end elsif change[0] == '~' parent_node[last_part] = change[3] end end obj end # Unpatch an object # # @param [Hash, Array] obj the object to be unpatched, can be an Array or a Hash # @param [Array] changes e.g. [[ '+', 'a.b', '45' ], [ '-', 'a.c', '5' ], [ '~', 'a.x', '45', '63']] # @param [Hash] options supports following keys: # * :delimiter (String) ['.'] delimiter string for representing nested keys in changes array # # @return the object after unpatch # # @since 0.0.1 def self.unpatch!(obj, changes, options = {}) delimiter = options[:delimiter] || '.' changes.reverse_each do |change| parts = decode_property_path(change[1], delimiter) last_part = parts.last parent_node = node(obj, parts[0, parts.size-1]) if change[0] == '+' if last_part.is_a?(Fixnum) parent_node.delete_at(last_part) else parent_node.delete(last_part) end elsif change[0] == '-' if last_part.is_a?(Fixnum) parent_node.insert(last_part, change[2]) else parent_node[last_part] = change[2] end elsif change[0] == '~' parent_node[last_part] = change[2] end end obj end end hashdiff-0.2.3/.yardopts0000644000175000017500000000001512650434317015351 0ustar terceiroterceiro--no-private hashdiff-0.2.3/Rakefile0000644000175000017500000000036112650434317015154 0ustar terceiroterceiro$:.push File.expand_path("../lib", __FILE__) require 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' task :default => :spec RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = "./spec/**/*_spec.rb" end hashdiff-0.2.3/hashdiff.gemspec0000644000175000017500000000156512650434317016637 0ustar terceiroterceiro$LOAD_PATH << File.expand_path("../lib", __FILE__) require 'hashdiff/version' Gem::Specification.new do |s| s.name = %q{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.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.authors = ["Liu Fengyun"] s.email = ["liufengyunchina@gmail.com"] s.homepage = "https://github.com/liufengyun/hashdiff" s.add_development_dependency("rspec", "~> 2.0") s.add_development_dependency("yard") s.add_development_dependency("bluecloth") end hashdiff-0.2.3/README.md0000644000175000017500000001365112650434317014774 0ustar terceiroterceiro# 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 is a ruby library to compute the smallest difference between two hashes. **Docs**: [Documentation](http://rubydoc.info/gems/hashdiff) ## 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? An algorithm that responds to this question has to do following: * Generate a list of additions, deletions and changes, so that `A + ChangeSet = B` and `B - ChangeSet = A`. * 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: * 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]]`. * It compares Arrays using the [LCS(longest common subsequence)](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem) algorithm. * It recognizes similar Hashes in an Array using a similarity value (0 < similarity <= 1). ## Usage To use the gem, add the following to your Gemfile: ```ruby gem 'hashdiff' ``` ## Quick Start ### Diff Two simple hashes: ```ruby a = {a:3, b:2} b = {} diff = HashDiff.diff(a, b) diff.should == [['-', 'a', 3], ['-', 'b', 2]] ``` More complex hashes: ```ruby 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.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]] ``` Arrays in hashes: ```ruby 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.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]] ``` ### Patch patch example: ```ruby a = {a: 3} b = {a: {a1: 1, a2: 2}} 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}] 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`. #### `:delimiter` You can specify `:delimiter` to be something other than the default dot. For example: ```ruby 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.should == [['-', 'a\tx', 2], ['-', 'a\tz', 4], ['-', 'b\tx', 3], ['~', 'b\tz', 45, 30], ['+', 'b\ty', 3]] ``` #### `:similarity` In cases where you have similar hash objects in arrays, you can pass a custom value for `:similarity` instead of the default `0.8`. This is interpreted as a ratio of similarity (default is 80% similar, whereas `:similarity => 0.5` would look for at least a 50% similarity). #### `: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). #### `:numeric_tolerance` The :numeric_tolerance option allows for a small numeric tolerance. ```ruby 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.should == [["~", "x", 5, 6]] ``` #### `:strip` The :strip option strips all strings before comparing. ```ruby a = {x:5, s:'foo '} b = {x:6, s:'foo'} diff = HashDiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :strip => true }) diff.should == [["~", "x", 5, 6]] ``` #### Specifying a custom comparison method It's possible to specify how the values of a key should be compared. ```ruby a = {a:'car', b:'boat', c:'plane'} b = {a:'bus', b:'truck', c:' plan'} diff = HashDiff.diff(a, b) do |path, obj1, obj2| case path when /a|b|c/ obj1.length == obj2.length end end diff.should == [['~', 'b', 'boat', 'truck']] ``` The yielded params of the comparison block is `|path, obj1, obj2|`, in which path is the key (or delimited compound key) to the value being compared. When comparing elements in array, the path is with the format `array[*]`. For example: ```ruby a = {a:'car', b:['boat', 'plane'] } b = {a:'bus', b:['truck', ' plan'] } diff = HashDiff.diff(a, b) do |path, obj1, obj2| case path when 'b[*]' obj1.length == obj2.length end end diff.should == [["~", "a", "car", "bus"], ["~", "b[1]", "plane", " plan"], ["-", "b[0]", "boat"], ["+", "b[0]", "truck"]] ``` 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. #### 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. ```ruby a = {a:'car', b:['boat', 'plane'] } b = {a:'car', b:['plane', 'boat'] } HashDiff.diff(a, b) => [["+", "b[0]", "plane"], ["-", "b[2]", "plane"]] b[:b].sort! HashDiff.diff(a, b) => [] ``` ### Special use cases #### Using HashDiff on JSON API results ```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]] ``` ## License HashDiff is distributed under the MIT-LICENSE. hashdiff-0.2.3/spec/0000755000175000017500000000000012650434317014441 5ustar terceiroterceirohashdiff-0.2.3/spec/hashdiff/0000755000175000017500000000000012650434317016215 5ustar terceiroterceirohashdiff-0.2.3/spec/hashdiff/util_spec.rb0000644000175000017500000000526212650434317020536 0ustar terceiroterceirorequire '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]") 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") 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 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 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 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 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 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 end it "should allow tolerance with numeric values" do 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 end it 'should compare strings exactly by default' do 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 end end end hashdiff-0.2.3/spec/hashdiff/patch_spec.rb0000644000175000017500000000737412650434317020666 0ustar terceiroterceirorequire '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) HashDiff.patch!(a, diff).should == 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 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) HashDiff.patch!(a, diff).should == b a = {"a" => 3} b = {"a" => {"a1" => 1, "a2" => 2}} HashDiff.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) HashDiff.patch!(a, diff).should == b a = {"a" => 1, "b" => [1, 2]} b = {"a" => 1, "b" => []} HashDiff.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) HashDiff.patch!(a, diff).should == b a = {"a" => 1, "b" => [1, 2]} b = {"a" => 1, "b" => nil} HashDiff.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) HashDiff.patch!(a, diff).should == b a = {"a" => 1, "b" => [1, 2]} b = {"a" => 1} HashDiff.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) HashDiff.patch!(a, diff).should == b a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} b = {"a" => 1} HashDiff.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) HashDiff.patch!(a, diff).should == b a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} b = {"a" => 1, "b" => {}} HashDiff.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) HashDiff.patch!(a, diff).should == b a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}} b = {"a" => 1, "b" => nil} HashDiff.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) HashDiff.patch!(a, diff).should == b a = {"a" => 1, "b" => nil} b = {"a" => 1} HashDiff.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}] diff = HashDiff.diff(a, b) HashDiff.patch!(a, diff).should == 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 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}] diff = HashDiff.diff(a, b) HashDiff.patch!(a, diff).should == 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 end end hashdiff-0.2.3/spec/hashdiff/diff_array_spec.rb0000644000175000017500000000301612650434317021662 0ustar terceiroterceirorequire 'spec_helper' describe HashDiff do it "should be able to diff two equal array" do a = [1, 2, 3] b = [1, 2, 3] diff = HashDiff.diff_array(a, b) diff.should == [] end it "should be 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.should == [['-', 2, 3], ['-', 1, 2], ['+', 1, 8], ['+', 2, 7]] end it "should be able to diff two arrays with nothing in common" do a = [1, 2] b = [] diff = HashDiff.diff_array(a, b) diff.should == [['-', 1, 2], ['-', 0, 1]] end it "should be able to diff an empty array with an non-empty array" do a = [] b = [1, 2] diff = HashDiff.diff_array(a, b) diff.should == [['+', 0, 1], ['+', 1, 2]] end it "should be 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.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 a = [1, 3, 4, 7] b = [2, 3, 7, 5] diff = HashDiff.diff_array(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) diff.should == [['+', 0, 1], ['-', 2, 3]] end end hashdiff-0.2.3/spec/hashdiff/diff_spec.rb0000644000175000017500000001766612650434317020504 0ustar terceiroterceirorequire 'spec_helper' describe HashDiff do it "should be able to diff two empty hashes" do diff = HashDiff.diff({}, {}) diff.should == [] end it "should be 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 = HashDiff.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 }) 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) 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"]] 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]}) 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 } }) 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 } }) 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} diff = HashDiff.diff(a, b) diff.should == [['+', 'g', 300]] diff = HashDiff.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}} diff = HashDiff.diff(a, b) diff.should == [['~', 'a', 3, {"a1" => 1, "a2" => 2}]] diff = HashDiff.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" => []} diff = HashDiff.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} diff = HashDiff.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} diff = HashDiff.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} diff = HashDiff.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" => {}} diff = HashDiff.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} diff = HashDiff.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}] diff = HashDiff.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] diff = HashDiff.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] diff = HashDiff.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'} diff = HashDiff.diff(a, b, :numeric_tolerance => 0.01) diff.should == [["~", "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]] 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]} diff = HashDiff.diff(a, b, :numeric_tolerance => 0.01) diff.should == [["~", "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]] 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"]] 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"]] 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]] end end context 'with custom comparison' do 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| case prefix when /a|b|c/ obj1.length == obj2.length end end diff.should == [['~', 'b', 'boat', 'truck']] end it 'should yield added keys' do x = { 'a' => 'car', 'b' => 'boat'} y = { 'a' => 'car' } diff = HashDiff.diff(x, y) do |prefix, obj1, obj2| case prefix when /b/ true end end 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| case prefix when 'a' obj1.length == obj2.length end end diff.should == [['~', 'b', 'boat', 'truck'], ['~', 'c', 'plane', ' plan']] end end end hashdiff-0.2.3/spec/hashdiff/best_diff_spec.rb0000644000175000017500000000371712650434317021511 0ustar terceiroterceirorequire '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}] } diff = HashDiff.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}] } diff = HashDiff.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'}] } diff = HashDiff.best_diff(a, b) do |path, obj1, obj2| case path when /^x\[.\]\..$/ obj1.length == obj2.length if obj1 and obj2 end end 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()"} ] } }} 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.should == [ ['~', 'menu.id', 'file', 'file 2'], ['~', 'menu.popup.menuitem[0].value', 'New', 'New1'], ['+', 'menu.popup.menuitem[1]', {"value" => "Open", "onclick" => "OpenDoc()"}] ] end end hashdiff-0.2.3/spec/hashdiff/lcs_spec.rb0000644000175000017500000000353512650434317020343 0ustar terceiroterceirorequire 'spec_helper' describe HashDiff do it "should be able to find LCS between two equal array" do a = [1, 2, 3] b = [1, 2, 3] lcs = HashDiff.lcs(a, b) lcs.should == [[0, 0], [1, 1], [2, 2]] end it "should be 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.should == [[0, 0], [1, 1], [2, 2]] end it "should strip strings when finding LCS if requested" do a = ['foo', 'bar', 'baz'] b = [' foo', 'bar', 'zab'] lcs = HashDiff.lcs(a, b, :strip => true) lcs.should == [[0, 0], [1, 1]] end it "should be able to find LCS with one common elements" do a = [1, 2, 3] b = [1, 8, 7] lcs = HashDiff.lcs(a, b) lcs.should == [[0, 0]] end it "should be 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.should == [[1, 1], [2, 3]] end it "should be 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.should == [[1, 1], [2, 3]] end it "should be 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.should == [[1, 1], [3, 2]] end it "should be able to find LCS with a similarity value" do a = [ {"value" => "New", "onclick" => "CreateNewDoc()"}, {"value" => "Close", "onclick" => "CloseDoc()"} ] b = [ {"value" => "New1", "onclick" => "CreateNewDoc()"}, {"value" => "Open", "onclick" => "OpenDoc()"}, {"value" => "Close", "onclick" => "CloseDoc()"} ] lcs = HashDiff.lcs(a, b, :similarity => 0.5) lcs.should == [[0, 0], [1, 2]] end end hashdiff-0.2.3/spec/spec_helper.rb0000644000175000017500000000036112650434317017257 0ustar terceiroterceiro$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib') require 'rubygems' require 'rspec' require 'rspec/autorun' require 'hashdiff' RSpec.configure do |config| config.mock_framework = :rspec config.include RSpec::Matchers end hashdiff-0.2.3/.gitignore0000644000175000017500000000056212650434317015502 0ustar terceiroterceiro# See http://help.github.com/ignore-files/ for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile ~/.gitignore_global # Ignore bundler config /.bundle /doc /.yardoc /Gemfile.lock *.swp *.bak *.gem hashdiff-0.2.3/Gemfile0000644000175000017500000000010612650434317014777 0ustar terceiroterceirosource "http://rubygems.org" gemspec group :test do gem 'rake' end