hashdiff 0.3.9 → 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.
- 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 [](http://travis-ci.org/liufengyun/hashdiff) [](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: []
|