hashdiff 0.3.4 → 0.3.5

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: 4ba86448df8842fe6323572feb2c89110954b775
4
+ data.tar.gz: e3d610f32c07e65e06f825e107371caaaf2e5896
5
5
  SHA512:
6
- metadata.gz: e80104dc13c931f44352337d0027fd774f8a54a67c2ae15b2870e05723c41aafbe00be06d5073078ccfcf44b320e3ae03693623f0d61cab95fa040671e708599
7
- data.tar.gz: 8efef12e5b3ff1c9a78dfc7655f121211d91e6b7d62521d33a5376b202e31913da440e2754f5ae0a3f1cba7189e2006db033027786c7402410ecf5dac58d44f2
6
+ metadata.gz: 7e7b28d277160a603df99c83022f6b8bd7bb27ac5d467e351e5834ba955195ed493249b7a7e213dafe0cb1aa630c5de46fe11a95b5942de4ba9c1f4e66262ec3
7
+ data.tar.gz: d5290cbc0b5c97038cce213e627fc20fcaf4e7405888c217c78b949c8fc98990ab6d3ae18b0f91d2909c069ffccc20a9d0d45aa572419252aa6eae960df6491d
data/Gemfile CHANGED
@@ -3,4 +3,5 @@ gemspec
3
3
 
4
4
  group :test do
5
5
  gem 'rake', '< 11'
6
+ gem 'codecov'
6
7
  end
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: 3}
76
- b = {a: {a1: 1, a2: 2}}
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: 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}]
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 six options available: `:delimiter`, `:similarity`,
95
- `:strict`, `:numeric_tolerance`, `:strip` and `:case_insensitive`.
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 comparisions ignore case.
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.
@@ -1,5 +1,9 @@
1
1
  # Change Log
2
2
 
3
+ ## v0.3.5 2017-08-06
4
+
5
+ * add option `array_path` #34
6
+
3
7
  ## v0.3.4 2017-05-01
4
8
 
5
9
  * performance improvement of HashDiff#similar? #31
@@ -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
- result.concat(diff(obj1[pair[0]], obj2[pair[1]], opts.merge(:prefix => "#{opts[:prefix]}[#{pair[0]}]")))
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 << ['-', "#{opts[:prefix]}[#{change[1]}]", change[2]]
120
+ result << ['-', change_key, change[2]]
114
121
  elsif change[0] == '+'
115
- result << ['+', "#{opts[:prefix]}[#{change[1]}]", change[2]]
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
- custom_result = custom_compare(opts[:comparison], "#{prefix}#{k}", obj1[k], nil)
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 << ['-', "#{prefix}#{k}", obj1[k]]
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 {|k| result.concat(diff(obj1[k], obj2[k], opts.merge(:prefix => "#{prefix}#{k}"))) }
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], "#{prefix}#{k}", nil, obj2[k])
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 << ['+', "#{prefix}#{k}", obj2[k]]
158
+ result << ['+', change_key, obj2[k]]
152
159
  end
153
160
  end
154
161
  end
@@ -6,7 +6,7 @@ module HashDiff
6
6
  def self.lcs(a, b, options = {})
7
7
  opts = { :similarity => 0.8 }.merge!(options)
8
8
 
9
- opts[:prefix] = "#{opts[:prefix]}[*]"
9
+ opts[:prefix] = prefix_append_array_index(opts[:prefix], '*', opts)
10
10
 
11
11
  return [] if a.size == 0 or b.size == 0
12
12
 
@@ -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 = decode_property_path(change[1], delimiter)
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 last_part.is_a?(Integer)
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 last_part.is_a?(Integer)
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 = decode_property_path(change[1], delimiter)
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 last_part.is_a?(Integer)
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 last_part.is_a?(Integer)
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]
@@ -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
- parts = path.split(delimiter).collect do |part|
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
@@ -1,3 +1,3 @@
1
1
  module HashDiff
2
- VERSION = '0.3.4'
2
+ VERSION = '0.3.5'
3
3
  end
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,10 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+ if ENV['CI'] == 'true'
4
+ require 'codecov'
5
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
6
+ end
7
+
1
8
  $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
2
9
 
3
10
  require 'rubygems'
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
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-05-01 00:00:00.000000000 Z
11
+ date: 2017-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec