hashdiff 1.1.2 → 1.2.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/.github/workflows/ci.yml +2 -3
- data/README.md +26 -1
- data/changelog.md +4 -0
- data/lib/hashdiff/compare_hashes.rb +56 -30
- data/lib/hashdiff/diff.rb +4 -1
- data/lib/hashdiff/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4752d45ff706f5b763bb7bc2b6b6c4302657caad5e45fce2d02730d962ef3d31
|
4
|
+
data.tar.gz: 5d9ff9c909184d3bbd798a3dbc8ea82d1c2752450c0c04671755aaa0d8d34c25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ee25347a9b92ec1ff0849c319221101d2a53c0194586227395d79ced3d7d4172a3a3448ca197886ae175dcc2ab5f681d13790f4a75d69b3c31cec8150c82779
|
7
|
+
data.tar.gz: 0da5f54d158ad5dbe806bd0c25279554c6e47d22faff4334ae427351ff00cca338ea490fb96246611cde5b505d9970b9216b8d6de5960a0532a349f738e0ed52
|
data/.github/workflows/ci.yml
CHANGED
@@ -11,7 +11,7 @@ jobs:
|
|
11
11
|
matrix:
|
12
12
|
ruby:
|
13
13
|
- 2.7
|
14
|
-
- 3.0
|
14
|
+
- '3.0'
|
15
15
|
- 3.1
|
16
16
|
- 3.2
|
17
17
|
- 3.3
|
@@ -22,7 +22,6 @@ jobs:
|
|
22
22
|
uses: ruby/setup-ruby@v1
|
23
23
|
with:
|
24
24
|
ruby-version: ${{ matrix.ruby }}
|
25
|
-
|
26
|
-
run: bundle install --jobs $(nproc) --retry 3
|
25
|
+
bundler-cache: true # 'bundle install' and cache gems
|
27
26
|
- name: Run rake
|
28
27
|
run: bundle exec rake
|
data/README.md
CHANGED
@@ -95,7 +95,8 @@ Hashdiff.unpatch!(b, diff).should == a
|
|
95
95
|
### Options
|
96
96
|
|
97
97
|
The following options are available: `:delimiter`, `:similarity`, `:strict`, `:ignore_keys`,
|
98
|
-
`:indifferent`, `:numeric_tolerance`, `:strip`, `:case_insensitive`, `:array_path
|
98
|
+
`:indifferent`, `:numeric_tolerance`, `:strip`, `:case_insensitive`, `:array_path`,
|
99
|
+
`:use_lcs`, and `:preserve_key_order`
|
99
100
|
|
100
101
|
#### `:delimiter`
|
101
102
|
|
@@ -235,6 +236,30 @@ diff = Hashdiff.diff(a, b, use_lcs: false)
|
|
235
236
|
diff.should == [["~", "x[1]", 1, 2], ["+", "x[3]", 3]]
|
236
237
|
```
|
237
238
|
|
239
|
+
#### `:preserve_key_order`
|
240
|
+
|
241
|
+
By default, the change set is ordered by operation type: deletions (-) first, then updates (~), and finally additions (+).
|
242
|
+
Within each operation group, keys are sorted alphabetically:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
a = {d: 1, c: 1, a: 1}
|
246
|
+
b = {d: 2, b: 2, a: 2}
|
247
|
+
|
248
|
+
diff = Hashdiff.diff(a, b)
|
249
|
+
diff.should == [["-", "c", 1], ["~", "a", 1, 2], ["~", "d", 1, 2], ["+", "b", 2]]
|
250
|
+
```
|
251
|
+
|
252
|
+
Setting :preserve_key_order to true processes keys in the order they appear in the first hash.
|
253
|
+
Keys that only exist in the second hash are appended in their original order:
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
a = {d: 1, c: 1, a: 1}
|
257
|
+
b = {d: 2, b: 2, a: 2}
|
258
|
+
|
259
|
+
diff = Hashdiff.diff(a, b, preserve_key_order: true)
|
260
|
+
diff.should == [["~", "d", 1, 2], ["-", "c", 1], ["~", "a", 1, 2], ["+", "b", 2]]
|
261
|
+
```
|
262
|
+
|
238
263
|
#### Specifying a custom comparison method
|
239
264
|
|
240
265
|
It's possible to specify how the values of a key should be compared.
|
data/changelog.md
CHANGED
@@ -20,9 +20,9 @@ module Hashdiff
|
|
20
20
|
obj2_keys = obj2_keys.map { |k| k.is_a?(Symbol) ? k.to_s : k }
|
21
21
|
end
|
22
22
|
|
23
|
-
added_keys =
|
24
|
-
common_keys =
|
25
|
-
deleted_keys =
|
23
|
+
added_keys = obj2_keys - obj1_keys
|
24
|
+
common_keys = obj1_keys & obj2_keys
|
25
|
+
deleted_keys = obj1_keys - obj2_keys
|
26
26
|
|
27
27
|
result = []
|
28
28
|
|
@@ -32,40 +32,66 @@ module Hashdiff
|
|
32
32
|
deleted_keys.delete k
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
handle_key = lambda do |k, type|
|
36
|
+
case type
|
37
|
+
when :deleted
|
38
|
+
# add deleted properties
|
39
|
+
k = opts[:indifferent] ? obj1_lookup[k] : k
|
40
|
+
change_key = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
|
41
|
+
custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, obj1[k], nil)
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
if custom_result
|
44
|
+
result.concat(custom_result)
|
45
|
+
else
|
46
|
+
result << ['-', change_key, obj1[k]]
|
47
|
+
end
|
48
|
+
when :common
|
49
|
+
# recursive comparison for common keys
|
50
|
+
prefix = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
k1 = opts[:indifferent] ? obj1_lookup[k] : k
|
53
|
+
k2 = opts[:indifferent] ? obj2_lookup[k] : k
|
54
|
+
result.concat(Hashdiff.diff(obj1[k1], obj2[k2], opts.merge(prefix: prefix)))
|
55
|
+
when :added
|
56
|
+
# added properties
|
57
|
+
change_key = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
|
51
58
|
|
52
|
-
|
53
|
-
|
54
|
-
result.concat(Hashdiff.diff(obj1[k1], obj2[k2], opts.merge(prefix: prefix)))
|
55
|
-
end
|
59
|
+
k = opts[:indifferent] ? obj2_lookup[k] : k
|
60
|
+
custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, nil, obj2[k])
|
56
61
|
|
57
|
-
|
58
|
-
|
59
|
-
|
62
|
+
if custom_result
|
63
|
+
result.concat(custom_result)
|
64
|
+
else
|
65
|
+
result << ['+', change_key, obj2[k]]
|
66
|
+
end
|
67
|
+
else
|
68
|
+
raise "Invalid type: #{type}"
|
69
|
+
end
|
70
|
+
end
|
60
71
|
|
61
|
-
|
62
|
-
|
72
|
+
if opts[:preserve_key_order]
|
73
|
+
# Building lookups to speed up key classification
|
74
|
+
added_keys_lookup = added_keys.each_with_object({}) { |k, h| h[k] = true }
|
75
|
+
common_keys_lookup = common_keys.each_with_object({}) { |k, h| h[k] = true }
|
76
|
+
deleted_keys_lookup = deleted_keys.each_with_object({}) { |k, h| h[k] = true }
|
63
77
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
78
|
+
# Iterate through all keys, preserving obj1's key order and appending any new keys from obj2. Shared keys
|
79
|
+
# (found in both obj1 and obj2) follow obj1's order since uniq only keeps the first occurrence.
|
80
|
+
(obj1_keys + obj2_keys).uniq.each do |k|
|
81
|
+
if added_keys_lookup[k]
|
82
|
+
handle_key.call(k, :added)
|
83
|
+
elsif common_keys_lookup[k]
|
84
|
+
handle_key.call(k, :common)
|
85
|
+
elsif deleted_keys_lookup[k]
|
86
|
+
handle_key.call(k, :deleted)
|
87
|
+
end
|
68
88
|
end
|
89
|
+
else
|
90
|
+
# Keys are first grouped by operation type (deletions first, then changes, then additions), and then sorted
|
91
|
+
# alphabetically within each group.
|
92
|
+
deleted_keys.sort_by(&:to_s).each { |k| handle_key.call(k, :deleted) }
|
93
|
+
common_keys.sort_by(&:to_s).each { |k| handle_key.call(k, :common) }
|
94
|
+
added_keys.sort_by(&:to_s).each { |k| handle_key.call(k, :added) }
|
69
95
|
end
|
70
96
|
|
71
97
|
result
|
data/lib/hashdiff/diff.rb
CHANGED
@@ -16,6 +16,7 @@ module Hashdiff
|
|
16
16
|
# * :strip (Boolean) [false] whether or not to call #strip on strings before comparing
|
17
17
|
# * :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.
|
18
18
|
# * :use_lcs (Boolean) [true] whether or not to use an implementation of the Longest common subsequence algorithm for comparing arrays, produces better diffs but is slower.
|
19
|
+
# * :preserve_key_order (Boolean) [false] If false, operations are grouped by type (-, ~, then +) then by hash key alphabetically. If true, preserves the original key order from the first hash and appends new keys from the second hash in order.
|
19
20
|
#
|
20
21
|
# @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.
|
21
22
|
#
|
@@ -62,6 +63,7 @@ module Hashdiff
|
|
62
63
|
# * :strip (Boolean) [false] whether or not to call #strip on strings before comparing
|
63
64
|
# * :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.
|
64
65
|
# * :use_lcs (Boolean) [true] whether or not to use an implementation of the Longest common subsequence algorithm for comparing arrays, produces better diffs but is slower.
|
66
|
+
# * :preserve_key_order (Boolean) [false] If false, operations are grouped by type (-, ~, then +) then by hash key alphabetically. If true, preserves the original key order from the first hash and appends new keys from the second hash in order.
|
65
67
|
#
|
66
68
|
#
|
67
69
|
# @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.
|
@@ -88,7 +90,8 @@ module Hashdiff
|
|
88
90
|
strip: false,
|
89
91
|
numeric_tolerance: 0,
|
90
92
|
array_path: false,
|
91
|
-
use_lcs: true
|
93
|
+
use_lcs: true,
|
94
|
+
preserve_key_order: false
|
92
95
|
}.merge!(options)
|
93
96
|
|
94
97
|
opts[:prefix] = [] if opts[:array_path] && opts[:prefix] == ''
|
data/lib/hashdiff/version.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: 1.
|
4
|
+
version: 1.2.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:
|
11
|
+
date: 2025-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bluecloth
|