hashdiff 0.3.5 → 0.3.6
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 +4 -4
- data/Gemfile +0 -1
- data/changelog.md +4 -0
- data/lib/hashdiff.rb +1 -0
- data/lib/hashdiff/diff.rb +7 -5
- data/lib/hashdiff/linear_compare_array.rb +155 -0
- data/lib/hashdiff/version.rb +1 -1
- data/spec/hashdiff/diff_array_spec.rb +7 -7
- data/spec/hashdiff/diff_spec.rb +26 -0
- data/spec/hashdiff/linear_compare_array_spec.rb +48 -0
- data/spec/spec_helper.rb +0 -7
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa9fedd172b3d7020ea1078bc10fc290bf77381c
|
4
|
+
data.tar.gz: cf7ad118f84f90035283165ce4bc8c473f642ccc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 970a50d8a7cd540d7bb8bd91736f2030fb48b2bacfe505621fc0e9fda93e5835bab16fcc0e40310c5b62774121bd80b279c52b97a2af024e46b63f8b1bb02114
|
7
|
+
data.tar.gz: 96de7b52391861d461a663ce2a7e12220558a65e6f6a001a68a54763a336bc5795fa5307ed3b41e84026060a5c80681b6914538a60112a305d100636589d2998
|
data/Gemfile
CHANGED
data/changelog.md
CHANGED
data/lib/hashdiff.rb
CHANGED
data/lib/hashdiff/diff.rb
CHANGED
@@ -77,7 +77,8 @@ module HashDiff
|
|
77
77
|
:strict => true,
|
78
78
|
:strip => false,
|
79
79
|
:numeric_tolerance => 0,
|
80
|
-
:array_path => false
|
80
|
+
:array_path => false,
|
81
|
+
:use_lcs => true
|
81
82
|
}.merge!(options)
|
82
83
|
|
83
84
|
opts[:prefix] = [] if opts[:array_path] && opts[:prefix] == ''
|
@@ -105,8 +106,8 @@ module HashDiff
|
|
105
106
|
end
|
106
107
|
|
107
108
|
result = []
|
108
|
-
if obj1.is_a?(Array)
|
109
|
-
changeset =
|
109
|
+
if obj1.is_a?(Array) && opts[:use_lcs]
|
110
|
+
changeset = diff_array_lcs(obj1, obj2, opts) do |lcs|
|
110
111
|
# use a's index for similarity
|
111
112
|
lcs.each do |pair|
|
112
113
|
prefix = prefix_append_array_index(opts[:prefix], pair[0], opts)
|
@@ -122,6 +123,8 @@ module HashDiff
|
|
122
123
|
result << ['+', change_key, change[2]]
|
123
124
|
end
|
124
125
|
end
|
126
|
+
elsif obj1.is_a?(Array) && !opts[:use_lcs]
|
127
|
+
result.concat(LinearCompareArray.call(obj1, obj2, opts))
|
125
128
|
elsif obj1.is_a?(Hash)
|
126
129
|
|
127
130
|
deleted_keys = obj1.keys - obj2.keys
|
@@ -170,7 +173,7 @@ module HashDiff
|
|
170
173
|
# @private
|
171
174
|
#
|
172
175
|
# diff array using LCS algorithm
|
173
|
-
def self.
|
176
|
+
def self.diff_array_lcs(a, b, options = {})
|
174
177
|
opts = {
|
175
178
|
:prefix => '',
|
176
179
|
:similarity => 0.8,
|
@@ -223,5 +226,4 @@ module HashDiff
|
|
223
226
|
|
224
227
|
change_set
|
225
228
|
end
|
226
|
-
|
227
229
|
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module HashDiff
|
2
|
+
# @private
|
3
|
+
#
|
4
|
+
# Used to compare arrays in a linear complexity, which produces longer diffs
|
5
|
+
# than using the lcs algorithm but is considerably faster
|
6
|
+
class LinearCompareArray
|
7
|
+
def self.call(old_array, new_array, options = {})
|
8
|
+
instance = self.new(old_array, new_array, options)
|
9
|
+
instance.call
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
return [] if old_array.empty? && new_array.empty?
|
14
|
+
|
15
|
+
self.old_index = 0
|
16
|
+
self.new_index = 0
|
17
|
+
# by comparing the array lengths we can expect that a number of items
|
18
|
+
# are either added or removed
|
19
|
+
self.expected_additions = new_array.length - old_array.length
|
20
|
+
|
21
|
+
loop do
|
22
|
+
if extra_items_in_old_array?
|
23
|
+
append_deletion(old_array[old_index], old_index)
|
24
|
+
elsif extra_items_in_new_array?
|
25
|
+
append_addition(new_array[new_index], new_index)
|
26
|
+
else
|
27
|
+
compare_at_index
|
28
|
+
end
|
29
|
+
|
30
|
+
self.old_index = old_index + 1
|
31
|
+
self.new_index = new_index + 1
|
32
|
+
break if iterated_through_both_arrays?
|
33
|
+
end
|
34
|
+
|
35
|
+
changes
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :old_array, :new_array, :options, :additions, :deletions, :differences
|
41
|
+
attr_accessor :old_index, :new_index, :expected_additions
|
42
|
+
|
43
|
+
def initialize(old_array, new_array, options)
|
44
|
+
@old_array = old_array
|
45
|
+
@new_array = new_array
|
46
|
+
@options = { prefix: '' }.merge!(options)
|
47
|
+
|
48
|
+
@additions = []
|
49
|
+
@deletions = []
|
50
|
+
@differences = []
|
51
|
+
end
|
52
|
+
|
53
|
+
def extra_items_in_old_array?
|
54
|
+
old_index < old_array.length && new_index >= new_array.length
|
55
|
+
end
|
56
|
+
|
57
|
+
def extra_items_in_new_array?
|
58
|
+
new_index < new_array.length && old_index >= old_array.length
|
59
|
+
end
|
60
|
+
|
61
|
+
def iterated_through_both_arrays?
|
62
|
+
old_index >= old_array.length && new_index >= new_array.length
|
63
|
+
end
|
64
|
+
|
65
|
+
def compare_at_index
|
66
|
+
difference = item_difference(old_array[old_index], new_array[new_index], old_index)
|
67
|
+
return if difference.empty?
|
68
|
+
|
69
|
+
index_after_additions = index_of_match_after_additions
|
70
|
+
append_addititions_before_match(index_after_additions)
|
71
|
+
|
72
|
+
index_after_deletions = index_of_match_after_deletions
|
73
|
+
append_deletions_before_match(index_after_deletions)
|
74
|
+
|
75
|
+
match = index_after_additions || index_after_deletions
|
76
|
+
|
77
|
+
append_differences(difference) unless match
|
78
|
+
end
|
79
|
+
|
80
|
+
def item_difference(old_item, new_item, item_index)
|
81
|
+
prefix = HashDiff.prefix_append_array_index(options[:prefix], item_index, options)
|
82
|
+
HashDiff.diff(old_item, new_item, options.merge(:prefix => prefix))
|
83
|
+
end
|
84
|
+
|
85
|
+
# look ahead in the new array to see if the current item appears later
|
86
|
+
# thereby having new items added
|
87
|
+
def index_of_match_after_additions
|
88
|
+
return unless expected_additions > 0
|
89
|
+
|
90
|
+
(1..expected_additions).each do |i|
|
91
|
+
next_difference = item_difference(
|
92
|
+
old_array[old_index],
|
93
|
+
new_array[new_index + i],
|
94
|
+
old_index
|
95
|
+
)
|
96
|
+
|
97
|
+
return new_index + i if next_difference.empty?
|
98
|
+
end
|
99
|
+
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# look ahead in the old array to see if the current item appears later
|
104
|
+
# thereby having items removed
|
105
|
+
def index_of_match_after_deletions
|
106
|
+
return unless expected_additions < 0
|
107
|
+
|
108
|
+
(1..(expected_additions.abs)).each do |i|
|
109
|
+
next_difference = item_difference(
|
110
|
+
old_array[old_index + i],
|
111
|
+
new_array[new_index],
|
112
|
+
old_index
|
113
|
+
)
|
114
|
+
|
115
|
+
return old_index + i if next_difference.empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def append_addititions_before_match(match_index)
|
122
|
+
return unless match_index
|
123
|
+
(new_index...match_index).each { |i| append_addition(new_array[i], i) }
|
124
|
+
self.expected_additions = expected_additions - (match_index - new_index)
|
125
|
+
self.new_index = match_index
|
126
|
+
end
|
127
|
+
|
128
|
+
def append_deletions_before_match(match_index)
|
129
|
+
return unless match_index
|
130
|
+
(old_index...match_index).each { |i| append_deletion(old_array[i], i) }
|
131
|
+
self.expected_additions = expected_additions + (match_index - new_index)
|
132
|
+
self.old_index = match_index
|
133
|
+
end
|
134
|
+
|
135
|
+
def append_addition(item, index)
|
136
|
+
key = HashDiff.prefix_append_array_index(options[:prefix], index, options)
|
137
|
+
additions << ['+', key, item]
|
138
|
+
end
|
139
|
+
|
140
|
+
def append_deletion(item, index)
|
141
|
+
key = HashDiff.prefix_append_array_index(options[:prefix], index, options)
|
142
|
+
deletions << ['-', key, item]
|
143
|
+
end
|
144
|
+
|
145
|
+
def append_differences(difference)
|
146
|
+
differences.concat(difference)
|
147
|
+
end
|
148
|
+
|
149
|
+
def changes
|
150
|
+
# this algorithm only allows there to be additions or deletions
|
151
|
+
# deletions are reverse so they don't change the index of earlier items
|
152
|
+
differences + additions + deletions.reverse
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/hashdiff/version.rb
CHANGED
@@ -5,7 +5,7 @@ describe HashDiff do
|
|
5
5
|
a = [1, 2, 3]
|
6
6
|
b = [1, 2, 3]
|
7
7
|
|
8
|
-
diff = HashDiff.
|
8
|
+
diff = HashDiff.diff_array_lcs(a, b)
|
9
9
|
diff.should == []
|
10
10
|
end
|
11
11
|
|
@@ -13,7 +13,7 @@ describe HashDiff do
|
|
13
13
|
a = [1, 2, 3]
|
14
14
|
b = [1, 8, 7]
|
15
15
|
|
16
|
-
diff = HashDiff.
|
16
|
+
diff = HashDiff.diff_array_lcs(a, b)
|
17
17
|
diff.should == [['-', 2, 3], ['-', 1, 2], ['+', 1, 8], ['+', 2, 7]]
|
18
18
|
end
|
19
19
|
|
@@ -21,7 +21,7 @@ describe HashDiff do
|
|
21
21
|
a = [1, 2]
|
22
22
|
b = []
|
23
23
|
|
24
|
-
diff = HashDiff.
|
24
|
+
diff = HashDiff.diff_array_lcs(a, b)
|
25
25
|
diff.should == [['-', 1, 2], ['-', 0, 1]]
|
26
26
|
end
|
27
27
|
|
@@ -29,7 +29,7 @@ describe HashDiff do
|
|
29
29
|
a = []
|
30
30
|
b = [1, 2]
|
31
31
|
|
32
|
-
diff = HashDiff.
|
32
|
+
diff = HashDiff.diff_array_lcs(a, b)
|
33
33
|
diff.should == [['+', 0, 1], ['+', 1, 2]]
|
34
34
|
end
|
35
35
|
|
@@ -37,7 +37,7 @@ describe HashDiff do
|
|
37
37
|
a = [1, 3, 5, 7]
|
38
38
|
b = [2, 3, 7, 5]
|
39
39
|
|
40
|
-
diff = HashDiff.
|
40
|
+
diff = HashDiff.diff_array_lcs(a, b)
|
41
41
|
diff.should == [['-', 0, 1], ['+', 0, 2], ['+', 2, 7], ['-', 4, 7]]
|
42
42
|
end
|
43
43
|
|
@@ -45,14 +45,14 @@ describe HashDiff do
|
|
45
45
|
a = [1, 3, 4, 7]
|
46
46
|
b = [2, 3, 7, 5]
|
47
47
|
|
48
|
-
diff = HashDiff.
|
48
|
+
diff = HashDiff.diff_array_lcs(a, b)
|
49
49
|
diff.should == [['-', 0, 1], ['+', 0, 2], ['-', 2, 4], ['+', 3, 5]]
|
50
50
|
end
|
51
51
|
|
52
52
|
it "should be able to diff two arrays with similar elements" do
|
53
53
|
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, 3]
|
54
54
|
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
55
|
-
diff = HashDiff.
|
55
|
+
diff = HashDiff.diff_array_lcs(a, b)
|
56
56
|
diff.should == [['+', 0, 1], ['-', 2, 3]]
|
57
57
|
end
|
58
58
|
|
data/spec/hashdiff/diff_spec.rb
CHANGED
@@ -310,4 +310,30 @@ describe HashDiff do
|
|
310
310
|
diff.should == [['-', [time], 'foo'], ['+', [0], 'bar']]
|
311
311
|
end
|
312
312
|
end
|
313
|
+
|
314
|
+
context 'when :use_lcs is false' do
|
315
|
+
it 'should show items in an array as changed' do
|
316
|
+
x = [:a, :b]
|
317
|
+
y = [:c, :d]
|
318
|
+
diff = HashDiff.diff(x, y, :use_lcs => false)
|
319
|
+
|
320
|
+
diff.should == [['~', '[0]', :a, :c], ['~', '[1]', :b, :d]]
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'should show additions to arrays' do
|
324
|
+
x = { :a => [0] }
|
325
|
+
y = { :a => [0, 1] }
|
326
|
+
diff = HashDiff.diff(x, y, :use_lcs => false)
|
327
|
+
|
328
|
+
diff.should == [['+', 'a[1]', 1]]
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'shows changes to nested arrays' do
|
332
|
+
x = { :a => [[0, 1]] }
|
333
|
+
y = { :a => [[1, 2]] }
|
334
|
+
diff = HashDiff.diff(x, y, :use_lcs => false)
|
335
|
+
|
336
|
+
diff.should == [['~', 'a[0][0]', 0, 1], ['~', 'a[0][1]', 1, 2]]
|
337
|
+
end
|
338
|
+
end
|
313
339
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HashDiff::LinearCompareArray do
|
4
|
+
it "should find no differences between two empty arrays" do
|
5
|
+
difference = described_class.call([], [])
|
6
|
+
difference.should == []
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should find added items when the old array is empty" do
|
10
|
+
difference = described_class.call([], [:a, :b])
|
11
|
+
difference.should == [['+', '[0]', :a], ['+', '[1]', :b]]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should find removed items when the new array is empty" do
|
15
|
+
difference = described_class.call([:a, :b], [])
|
16
|
+
difference.should == [['-', '[1]', :b], ['-', '[0]', :a]]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should find no differences between identical arrays" do
|
20
|
+
difference = described_class.call([:a, :b], [:a, :b])
|
21
|
+
difference.should == []
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should find added items in an array" do
|
25
|
+
difference = described_class.call([:a, :d], [:a, :b, :c, :d])
|
26
|
+
difference.should == [['+', '[1]', :b], ['+', '[2]', :c]]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should find removed items in an array" do
|
30
|
+
difference = described_class.call([:a, :b, :c, :d, :e, :f], [:a, :d, :f])
|
31
|
+
difference.should == [['-', '[4]', :e], ['-', '[2]', :c], ['-', '[1]', :b]]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should show additions and deletions as changed items" do
|
35
|
+
difference = described_class.call([:a, :b, :c], [:c, :b, :a])
|
36
|
+
difference.should == [['~', '[0]', :a, :c], ['~', '[2]', :c, :a]]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should show changed items in a hash" do
|
40
|
+
difference = described_class.call([{ :a => :b }], [{ :a => :c }])
|
41
|
+
difference.should == [['~', '[0].a', :b, :c]]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should show changed items and added items" do
|
45
|
+
difference = described_class.call([{ :a => 1, :b => 2 }], [{ :a => 2, :b => 2 }, :item])
|
46
|
+
difference.should == [['~', '[0].a', 1, 2], ['+', '[1]', :item]]
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hashdiff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Liu Fengyun
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -73,6 +73,7 @@ files:
|
|
73
73
|
- lib/hashdiff.rb
|
74
74
|
- lib/hashdiff/diff.rb
|
75
75
|
- lib/hashdiff/lcs.rb
|
76
|
+
- lib/hashdiff/linear_compare_array.rb
|
76
77
|
- lib/hashdiff/patch.rb
|
77
78
|
- lib/hashdiff/util.rb
|
78
79
|
- lib/hashdiff/version.rb
|
@@ -80,6 +81,7 @@ files:
|
|
80
81
|
- spec/hashdiff/diff_array_spec.rb
|
81
82
|
- spec/hashdiff/diff_spec.rb
|
82
83
|
- spec/hashdiff/lcs_spec.rb
|
84
|
+
- spec/hashdiff/linear_compare_array_spec.rb
|
83
85
|
- spec/hashdiff/patch_spec.rb
|
84
86
|
- spec/hashdiff/util_spec.rb
|
85
87
|
- spec/spec_helper.rb
|