hashdiff 0.3.4 → 0.3.5
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 +1 -0
- data/README.md +43 -7
- data/changelog.md +4 -0
- data/lib/hashdiff/diff.rb +21 -14
- data/lib/hashdiff/lcs.rb +1 -1
- data/lib/hashdiff/patch.rb +11 -7
- data/lib/hashdiff/util.rb +20 -6
- data/lib/hashdiff/version.rb +1 -1
- data/spec/hashdiff/best_diff_spec.rb +9 -0
- data/spec/hashdiff/diff_spec.rb +36 -0
- data/spec/hashdiff/patch_spec.rb +22 -0
- data/spec/spec_helper.rb +7 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ba86448df8842fe6323572feb2c89110954b775
|
|
4
|
+
data.tar.gz: e3d610f32c07e65e06f825e107371caaaf2e5896
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e7b28d277160a603df99c83022f6b8bd7bb27ac5d467e351e5834ba955195ed493249b7a7e213dafe0cb1aa630c5de46fe11a95b5942de4ba9c1f4e66262ec3
|
|
7
|
+
data.tar.gz: d5290cbc0b5c97038cce213e627fc20fcaf4e7405888c217c78b949c8fc98990ab6d3ae18b0f91d2909c069ffccc20a9d0d45aa572419252aa6eae960df6491d
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -72,8 +72,8 @@ diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-
|
|
|
72
72
|
patch example:
|
|
73
73
|
|
|
74
74
|
```ruby
|
|
75
|
-
a = {a
|
|
76
|
-
b = {a
|
|
75
|
+
a = {'a' => 3}
|
|
76
|
+
b = {'a' => {'a1' => 1, 'a2' => 2}}
|
|
77
77
|
|
|
78
78
|
diff = HashDiff.diff(a, b)
|
|
79
79
|
HashDiff.patch!(a, diff).should == b
|
|
@@ -82,8 +82,8 @@ HashDiff.patch!(a, diff).should == b
|
|
|
82
82
|
unpatch example:
|
|
83
83
|
|
|
84
84
|
```ruby
|
|
85
|
-
a = [{a
|
|
86
|
-
b = [1, {a
|
|
85
|
+
a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
|
|
86
|
+
b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
|
|
87
87
|
|
|
88
88
|
diff = HashDiff.diff(a, b) # diff two array is OK
|
|
89
89
|
HashDiff.unpatch!(b, diff).should == a
|
|
@@ -91,8 +91,9 @@ HashDiff.unpatch!(b, diff).should == a
|
|
|
91
91
|
|
|
92
92
|
### Options
|
|
93
93
|
|
|
94
|
-
There are
|
|
95
|
-
`:strict`, `:numeric_tolerance`, `:strip
|
|
94
|
+
There are seven options available: `:delimiter`, `:similarity`,
|
|
95
|
+
`:strict`, `:numeric_tolerance`, `:strip`, `:case_insensitive`
|
|
96
|
+
and `:array_path`.
|
|
96
97
|
|
|
97
98
|
#### `:delimiter`
|
|
98
99
|
|
|
@@ -140,7 +141,7 @@ diff.should == [["~", "x", 5, 6]]
|
|
|
140
141
|
|
|
141
142
|
#### `:case_insensitive`
|
|
142
143
|
|
|
143
|
-
The :case_insensitive option makes string
|
|
144
|
+
The :case_insensitive option makes string comparisons ignore case.
|
|
144
145
|
|
|
145
146
|
```ruby
|
|
146
147
|
a = {x:5, s:'FooBar'}
|
|
@@ -150,6 +151,39 @@ diff = HashDiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :case_ins
|
|
|
150
151
|
diff.should == [["~", "x", 5, 6]]
|
|
151
152
|
```
|
|
152
153
|
|
|
154
|
+
#### `:array_path`
|
|
155
|
+
|
|
156
|
+
The :array_path option represents the path of the diff in an array rather than
|
|
157
|
+
a string. This can be used to show differences in between hash key types and
|
|
158
|
+
is useful for `patch!` when used on hashes without string keys.
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
a = {x:5}
|
|
162
|
+
b = {'x'=>6}
|
|
163
|
+
|
|
164
|
+
diff = HashDiff.diff(a, b, :array_path => true)
|
|
165
|
+
diff.should == [['-', [:x], 5], ['+', ['x'], 6]]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
For cases where there are arrays in paths their index will be added to the path.
|
|
169
|
+
```ruby
|
|
170
|
+
a = {x:[0,1]}
|
|
171
|
+
b = {x:[0,2]}
|
|
172
|
+
|
|
173
|
+
diff = HashDiff.diff(a, b, :array_path => true)
|
|
174
|
+
diff.should == [["-", [:x, 1], 1], ["+", [:x, 1], 2]]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
This shouldn't cause problems if you are comparing an array with a hash:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
a = {x:{0=>1}}
|
|
181
|
+
b = {x:[1]}
|
|
182
|
+
|
|
183
|
+
diff = HashDiff.diff(a, b, :array_path => true)
|
|
184
|
+
diff.should == [["~", [:a], [1], {0=>1}]]
|
|
185
|
+
```
|
|
186
|
+
|
|
153
187
|
#### Specifying a custom comparison method
|
|
154
188
|
|
|
155
189
|
It's possible to specify how the values of a key should be compared.
|
|
@@ -186,6 +220,8 @@ diff.should == [["~", "a", "car", "bus"], ["~", "b[1]", "plane", " plan"], ["-",
|
|
|
186
220
|
|
|
187
221
|
When a comparison block is given, it'll be given priority over other specified options. If the block returns value other than `true` or `false`, then the two values will be compared with other specified options.
|
|
188
222
|
|
|
223
|
+
When used in conjunction with the `array_path` option, the path passed in as an argument will be an array. When determining the ordering of an array a key of `"*"` will be used in place of the `key[*]` field. It is possible, if you have hashes with integer or `"*"` keys, to have problems distinguishing between arrays and hashes - although this shouldn't be an issue unless your data is very difficult to predict and/or your custom rules are very specific.
|
|
224
|
+
|
|
189
225
|
#### Sorting arrays before comparison
|
|
190
226
|
|
|
191
227
|
An order difference alone between two arrays can create too many diffs to be useful. Consider sorting them prior to diffing.
|
data/changelog.md
CHANGED
data/lib/hashdiff/diff.rb
CHANGED
|
@@ -11,6 +11,7 @@ module HashDiff
|
|
|
11
11
|
# * :delimiter (String) ['.'] the delimiter used when returning nested key references
|
|
12
12
|
# * :numeric_tolerance (Numeric) [0] should be a positive numeric value. Value by which numeric differences must be greater than. By default, numeric values are compared exactly; with the :tolerance option, the difference between numeric values must be greater than the given value.
|
|
13
13
|
# * :strip (Boolean) [false] whether or not to call #strip on strings before comparing
|
|
14
|
+
# * :array_path (Boolean) [false] whether to return the path references for nested values in an array, can be used for patch compatibility with non string keys.
|
|
14
15
|
#
|
|
15
16
|
# @yield [path, value1, value2] Optional block is used to compare each value, instead of default #==. If the block returns value other than true of false, then other specified comparison options will be used to do the comparison.
|
|
16
17
|
#
|
|
@@ -53,6 +54,7 @@ module HashDiff
|
|
|
53
54
|
# * :delimiter (String) ['.'] the delimiter used when returning nested key references
|
|
54
55
|
# * :numeric_tolerance (Numeric) [0] should be a positive numeric value. Value by which numeric differences must be greater than. By default, numeric values are compared exactly; with the :tolerance option, the difference between numeric values must be greater than the given value.
|
|
55
56
|
# * :strip (Boolean) [false] whether or not to call #strip on strings before comparing
|
|
57
|
+
# * :array_path (Boolean) [false] whether to return the path references for nested values in an array, can be used for patch compatibility with non string keys.
|
|
56
58
|
#
|
|
57
59
|
# @yield [path, value1, value2] Optional block is used to compare each value, instead of default #==. If the block returns value other than true of false, then other specified comparison options will be used to do the comparison.
|
|
58
60
|
#
|
|
@@ -74,9 +76,12 @@ module HashDiff
|
|
|
74
76
|
:delimiter => '.',
|
|
75
77
|
:strict => true,
|
|
76
78
|
:strip => false,
|
|
77
|
-
:numeric_tolerance => 0
|
|
79
|
+
:numeric_tolerance => 0,
|
|
80
|
+
:array_path => false
|
|
78
81
|
}.merge!(options)
|
|
79
82
|
|
|
83
|
+
opts[:prefix] = [] if opts[:array_path] && opts[:prefix] == ''
|
|
84
|
+
|
|
80
85
|
opts[:comparison] = block if block_given?
|
|
81
86
|
|
|
82
87
|
# prefer to compare with provided block
|
|
@@ -104,23 +109,20 @@ module HashDiff
|
|
|
104
109
|
changeset = diff_array(obj1, obj2, opts) do |lcs|
|
|
105
110
|
# use a's index for similarity
|
|
106
111
|
lcs.each do |pair|
|
|
107
|
-
|
|
112
|
+
prefix = prefix_append_array_index(opts[:prefix], pair[0], opts)
|
|
113
|
+
result.concat(diff(obj1[pair[0]], obj2[pair[1]], opts.merge(:prefix => prefix)))
|
|
108
114
|
end
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
changeset.each do |change|
|
|
118
|
+
change_key = prefix_append_array_index(opts[:prefix], change[1], opts)
|
|
112
119
|
if change[0] == '-'
|
|
113
|
-
result << ['-',
|
|
120
|
+
result << ['-', change_key, change[2]]
|
|
114
121
|
elsif change[0] == '+'
|
|
115
|
-
result << ['+',
|
|
122
|
+
result << ['+', change_key, change[2]]
|
|
116
123
|
end
|
|
117
124
|
end
|
|
118
125
|
elsif obj1.is_a?(Hash)
|
|
119
|
-
if opts[:prefix].empty?
|
|
120
|
-
prefix = ""
|
|
121
|
-
else
|
|
122
|
-
prefix = "#{opts[:prefix]}#{opts[:delimiter]}"
|
|
123
|
-
end
|
|
124
126
|
|
|
125
127
|
deleted_keys = obj1.keys - obj2.keys
|
|
126
128
|
common_keys = obj1.keys & obj2.keys
|
|
@@ -128,27 +130,32 @@ module HashDiff
|
|
|
128
130
|
|
|
129
131
|
# add deleted properties
|
|
130
132
|
deleted_keys.sort_by{|k,v| k.to_s }.each do |k|
|
|
131
|
-
|
|
133
|
+
change_key = prefix_append_key(opts[:prefix], k, opts)
|
|
134
|
+
custom_result = custom_compare(opts[:comparison], change_key, obj1[k], nil)
|
|
132
135
|
|
|
133
136
|
if custom_result
|
|
134
137
|
result.concat(custom_result)
|
|
135
138
|
else
|
|
136
|
-
result << ['-',
|
|
139
|
+
result << ['-', change_key, obj1[k]]
|
|
137
140
|
end
|
|
138
141
|
end
|
|
139
142
|
|
|
140
143
|
# recursive comparison for common keys
|
|
141
|
-
common_keys.sort_by{|k,v| k.to_s }.each
|
|
144
|
+
common_keys.sort_by{|k,v| k.to_s }.each do |k|
|
|
145
|
+
prefix = prefix_append_key(opts[:prefix], k, opts)
|
|
146
|
+
result.concat(diff(obj1[k], obj2[k], opts.merge(:prefix => prefix)))
|
|
147
|
+
end
|
|
142
148
|
|
|
143
149
|
# added properties
|
|
144
150
|
added_keys.sort_by{|k,v| k.to_s }.each do |k|
|
|
151
|
+
change_key = prefix_append_key(opts[:prefix], k, opts)
|
|
145
152
|
unless obj1.key?(k)
|
|
146
|
-
custom_result = custom_compare(opts[:comparison],
|
|
153
|
+
custom_result = custom_compare(opts[:comparison], change_key, nil, obj2[k])
|
|
147
154
|
|
|
148
155
|
if custom_result
|
|
149
156
|
result.concat(custom_result)
|
|
150
157
|
else
|
|
151
|
-
result << ['+',
|
|
158
|
+
result << ['+', change_key, obj2[k]]
|
|
152
159
|
end
|
|
153
160
|
end
|
|
154
161
|
end
|
data/lib/hashdiff/lcs.rb
CHANGED
data/lib/hashdiff/patch.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
#
|
|
2
2
|
# This module provides methods to diff two hash, patch and unpatch hash
|
|
3
3
|
#
|
|
4
4
|
module HashDiff
|
|
@@ -17,19 +17,21 @@ module HashDiff
|
|
|
17
17
|
delimiter = options[:delimiter] || '.'
|
|
18
18
|
|
|
19
19
|
changes.each do |change|
|
|
20
|
-
parts =
|
|
20
|
+
parts = change[1]
|
|
21
|
+
parts = decode_property_path(parts, delimiter) unless parts.is_a?(Array)
|
|
22
|
+
|
|
21
23
|
last_part = parts.last
|
|
22
24
|
|
|
23
25
|
parent_node = node(obj, parts[0, parts.size-1])
|
|
24
26
|
|
|
25
27
|
if change[0] == '+'
|
|
26
|
-
if
|
|
28
|
+
if parent_node.is_a?(Array)
|
|
27
29
|
parent_node.insert(last_part, change[2])
|
|
28
30
|
else
|
|
29
31
|
parent_node[last_part] = change[2]
|
|
30
32
|
end
|
|
31
33
|
elsif change[0] == '-'
|
|
32
|
-
if
|
|
34
|
+
if parent_node.is_a?(Array)
|
|
33
35
|
parent_node.delete_at(last_part)
|
|
34
36
|
else
|
|
35
37
|
parent_node.delete(last_part)
|
|
@@ -56,19 +58,21 @@ module HashDiff
|
|
|
56
58
|
delimiter = options[:delimiter] || '.'
|
|
57
59
|
|
|
58
60
|
changes.reverse_each do |change|
|
|
59
|
-
parts =
|
|
61
|
+
parts = change[1]
|
|
62
|
+
parts = decode_property_path(parts, delimiter) unless parts.is_a?(Array)
|
|
63
|
+
|
|
60
64
|
last_part = parts.last
|
|
61
65
|
|
|
62
66
|
parent_node = node(obj, parts[0, parts.size-1])
|
|
63
67
|
|
|
64
68
|
if change[0] == '+'
|
|
65
|
-
if
|
|
69
|
+
if parent_node.is_a?(Array)
|
|
66
70
|
parent_node.delete_at(last_part)
|
|
67
71
|
else
|
|
68
72
|
parent_node.delete(last_part)
|
|
69
73
|
end
|
|
70
74
|
elsif change[0] == '-'
|
|
71
|
-
if
|
|
75
|
+
if parent_node.is_a?(Array)
|
|
72
76
|
parent_node.insert(last_part, change[2])
|
|
73
77
|
else
|
|
74
78
|
parent_node[last_part] = change[2]
|
data/lib/hashdiff/util.rb
CHANGED
|
@@ -55,19 +55,17 @@ module HashDiff
|
|
|
55
55
|
#
|
|
56
56
|
# e.g. "a.b[3].c" => ['a', 'b', 3, 'c']
|
|
57
57
|
def self.decode_property_path(path, delimiter='.')
|
|
58
|
-
|
|
58
|
+
path.split(delimiter).inject([]) do |memo, part|
|
|
59
59
|
if part =~ /^(.*)\[(\d+)\]$/
|
|
60
60
|
if $1.size > 0
|
|
61
|
-
[$1, $2.to_i]
|
|
61
|
+
memo + [$1, $2.to_i]
|
|
62
62
|
else
|
|
63
|
-
$2.to_i
|
|
63
|
+
memo + [$2.to_i]
|
|
64
64
|
end
|
|
65
65
|
else
|
|
66
|
-
part
|
|
66
|
+
memo + [part]
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
|
-
|
|
70
|
-
parts.flatten
|
|
71
69
|
end
|
|
72
70
|
|
|
73
71
|
# @private
|
|
@@ -129,4 +127,20 @@ module HashDiff
|
|
|
129
127
|
end
|
|
130
128
|
end
|
|
131
129
|
end
|
|
130
|
+
|
|
131
|
+
def self.prefix_append_key(prefix, key, opts)
|
|
132
|
+
if opts[:array_path]
|
|
133
|
+
prefix + [key]
|
|
134
|
+
else
|
|
135
|
+
prefix.empty? ? "#{key}" : "#{prefix}#{opts[:delimiter]}#{key}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.prefix_append_array_index(prefix, array_index, opts)
|
|
140
|
+
if opts[:array_path]
|
|
141
|
+
prefix + [array_index]
|
|
142
|
+
else
|
|
143
|
+
"#{prefix}[#{array_index}]"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
132
146
|
end
|
data/lib/hashdiff/version.rb
CHANGED
|
@@ -62,4 +62,13 @@ describe HashDiff do
|
|
|
62
62
|
['+', 'menu.popup.menuitem[1]', {"value" => "Open", "onclick" => "OpenDoc()"}]
|
|
63
63
|
]
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
it "should be able to have an array_path specified" do
|
|
67
|
+
a = {'x' => [{'a' => 1, 'c' => 3, 'e' => 5}, {'y' => 3}]}
|
|
68
|
+
b = {'x' => [{'a' => 1, 'b' => 2, 'e' => 5}] }
|
|
69
|
+
|
|
70
|
+
diff = HashDiff.best_diff(a, b, :array_path => true)
|
|
71
|
+
diff.should == [["-", ["x", 0, "c"], 3], ["+", ["x", 0, "b"], 2], ["-", ["x", 1], {"y"=>3}]]
|
|
72
|
+
end
|
|
73
|
+
|
|
65
74
|
end
|
data/spec/hashdiff/diff_spec.rb
CHANGED
|
@@ -274,4 +274,40 @@ describe HashDiff do
|
|
|
274
274
|
diff.should == [['~', 'b', 'boat', 'truck'], ['~', 'c', 'plane', ' plan']]
|
|
275
275
|
end
|
|
276
276
|
end
|
|
277
|
+
|
|
278
|
+
context 'when :array_path is true' do
|
|
279
|
+
it 'should return the diff path in an array rather than a string' do
|
|
280
|
+
x = { 'a' => 'foo' }
|
|
281
|
+
y = { 'a' => 'bar' }
|
|
282
|
+
diff = HashDiff.diff(x, y, :array_path => true)
|
|
283
|
+
|
|
284
|
+
diff.should == [['~', ['a'], 'foo', 'bar']]
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it 'should show array indexes in paths' do
|
|
288
|
+
x = { 'a' => [0, 1, 2] }
|
|
289
|
+
y = { 'a' => [0, 1, 2, 3] }
|
|
290
|
+
|
|
291
|
+
diff = HashDiff.diff(x, y, :array_path => true)
|
|
292
|
+
|
|
293
|
+
diff.should == [['+', ['a', 3], 3]]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'should show differences with string and symbol keys' do
|
|
297
|
+
x = { 'a' => 'foo' }
|
|
298
|
+
y = { :a => 'bar' }
|
|
299
|
+
|
|
300
|
+
diff = HashDiff.diff(x, y, :array_path => true)
|
|
301
|
+
diff.should == [['-', ['a'], 'foo'], ['+', [:a], 'bar']]
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'should support other key types' do
|
|
305
|
+
time = Time.now
|
|
306
|
+
x = { time => 'foo' }
|
|
307
|
+
y = { 0 => 'bar' }
|
|
308
|
+
|
|
309
|
+
diff = HashDiff.diff(x, y, :array_path => true)
|
|
310
|
+
diff.should == [['-', [time], 'foo'], ['+', [0], 'bar']]
|
|
311
|
+
end
|
|
312
|
+
end
|
|
277
313
|
end
|
data/spec/hashdiff/patch_spec.rb
CHANGED
|
@@ -157,5 +157,27 @@ describe HashDiff do
|
|
|
157
157
|
HashDiff.unpatch!(b, diff, :delimiter => "\n").should == a
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
it "should be able to patch when the diff is generated with an array_path" do
|
|
161
|
+
a = {"a" => 1, "b" => 1}
|
|
162
|
+
b = {"a" => 1, "b" => 2}
|
|
163
|
+
diff = HashDiff.diff(a, b, :array_path => true)
|
|
160
164
|
|
|
165
|
+
HashDiff.patch!(a, diff).should == b
|
|
166
|
+
|
|
167
|
+
a = {"a" => 1, "b" => 1}
|
|
168
|
+
b = {"a" => 1, "b" => 2}
|
|
169
|
+
HashDiff.unpatch!(b, diff).should == a
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it "should be able to use non string keys when diff is generated with an array_path" do
|
|
173
|
+
a = {"a" => 1, :a => 2, 0 => 3}
|
|
174
|
+
b = {"a" => 5, :a => 6, 0 => 7}
|
|
175
|
+
diff = HashDiff.diff(a, b, :array_path => true)
|
|
176
|
+
|
|
177
|
+
HashDiff.patch!(a, diff).should == b
|
|
178
|
+
|
|
179
|
+
a = {"a" => 1, :a => 2, 0 => 3}
|
|
180
|
+
b = {"a" => 5, :a => 6, 0 => 7}
|
|
181
|
+
HashDiff.unpatch!(b, diff).should == a
|
|
182
|
+
end
|
|
161
183
|
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.5
|
|
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-
|
|
11
|
+
date: 2017-08-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|