hashdiff 0.3.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 284b7371148ba31499e35fdfb3783faced5a8498
4
- data.tar.gz: 3d902c91f2d00eb00df4cb96d27f5d6c242e9ee8
3
+ metadata.gz: 204c04301ec7d5b755863a0ec7b9001930a569c1
4
+ data.tar.gz: c0bf2c27e34271b637da82792549104a5b0ca2cb
5
5
  SHA512:
6
- metadata.gz: e80104dc13c931f44352337d0027fd774f8a54a67c2ae15b2870e05723c41aafbe00be06d5073078ccfcf44b320e3ae03693623f0d61cab95fa040671e708599
7
- data.tar.gz: 8efef12e5b3ff1c9a78dfc7655f121211d91e6b7d62521d33a5376b202e31913da440e2754f5ae0a3f1cba7189e2006db033027786c7402410ecf5dac58d44f2
6
+ metadata.gz: 68f07faf7b58132dae314317cf90e1dae5549218d93c19be33df0460659d2e74a861f458c9f6401909ce78ca1b0cb649cc843e0a804a4dad70e3c824568bfd7c
7
+ data.tar.gz: ba2ff10babe7886c263cbac1c761a38e205880dc323199746ed6845762b462a08b7664f9f1159b5765751b020dfd38ac365fcec04e350d894f97df6b519f3c71
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ require: rubocop-rspec
2
+ Metrics/PerceivedComplexity:
3
+ Enabled: false
4
+ Metrics/CyclomaticComplexity:
5
+ Enabled: false
6
+ Metrics/MethodLength:
7
+ Enabled: false
8
+ Metrics/AbcSize:
9
+ Enabled: false
10
+ Metrics/LineLength:
11
+ Enabled: false
12
+ Metrics/ClassLength:
13
+ Enabled: false
14
+ Metrics/BlockLength:
15
+ Enabled: false
16
+ Metrics/ModuleLength:
17
+ Enabled: false
18
+ Style/Documentation:
19
+ Enabled: false
20
+ Style/FrozenStringLiteralComment:
21
+ Enabled: true
22
+ EnforcedStyle: always
23
+ RSpec/ExampleLength:
24
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,10 +1,11 @@
1
1
  sudo: false
2
2
  language: ruby
3
+ cache: bundler
3
4
  rvm:
4
- - 1.9.3
5
5
  - 2.0.0
6
6
  - 2.1.10
7
- - 2.2.6
8
- - 2.3.3
9
- - 2.4.0
10
- script: "bundle exec rake spec"
7
+ - 2.2.8
8
+ - 2.3.4
9
+ - 2.4.2
10
+ - 2.5.3
11
+ - 2.6.0
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
- source "http://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'http://rubygems.org'
2
4
  gemspec
3
5
 
4
6
  group :test do
data/README.md CHANGED
@@ -1,14 +1,17 @@
1
- # HashDiff [![Build Status](https://secure.travis-ci.org/liufengyun/hashdiff.png)](http://travis-ci.org/liufengyun/hashdiff) [![Gem Version](https://badge.fury.io/rb/hashdiff.png)](http://badge.fury.io/rb/hashdiff)
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
- HashDiff is a ruby library to compute the smallest difference between two hashes.
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
- HashDiff does not monkey-patch any existing class. All features are contained inside the `HashDiff` module.
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
- ## Why HashDiff?
11
+
12
+ __WARNING__: Don't use the library for comparing large arrays, say ~10K (see #49).
13
+
14
+ ## Why Hashdiff?
12
15
 
13
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?
14
17
 
@@ -18,7 +21,7 @@ An algorithm that responds to this question has to do following:
18
21
  * Compute recursively -- Arrays and Hashes may be nested arbitrarily in A or B.
19
22
  * Compute the smallest change -- it should recognize similar child Hashes or child Arrays between A and B.
20
23
 
21
- HashDiff answers the question above using an opinionated approach:
24
+ Hashdiff answers the question above using an opinionated approach:
22
25
 
23
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]`.
24
27
  * The change set can be represented using the dot-syntax representation. For example, `[['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]`.
@@ -43,7 +46,7 @@ Two simple hashes:
43
46
  a = {a:3, b:2}
44
47
  b = {}
45
48
 
46
- diff = HashDiff.diff(a, b)
49
+ diff = Hashdiff.diff(a, b)
47
50
  diff.should == [['-', 'a', 3], ['-', 'b', 2]]
48
51
  ```
49
52
 
@@ -53,7 +56,7 @@ More complex hashes:
53
56
  a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
54
57
  b = {a:{y:3}, b:{y:3, z:30}}
55
58
 
56
- diff = HashDiff.diff(a, b)
59
+ diff = Hashdiff.diff(a, b)
57
60
  diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]
58
61
  ```
59
62
 
@@ -63,7 +66,7 @@ Arrays in hashes:
63
66
  a = {a:[{x:2, y:3, z:4}, {x:11, y:22, z:33}], b:{x:3, z:45}}
64
67
  b = {a:[{y:3}, {x:11, z:33}], b:{y:22}}
65
68
 
66
- diff = HashDiff.best_diff(a, b)
69
+ diff = Hashdiff.best_diff(a, b)
67
70
  diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-', 'b.x', 3], ['-', 'b.z', 45], ['+', 'b.y', 22]]
68
71
  ```
69
72
 
@@ -72,27 +75,28 @@ diff.should == [['-', 'a[0].x', 2], ['-', 'a[0].z', 4], ['-', 'a[1].y', 22], ['-
72
75
  patch example:
73
76
 
74
77
  ```ruby
75
- a = {a: 3}
76
- b = {a: {a1: 1, a2: 2}}
78
+ a = {'a' => 3}
79
+ b = {'a' => {'a1' => 1, 'a2' => 2}}
77
80
 
78
- diff = HashDiff.diff(a, b)
79
- HashDiff.patch!(a, diff).should == b
81
+ diff = Hashdiff.diff(a, b)
82
+ Hashdiff.patch!(a, diff).should == b
80
83
  ```
81
84
 
82
85
  unpatch example:
83
86
 
84
87
  ```ruby
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}]
88
+ a = [{'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}, {'x' => 5, 'y' => 6, 'z' => 3}, 1]
89
+ b = [1, {'a' => 1, 'b' => 2, 'c' => 3, 'e' => 5}]
87
90
 
88
- diff = HashDiff.diff(a, b) # diff two array is OK
89
- HashDiff.unpatch!(b, diff).should == a
91
+ diff = Hashdiff.diff(a, b) # diff two array is OK
92
+ Hashdiff.unpatch!(b, diff).should == a
90
93
  ```
91
94
 
92
95
  ### Options
93
96
 
94
- There are six options available: `:delimiter`, `:similarity`,
95
- `:strict`, `:numeric_tolerance`, `:strip` and `:case_insensitive`.
97
+ There are eight options available: `:delimiter`, `:similarity`,
98
+ `:strict`, `:numeric_tolerance`, `:strip`, `:case_insensitive`, `:array_path`
99
+ and `:use_lcs`
96
100
 
97
101
  #### `:delimiter`
98
102
 
@@ -102,7 +106,7 @@ You can specify `:delimiter` to be something other than the default dot. For exa
102
106
  a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
103
107
  b = {a:{y:3}, b:{y:3, z:30}}
104
108
 
105
- diff = HashDiff.diff(a, b, :delimiter => '\t')
109
+ diff = Hashdiff.diff(a, b, :delimiter => '\t')
106
110
  diff.should == [['-', 'a\tx', 2], ['-', 'a\tz', 4], ['-', 'b\tx', 3], ['~', 'b\tz', 45, 30], ['+', 'b\ty', 3]]
107
111
  ```
108
112
 
@@ -122,7 +126,7 @@ The :numeric_tolerance option allows for a small numeric tolerance.
122
126
  a = {x:5, y:3.75, z:7}
123
127
  b = {x:6, y:3.76, z:7}
124
128
 
125
- diff = HashDiff.diff(a, b, :numeric_tolerance => 0.1)
129
+ diff = Hashdiff.diff(a, b, :numeric_tolerance => 0.1)
126
130
  diff.should == [["~", "x", 5, 6]]
127
131
  ```
128
132
 
@@ -134,22 +138,77 @@ The :strip option strips all strings before comparing.
134
138
  a = {x:5, s:'foo '}
135
139
  b = {x:6, s:'foo'}
136
140
 
137
- diff = HashDiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :strip => true })
141
+ diff = Hashdiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :strip => true })
138
142
  diff.should == [["~", "x", 5, 6]]
139
143
  ```
140
144
 
141
145
  #### `:case_insensitive`
142
146
 
143
- The :case_insensitive option makes string comparisions ignore case.
147
+ The :case_insensitive option makes string comparisons ignore case.
144
148
 
145
149
  ```ruby
146
150
  a = {x:5, s:'FooBar'}
147
151
  b = {x:6, s:'foobar'}
148
152
 
149
- diff = HashDiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :case_insensitive => true })
153
+ diff = Hashdiff.diff(a, b, :comparison => { :numeric_tolerance => 0.1, :case_insensitive => true })
150
154
  diff.should == [["~", "x", 5, 6]]
151
155
  ```
152
156
 
157
+ #### `:array_path`
158
+
159
+ The :array_path option represents the path of the diff in an array rather than
160
+ a string. This can be used to show differences in between hash key types and
161
+ is useful for `patch!` when used on hashes without string keys.
162
+
163
+ ```ruby
164
+ a = {x:5}
165
+ b = {'x'=>6}
166
+
167
+ diff = Hashdiff.diff(a, b, :array_path => true)
168
+ diff.should == [['-', [:x], 5], ['+', ['x'], 6]]
169
+ ```
170
+
171
+ For cases where there are arrays in paths their index will be added to the path.
172
+ ```ruby
173
+ a = {x:[0,1]}
174
+ b = {x:[0,2]}
175
+
176
+ diff = Hashdiff.diff(a, b, :array_path => true)
177
+ diff.should == [["-", [:x, 1], 1], ["+", [:x, 1], 2]]
178
+ ```
179
+
180
+ This shouldn't cause problems if you are comparing an array with a hash:
181
+
182
+ ```ruby
183
+ a = {x:{0=>1}}
184
+ b = {x:[1]}
185
+
186
+ diff = Hashdiff.diff(a, b, :array_path => true)
187
+ diff.should == [["~", [:x], {0=>1}, [1]]]
188
+ ```
189
+
190
+ #### `:use_lcs`
191
+
192
+ The :use_lcs option is used to specify whether a
193
+ [Longest common subsequence](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem)
194
+ (LCS) algorithm is used to determine differences in arrays. This defaults to
195
+ `true` but can be changed to `false` for significantly faster array comparisons
196
+ (O(n) complexity rather than O(n<sup>2</sup>) for LCS).
197
+
198
+ When :use_lcs is false the results of array comparisons have a tendency to
199
+ show changes at indexes rather than additions and subtractions when :use_lcs is
200
+ true.
201
+
202
+ Note, currently the :similarity option has no effect when :use_lcs is false.
203
+
204
+ ```ruby
205
+ a = {x: [0, 1, 2]}
206
+ b = {x: [0, 2, 2, 3]}
207
+
208
+ diff = Hashdiff.diff(a, b, :use_lcs => false)
209
+ diff.should == [["~", "x[1]", 1, 2], ["+", "x[3]", 3]]
210
+ ```
211
+
153
212
  #### Specifying a custom comparison method
154
213
 
155
214
  It's possible to specify how the values of a key should be compared.
@@ -158,7 +217,7 @@ It's possible to specify how the values of a key should be compared.
158
217
  a = {a:'car', b:'boat', c:'plane'}
159
218
  b = {a:'bus', b:'truck', c:' plan'}
160
219
 
161
- diff = HashDiff.diff(a, b) do |path, obj1, obj2|
220
+ diff = Hashdiff.diff(a, b) do |path, obj1, obj2|
162
221
  case path
163
222
  when /a|b|c/
164
223
  obj1.length == obj2.length
@@ -174,7 +233,7 @@ The yielded params of the comparison block is `|path, obj1, obj2|`, in which pat
174
233
  a = {a:'car', b:['boat', 'plane'] }
175
234
  b = {a:'bus', b:['truck', ' plan'] }
176
235
 
177
- diff = HashDiff.diff(a, b) do |path, obj1, obj2|
236
+ diff = Hashdiff.diff(a, b) do |path, obj1, obj2|
178
237
  case path
179
238
  when 'b[*]'
180
239
  obj1.length == obj2.length
@@ -186,6 +245,8 @@ diff.should == [["~", "a", "car", "bus"], ["~", "b[1]", "plane", " plan"], ["-",
186
245
 
187
246
  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
247
 
248
+ 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.
249
+
189
250
  #### Sorting arrays before comparison
190
251
 
191
252
  An order difference alone between two arrays can create too many diffs to be useful. Consider sorting them prior to diffing.
@@ -194,14 +255,18 @@ An order difference alone between two arrays can create too many diffs to be use
194
255
  a = {a:'car', b:['boat', 'plane'] }
195
256
  b = {a:'car', b:['plane', 'boat'] }
196
257
 
197
- HashDiff.diff(a, b) => [["+", "b[0]", "plane"], ["-", "b[2]", "plane"]]
258
+ Hashdiff.diff(a, b) => [["+", "b[0]", "plane"], ["-", "b[2]", "plane"]]
198
259
 
199
260
  b[:b].sort!
200
261
 
201
- HashDiff.diff(a, b) => []
262
+ Hashdiff.diff(a, b) => []
202
263
  ```
203
264
 
204
- ## License
265
+ ## Maintainers
266
+
267
+ - Krzysztof Rybka ([@krzysiek1507](https://github.com/krzysiek1507))
268
+ - Fengyun Liu ([@liufengyun](https://github.com/liufengyun))
205
269
 
206
- HashDiff is distributed under the MIT-LICENSE.
270
+ ## License
207
271
 
272
+ Hashdiff is distributed under the MIT-LICENSE.
data/Rakefile CHANGED
@@ -1,13 +1,18 @@
1
- $:.push File.expand_path("../lib", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+
5
+ require 'rubocop/rake_task'
2
6
 
3
7
  require 'bundler'
4
8
  Bundler::GemHelper.install_tasks
5
9
 
6
10
  require 'rspec/core/rake_task'
7
11
 
8
- task :default => :spec
12
+ RuboCop::RakeTask.new
13
+
14
+ task default: %w[spec rubocop]
9
15
 
10
16
  RSpec::Core::RakeTask.new(:spec) do |spec|
11
- spec.pattern = "./spec/**/*_spec.rb"
17
+ spec.pattern = './spec/**/*_spec.rb'
12
18
  end
13
-
data/changelog.md CHANGED
@@ -1,8 +1,34 @@
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
+
9
+ ## v0.3.9 2019-04-22
10
+
11
+ * Performance tweak (thanks @krzysiek1507: #51 #52 #53)
12
+
13
+ ## v0.3.8 2018-12-30
14
+
15
+ * Add Rubocop and drops Ruby 1.9 support #47
16
+
17
+ ## v0.3.7 2017-10-08
18
+
19
+ * remove 1.8.7 support from gemspec #39
20
+
21
+ ## v0.3.6 2017-08-22
22
+
23
+ * add option `use_lcs` #35
24
+
25
+ ## v0.3.5 2017-08-06
26
+
27
+ * add option `array_path` #34
28
+
3
29
  ## v0.3.4 2017-05-01
4
30
 
5
- * performance improvement of HashDiff#similar? #31
31
+ * performance improvement of `#similar?` #31
6
32
 
7
33
  ## v0.3.2 2016-12-27
8
34
 
@@ -44,7 +70,7 @@
44
70
  ## v0.0.5 2012-7-1
45
71
 
46
72
  * fix a bug in judging whehter two objects are similiar.
47
- * add more spec test for HashDiff.best_diff
73
+ * add more spec test for `.best_diff`
48
74
 
49
75
  ## v0.0.4 2012-6-24
50
76
 
@@ -57,4 +83,3 @@ For example, `diff({a:2, c:[4, 5]}, {a:2}) will generate following output:
57
83
  instead of following:
58
84
 
59
85
  [['-', 'c[0]', 4], ['-', 'c[1]', 5], ['-', 'c', []]]
60
-
data/hashdiff.gemspec CHANGED
@@ -1,25 +1,40 @@
1
- $LOAD_PATH << File.expand_path("../lib", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH << File.expand_path('lib', __dir__)
2
4
  require 'hashdiff/version'
3
5
 
4
6
  Gem::Specification.new do |s|
5
- s.name = %q{hashdiff}
6
- s.version = HashDiff::VERSION
7
+ s.name = 'hashdiff'
8
+ s.version = Hashdiff::VERSION
7
9
  s.license = 'MIT'
8
- s.summary = %q{ HashDiff is a diff lib to compute the smallest difference between two hashes. }
9
- s.description = %q{ HashDiff is a diff lib to compute the smallest difference between two hashes. }
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. '
10
12
 
11
13
  s.files = `git ls-files`.split("\n")
12
14
  s.test_files = `git ls-files -- Appraisals {spec}/*`.split("\n")
13
15
 
14
16
  s.require_paths = ['lib']
15
- s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
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.'
19
+
20
+ s.authors = ['Liu Fengyun']
21
+ s.email = ['liufengyunchina@gmail.com']
16
22
 
17
- s.authors = ["Liu Fengyun"]
18
- s.email = ["liufengyunchina@gmail.com"]
23
+ s.homepage = 'https://github.com/liufengyun/hashdiff'
19
24
 
20
- s.homepage = "https://github.com/liufengyun/hashdiff"
25
+ s.add_development_dependency('bluecloth')
26
+ s.add_development_dependency('rspec', '~> 2.0')
27
+ s.add_development_dependency('rubocop')
28
+ s.add_development_dependency('rubocop-rspec')
29
+ s.add_development_dependency('yard')
21
30
 
22
- s.add_development_dependency("rspec", "~> 2.0")
23
- s.add_development_dependency("yard")
24
- s.add_development_dependency("bluecloth")
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
25
40
  end
@@ -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