hashdiff 0.3.9 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +31 -26
- data/changelog.md +8 -2
- data/hashdiff.gemspec +14 -3
- data/lib/hashdiff.rb +12 -6
- data/lib/hashdiff/compare_hashes.rb +56 -0
- data/lib/hashdiff/diff.rb +10 -69
- data/lib/hashdiff/lcs.rb +1 -1
- data/lib/hashdiff/lcs_compare_arrays.rb +32 -0
- data/lib/hashdiff/linear_compare_array.rb +5 -5
- data/lib/hashdiff/patch.rb +1 -1
- data/lib/hashdiff/util.rb +13 -2
- data/lib/hashdiff/version.rb +2 -2
- data/spec/{hash_diff → hashdiff}/best_diff_spec.rb +1 -1
- data/spec/{hash_diff → hashdiff}/diff_array_spec.rb +1 -1
- data/spec/{hash_diff → hashdiff}/diff_spec.rb +15 -1
- data/spec/{hash_diff → hashdiff}/lcs_spec.rb +1 -1
- data/spec/{hash_diff → hashdiff}/linear_compare_array_spec.rb +1 -1
- data/spec/{hash_diff → hashdiff}/patch_spec.rb +1 -1
- data/spec/{hash_diff → hashdiff}/util_spec.rb +3 -3
- metadata +22 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 204c04301ec7d5b755863a0ec7b9001930a569c1
|
4
|
+
data.tar.gz: c0bf2c27e34271b637da82792549104a5b0ca2cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68f07faf7b58132dae314317cf90e1dae5549218d93c19be33df0460659d2e74a861f458c9f6401909ce78ca1b0cb649cc843e0a804a4dad70e3c824568bfd7c
|
7
|
+
data.tar.gz: ba2ff10babe7886c263cbac1c761a38e205880dc323199746ed6845762b462a08b7664f9f1159b5765751b020dfd38ac365fcec04e350d894f97df6b519f3c71
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
#
|
1
|
+
# Hashdiff [![Build Status](https://secure.travis-ci.org/liufengyun/hashdiff.svg)](http://travis-ci.org/liufengyun/hashdiff) [![Gem Version](https://badge.fury.io/rb/hashdiff.svg)](http://badge.fury.io/rb/hashdiff)
|
2
2
|
|
3
|
-
|
3
|
+
Hashdiff is a ruby library to compute the smallest difference between two hashes.
|
4
4
|
|
5
5
|
It also supports comparing two arrays.
|
6
6
|
|
7
|
-
|
7
|
+
Hashdiff does not monkey-patch any existing class. All features are contained inside the `Hashdiff` module.
|
8
8
|
|
9
9
|
**Docs**: [Documentation](http://rubydoc.info/gems/hashdiff)
|
10
10
|
|
11
11
|
|
12
12
|
__WARNING__: Don't use the library for comparing large arrays, say ~10K (see #49).
|
13
13
|
|
14
|
-
## Why
|
14
|
+
## Why Hashdiff?
|
15
15
|
|
16
16
|
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?
|
17
17
|
|
@@ -21,7 +21,7 @@ An algorithm that responds to this question has to do following:
|
|
21
21
|
* Compute recursively -- Arrays and Hashes may be nested arbitrarily in A or B.
|
22
22
|
* Compute the smallest change -- it should recognize similar child Hashes or child Arrays between A and B.
|
23
23
|
|
24
|
-
|
24
|
+
Hashdiff answers the question above using an opinionated approach:
|
25
25
|
|
26
26
|
* 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]`.
|
27
27
|
* The change set can be represented using the dot-syntax representation. For example, `[['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]`.
|
@@ -46,7 +46,7 @@ Two simple hashes:
|
|
46
46
|
a = {a:3, b:2}
|
47
47
|
b = {}
|
48
48
|
|
49
|
-
diff =
|
49
|
+
diff = Hashdiff.diff(a, b)
|
50
50
|
diff.should == [['-', 'a', 3], ['-', 'b', 2]]
|
51
51
|
```
|
52
52
|
|
@@ -56,7 +56,7 @@ More complex hashes:
|
|
56
56
|
a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
|
57
57
|
b = {a:{y:3}, b:{y:3, z:30}}
|
58
58
|
|
59
|
-
diff =
|
59
|
+
diff = Hashdiff.diff(a, b)
|
60
60
|
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]
|
61
61
|
```
|
62
62
|
|
@@ -66,7 +66,7 @@ Arrays in hashes:
|
|
66
66
|
a = {a:[{x:2, y:3, z:4}, {x:11, y:22, z:33}], b:{x:3, z:45}}
|
67
67
|
b = {a:[{y:3}, {x:11, z:33}], b:{y:22}}
|
68
68
|
|
69
|
-
diff =
|
69
|
+
diff = Hashdiff.best_diff(a, b)
|
70
70
|
diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]
|
71
71
|
```
|
72
72
|
|
@@ -78,8 +78,8 @@ patch example:
|
|
78
78
|
a = {'a' => 3}
|
79
79
|
b = {'a' => {'a1' => 1, 'a2' => 2}}
|
80
80
|
|
81
|
-
diff =
|
82
|
-
|
81
|
+
diff = Hashdiff.diff(a, b)
|
82
|
+
Hashdiff.patch!(a, diff).should == b
|
83
83
|
```
|
84
84
|
|
85
85
|
unpatch example:
|
@@ -88,8 +88,8 @@ unpatch example:
|
|
88
88
|
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
|
89
89
|
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
90
90
|
|
91
|
-
diff =
|
92
|
-
|
91
|
+
diff = Hashdiff.diff(a, b) # diff two array is OK
|
92
|
+
Hashdiff.unpatch!(b, diff).should == a
|
93
93
|
```
|
94
94
|
|
95
95
|
### Options
|
@@ -106,7 +106,7 @@ You can specify `:delimiter` to be something other than the default dot. For exa
|
|
106
106
|
a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
|
107
107
|
b = {a:{y:3}, b:{y:3, z:30}}
|
108
108
|
|
109
|
-
diff =
|
109
|
+
diff = Hashdiff.diff(a, b, :delimiter => '\t')
|
110
110
|
diff.should == [['-', 'a\tx', 2], ['-', 'a\tz', 4], ['-', 'b\tx', 3], ['~', 'b\tz', 45, 30], ['+', 'b\ty', 3]]
|
111
111
|
```
|
112
112
|
|
@@ -126,7 +126,7 @@ The :numeric_tolerance option allows for a small numeric tolerance.
|
|
126
126
|
a = {x:5, y:3.75, z:7}
|
127
127
|
b = {x:6, y:3.76, z:7}
|
128
128
|
|
129
|
-
diff =
|
129
|
+
diff = Hashdiff.diff(a, b, :numeric_tolerance => 0.1)
|
130
130
|
diff.should == [["~", "x", 5, 6]]
|
131
131
|
```
|
132
132
|
|
@@ -138,7 +138,7 @@ The :strip option strips all strings before comparing.
|
|
138
138
|
a = {x:5, s:'foo '}
|
139
139
|
b = {x:6, s:'foo'}
|
140
140
|
|
141
|
-
diff =
|
141
|
+
diff = Hashdiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :strip => true })
|
142
142
|
diff.should == [["~", "x", 5, 6]]
|
143
143
|
```
|
144
144
|
|
@@ -150,7 +150,7 @@ The :case_insensitive option makes string comparisons ignore case.
|
|
150
150
|
a = {x:5, s:'FooBar'}
|
151
151
|
b = {x:6, s:'foobar'}
|
152
152
|
|
153
|
-
diff =
|
153
|
+
diff = Hashdiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :case_insensitive => true })
|
154
154
|
diff.should == [["~", "x", 5, 6]]
|
155
155
|
```
|
156
156
|
|
@@ -164,7 +164,7 @@ is useful for `patch!` when used on hashes without string keys.
|
|
164
164
|
a = {x:5}
|
165
165
|
b = {'x'=>6}
|
166
166
|
|
167
|
-
diff =
|
167
|
+
diff = Hashdiff.diff(a, b, :array_path => true)
|
168
168
|
diff.should == [['-', [:x], 5], ['+', ['x'], 6]]
|
169
169
|
```
|
170
170
|
|
@@ -173,7 +173,7 @@ For cases where there are arrays in paths their index will be added to the path.
|
|
173
173
|
a = {x:[0,1]}
|
174
174
|
b = {x:[0,2]}
|
175
175
|
|
176
|
-
diff =
|
176
|
+
diff = Hashdiff.diff(a, b, :array_path => true)
|
177
177
|
diff.should == [["-", [:x, 1], 1], ["+", [:x, 1], 2]]
|
178
178
|
```
|
179
179
|
|
@@ -183,8 +183,8 @@ This shouldn't cause problems if you are comparing an array with a hash:
|
|
183
183
|
a = {x:{0=>1}}
|
184
184
|
b = {x:[1]}
|
185
185
|
|
186
|
-
diff =
|
187
|
-
diff.should == [["~", [:
|
186
|
+
diff = Hashdiff.diff(a, b, :array_path => true)
|
187
|
+
diff.should == [["~", [:x], {0=>1}, [1]]]
|
188
188
|
```
|
189
189
|
|
190
190
|
#### `:use_lcs`
|
@@ -205,7 +205,7 @@ Note, currently the :similarity option has no effect when :use_lcs is false.
|
|
205
205
|
a = {x: [0, 1, 2]}
|
206
206
|
b = {x: [0, 2, 2, 3]}
|
207
207
|
|
208
|
-
diff =
|
208
|
+
diff = Hashdiff.diff(a, b, :use_lcs => false)
|
209
209
|
diff.should == [["~", "x[1]", 1, 2], ["+", "x[3]", 3]]
|
210
210
|
```
|
211
211
|
|
@@ -217,7 +217,7 @@ It's possible to specify how the values of a key should be compared.
|
|
217
217
|
a = {a:'car', b:'boat', c:'plane'}
|
218
218
|
b = {a:'bus', b:'truck', c:' plan'}
|
219
219
|
|
220
|
-
diff =
|
220
|
+
diff = Hashdiff.diff(a, b) do |path, obj1, obj2|
|
221
221
|
case path
|
222
222
|
when /a|b|c/
|
223
223
|
obj1.length == obj2.length
|
@@ -233,7 +233,7 @@ The yielded params of the comparison block is `|path, obj1, obj2|`, in which pat
|
|
233
233
|
a = {a:'car', b:['boat', 'plane'] }
|
234
234
|
b = {a:'bus', b:['truck', ' plan'] }
|
235
235
|
|
236
|
-
diff =
|
236
|
+
diff = Hashdiff.diff(a, b) do |path, obj1, obj2|
|
237
237
|
case path
|
238
238
|
when 'b[*]'
|
239
239
|
obj1.length == obj2.length
|
@@ -255,13 +255,18 @@ An order difference alone between two arrays can create too many diffs to be use
|
|
255
255
|
a = {a:'car', b:['boat', 'plane'] }
|
256
256
|
b = {a:'car', b:['plane', 'boat'] }
|
257
257
|
|
258
|
-
|
258
|
+
Hashdiff.diff(a, b) => [["+", "b[0]", "plane"], ["-", "b[2]", "plane"]]
|
259
259
|
|
260
260
|
b[:b].sort!
|
261
261
|
|
262
|
-
|
262
|
+
Hashdiff.diff(a, b) => []
|
263
263
|
```
|
264
264
|
|
265
|
+
## Maintainers
|
266
|
+
|
267
|
+
- Krzysztof Rybka ([@krzysiek1507](https://github.com/krzysiek1507))
|
268
|
+
- Fengyun Liu ([@liufengyun](https://github.com/liufengyun))
|
269
|
+
|
265
270
|
## License
|
266
271
|
|
267
|
-
|
272
|
+
Hashdiff is distributed under the MIT-LICENSE.
|
data/changelog.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## v0.4.0 2019-05-28
|
4
|
+
|
5
|
+
* refactoring (#56 #57 #59 #61 krzysiek1507)
|
6
|
+
* fix typo in README (#64 @pboling)
|
7
|
+
* change HashDiff to Hashdiff (#65 @jfelchner)
|
8
|
+
|
3
9
|
## v0.3.9 2019-04-22
|
4
10
|
|
5
11
|
* Performance tweak (thanks @krzysiek1507: #51 #52 #53)
|
@@ -22,7 +28,7 @@
|
|
22
28
|
|
23
29
|
## v0.3.4 2017-05-01
|
24
30
|
|
25
|
-
* performance improvement of
|
31
|
+
* performance improvement of `#similar?` #31
|
26
32
|
|
27
33
|
## v0.3.2 2016-12-27
|
28
34
|
|
@@ -64,7 +70,7 @@
|
|
64
70
|
## v0.0.5 2012-7-1
|
65
71
|
|
66
72
|
* fix a bug in judging whehter two objects are similiar.
|
67
|
-
* add more spec test for
|
73
|
+
* add more spec test for `.best_diff`
|
68
74
|
|
69
75
|
## v0.0.4 2012-6-24
|
70
76
|
|
data/hashdiff.gemspec
CHANGED
@@ -5,16 +5,17 @@ require 'hashdiff/version'
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = 'hashdiff'
|
8
|
-
s.version =
|
8
|
+
s.version = Hashdiff::VERSION
|
9
9
|
s.license = 'MIT'
|
10
|
-
s.summary = '
|
11
|
-
s.description = '
|
10
|
+
s.summary = ' Hashdiff is a diff lib to compute the smallest difference between two hashes. '
|
11
|
+
s.description = ' Hashdiff is a diff lib to compute the smallest difference between two hashes. '
|
12
12
|
|
13
13
|
s.files = `git ls-files`.split("\n")
|
14
14
|
s.test_files = `git ls-files -- Appraisals {spec}/*`.split("\n")
|
15
15
|
|
16
16
|
s.require_paths = ['lib']
|
17
17
|
s.required_ruby_version = Gem::Requirement.new('>= 2.0.0')
|
18
|
+
s.post_install_message = '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.'
|
18
19
|
|
19
20
|
s.authors = ['Liu Fengyun']
|
20
21
|
s.email = ['liufengyunchina@gmail.com']
|
@@ -26,4 +27,14 @@ Gem::Specification.new do |s|
|
|
26
27
|
s.add_development_dependency('rubocop')
|
27
28
|
s.add_development_dependency('rubocop-rspec')
|
28
29
|
s.add_development_dependency('yard')
|
30
|
+
|
31
|
+
if s.respond_to?(:metadata)
|
32
|
+
s.metadata = {
|
33
|
+
'bug_tracker_uri' => 'https://github.com/liufengyun/hashdiff/issues',
|
34
|
+
'changelog_uri' => 'https://github.com/liufengyun/hashdiff/blob/master/changelog.md',
|
35
|
+
'documentation_uri' => 'https://www.rubydoc.info/gems/hashdiff',
|
36
|
+
'homepage_uri' => 'https://github.com/liufengyun/hashdiff',
|
37
|
+
'source_code_uri' => 'https://github.com/liufengyun/hashdiff'
|
38
|
+
}
|
39
|
+
end
|
29
40
|
end
|
data/lib/hashdiff.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
require 'hashdiff/util'
|
4
|
+
require 'hashdiff/compare_hashes'
|
5
|
+
require 'hashdiff/lcs'
|
6
|
+
require 'hashdiff/lcs_compare_arrays'
|
7
|
+
require 'hashdiff/linear_compare_array'
|
8
|
+
require 'hashdiff/diff'
|
9
|
+
require 'hashdiff/patch'
|
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.'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hashdiff
|
4
|
+
# @private
|
5
|
+
# Used to compare hashes
|
6
|
+
class CompareHashes
|
7
|
+
class << self
|
8
|
+
def call(obj1, obj2, opts = {})
|
9
|
+
return [] if obj1.empty? && obj2.empty?
|
10
|
+
|
11
|
+
obj1_keys = obj1.keys
|
12
|
+
obj2_keys = obj2.keys
|
13
|
+
|
14
|
+
added_keys = (obj2_keys - obj1_keys).sort_by(&:to_s)
|
15
|
+
common_keys = (obj1_keys & obj2_keys).sort_by(&:to_s)
|
16
|
+
deleted_keys = (obj1_keys - obj2_keys).sort_by(&:to_s)
|
17
|
+
|
18
|
+
result = []
|
19
|
+
|
20
|
+
# add deleted properties
|
21
|
+
deleted_keys.each do |k|
|
22
|
+
change_key = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
|
23
|
+
custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, obj1[k], nil)
|
24
|
+
|
25
|
+
if custom_result
|
26
|
+
result.concat(custom_result)
|
27
|
+
else
|
28
|
+
result << ['-', change_key, obj1[k]]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# recursive comparison for common keys
|
33
|
+
common_keys.each do |k|
|
34
|
+
prefix = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
|
35
|
+
|
36
|
+
result.concat(Hashdiff.diff(obj1[k], obj2[k], opts.merge(prefix: prefix)))
|
37
|
+
end
|
38
|
+
|
39
|
+
# added properties
|
40
|
+
added_keys.each do |k|
|
41
|
+
change_key = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
|
42
|
+
|
43
|
+
custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, nil, obj2[k])
|
44
|
+
|
45
|
+
if custom_result
|
46
|
+
result.concat(custom_result)
|
47
|
+
else
|
48
|
+
result << ['+', change_key, obj2[k]]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/hashdiff/diff.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
3
|
+
module Hashdiff
|
4
4
|
# Best diff two objects, which tries to generate the smallest change set using different similarity values.
|
5
5
|
#
|
6
|
-
#
|
6
|
+
# Hashdiff.best_diff is useful in case of comparing two objects which include similar hashes in arrays.
|
7
7
|
#
|
8
8
|
# @param [Array, Hash] obj1
|
9
9
|
# @param [Array, Hash] obj2
|
@@ -23,7 +23,7 @@ module HashDiff
|
|
23
23
|
# @example
|
24
24
|
# a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]}
|
25
25
|
# b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] }
|
26
|
-
# diff =
|
26
|
+
# diff = Hashdiff.best_diff(a, b)
|
27
27
|
# diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1].y', 3], ['-', 'x[1]', {}]]
|
28
28
|
#
|
29
29
|
# @since 0.0.1
|
@@ -69,7 +69,7 @@ module HashDiff
|
|
69
69
|
# a = {"a" => 1, "b" => {"b1" => 1, "b2" =>2}}
|
70
70
|
# b = {"a" => 1, "b" => {}}
|
71
71
|
#
|
72
|
-
# diff =
|
72
|
+
# diff = Hashdiff.diff(a, b)
|
73
73
|
# diff.should == [['-', 'b.b1', 1], ['-', 'b.b2', 2]]
|
74
74
|
#
|
75
75
|
# @since 0.0.1
|
@@ -95,78 +95,19 @@ module HashDiff
|
|
95
95
|
|
96
96
|
return [] if obj1.nil? && obj2.nil?
|
97
97
|
|
98
|
-
return [['~', opts[:prefix],
|
99
|
-
|
100
|
-
return [['~', opts[:prefix], obj1, nil]] if obj2.nil?
|
98
|
+
return [['~', opts[:prefix], obj1, obj2]] if obj1.nil? || obj2.nil?
|
101
99
|
|
102
100
|
return [['~', opts[:prefix], obj1, obj2]] unless comparable?(obj1, obj2, opts[:strict])
|
103
101
|
|
104
|
-
|
105
|
-
if obj1.is_a?(Array) && opts[:use_lcs]
|
106
|
-
changeset = diff_array_lcs(obj1, obj2, opts) do |lcs|
|
107
|
-
# use a's index for similarity
|
108
|
-
lcs.each do |pair|
|
109
|
-
prefix = prefix_append_array_index(opts[:prefix], pair[0], opts)
|
110
|
-
result.concat(diff(obj1[pair[0]], obj2[pair[1]], opts.merge(prefix: prefix)))
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
changeset.each do |change|
|
115
|
-
change_key = prefix_append_array_index(opts[:prefix], change[1], opts)
|
116
|
-
if change[0] == '-'
|
117
|
-
result << ['-', change_key, change[2]]
|
118
|
-
elsif change[0] == '+'
|
119
|
-
result << ['+', change_key, change[2]]
|
120
|
-
end
|
121
|
-
end
|
122
|
-
elsif obj1.is_a?(Array) && !opts[:use_lcs]
|
123
|
-
result.concat(LinearCompareArray.call(obj1, obj2, opts))
|
124
|
-
elsif obj1.is_a?(Hash)
|
125
|
-
obj1_keys = obj1.keys
|
126
|
-
obj2_keys = obj2.keys
|
127
|
-
|
128
|
-
deleted_keys = (obj1_keys - obj2_keys).sort_by(&:to_s)
|
129
|
-
common_keys = (obj1_keys & obj2_keys).sort_by(&:to_s)
|
130
|
-
added_keys = (obj2_keys - obj1_keys).sort_by(&:to_s)
|
131
|
-
|
132
|
-
# add deleted properties
|
133
|
-
deleted_keys.each do |k|
|
134
|
-
change_key = prefix_append_key(opts[:prefix], k, opts)
|
135
|
-
custom_result = custom_compare(opts[:comparison], change_key, obj1[k], nil)
|
136
|
-
|
137
|
-
if custom_result
|
138
|
-
result.concat(custom_result)
|
139
|
-
else
|
140
|
-
result << ['-', change_key, obj1[k]]
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# recursive comparison for common keys
|
145
|
-
common_keys.each do |k|
|
146
|
-
prefix = prefix_append_key(opts[:prefix], k, opts)
|
147
|
-
result.concat(diff(obj1[k], obj2[k], opts.merge(prefix: prefix)))
|
148
|
-
end
|
102
|
+
return LcsCompareArrays.call(obj1, obj2, opts) if obj1.is_a?(Array) && opts[:use_lcs]
|
149
103
|
|
150
|
-
|
151
|
-
added_keys.each do |k|
|
152
|
-
change_key = prefix_append_key(opts[:prefix], k, opts)
|
153
|
-
next if obj1.key?(k)
|
104
|
+
return LinearCompareArray.call(obj1, obj2, opts) if obj1.is_a?(Array) && !opts[:use_lcs]
|
154
105
|
|
155
|
-
|
106
|
+
return CompareHashes.call(obj1, obj2, opts) if obj1.is_a?(Hash)
|
156
107
|
|
157
|
-
|
158
|
-
result.concat(custom_result)
|
159
|
-
else
|
160
|
-
result << ['+', change_key, obj2[k]]
|
161
|
-
end
|
162
|
-
end
|
163
|
-
else
|
164
|
-
return [] if compare_values(obj1, obj2, opts)
|
165
|
-
|
166
|
-
return [['~', opts[:prefix], obj1, obj2]]
|
167
|
-
end
|
108
|
+
return [] if compare_values(obj1, obj2, opts)
|
168
109
|
|
169
|
-
|
110
|
+
[['~', opts[:prefix], obj1, obj2]]
|
170
111
|
end
|
171
112
|
|
172
113
|
# @private
|
data/lib/hashdiff/lcs.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hashdiff
|
4
|
+
# @private
|
5
|
+
# Used to compare arrays using the lcs algorithm
|
6
|
+
class LcsCompareArrays
|
7
|
+
class << self
|
8
|
+
def call(obj1, obj2, opts = {})
|
9
|
+
result = []
|
10
|
+
|
11
|
+
changeset = Hashdiff.diff_array_lcs(obj1, obj2, opts) do |lcs|
|
12
|
+
# use a's index for similarity
|
13
|
+
lcs.each do |pair|
|
14
|
+
prefix = Hashdiff.prefix_append_array_index(opts[:prefix], pair[0], opts)
|
15
|
+
|
16
|
+
result.concat(Hashdiff.diff(obj1[pair[0]], obj2[pair[1]], opts.merge(prefix: prefix)))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
changeset.each do |change|
|
21
|
+
next if change[0] != '-' && change[0] != '+'
|
22
|
+
|
23
|
+
change_key = Hashdiff.prefix_append_array_index(opts[:prefix], change[1], opts)
|
24
|
+
|
25
|
+
result << [change[0], change_key, change[2]]
|
26
|
+
end
|
27
|
+
|
28
|
+
result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
3
|
+
module Hashdiff
|
4
4
|
# @private
|
5
5
|
#
|
6
6
|
# Used to compare arrays in a linear complexity, which produces longer diffs
|
@@ -80,8 +80,8 @@ module HashDiff
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def item_difference(old_item, new_item, item_index)
|
83
|
-
prefix =
|
84
|
-
|
83
|
+
prefix = Hashdiff.prefix_append_array_index(options[:prefix], item_index, options)
|
84
|
+
Hashdiff.diff(old_item, new_item, options.merge(prefix: prefix))
|
85
85
|
end
|
86
86
|
|
87
87
|
# look ahead in the new array to see if the current item appears later
|
@@ -137,12 +137,12 @@ module HashDiff
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def append_addition(item, index)
|
140
|
-
key =
|
140
|
+
key = Hashdiff.prefix_append_array_index(options[:prefix], index, options)
|
141
141
|
additions << ['+', key, item]
|
142
142
|
end
|
143
143
|
|
144
144
|
def append_deletion(item, index)
|
145
|
-
key =
|
145
|
+
key = Hashdiff.prefix_append_array_index(options[:prefix], index, options)
|
146
146
|
deletions << ['-', key, item]
|
147
147
|
end
|
148
148
|
|
data/lib/hashdiff/patch.rb
CHANGED
data/lib/hashdiff/util.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
3
|
+
module Hashdiff
|
4
4
|
# @private
|
5
5
|
#
|
6
6
|
# judge whether two objects are similar
|
7
7
|
def self.similar?(obja, objb, options = {})
|
8
|
-
return compare_values(obja, objb, options)
|
8
|
+
return compare_values(obja, objb, options) if !options[:comparison] && !any_hash_or_array?(obja, objb)
|
9
9
|
|
10
10
|
count_a = count_nodes(obja)
|
11
11
|
count_b = count_nodes(objb)
|
@@ -140,4 +140,15 @@ module HashDiff
|
|
140
140
|
"#{prefix}[#{array_index}]"
|
141
141
|
end
|
142
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)
|
152
|
+
end
|
153
|
+
end
|
143
154
|
end
|
data/lib/hashdiff/version.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe
|
5
|
+
describe Hashdiff do
|
6
6
|
it 'is able to diff two empty hashes' do
|
7
7
|
diff = described_class.diff({}, {})
|
8
8
|
diff.should == []
|
@@ -275,6 +275,20 @@ describe HashDiff do
|
|
275
275
|
end
|
276
276
|
diff.should == [['~', 'b', 'boat', 'truck'], ['~', 'c', 'plane', ' plan']]
|
277
277
|
end
|
278
|
+
|
279
|
+
it 'compares nested arrays using proc specified in block' do
|
280
|
+
a = { a: 'car', b: %w[boat plane] }
|
281
|
+
b = { a: 'bus', b: ['truck', ' plan'] }
|
282
|
+
|
283
|
+
diff = described_class.diff(a, b) do |path, obj1, obj2|
|
284
|
+
case path
|
285
|
+
when 'b[*]'
|
286
|
+
obj1.length == obj2.length
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
expect(diff).to eq [['~', 'a', 'car', 'bus'], ['~', 'b[1]', 'plane', ' plan'], ['-', 'b[0]', 'boat'], ['+', 'b[0]', 'truck']]
|
291
|
+
end
|
278
292
|
end
|
279
293
|
|
280
294
|
context 'when :array_path is true' do
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe
|
5
|
+
describe Hashdiff do
|
6
6
|
it 'is able to decode property path' do
|
7
7
|
decoded = described_class.send(:decode_property_path, 'a.b[0].c.city[5]')
|
8
8
|
decoded.should == ['a', 'b', 0, 'c', 'city', 5]
|
@@ -21,11 +21,11 @@ describe HashDiff do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'is able to tell similiar empty hash' do
|
24
|
-
described_class.similar?({}, {}, 1).should be true
|
24
|
+
described_class.similar?({}, {}, similarity: 1).should be true
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'is able to tell similiar empty array' do
|
28
|
-
described_class.similar?([], [], 1).should be true
|
28
|
+
described_class.similar?([], [], similarity: 1).should be true
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'is able to tell similiar hash with values within tolerance' do
|
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.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Liu Fengyun
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-05-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bluecloth
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
description: "
|
83
|
+
description: " Hashdiff is a diff lib to compute the smallest difference between two
|
84
84
|
hashes. "
|
85
85
|
email:
|
86
86
|
- liufengyunchina@gmail.com
|
@@ -100,25 +100,34 @@ files:
|
|
100
100
|
- changelog.md
|
101
101
|
- hashdiff.gemspec
|
102
102
|
- lib/hashdiff.rb
|
103
|
+
- lib/hashdiff/compare_hashes.rb
|
103
104
|
- lib/hashdiff/diff.rb
|
104
105
|
- lib/hashdiff/lcs.rb
|
106
|
+
- lib/hashdiff/lcs_compare_arrays.rb
|
105
107
|
- lib/hashdiff/linear_compare_array.rb
|
106
108
|
- lib/hashdiff/patch.rb
|
107
109
|
- lib/hashdiff/util.rb
|
108
110
|
- lib/hashdiff/version.rb
|
109
|
-
- spec/
|
110
|
-
- spec/
|
111
|
-
- spec/
|
112
|
-
- spec/
|
113
|
-
- spec/
|
114
|
-
- spec/
|
115
|
-
- spec/
|
111
|
+
- spec/hashdiff/best_diff_spec.rb
|
112
|
+
- spec/hashdiff/diff_array_spec.rb
|
113
|
+
- spec/hashdiff/diff_spec.rb
|
114
|
+
- spec/hashdiff/lcs_spec.rb
|
115
|
+
- spec/hashdiff/linear_compare_array_spec.rb
|
116
|
+
- spec/hashdiff/patch_spec.rb
|
117
|
+
- spec/hashdiff/util_spec.rb
|
116
118
|
- spec/spec_helper.rb
|
117
119
|
homepage: https://github.com/liufengyun/hashdiff
|
118
120
|
licenses:
|
119
121
|
- MIT
|
120
|
-
metadata:
|
121
|
-
|
122
|
+
metadata:
|
123
|
+
bug_tracker_uri: https://github.com/liufengyun/hashdiff/issues
|
124
|
+
changelog_uri: https://github.com/liufengyun/hashdiff/blob/master/changelog.md
|
125
|
+
documentation_uri: https://www.rubydoc.info/gems/hashdiff
|
126
|
+
homepage_uri: https://github.com/liufengyun/hashdiff
|
127
|
+
source_code_uri: https://github.com/liufengyun/hashdiff
|
128
|
+
post_install_message: The HashDiff constant used by this gem conflicts with another
|
129
|
+
gem of a similar name. As of version 1.0 the HashDiff constant will be completely
|
130
|
+
removed and replaced by Hashdiff. For more information see https://github.com/liufengyun/hashdiff/issues/45.
|
122
131
|
rdoc_options: []
|
123
132
|
require_paths:
|
124
133
|
- lib
|
@@ -137,5 +146,5 @@ rubyforge_project:
|
|
137
146
|
rubygems_version: 2.5.2.3
|
138
147
|
signing_key:
|
139
148
|
specification_version: 4
|
140
|
-
summary:
|
149
|
+
summary: Hashdiff is a diff lib to compute the smallest difference between two hashes.
|
141
150
|
test_files: []
|