hashdiff_sym 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/LICENSE +19 -0
- data/README.md +22 -0
- data/Rakefile +13 -0
- data/changelog.md +48 -0
- data/hashdiff_sym.gemspec +25 -0
- data/lib/hashdiff_sym/diff.rb +220 -0
- data/lib/hashdiff_sym/lcs.rb +69 -0
- data/lib/hashdiff_sym/patch.rb +84 -0
- data/lib/hashdiff_sym/util.rb +147 -0
- data/lib/hashdiff_sym/version.rb +3 -0
- data/lib/hashdiff_sym.rb +5 -0
- data/spec/hashdiff_sym/best_diff_spec.rb +65 -0
- data/spec/hashdiff_sym/diff_array_spec.rb +60 -0
- data/spec/hashdiff_sym/diff_spec.rb +263 -0
- data/spec/hashdiff_sym/lcs_spec.rb +75 -0
- data/spec/hashdiff_sym/patch_spec.rb +154 -0
- data/spec/hashdiff_sym/util_spec.rb +78 -0
- data/spec/spec_helper.rb +13 -0
- metadata +112 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
module HashDiffSym
|
2
|
+
|
3
|
+
# @private
|
4
|
+
#
|
5
|
+
# judge whether two objects are similar
|
6
|
+
def self.similar?(a, b, options = {})
|
7
|
+
opts = { :similarity => 0.8 }.merge(options)
|
8
|
+
|
9
|
+
count_a = count_nodes(a)
|
10
|
+
count_b = count_nodes(b)
|
11
|
+
diffs = count_diff diff(a, b, opts)
|
12
|
+
|
13
|
+
if count_a + count_b == 0
|
14
|
+
return true
|
15
|
+
else
|
16
|
+
(1 - diffs.to_f/(count_a + count_b).to_f) >= opts[:similarity]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @private
|
21
|
+
#
|
22
|
+
# count node differences
|
23
|
+
def self.count_diff(diffs)
|
24
|
+
diffs.inject(0) do |sum, item|
|
25
|
+
old_change_count = count_nodes(item[2])
|
26
|
+
new_change_count = count_nodes(item[3])
|
27
|
+
sum += (old_change_count + new_change_count)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @private
|
32
|
+
#
|
33
|
+
# count total nodes for an object
|
34
|
+
def self.count_nodes(obj)
|
35
|
+
return 0 unless obj
|
36
|
+
|
37
|
+
count = 0
|
38
|
+
if obj.is_a?(Array)
|
39
|
+
obj.each {|e| count += count_nodes(e) }
|
40
|
+
elsif obj.is_a?(Hash)
|
41
|
+
obj.each {|k, v| count += count_nodes(v) }
|
42
|
+
else
|
43
|
+
return 1
|
44
|
+
end
|
45
|
+
|
46
|
+
count
|
47
|
+
end
|
48
|
+
|
49
|
+
# @private
|
50
|
+
#
|
51
|
+
# decode property path into an array
|
52
|
+
# @param [String] path Property-string
|
53
|
+
# @param [String] delimiter Property-string delimiter
|
54
|
+
#
|
55
|
+
# e.g. "a.b[3].c" => ['a', 'b', 3, 'c']
|
56
|
+
def self.decode_property_path(path, delimiter='.')
|
57
|
+
parts = path.split(delimiter).collect do |part|
|
58
|
+
if part =~ /^(.*)\[(\d+)\]$/
|
59
|
+
if $1.size > 0
|
60
|
+
[import_key($1), $2.to_i]
|
61
|
+
else
|
62
|
+
$2.to_i
|
63
|
+
end
|
64
|
+
else
|
65
|
+
import_key(part)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
parts.flatten
|
70
|
+
end
|
71
|
+
|
72
|
+
# @private
|
73
|
+
#
|
74
|
+
# get the node of hash by given path parts
|
75
|
+
def self.node(hash, parts)
|
76
|
+
temp = hash
|
77
|
+
parts.each do |part|
|
78
|
+
temp = temp[part]
|
79
|
+
end
|
80
|
+
temp
|
81
|
+
end
|
82
|
+
|
83
|
+
# @private
|
84
|
+
#
|
85
|
+
# check for equality or "closeness" within given tolerance
|
86
|
+
def self.compare_values(obj1, obj2, options = {})
|
87
|
+
if (options[:numeric_tolerance].is_a? Numeric) &&
|
88
|
+
[obj1, obj2].all? { |v| v.is_a? Numeric }
|
89
|
+
return (obj1 - obj2).abs <= options[:numeric_tolerance]
|
90
|
+
end
|
91
|
+
|
92
|
+
if options[:strip] == true
|
93
|
+
obj1 = obj1.strip if obj1.respond_to?(:strip)
|
94
|
+
obj2 = obj2.strip if obj2.respond_to?(:strip)
|
95
|
+
end
|
96
|
+
|
97
|
+
if options[:case_insensitive] == true
|
98
|
+
obj1 = obj1.downcase if obj1.respond_to?(:downcase)
|
99
|
+
obj2 = obj2.downcase if obj2.respond_to?(:downcase)
|
100
|
+
end
|
101
|
+
|
102
|
+
obj1 == obj2
|
103
|
+
end
|
104
|
+
|
105
|
+
# @private
|
106
|
+
#
|
107
|
+
# check if objects are comparable
|
108
|
+
def self.comparable?(obj1, obj2, strict = true)
|
109
|
+
[Array, Hash].each do |type|
|
110
|
+
return true if obj1.is_a?(type) && obj2.is_a?(type)
|
111
|
+
end
|
112
|
+
return true if !strict && obj1.is_a?(Numeric) && obj2.is_a?(Numeric)
|
113
|
+
obj1.is_a?(obj2.class) && obj2.is_a?(obj1.class)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @private
|
117
|
+
#
|
118
|
+
# try custom comparison
|
119
|
+
def self.custom_compare(method, key, obj1, obj2)
|
120
|
+
if method
|
121
|
+
res = method.call(key, obj1, obj2)
|
122
|
+
|
123
|
+
# nil != false here
|
124
|
+
if res == false
|
125
|
+
return [['~', key, obj1, obj2]]
|
126
|
+
elsif res == true
|
127
|
+
return []
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.export_key(key)
|
133
|
+
if key.class == Symbol
|
134
|
+
":#{key}"
|
135
|
+
else
|
136
|
+
key
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.import_key(key)
|
141
|
+
if key[0] == ":"
|
142
|
+
key[1..-1].to_sym
|
143
|
+
else
|
144
|
+
key
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/hashdiff_sym.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiffSym 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}] }
|
7
|
+
|
8
|
+
diff = HashDiffSym.best_diff(a, b)
|
9
|
+
diff.should == [["-", "x[0].c", 3], ["+", "x[0].b", 2], ["-", "x[1]", {"y"=>3}]]
|
10
|
+
end
|
11
|
+
|
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}] }
|
15
|
+
|
16
|
+
diff = HashDiffSym.best_diff(a, b, :delimiter => "\t")
|
17
|
+
diff.should == [["-", "x[0]\tc", 3], ["+", "x[0]\tb", 2], ["-", "x[1]", {"y"=>3}]]
|
18
|
+
end
|
19
|
+
|
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'}] }
|
23
|
+
|
24
|
+
diff = HashDiffSym.best_diff(a, b) do |path, obj1, obj2|
|
25
|
+
case path
|
26
|
+
when /^x\[.\]\..$/
|
27
|
+
obj1.length == obj2.length if obj1 and obj2
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
diff.should == [["-", "x[0].c", 'goat'], ["+", "x[0].b", 'cow'], ["-", "x[1]", {"y"=>'baz'}]]
|
32
|
+
end
|
33
|
+
|
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()"}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
}}
|
45
|
+
|
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()"}
|
54
|
+
]
|
55
|
+
}
|
56
|
+
}}
|
57
|
+
|
58
|
+
diff = HashDiffSym.best_diff(a, b)
|
59
|
+
diff.should == [
|
60
|
+
['~', 'menu.id', 'file', 'file 2'],
|
61
|
+
['~', 'menu.popup.menuitem[0].value', 'New', 'New1'],
|
62
|
+
['+', 'menu.popup.menuitem[1]', {"value" => "Open", "onclick" => "OpenDoc()"}]
|
63
|
+
]
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiffSym do
|
4
|
+
it "should be able to diff two equal array" do
|
5
|
+
a = [1, 2, 3]
|
6
|
+
b = [1, 2, 3]
|
7
|
+
|
8
|
+
diff = HashDiffSym.diff_array(a, b)
|
9
|
+
diff.should == []
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to diff two arrays with one element in common" do
|
13
|
+
a = [1, 2, 3]
|
14
|
+
b = [1, 8, 7]
|
15
|
+
|
16
|
+
diff = HashDiffSym.diff_array(a, b)
|
17
|
+
diff.should == [['-', 2, 3], ['-', 1, 2], ['+', 1, 8], ['+', 2, 7]]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to diff two arrays with nothing in common" do
|
21
|
+
a = [1, 2]
|
22
|
+
b = []
|
23
|
+
|
24
|
+
diff = HashDiffSym.diff_array(a, b)
|
25
|
+
diff.should == [['-', 1, 2], ['-', 0, 1]]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be able to diff an empty array with an non-empty array" do
|
29
|
+
a = []
|
30
|
+
b = [1, 2]
|
31
|
+
|
32
|
+
diff = HashDiffSym.diff_array(a, b)
|
33
|
+
diff.should == [['+', 0, 1], ['+', 1, 2]]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be able to diff two arrays with two elements in common" do
|
37
|
+
a = [1, 3, 5, 7]
|
38
|
+
b = [2, 3, 7, 5]
|
39
|
+
|
40
|
+
diff = HashDiffSym.diff_array(a, b)
|
41
|
+
diff.should == [['-', 0, 1], ['+', 0, 2], ['+', 2, 7], ['-', 4, 7]]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should be able to test two arrays with two common elements in different order" do
|
45
|
+
a = [1, 3, 4, 7]
|
46
|
+
b = [2, 3, 7, 5]
|
47
|
+
|
48
|
+
diff = HashDiffSym.diff_array(a, b)
|
49
|
+
diff.should == [['-', 0, 1], ['+', 0, 2], ['-', 2, 4], ['+', 3, 5]]
|
50
|
+
end
|
51
|
+
|
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 = HashDiffSym.diff_array(a, b)
|
56
|
+
diff.should == [['+', 0, 1], ['-', 2, 3]]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiffSym do
|
4
|
+
it "should be able to diff two empty hashes" do
|
5
|
+
diff = HashDiffSym.diff({}, {})
|
6
|
+
diff.should == []
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be able to diff an hash with an empty hash" do
|
10
|
+
a = { 'a' => 3, 'b' => 2 }
|
11
|
+
b = {}
|
12
|
+
|
13
|
+
diff = HashDiffSym.diff(a, b)
|
14
|
+
diff.should == [['-', 'a', 3], ['-', 'b', 2]]
|
15
|
+
|
16
|
+
diff = HashDiffSym.diff(b, a)
|
17
|
+
diff.should == [['+', 'a', 3], ['+', 'b', 2]]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to diff two equal hashes" do
|
21
|
+
diff = HashDiffSym.diff({ 'a' => 2, 'b' => 2}, { 'a' => 2, 'b' => 2 })
|
22
|
+
diff.should == []
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be able to diff two hashes with equivalent numerics, when strict is false" do
|
26
|
+
diff = HashDiffSym.diff({ 'a' => 2.0, 'b' => 2 }, { 'a' => 2, 'b' => 2.0 }, :strict => false)
|
27
|
+
diff.should == []
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should be able to diff changes in hash value" do
|
31
|
+
diff = HashDiffSym.diff({ 'a' => 2, 'b' => 3, 'c' => " hello" }, { 'a' => 2, 'b' => 4, 'c' => "hello" })
|
32
|
+
diff.should == [['~', 'b', 3, 4], ['~', 'c', " hello", "hello"]]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to diff changes in hash value which is array" do
|
36
|
+
diff = HashDiffSym.diff({ 'a' => 2, 'b' => [1, 2, 3] }, { 'a' => 2, 'b' => [1, 3, 4]})
|
37
|
+
diff.should == [['-', 'b[1]', 2], ['+', 'b[2]', 4]]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be able to diff changes in hash value which is hash" do
|
41
|
+
diff = HashDiffSym.diff({ 'a' => { 'x' => 2, 'y' => 3, 'z' => 4 }, 'b' => { 'x' => 3, 'z' => 45 } },
|
42
|
+
{ 'a' => { 'y' => 3 }, 'b' => { 'y' => 3, 'z' => 30 } })
|
43
|
+
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be able to diff similar objects in array" do
|
47
|
+
diff = HashDiffSym.best_diff({ 'a' => [{ 'x' => 2, 'y' => 3, 'z' => 4 }, { 'x' => 11, 'y' => 22, 'z' => 33 }], 'b' => { 'x' => 3, 'z' => 45 } },
|
48
|
+
{ 'a' => [{ 'y' => 3 }, { 'x' => 11, 'z' => 33 }], 'b' => { 'y' => 22 } })
|
49
|
+
diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should be able to diff addition of key value pair' do
|
53
|
+
a = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200}
|
54
|
+
b = {"a"=>3, "c"=>11, "d"=>45, "e"=>100, "f"=>200, "g"=>300}
|
55
|
+
|
56
|
+
diff = HashDiffSym.diff(a, b)
|
57
|
+
diff.should == [['+', 'g', 300]]
|
58
|
+
|
59
|
+
diff = HashDiffSym.diff(b, a)
|
60
|
+
diff.should == [['-', 'g', 300]]
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should be able to diff value type changes' do
|
64
|
+
a = {"a" => 3}
|
65
|
+
b = {"a" => {"a1" => 1, "a2" => 2}}
|
66
|
+
|
67
|
+
diff = HashDiffSym.diff(a, b)
|
68
|
+
diff.should == [['~', 'a', 3, {"a1" => 1, "a2" => 2}]]
|
69
|
+
|
70
|
+
diff = HashDiffSym.diff(b, a)
|
71
|
+
diff.should == [['~', 'a', {"a1" => 1, "a2" => 2}, 3]]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should be able to diff value changes: array <=> []" do
|
75
|
+
a = {"a" => 1, "b" => [1, 2]}
|
76
|
+
b = {"a" => 1, "b" => []}
|
77
|
+
|
78
|
+
diff = HashDiffSym.diff(a, b)
|
79
|
+
diff.should == [['-', 'b[1]', 2], ['-', 'b[0]', 1]]
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should be able to diff value changes: array <=> nil" do
|
83
|
+
a = {"a" => 1, "b" => [1, 2]}
|
84
|
+
b = {"a" => 1, "b" => nil}
|
85
|
+
|
86
|
+
diff = HashDiffSym.diff(a, b)
|
87
|
+
diff.should == [["~", "b", [1, 2], nil]]
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should be able to diff value chagnes: remove array completely" do
|
91
|
+
a = {"a" => 1, "b" => [1, 2]}
|
92
|
+
b = {"a" => 1}
|
93
|
+
|
94
|
+
diff = HashDiffSym.diff(a, b)
|
95
|
+
diff.should == [["-", "b", [1, 2]]]
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should be able to diff value changes: remove whole hash" do
|
99
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
100
|
+
b = {"a" => 1}
|
101
|
+
|
102
|
+
diff = HashDiffSym.diff(a, b)
|
103
|
+
diff.should == [["-", "b", {"b1"=>1, "b2"=>2}]]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should be able to diff value changes: hash <=> {}" do
|
107
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
108
|
+
b = {"a" => 1, "b" => {}}
|
109
|
+
|
110
|
+
diff = HashDiffSym.diff(a, b)
|
111
|
+
diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should be able to diff value changes: hash <=> nil" do
|
115
|
+
a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
116
|
+
b = {"a" => 1, "b" => nil}
|
117
|
+
|
118
|
+
diff = HashDiffSym.diff(a, b)
|
119
|
+
diff.should == [["~", "b", {"b1"=>1, "b2"=>2}, nil]]
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should be able to diff similar objects in array" do
|
123
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
|
124
|
+
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
125
|
+
|
126
|
+
diff = HashDiffSym.diff(a, b)
|
127
|
+
diff.should == [['-', '[0].d', 4], ['+', '[0]', 1], ['-', '[2]', 3]]
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should be able to diff similar & equal objects in array" do
|
131
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 3]
|
132
|
+
b = [{'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}, 3]
|
133
|
+
|
134
|
+
diff = HashDiffSym.diff(a, b)
|
135
|
+
diff.should == [["-", "[0].d", 4], ["-", "[1]", {"x"=>5, "y"=>6, "z"=>3}]]
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should use custom delimiter when provided" do
|
139
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 3]
|
140
|
+
b = [{'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}, 3]
|
141
|
+
|
142
|
+
diff = HashDiffSym.diff(a, b, :similarity => 0.8, :delimiter => "\t")
|
143
|
+
diff.should == [["-", "[0]\td", 4], ["-", "[1]", {"x"=>5, "y"=>6, "z"=>3}]]
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should transform symbols to string correctly" do
|
147
|
+
diff = HashDiffSym.diff({ 'a' => { x: 2, y: 3, z: 4 }, 'b' => { x: 3, z: [1, 2, 3] } },
|
148
|
+
{ 'a' => { y: 3 }, 'b' => { y: 3, z: [2, 3, 4] } })
|
149
|
+
diff.should == [['-', 'a.:x', 2], ['-', 'a.:z', 4], ['-', 'b.:x', 3], ["-", "b.:z[0]", 1], ["+", "b.:z[2]", 4], ['+', 'b.:y', 3]]
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'when :numeric_tolerance requested' do
|
153
|
+
it "should be able to diff changes in hash value" do
|
154
|
+
a = {'a' => 0.558, 'b' => 0.0, 'c' => 0.65, 'd' => 'fin'}
|
155
|
+
b = {'a' => 0.557, 'b' => 'hats', 'c' => 0.67, 'd' => 'fin'}
|
156
|
+
|
157
|
+
diff = HashDiffSym.diff(a, b, :numeric_tolerance => 0.01)
|
158
|
+
diff.should == [["~", "b", 0.0, 'hats'], ["~", "c", 0.65, 0.67]]
|
159
|
+
|
160
|
+
diff = HashDiffSym.diff(b, a, :numeric_tolerance => 0.01)
|
161
|
+
diff.should == [["~", "b", 'hats', 0.0], ["~", "c", 0.67, 0.65]]
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should be able to diff changes in nested values" do
|
165
|
+
a = {'a' => {'x' => 0.4, 'y' => 0.338}, 'b' => [13, 68.03]}
|
166
|
+
b = {'a' => {'x' => 0.6, 'y' => 0.341}, 'b' => [14, 68.025]}
|
167
|
+
|
168
|
+
diff = HashDiffSym.diff(a, b, :numeric_tolerance => 0.01)
|
169
|
+
diff.should == [["~", "a.x", 0.4, 0.6], ["-", "b[0]", 13], ["+", "b[0]", 14]]
|
170
|
+
|
171
|
+
diff = HashDiffSym.diff(b, a, :numeric_tolerance => 0.01)
|
172
|
+
diff.should == [["~", "a.x", 0.6, 0.4], ["-", "b[0]", 14], ["+", "b[0]", 13]]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'when :strip requested' do
|
177
|
+
it "should strip strings before comparing" do
|
178
|
+
a = { 'a' => " foo", 'b' => "fizz buzz"}
|
179
|
+
b = { 'a' => "foo", 'b' => "fizzbuzz"}
|
180
|
+
diff = HashDiffSym.diff(a, b, :strip => true)
|
181
|
+
diff.should == [['~', 'b', "fizz buzz", "fizzbuzz"]]
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should strip nested strings before comparing" do
|
185
|
+
a = { 'a' => { 'x' => " foo" }, 'b' => ["fizz buzz", "nerf"] }
|
186
|
+
b = { 'a' => { 'x' => "foo" }, 'b' => ["fizzbuzz", "nerf"] }
|
187
|
+
diff = HashDiffSym.diff(a, b, :strip => true)
|
188
|
+
diff.should == [['-', 'b[0]', "fizz buzz"], ['+', 'b[0]', "fizzbuzz"]]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'when :case_insensitive requested' do
|
193
|
+
it "should strip strings before comparing" do
|
194
|
+
a = { 'a' => "Foo", 'b' => "fizz buzz"}
|
195
|
+
b = { 'a' => "foo", 'b' => "fizzBuzz"}
|
196
|
+
diff = HashDiffSym.diff(a, b, :case_insensitive => true)
|
197
|
+
diff.should == [['~', 'b', "fizz buzz", "fizzBuzz"]]
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should ignore case on nested strings before comparing" do
|
201
|
+
a = { 'a' => { 'x' => "Foo" }, 'b' => ["fizz buzz", "nerf"] }
|
202
|
+
b = { 'a' => { 'x' => "foo" }, 'b' => ["fizzbuzz", "nerf"] }
|
203
|
+
diff = HashDiffSym.diff(a, b, :case_insensitive => true)
|
204
|
+
diff.should == [['-', 'b[0]', "fizz buzz"], ['+', 'b[0]', "fizzbuzz"]]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'when both :strip and :numeric_tolerance requested' do
|
209
|
+
it 'should apply filters to proper object types' do
|
210
|
+
a = { 'a' => " foo", 'b' => 35, 'c' => 'bar', 'd' => 'baz' }
|
211
|
+
b = { 'a' => "foo", 'b' => 35.005, 'c' => 'bar', 'd' => 18.5}
|
212
|
+
diff = HashDiffSym.diff(a, b, :strict => false, :numeric_tolerance => 0.01, :strip => true)
|
213
|
+
diff.should == [['~', 'd', "baz", 18.5]]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context "when both :strip and :case_insensitive requested" do
|
218
|
+
it "should apply both filters to strings" do
|
219
|
+
a = { 'a' => " Foo", 'b' => "fizz buzz"}
|
220
|
+
b = { 'a' => "foo", 'b' => "fizzBuzz"}
|
221
|
+
diff = HashDiffSym.diff(a, b, :case_insensitive => true, :strip => true)
|
222
|
+
diff.should == [['~', 'b', "fizz buzz", "fizzBuzz"]]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'with custom comparison' do
|
227
|
+
let(:a) { { 'a' => 'car', 'b' => 'boat', 'c' => 'plane'} }
|
228
|
+
let(:b) { { 'a' => 'bus', 'b' => 'truck', 'c' => ' plan'} }
|
229
|
+
|
230
|
+
it 'should compare using proc specified in block' do
|
231
|
+
diff = HashDiffSym.diff(a, b) do |prefix, obj1, obj2|
|
232
|
+
case prefix
|
233
|
+
when /a|b|c/
|
234
|
+
obj1.length == obj2.length
|
235
|
+
end
|
236
|
+
end
|
237
|
+
diff.should == [['~', 'b', 'boat', 'truck']]
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should yield added keys' do
|
241
|
+
x = { 'a' => 'car', 'b' => 'boat'}
|
242
|
+
y = { 'a' => 'car' }
|
243
|
+
|
244
|
+
diff = HashDiffSym.diff(x, y) do |prefix, obj1, obj2|
|
245
|
+
case prefix
|
246
|
+
when /b/
|
247
|
+
true
|
248
|
+
end
|
249
|
+
end
|
250
|
+
diff.should == []
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'should compare with both proc and :strip when both provided' do
|
254
|
+
diff = HashDiffSym.diff(a, b, :strip => true) do |prefix, obj1, obj2|
|
255
|
+
case prefix
|
256
|
+
when 'a'
|
257
|
+
obj1.length == obj2.length
|
258
|
+
end
|
259
|
+
end
|
260
|
+
diff.should == [['~', 'b', 'boat', 'truck'], ['~', 'c', 'plane', ' plan']]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiffSym do
|
4
|
+
it "should be able to find LCS between two equal array" do
|
5
|
+
a = [1, 2, 3]
|
6
|
+
b = [1, 2, 3]
|
7
|
+
|
8
|
+
lcs = HashDiffSym.lcs(a, b)
|
9
|
+
lcs.should == [[0, 0], [1, 1], [2, 2]]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to find LCS between two close arrays" do
|
13
|
+
a = [1.05, 2, 3.25]
|
14
|
+
b = [1.06, 2, 3.24]
|
15
|
+
|
16
|
+
lcs = HashDiffSym.lcs(a, b, :numeric_tolerance => 0.1)
|
17
|
+
lcs.should == [[0, 0], [1, 1], [2, 2]]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should strip strings when finding LCS if requested" do
|
21
|
+
a = ['foo', 'bar', 'baz']
|
22
|
+
b = [' foo', 'bar', 'zab']
|
23
|
+
|
24
|
+
lcs = HashDiffSym.lcs(a, b, :strip => true)
|
25
|
+
lcs.should == [[0, 0], [1, 1]]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be able to find LCS with one common elements" do
|
29
|
+
a = [1, 2, 3]
|
30
|
+
b = [1, 8, 7]
|
31
|
+
|
32
|
+
lcs = HashDiffSym.lcs(a, b)
|
33
|
+
lcs.should == [[0, 0]]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be able to find LCS with two common elements" do
|
37
|
+
a = [1, 3, 5, 7]
|
38
|
+
b = [2, 3, 7, 5]
|
39
|
+
|
40
|
+
lcs = HashDiffSym.lcs(a, b)
|
41
|
+
lcs.should == [[1, 1], [2, 3]]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should be able to find LCS with two close elements" do
|
45
|
+
a = [1, 3.05, 5, 7]
|
46
|
+
b = [2, 3.06, 7, 5]
|
47
|
+
|
48
|
+
lcs = HashDiffSym.lcs(a, b, :numeric_tolerance => 0.1)
|
49
|
+
lcs.should == [[1, 1], [2, 3]]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should be able to find LCS with two common elements in different ordering" do
|
53
|
+
a = [1, 3, 4, 7]
|
54
|
+
b = [2, 3, 7, 5]
|
55
|
+
|
56
|
+
lcs = HashDiffSym.lcs(a, b)
|
57
|
+
lcs.should == [[1, 1], [3, 2]]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be able to find LCS with a similarity value" do
|
61
|
+
a = [
|
62
|
+
{"value" => "New", "onclick" => "CreateNewDoc()"},
|
63
|
+
{"value" => "Close", "onclick" => "CloseDoc()"}
|
64
|
+
]
|
65
|
+
b = [
|
66
|
+
{"value" => "New1", "onclick" => "CreateNewDoc()"},
|
67
|
+
{"value" => "Open", "onclick" => "OpenDoc()"},
|
68
|
+
{"value" => "Close", "onclick" => "CloseDoc()"}
|
69
|
+
]
|
70
|
+
|
71
|
+
lcs = HashDiffSym.lcs(a, b, :similarity => 0.5)
|
72
|
+
lcs.should == [[0, 0], [1, 2]]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|