hashdiff 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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