hashdiff 1.0.1 → 1.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9b50073f973250c144bb05272510f462dda18a7f5c9a8719156065773fdcaaa
4
- data.tar.gz: 903439ed3ea22994d072201509506232fb77482506494bbb8c8419dc9fb8d546
3
+ metadata.gz: 99e198ac76f687f33cb2537c32bd81ccdd45798502cba565a624d1107a7e85ca
4
+ data.tar.gz: 7eba9f454570311e7b784d3da51db6827331f6e9c73d98d113b407d57bc60626
5
5
  SHA512:
6
- metadata.gz: e1c00d4710a67688a35197197da93ac3bfabca5f5a51edc152bfbd341a16548e762ddf2290364b7fc3a20e99cdb6d8d909ff84e766468fe623eb3977afd6d90f
7
- data.tar.gz: 2d860929e99458cd89ee60ce877a87557d6bb9d52dcd25e0989f1a0a7b372d026bb646ee2b19498f6637a485f15fcd1e68cf1e1c0dbe666ad5af1792d60a893a
6
+ metadata.gz: e8bf11d2563db1d99348a17bd6074e51cfd35dc95968bf43eca5f115b7af7547c5a08457b031b880442e4d8111343d3e096c8d5075c83a63c858fcd8627d4140
7
+ data.tar.gz: 6b318cd78d7f8dac01d1fea139fbc81d4724dc2c9a7f4cb0b21d5c54b38da5384908e6ba91b40503d7c501d596d4f91ecaf348122679f62fb91f0a2dc6e8e30a
@@ -0,0 +1,27 @@
1
+ name: ci
2
+
3
+ on:
4
+ - pull_request
5
+ - push
6
+
7
+ jobs:
8
+ build:
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ ruby:
13
+ - 2.7
14
+ - '3.0'
15
+ - 3.1
16
+ - 3.2
17
+ - 3.3
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - name: Set up Ruby
22
+ uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: ${{ matrix.ruby }}
25
+ bundler-cache: true # 'bundle install' and cache gems
26
+ - name: Run rake
27
+ run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -9,7 +9,7 @@ Metrics/MethodLength:
9
9
  Enabled: false
10
10
  Metrics/AbcSize:
11
11
  Enabled: false
12
- Metrics/LineLength:
12
+ Layout/LineLength:
13
13
  Enabled: false
14
14
  Metrics/ClassLength:
15
15
  Enabled: false
@@ -17,16 +17,26 @@ Metrics/BlockLength:
17
17
  Enabled: false
18
18
  Metrics/ModuleLength:
19
19
  Enabled: false
20
+ Style/CaseLikeIf:
21
+ Enabled: false
20
22
  Style/Documentation:
21
23
  Enabled: false
22
24
  Style/FrozenStringLiteralComment:
23
25
  Enabled: true
24
26
  EnforcedStyle: always
27
+ Style/OptionalBooleanParameter:
28
+ Enabled: false
25
29
  Style/NumericPredicate:
26
30
  Enabled: false
27
31
  Style/RedundantFreeze:
28
32
  Enabled: false
33
+ Style/RedundantReturn:
34
+ Enabled: false
29
35
  RSpec/ExampleLength:
30
36
  Enabled: false
31
37
  RSpec/DescribeClass:
32
38
  Enabled: false
39
+ RSpec/SpecFilePathFormat:
40
+ Enabled: false
41
+ RSpec/NoExpectationExample:
42
+ Enabled: false
data/Gemfile CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'http://rubygems.org'
3
+ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem 'rake', '< 11'
7
+ gem 'rake'
8
8
  end
data/README.md CHANGED
@@ -1,4 +1,4 @@
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)
1
+ # Hashdiff [![Build Status](https://github.com/liufengyun/hashdiff/workflows/ci/badge.svg)](https://github.com/liufengyun/hashdiff/actions?query=workflow%3Aci) [![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
 
@@ -94,9 +94,9 @@ Hashdiff.unpatch!(b, diff).should == a
94
94
 
95
95
  ### Options
96
96
 
97
- There are eight options available: `:delimiter`, `:similarity`,
98
- `:strict`, `:indifferent`, `:numeric_tolerance`, `:strip`, `:case_insensitive`,
99
- `:array_path` and `:use_lcs`
97
+ The following options are available: `:delimiter`, `:similarity`, `:strict`, `:ignore_keys`,
98
+ `:indifferent`, `:numeric_tolerance`, `:strip`, `:case_insensitive`, `:array_path`,
99
+ `:use_lcs`, and `:preserve_key_order`
100
100
 
101
101
  #### `:delimiter`
102
102
 
@@ -118,6 +118,29 @@ In cases where you have similar hash objects in arrays, you can pass a custom va
118
118
 
119
119
  The `:strict` option, which defaults to `true`, specifies whether numeric types are compared on type as well as value. By default, an Integer will never be equal to a Float (e.g. 4 != 4.0). Setting `:strict` to false makes the comparison looser (e.g. 4 == 4.0).
120
120
 
121
+ #### `:ignore_keys`
122
+
123
+ The `:ignore_keys` option allows you to specify one or more keys to ignore, which defaults to `[]` (none). Ignored keys are ignored at all levels in both hashes. For example:
124
+
125
+ ```ruby
126
+ a = { a: 4, g: 0, b: { a: 5, c: 6, e: 1 } }
127
+ b = { b: { a: 7, c: 3, f: 1 }, d: 8 }
128
+ diff = Hashdiff.diff(a, b, ignore_keys: %i[a f])
129
+ diff.should == [['-', 'g', 0], ['-', 'b.e', 1], ['~', 'b.c', 6, 3], ['+', 'd', 8]]
130
+ ```
131
+ If you wish instead to ignore keys at a particlar level you should
132
+ use a [custom comparison method](https://github.com/liufengyun/hashdiff#specifying-a-custom-comparison-method) instead. For example to diff only at the 2nd level of both hashes:
133
+
134
+ ```ruby
135
+ a = { a: 4, g: 0, b: { a: 5, c: 6, e: 1 } }
136
+ b = { b: { a: 7, c: 3, f: 1 }, d: 8 }
137
+ diff = Hashdiff.diff(a, b) do |path, _e, _a|
138
+ arr = path.split('.')
139
+ true if %w[a f].include?(arr.last) && arr.size == 2 # note '.' is the default delimiter
140
+ end
141
+ diff.should == [['-', 'a', 4], ['-', 'g', 0], ['-', 'b.e', 1], ['~', 'b.c', 6, 3], ['+', 'd', 8]]
142
+ ```
143
+
121
144
  #### `:indifferent`
122
145
 
123
146
  The `:indifferent` option, which defaults to `false`, specifies whether to treat hash keys indifferently. Setting `:indifferent` to true has the effect of ignoring differences between symbol keys (ie. {a: 1} ~= {'a' => 1})
@@ -213,6 +236,30 @@ diff = Hashdiff.diff(a, b, use_lcs: false)
213
236
  diff.should == [["~", "x[1]", 1, 2], ["+", "x[3]", 3]]
214
237
  ```
215
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
+
216
263
  #### Specifying a custom comparison method
217
264
 
218
265
  It's possible to specify how the values of a key should be compared.
data/changelog.md CHANGED
@@ -1,57 +1,84 @@
1
1
  # Change Log
2
2
 
3
+ ## v1.2.1 2025-09-06
4
+
5
+ * Use HTTPS for the source in the Gemfile [#101](https://github.com/liufengyun/hashdiff/issues/101) ([@krzysiek1507](https://github.com/krzysiek1507))
6
+
7
+ ## v1.2.0 2025-5-20
8
+
9
+ * Added :preserve_key_order option to maintain original hash key order [#99](https://github.com/liufengyun/hashdiff/issues/99) ([@robkiessling](https://github.com/robkiessling))
10
+
11
+ ## v1.1.2 2024-11-12
12
+
13
+ * Fix bundler cache [#96](https://github.com/liufengyun/hashdiff/issues/96) ([@olleolleolle](https://github.com/olleolleolle))
14
+ * Quote the '3.0' in YAML [#95](https://github.com/liufengyun/hashdiff/issues/95) ([@olleolleolle](https://github.com/olleolleolle))
15
+ * Fix version in source code [#97](https://github.com/liufengyun/hashdiff/issues/97) ([@liufengyun](https://github.com/liufengyun))
16
+
17
+ ## v1.1.1 2024-08-02
18
+
19
+ * Fix bug in ignore_keys option [#88](https://github.com/liufengyun/hashdiff/issues/88) ([@Matzfan](https://github.com/Matzfan))
20
+ * Exclude spec files from gem package [#94](https://github.com/liufengyun/hashdiff/issues/94) ([@amatsuda](https://github.com/amatsuda))
21
+
22
+ ## v1.1.0 2023-12-14
23
+
24
+ * Add ignore_keys option ([#86](https://github.com/liufengyun/hashdiff/issues/86) [@Matzfan](https://github.com/Matzfan))
25
+ * Remove pinned version of rake < 11
26
+ * Bump rspec dep ~> 3.5
27
+ * Bump rubocop dep >= 1.52.1
28
+ * Bump rubocop-rspec dep > 1.16.0
29
+
3
30
  ## v1.0.1 2020-02-25
4
31
 
5
32
  * Add indifferent option
6
33
 
7
34
  ## v1.0.0 2019-06-06
8
35
 
9
- * Fix typo in readme (#72 @koic)
10
- * Fix Rubocop offence (#73 @koic)
11
- * Bumps version to v1.0.0 (#74 @jfelchner)
36
+ * Fix typo in readme ([#72](https://github.com/liufengyun/hashdiff/issues/72) [@koic](https://github.com/koic))
37
+ * Fix Rubocop offence ([#73](https://github.com/liufengyun/hashdiff/issues/73) [@koic](https://github.com/koic))
38
+ * Bumps version to v1.0.0 ([#74](https://github.com/liufengyun/hashdiff/issues/74) [@jfelchner](https://github.com/jfelchner))
12
39
 
13
40
  ## v1.0.0.beta1 2019-06-06
14
41
 
15
- * fix warnings in ci (#69 @y-yagi)
16
- * drop warnings of the constant change (#70 @jfelchner)
42
+ * fix warnings in ci ([#69](https://github.com/liufengyun/hashdiff/issues/69) [@y-yagi](https://github.com/y-yagi))
43
+ * drop warnings of the constant change ([#70](https://github.com/liufengyun/hashdiff/issues/70) [@jfelchner](https://github.com/jfelchner))
17
44
 
18
45
  ## v0.4.0 2019-05-28
19
46
 
20
- * refactoring (#56 #57 #59 #61 krzysiek1507)
21
- * fix typo in README (#64 @pboling)
22
- * change HashDiff to Hashdiff (#65 @jfelchner)
47
+ * refactoring ([#56](https://github.com/liufengyun/hashdiff/issues/56) [#57](https://github.com/liufengyun/hashdiff/issues/57) [#59](https://github.com/liufengyun/hashdiff/issues/59) [#61](https://github.com/liufengyun/hashdiff/issues/61) [@krzysiek1507](https://github.com/krzysiek1507))
48
+ * fix typo in README ([#64](https://github.com/liufengyun/hashdiff/issues/64) [@pboling](https://github.com/pboling))
49
+ * change HashDiff to Hashdiff ([#65](https://github.com/liufengyun/hashdiff/issues/65) [@jfelchner](https://github.com/jfelchner))
23
50
 
24
51
  ## v0.3.9 2019-04-22
25
52
 
26
- * Performance tweak (thanks @krzysiek1507: #51 #52 #53)
53
+ * Performance tweak (thanks [@krzysiek1507](https://github.com/krzysiek1507): [#51](https://github.com/liufengyun/hashdiff/issues/51) [#52](https://github.com/liufengyun/hashdiff/issues/52) [#53](https://github.com/liufengyun/hashdiff/issues/53))
27
54
 
28
55
  ## v0.3.8 2018-12-30
29
56
 
30
- * Add Rubocop and drops Ruby 1.9 support #47
57
+ * Add Rubocop and drops Ruby 1.9 support [#47](https://github.com/liufengyun/hashdiff/issues/47)
31
58
 
32
59
  ## v0.3.7 2017-10-08
33
60
 
34
- * remove 1.8.7 support from gemspec #39
61
+ * remove 1.8.7 support from gemspec [#39](https://github.com/liufengyun/hashdiff/issues/39)
35
62
 
36
63
  ## v0.3.6 2017-08-22
37
64
 
38
- * add option `use_lcs` #35
65
+ * add option `use_lcs` [#35](https://github.com/liufengyun/hashdiff/issues/35)
39
66
 
40
67
  ## v0.3.5 2017-08-06
41
68
 
42
- * add option `array_path` #34
69
+ * add option `array_path` [#34](https://github.com/liufengyun/hashdiff/issues/34)
43
70
 
44
71
  ## v0.3.4 2017-05-01
45
72
 
46
- * performance improvement of `#similar?` #31
73
+ * performance improvement of `#similar?` [#31](https://github.com/liufengyun/hashdiff/issues/31)
47
74
 
48
75
  ## v0.3.2 2016-12-27
49
76
 
50
- * replace `Fixnum` by `Integer` #28
77
+ * replace `Fixnum` by `Integer` [#28](https://github.com/liufengyun/hashdiff/issues/28)
51
78
 
52
79
  ## v0.3.1 2016-11-24
53
80
 
54
- * fix an error when a hash has mixed types #26
81
+ * fix an error when a hash has mixed types [#26](https://github.com/liufengyun/hashdiff/issues/26)
55
82
 
56
83
  ## v0.3.0 2016-2-11
57
84
 
@@ -59,7 +86,7 @@
59
86
 
60
87
  ## v0.2.3 2015-11-5
61
88
 
62
- * improve performance of LCS algorithm #12
89
+ * improve performance of LCS algorithm [#12](https://github.com/liufengyun/hashdiff/issues/12)
63
90
 
64
91
  ## v0.2.2 2014-10-6
65
92
 
data/hashdiff.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.summary = ' Hashdiff is a diff lib to compute the smallest difference between two hashes. '
11
11
  s.description = ' Hashdiff is a diff lib to compute the smallest difference between two hashes. '
12
12
 
13
- s.files = `git ls-files`.split("\n")
13
+ s.files = `git ls-files`.split("\n").grep_v(%r{^spec/})
14
14
  s.test_files = `git ls-files -- Appraisals {spec}/*`.split("\n")
15
15
 
16
16
  s.require_paths = ['lib']
@@ -22,9 +22,9 @@ Gem::Specification.new do |s|
22
22
  s.homepage = 'https://github.com/liufengyun/hashdiff'
23
23
 
24
24
  s.add_development_dependency('bluecloth')
25
- s.add_development_dependency('rspec', '~> 2.0')
26
- s.add_development_dependency('rubocop', '~> 0.49.1') # last version that works with ruby 2.0
27
- s.add_development_dependency('rubocop-rspec')
25
+ s.add_development_dependency('rspec', '~> 3.5')
26
+ s.add_development_dependency('rubocop', '>= 1.52.1') # earliest version that works with Ruby 3.3
27
+ s.add_development_dependency('rubocop-rspec', '> 1.16.0') # https://github.com/rubocop/rubocop-rspec/issues/461
28
28
  s.add_development_dependency('yard')
29
29
 
30
30
  if s.respond_to?(:metadata)
@@ -20,46 +20,78 @@ 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 = (obj2_keys - obj1_keys).sort_by(&:to_s)
24
- common_keys = (obj1_keys & obj2_keys).sort_by(&:to_s)
25
- deleted_keys = (obj1_keys - obj2_keys).sort_by(&:to_s)
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
 
29
- # add deleted properties
30
- deleted_keys.each do |k|
31
- k = opts[:indifferent] ? obj1_lookup[k] : k
32
- change_key = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
33
- custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, obj1[k], nil)
34
-
35
- if custom_result
36
- result.concat(custom_result)
37
- else
38
- result << ['-', change_key, obj1[k]]
39
- end
29
+ opts[:ignore_keys].each do |k|
30
+ added_keys.delete k
31
+ common_keys.delete k
32
+ deleted_keys.delete k
40
33
  end
41
34
 
42
- # recursive comparison for common keys
43
- common_keys.each do |k|
44
- prefix = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
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)
45
42
 
46
- k1 = opts[:indifferent] ? obj1_lookup[k] : k
47
- k2 = opts[:indifferent] ? obj2_lookup[k] : k
48
- result.concat(Hashdiff.diff(obj1[k1], obj2[k2], opts.merge(prefix: prefix)))
49
- end
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)
50
51
 
51
- # added properties
52
- added_keys.each do |k|
53
- change_key = Hashdiff.prefix_append_key(opts[:prefix], k, opts)
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)
54
58
 
55
- k = opts[:indifferent] ? obj2_lookup[k] : k
56
- custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, nil, obj2[k])
59
+ k = opts[:indifferent] ? obj2_lookup[k] : k
60
+ custom_result = Hashdiff.custom_compare(opts[:comparison], change_key, nil, obj2[k])
57
61
 
58
- if custom_result
59
- result.concat(custom_result)
62
+ if custom_result
63
+ result.concat(custom_result)
64
+ else
65
+ result << ['+', change_key, obj2[k]]
66
+ end
60
67
  else
61
- result << ['+', change_key, obj2[k]]
68
+ raise "Invalid type: #{type}"
69
+ end
70
+ end
71
+
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 }
77
+
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
62
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) }
63
95
  end
64
96
 
65
97
  result
data/lib/hashdiff/diff.rb CHANGED
@@ -9,12 +9,14 @@ module Hashdiff
9
9
  # @param [Array, Hash] obj2
10
10
  # @param [Hash] options the options to use when comparing
11
11
  # * :strict (Boolean) [true] whether numeric values will be compared on type as well as value. Set to false to allow comparing Integer, Float, BigDecimal to each other
12
+ # * :ignore_keys (Symbol, String or Array) [[]] a list of keys to ignore. No comparison is made for the specified key(s) in either hash
12
13
  # * :indifferent (Boolean) [false] whether to treat hash keys indifferently. Set to true to ignore differences between symbol keys (ie. {a: 1} ~= {'a' => 1})
13
14
  # * :delimiter (String) ['.'] the delimiter used when returning nested key references
14
15
  # * :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.
15
16
  # * :strip (Boolean) [false] whether or not to call #strip on strings before comparing
16
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.
17
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.
18
20
  #
19
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.
20
22
  #
@@ -53,6 +55,7 @@ module Hashdiff
53
55
  # @param [Array, Hash] obj2
54
56
  # @param [Hash] options the options to use when comparing
55
57
  # * :strict (Boolean) [true] whether numeric values will be compared on type as well as value. Set to false to allow comparing Integer, Float, BigDecimal to each other
58
+ # * :ignore_keys (Symbol, String or Array) [[]] a list of keys to ignore. No comparison is made for the specified key(s) in either hash
56
59
  # * :indifferent (Boolean) [false] whether to treat hash keys indifferently. Set to true to ignore differences between symbol keys (ie. {a: 1} ~= {'a' => 1})
57
60
  # * :similarity (Numeric) [0.8] should be between (0, 1]. Meaningful if there are similar hashes in arrays. See {best_diff}.
58
61
  # * :delimiter (String) ['.'] the delimiter used when returning nested key references
@@ -60,6 +63,7 @@ module Hashdiff
60
63
  # * :strip (Boolean) [false] whether or not to call #strip on strings before comparing
61
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.
62
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.
63
67
  #
64
68
  #
65
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.
@@ -81,15 +85,19 @@ module Hashdiff
81
85
  similarity: 0.8,
82
86
  delimiter: '.',
83
87
  strict: true,
88
+ ignore_keys: [],
84
89
  indifferent: false,
85
90
  strip: false,
86
91
  numeric_tolerance: 0,
87
92
  array_path: false,
88
- use_lcs: true
93
+ use_lcs: true,
94
+ preserve_key_order: false
89
95
  }.merge!(options)
90
96
 
91
97
  opts[:prefix] = [] if opts[:array_path] && opts[:prefix] == ''
92
98
 
99
+ opts[:ignore_keys] = [*opts[:ignore_keys]]
100
+
93
101
  opts[:comparison] = block if block_given?
94
102
 
95
103
  # prefer to compare with provided block
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hashdiff
4
- VERSION = '1.0.1'.freeze
4
+ VERSION = '1.2.1'.freeze
5
5
  end
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.0.1
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Liu Fengyun
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-27 00:00:00.000000000 Z
11
+ date: 2025-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bluecloth
@@ -30,42 +30,42 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.0'
33
+ version: '3.5'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.0'
40
+ version: '3.5'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rubocop
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 0.49.1
47
+ version: 1.52.1
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 0.49.1
54
+ version: 1.52.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rubocop-rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - ">"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 1.16.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - ">"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 1.16.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: yard
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -88,6 +88,7 @@ executables: []
88
88
  extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
+ - ".github/workflows/ci.yml"
91
92
  - ".gitignore"
92
93
  - ".rspec"
93
94
  - ".rubocop.yml"
@@ -108,15 +109,6 @@ files:
108
109
  - lib/hashdiff/patch.rb
109
110
  - lib/hashdiff/util.rb
110
111
  - lib/hashdiff/version.rb
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/readme_spec.rb
118
- - spec/hashdiff/util_spec.rb
119
- - spec/spec_helper.rb
120
112
  homepage: https://github.com/liufengyun/hashdiff
121
113
  licenses:
122
114
  - MIT
@@ -141,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
133
  - !ruby/object:Gem::Version
142
134
  version: '0'
143
135
  requirements: []
144
- rubygems_version: 3.0.6
136
+ rubygems_version: 3.3.5
145
137
  signing_key:
146
138
  specification_version: 4
147
139
  summary: Hashdiff is a diff lib to compute the smallest difference between two hashes.
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe Hashdiff do
6
- it 'is able to best diff' do
7
- a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] }
8
- b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] }
9
-
10
- diff = described_class.best_diff(a, b)
11
- diff.should == [['-', 'x[0].c', 3], ['+', 'x[0].b', 2], ['-', 'x[1]', { 'y' => 3 }]]
12
- end
13
-
14
- it 'uses custom delimiter when provided' do
15
- a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] }
16
- b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] }
17
-
18
- diff = described_class.best_diff(a, b, delimiter: "\t")
19
- diff.should == [['-', "x[0]\tc", 3], ['+', "x[0]\tb", 2], ['-', 'x[1]', { 'y' => 3 }]]
20
- end
21
-
22
- it 'uses custom comparison when provided' do
23
- a = { 'x' => [{ 'a' => 'foo', 'c' => 'goat', 'e' => 'snake' }, { 'y' => 'baz' }] }
24
- b = { 'x' => [{ 'a' => 'bar', 'b' => 'cow', 'e' => 'puppy' }] }
25
-
26
- diff = described_class.best_diff(a, b) do |path, obj1, obj2|
27
- case path
28
- when /^x\[.\]\..$/
29
- obj1.length == obj2.length if obj1 && obj2
30
- end
31
- end
32
-
33
- diff.should == [['-', 'x[0].c', 'goat'], ['+', 'x[0].b', 'cow'], ['-', 'x[1]', { 'y' => 'baz' }]]
34
- end
35
-
36
- it 'is able to best diff array in hash' do
37
- a = { 'menu' => {
38
- 'id' => 'file',
39
- 'value' => 'File',
40
- 'popup' => {
41
- 'menuitem' => [
42
- { 'value' => 'New', 'onclick' => 'CreateNewDoc()' },
43
- { 'value' => 'Close', 'onclick' => 'CloseDoc()' }
44
- ]
45
- }
46
- } }
47
-
48
- b = { 'menu' => {
49
- 'id' => 'file 2',
50
- 'value' => 'File',
51
- 'popup' => {
52
- 'menuitem' => [
53
- { 'value' => 'New1', 'onclick' => 'CreateNewDoc()' },
54
- { 'value' => 'Open', 'onclick' => 'OpenDoc()' },
55
- { 'value' => 'Close', 'onclick' => 'CloseDoc()' }
56
- ]
57
- }
58
- } }
59
-
60
- diff = described_class.best_diff(a, b)
61
- diff.should == [
62
- ['~', 'menu.id', 'file', 'file 2'],
63
- ['~', 'menu.popup.menuitem[0].value', 'New', 'New1'],
64
- ['+', 'menu.popup.menuitem[1]', { 'value' => 'Open', 'onclick' => 'OpenDoc()' }]
65
- ]
66
- end
67
-
68
- it 'is able to have an array_path specified' do
69
- a = { 'x' => [{ 'a' => 1, 'c' => 3, 'e' => 5 }, { 'y' => 3 }] }
70
- b = { 'x' => [{ 'a' => 1, 'b' => 2, 'e' => 5 }] }
71
-
72
- diff = described_class.best_diff(a, b, array_path: true)
73
- diff.should == [['-', ['x', 0, 'c'], 3], ['+', ['x', 0, 'b'], 2], ['-', ['x', 1], { 'y' => 3 }]]
74
- end
75
- end