hashdiff 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/hashdiff/util.rb CHANGED
@@ -1,21 +1,22 @@
1
- module HashDiff
1
+ # frozen_string_literal: true
2
2
 
3
+ module Hashdiff
3
4
  # @private
4
5
  #
5
6
  # judge whether two objects are similar
6
- def self.similar?(a, b, options = {})
7
- return compare_values(a, b, options) unless a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
8
- opts = { :similarity => 0.8 }.merge(options)
7
+ def self.similar?(obja, objb, options = {})
8
+ return compare_values(obja, objb, options) if !options[:comparison] && !any_hash_or_array?(obja, objb)
9
9
 
10
- count_a = count_nodes(a)
11
- count_b = count_nodes(b)
12
- diffs = count_diff diff(a, b, opts)
10
+ count_a = count_nodes(obja)
11
+ count_b = count_nodes(objb)
13
12
 
14
- if count_a + count_b == 0
15
- return true
16
- else
17
- (1 - diffs.to_f/(count_a + count_b).to_f) >= opts[:similarity]
18
- end
13
+ return true if (count_a + count_b).zero?
14
+
15
+ opts = { similarity: 0.8 }.merge!(options)
16
+
17
+ diffs = count_diff diff(obja, objb, opts)
18
+
19
+ (1 - diffs.to_f / (count_a + count_b).to_f) >= opts[:similarity]
19
20
  end
20
21
 
21
22
  # @private
@@ -25,7 +26,7 @@ module HashDiff
25
26
  diffs.inject(0) do |sum, item|
26
27
  old_change_count = count_nodes(item[2])
27
28
  new_change_count = count_nodes(item[3])
28
- sum += (old_change_count + new_change_count)
29
+ sum + (old_change_count + new_change_count)
29
30
  end
30
31
  end
31
32
 
@@ -37,9 +38,9 @@ module HashDiff
37
38
 
38
39
  count = 0
39
40
  if obj.is_a?(Array)
40
- obj.each {|e| count += count_nodes(e) }
41
+ obj.each { |e| count += count_nodes(e) }
41
42
  elsif obj.is_a?(Hash)
42
- obj.each {|k, v| count += count_nodes(v) }
43
+ obj.each_value { |v| count += count_nodes(v) }
43
44
  else
44
45
  return 1
45
46
  end
@@ -54,20 +55,18 @@ module HashDiff
54
55
  # @param [String] delimiter Property-string delimiter
55
56
  #
56
57
  # e.g. "a.b[3].c" => ['a', 'b', 3, 'c']
57
- def self.decode_property_path(path, delimiter='.')
58
- parts = path.split(delimiter).collect do |part|
58
+ def self.decode_property_path(path, delimiter = '.')
59
+ path.split(delimiter).inject([]) do |memo, part|
59
60
  if part =~ /^(.*)\[(\d+)\]$/
60
- if $1.size > 0
61
- [$1, $2.to_i]
61
+ if !Regexp.last_match(1).empty?
62
+ memo + [Regexp.last_match(1), Regexp.last_match(2).to_i]
62
63
  else
63
- $2.to_i
64
+ memo + [Regexp.last_match(2).to_i]
64
65
  end
65
66
  else
66
- part
67
+ memo + [part]
67
68
  end
68
69
  end
69
-
70
- parts.flatten
71
70
  end
72
71
 
73
72
  # @private
@@ -85,8 +84,8 @@ module HashDiff
85
84
  #
86
85
  # check for equality or "closeness" within given tolerance
87
86
  def self.compare_values(obj1, obj2, options = {})
88
- if (options[:numeric_tolerance].is_a? Numeric) &&
89
- [obj1, obj2].all? { |v| v.is_a? Numeric }
87
+ if options[:numeric_tolerance].is_a?(Numeric) &&
88
+ obj1.is_a?(Numeric) && obj2.is_a?(Numeric)
90
89
  return (obj1 - obj2).abs <= options[:numeric_tolerance]
91
90
  end
92
91
 
@@ -107,10 +106,9 @@ module HashDiff
107
106
  #
108
107
  # check if objects are comparable
109
108
  def self.comparable?(obj1, obj2, strict = true)
110
- [Array, Hash].each do |type|
111
- return true if obj1.is_a?(type) && obj2.is_a?(type)
112
- end
109
+ return true if (obj1.is_a?(Array) || obj1.is_a?(Hash)) && obj2.is_a?(obj1.class)
113
110
  return true if !strict && obj1.is_a?(Numeric) && obj2.is_a?(Numeric)
111
+
114
112
  obj1.is_a?(obj2.class) && obj2.is_a?(obj1.class)
115
113
  end
116
114
 
@@ -118,15 +116,39 @@ module HashDiff
118
116
  #
119
117
  # try custom comparison
120
118
  def self.custom_compare(method, key, obj1, obj2)
121
- if method
122
- res = method.call(key, obj1, obj2)
123
-
124
- # nil != false here
125
- if res == false
126
- return [['~', key, obj1, obj2]]
127
- elsif res == true
128
- return []
129
- end
119
+ return unless method
120
+
121
+ res = method.call(key, obj1, obj2)
122
+
123
+ # nil != false here
124
+ return [['~', key, obj1, obj2]] if res == false
125
+ return [] if res == true
126
+ end
127
+
128
+ def self.prefix_append_key(prefix, key, opts)
129
+ if opts[:array_path]
130
+ prefix + [key]
131
+ else
132
+ prefix.empty? ? key.to_s : "#{prefix}#{opts[:delimiter]}#{key}"
133
+ end
134
+ end
135
+
136
+ def self.prefix_append_array_index(prefix, array_index, opts)
137
+ if opts[:array_path]
138
+ prefix + [array_index]
139
+ else
140
+ "#{prefix}[#{array_index}]"
141
+ end
142
+ end
143
+
144
+ class << self
145
+ private
146
+
147
+ # @private
148
+ #
149
+ # checks if both objects are Arrays or Hashes
150
+ def any_hash_or_array?(obja, objb)
151
+ obja.is_a?(Array) || obja.is_a?(Hash) || objb.is_a?(Array) || objb.is_a?(Hash)
130
152
  end
131
153
  end
132
154
  end
@@ -1,3 +1,5 @@
1
- module HashDiff
2
- VERSION = '0.3.4'
1
+ # frozen_string_literal: true
2
+
3
+ module Hashdiff
4
+ VERSION = '0.4.0'.freeze
3
5
  end
data/lib/hashdiff.rb CHANGED
@@ -1,5 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'hashdiff/util'
4
+ require 'hashdiff/compare_hashes'
2
5
  require 'hashdiff/lcs'
6
+ require 'hashdiff/lcs_compare_arrays'
7
+ require 'hashdiff/linear_compare_array'
3
8
  require 'hashdiff/diff'
4
9
  require 'hashdiff/patch'
5
10
  require 'hashdiff/version'
11
+
12
+ HashDiff = Hashdiff
13
+
14
+ warn 'The HashDiff constant used by this gem conflicts with another gem of a similar name. As of version 1.0 the HashDiff constant will be completely removed and replaced by Hashdiff. For more information see https://github.com/liufengyun/hashdiff/issues/45.'
@@ -1,65 +1,75 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
- describe HashDiff do
4
- it "should be able to best diff" do
5
- a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]}
6
- b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] }
5
+ describe Hashdiff do
6
+ it 'is able to best diff' do
7
+ a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] }
8
+ b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] }
7
9
 
8
- diff = HashDiff.best_diff(a, b)
9
- diff.should == [["-", "x[0].c", 3], ["+", "x[0].b", 2], ["-", "x[1]", {"y"=>3}]]
10
+ diff = described_class.best_diff(a, b)
11
+ diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1]', { 'y' => 3 }]]
10
12
  end
11
13
 
12
- it "should use custom delimiter when provided" do
13
- a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]}
14
- b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] }
14
+ it 'uses custom delimiter when provided' do
15
+ a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] }
16
+ b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] }
15
17
 
16
- diff = HashDiff.best_diff(a, b, :delimiter => "\t")
17
- diff.should == [["-", "x[0]\tc", 3], ["+", "x[0]\tb", 2], ["-", "x[1]", {"y"=>3}]]
18
+ diff = described_class.best_diff(a, b, delimiter: "\t")
19
+ diff.should == [['-', "x[0]\tc", 3], ['+', "x[0]\tb", 2], ['-', 'x[1]', { 'y' => 3 }]]
18
20
  end
19
21
 
20
- it "should use custom comparison when provided" do
21
- a = {'x' => [{'a' => 'foo', 'c' => 'goat', 'e' => 'snake'}, {'y' => 'baz'}]}
22
- b = {'x' => [{'a' => 'bar', 'b' => 'cow', 'e' => 'puppy'}] }
22
+ it 'uses custom comparison when provided' do
23
+ a = { 'x' => [{ 'a' => 'foo', 'c' => 'goat', 'e' => 'snake' }, { 'y' => 'baz' }] }
24
+ b = { 'x' => [{ 'a' => 'bar', 'b' => 'cow', 'e' => 'puppy' }] }
23
25
 
24
- diff = HashDiff.best_diff(a, b) do |path, obj1, obj2|
26
+ diff = described_class.best_diff(a, b) do |path, obj1, obj2|
25
27
  case path
26
28
  when /^x\[.\]\..$/
27
- obj1.length == obj2.length if obj1 and obj2
29
+ obj1.length == obj2.length if obj1 && obj2
28
30
  end
29
31
  end
30
32
 
31
- diff.should == [["-", "x[0].c", 'goat'], ["+", "x[0].b", 'cow'], ["-", "x[1]", {"y"=>'baz'}]]
33
+ diff.should == [['-', 'x[0].c', 'goat'], ['+', 'x[0].b', 'cow'], ['-', 'x[1]', { 'y' => 'baz' }]]
32
34
  end
33
35
 
34
- it "should be able to best diff array in hash" do
35
- a = {"menu" => {
36
- "id" => "file",
37
- "value" => "File",
38
- "popup" => {
39
- "menuitem" => [
40
- {"value" => "New", "onclick" => "CreateNewDoc()"},
41
- {"value" => "Close", "onclick" => "CloseDoc()"}
36
+ it 'is able to best diff array in hash' do
37
+ a = { 'menu' => {
38
+ 'id' => 'file',
39
+ 'value' => 'File',
40
+ 'popup' => {
41
+ 'menuitem' => [
42
+ { 'value' => 'New', 'onclick' => 'CreateNewDoc()' },
43
+ { 'value' => 'Close', 'onclick' => 'CloseDoc()' }
42
44
  ]
43
45
  }
44
- }}
46
+ } }
45
47
 
46
- b = {"menu" => {
47
- "id" => "file 2",
48
- "value" => "File",
49
- "popup" => {
50
- "menuitem" => [
51
- {"value" => "New1", "onclick" => "CreateNewDoc()"},
52
- {"value" => "Open", "onclick" => "OpenDoc()"},
53
- {"value" => "Close", "onclick" => "CloseDoc()"}
48
+ b = { 'menu' => {
49
+ 'id' => 'file 2',
50
+ 'value' => 'File',
51
+ 'popup' => {
52
+ 'menuitem' => [
53
+ { 'value' => 'New1', 'onclick' => 'CreateNewDoc()' },
54
+ { 'value' => 'Open', 'onclick' => 'OpenDoc()' },
55
+ { 'value' => 'Close', 'onclick' => 'CloseDoc()' }
54
56
  ]
55
57
  }
56
- }}
58
+ } }
57
59
 
58
- diff = HashDiff.best_diff(a, b)
60
+ diff = described_class.best_diff(a, b)
59
61
  diff.should == [
60
62
  ['~', 'menu.id', 'file', 'file 2'],
61
63
  ['~', 'menu.popup.menuitem[0].value', 'New', 'New1'],
62
- ['+', 'menu.popup.menuitem[1]', {"value" => "Open", "onclick" => "OpenDoc()"}]
64
+ ['+', 'menu.popup.menuitem[1]', { 'value' => 'Open', 'onclick' => 'OpenDoc()' }]
63
65
  ]
64
66
  end
67
+
68
+ it 'is able to have an array_path specified' do
69
+ a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] }
70
+ b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] }
71
+
72
+ diff = described_class.best_diff(a, b, array_path: true)
73
+ diff.should == [['-', ['x', 0, 'c'], 3], ['+', ['x', 0, 'b'], 2], ['-', ['x', 1], { 'y' => 3 }]]
74
+ end
65
75
  end
@@ -1,60 +1,60 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
- describe HashDiff do
4
- it "should be able to diff two equal array" do
5
+ describe Hashdiff do
6
+ it 'is able to diff two equal array' do
5
7
  a = [1, 2, 3]
6
8
  b = [1, 2, 3]
7
9
 
8
- diff = HashDiff.diff_array(a, b)
10
+ diff = described_class.diff_array_lcs(a, b)
9
11
  diff.should == []
10
12
  end
11
13
 
12
- it "should be able to diff two arrays with one element in common" do
14
+ it 'is able to diff two arrays with one element in common' do
13
15
  a = [1, 2, 3]
14
16
  b = [1, 8, 7]
15
17
 
16
- diff = HashDiff.diff_array(a, b)
18
+ diff = described_class.diff_array_lcs(a, b)
17
19
  diff.should == [['-', 2, 3], ['-', 1, 2], ['+', 1, 8], ['+', 2, 7]]
18
20
  end
19
21
 
20
- it "should be able to diff two arrays with nothing in common" do
22
+ it 'is able to diff two arrays with nothing in common' do
21
23
  a = [1, 2]
22
24
  b = []
23
25
 
24
- diff = HashDiff.diff_array(a, b)
26
+ diff = described_class.diff_array_lcs(a, b)
25
27
  diff.should == [['-', 1, 2], ['-', 0, 1]]
26
28
  end
27
29
 
28
- it "should be able to diff an empty array with an non-empty array" do
30
+ it 'is able to diff an empty array with an non-empty array' do
29
31
  a = []
30
32
  b = [1, 2]
31
33
 
32
- diff = HashDiff.diff_array(a, b)
34
+ diff = described_class.diff_array_lcs(a, b)
33
35
  diff.should == [['+', 0, 1], ['+', 1, 2]]
34
36
  end
35
37
 
36
- it "should be able to diff two arrays with two elements in common" do
38
+ it 'is able to diff two arrays with two elements in common' do
37
39
  a = [1, 3, 5, 7]
38
40
  b = [2, 3, 7, 5]
39
41
 
40
- diff = HashDiff.diff_array(a, b)
42
+ diff = described_class.diff_array_lcs(a, b)
41
43
  diff.should == [['-', 0, 1], ['+', 0, 2], ['+', 2, 7], ['-', 4, 7]]
42
44
  end
43
45
 
44
- it "should be able to test two arrays with two common elements in different order" do
46
+ it 'is able to test two arrays with two common elements in different order' do
45
47
  a = [1, 3, 4, 7]
46
48
  b = [2, 3, 7, 5]
47
49
 
48
- diff = HashDiff.diff_array(a, b)
50
+ diff = described_class.diff_array_lcs(a, b)
49
51
  diff.should == [['-', 0, 1], ['+', 0, 2], ['-', 2, 4], ['+', 3, 5]]
50
52
  end
51
53
 
52
- it "should be able to diff two arrays with similar elements" do
53
- a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
54
- b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
55
- diff = HashDiff.diff_array(a, b)
54
+ it 'is able to diff two arrays with similar elements' do
55
+ a = [{ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5 }, 3]
56
+ b = [1, { 'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5 }]
57
+ diff = described_class.diff_array_lcs(a, b)
56
58
  diff.should == [['+', 0, 1], ['-', 2, 3]]
57
59
  end
58
-
59
60
  end
60
-