subhash 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4438f0e738721e6c962927ac4ca4c9d532bd1257
4
+ data.tar.gz: c70b1bb50ed69081842f8e27ec5f0589b0544d50
5
+ SHA512:
6
+ metadata.gz: 0a6d52b5a2a87cc274534d49a5b53bcd9a834512ef3b6b25861eb660536e06614c84ad72c448b6ea237f913b1d9aa6d17e9fa985a2198f7f5bf398721bbdb172
7
+ data.tar.gz: 04c7cad6557626bcb02e479ae44bf155e9f810c843f80b7e2769c3f8cc99fd4062e3891eec8890dea383eb0a590115a6b43f6758180479e80d77938bf9cec72b
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ [gerrit]
2
+ host=review.forj.forj.io
3
+ port=29418
4
+ project=forj-oss/rhash.git
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,33 @@
1
+ # Copyright 2013 Hewlett-Packard Development Company, L.P.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # use: rubocop --show-cops
16
+ # to validate configuration.
17
+ # rubocop config
18
+ AllCops:
19
+ Include:
20
+ - '**/Rakefile'
21
+ Style/HashSyntax:
22
+ EnforcedStyle: hash_rockets
23
+
24
+ # lets start with 40, but 10 is way to small..
25
+ Metrics/MethodLength:
26
+ Max: 40
27
+ # If Method length is increased, class length need to be extended as well.
28
+ Metrics/ClassLength:
29
+ Max: 150
30
+
31
+ # allow arguments to be longer than 15
32
+ Metrics/AbcSize:
33
+ Max: 40
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rh.gemspec
4
+ gemspec
@@ -0,0 +1,115 @@
1
+ # Recursive Hash of Hashes/Arrays
2
+
3
+ SubHash is a set of tools which facilitate data structured as tree of Hash or Array to be
4
+ set and get.
5
+
6
+ Imagine you have a yaml file to load. If your yaml file is well structured, it will be stored
7
+ in memory as Hash of Hashes, or even Arrays or any kind of type recognized by ruby.
8
+
9
+ So, if you want to access a data in a strong tree of hash, how do you write this?
10
+
11
+ puts data[key_l1][key_l2][key_l3][mykey]
12
+ => myvalue
13
+
14
+ What's happen if key\_l1 doesn't exist? or exist but contains a nil or any other value instead of a Hash?
15
+ An exception is generated. So, you need to add exception to avoid this.
16
+
17
+ But your code may become a little complex if you need to check if those layers exists or not...
18
+
19
+ So, imagine that instead of that, you do:
20
+
21
+ puts data.rh_get(key_l1, key_l2, key_l3, mykey)
22
+
23
+ Interesting, right?
24
+
25
+ Imagine the set, now:
26
+
27
+ data.rh_set(MyNewValue, key_l1, key_l2, key_l3, mykey)
28
+
29
+ Seems easier, right?
30
+
31
+ And you can check if keys exists, as well as until which level of Hash, I found the path to my key:
32
+
33
+ data.rh_exist?(key_l1, key_l2, key_l3, mykey)
34
+ => true/false
35
+
36
+ data.rh_lexist?(key_l1, key_l2, key_l3, mykey)
37
+ => can be 0, 1, 2 ,3 or 4, depending on the path existence to access mykey...
38
+
39
+ If you think this can help you, subhash provides the following functions to the Ruby Hash/Array object:
40
+
41
+ - Hash.rh\_get
42
+ - Hash.rh\_set
43
+ - Hash.rh\_exist? or Hash.rh\_lexist?
44
+ - Hash.rh\_del
45
+ - Hash.rh\_merge
46
+ - Array.rh\_merge
47
+
48
+ Thanks to set of functions, you can easily create a tree of Hash or Array.
49
+
50
+ Ex:
51
+
52
+ data = {}
53
+
54
+ data.rh_set(:value, :level1, :level2, :level3)
55
+ # => { :level1 => { :level2 => { :level3 => :value } } }
56
+
57
+ # And it is easy to add or remove
58
+ data.rh_set(:value2, :level1, :level2, :level3)
59
+ # => { :level1 => { :level2 => { :level3 => :value } } }
60
+ data.rh_set(:value, :level1, :level2, :level3_second)
61
+ # => { :level1 => { :level2 => { :level3 => :value, :level3_second => :value } } }
62
+ data.rh_del(:level1, :level2, :level3)
63
+ # => { :level1 => { :level2 => { } } }
64
+
65
+ ## Installation
66
+
67
+ Add this line to your application's Gemfile:
68
+
69
+ ```ruby
70
+ gem 'subhash'
71
+ ```
72
+
73
+ And then execute:
74
+
75
+ $ bundle
76
+
77
+ Or install it yourself as:
78
+
79
+ $ gem install subhash
80
+
81
+ ## Usage
82
+
83
+ To use Recursive Hash, just add this require in your code:
84
+
85
+ require 'subhash'
86
+
87
+ Hash and Array object are enhanced and provide those features to any Hash or Array. So, you just need to
88
+ call wanted functions.
89
+
90
+ Ex:
91
+
92
+ data = Hash.new
93
+ data.rh_set(:value, :key_lvl1)
94
+
95
+ ## Development
96
+
97
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
98
+
99
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
100
+
101
+ ## Contributing
102
+
103
+ 1. clone it ( https://review.forj.io/forj-oss/rhash )
104
+ 2. add an alias to push your code
105
+
106
+ alias git-push='git push origin HEAD:refs/for/$(git branch -v|grep "^\*"|awk '\''{printf $2}'\'')'
107
+
108
+ or install git-review
109
+
110
+ 3. push your code
111
+ * with git-push
112
+ * with git-review. See http://www.mediawiki.org/wiki/Gerrit/git-review
113
+
114
+
115
+ Enjoy!!!
@@ -0,0 +1,27 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+ require 'rdoc/task'
5
+
6
+ task :default => [:lint, :spec]
7
+
8
+ desc 'Run the specs.'
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = 'spec/*_spec.rb'
11
+ t.rspec_opts = '-f doc'
12
+ end
13
+
14
+ desc 'Generate lorj documentation'
15
+ RDoc::Task.new do |rdoc|
16
+ rdoc.main = 'README.md'
17
+ rdoc.rdoc_files.include('README.md', 'lib', 'example', 'bin')
18
+ end
19
+
20
+ desc 'Run RuboCop on the project'
21
+ RuboCop::RakeTask.new(:lint) do |task|
22
+ task.formatters = ['progress']
23
+ task.verbose = true
24
+ task.fail_on_error = true
25
+ end
26
+
27
+ task :build => [:lint, :spec]
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rh'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ puts "Recursive Hash module
14
+ Short list of functions:
15
+ - Hash.rh_set(value, *keys) => value
16
+ - Hash.rh_get(*keys) => value found or nil
17
+ - Hash.rh_exist?(*keys) => true/false
18
+ - Hash.rh_lexist?(*keys) => number
19
+ - Hash.rh_clone() => Hash
20
+ - Hash.rh_del(*keys) => data removed
21
+ - Hash.rh_merge(hash) => Hash/Array merged
22
+ - Hash.rh_merge!(hash) => Hash/Array merged
23
+ - Array.rh_merge(Array) => Array/Hash merged
24
+ - Array.rh_merge!(Array) => Array/Hash merged"
25
+
26
+ require 'irb'
27
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,844 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # (c) Copyright 2014 Hewlett-Packard Development Company, L.P.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'rubygems'
19
+ require 'yaml'
20
+
21
+ # Adding rh_clone at object level. This be able to use a generic rh_clone
22
+ # redefined per object Hash and Array.
23
+ class Object
24
+ alias_method :rh_clone, :clone
25
+ end
26
+
27
+ # Rh common module included in Hash and Array class.
28
+ module Rh
29
+ public
30
+
31
+ # Function which will parse arrays in hierarchie and will remove any control
32
+ # element (index 0)
33
+ def rh_remove_control(result)
34
+ return unless [Hash, Array].include?(result.class)
35
+
36
+ if result.is_a?(Hash)
37
+ result.each { |elem| rh_remove_control(elem) }
38
+ else
39
+ result.delete_at(0) if result[0].is_a?(Hash) && result[0].key?(:__control)
40
+ result.each_index { |index| rh_remove_control(result[index]) }
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Internal function to determine if result and data key contains both Hash or
47
+ # Array and if so, do the merge task on those sub Hash/Array
48
+ #
49
+ def _rh_merge_recursive(result, key, data)
50
+ return false unless [Array, Hash].include?(data.class)
51
+
52
+ value = data[key]
53
+ return false unless [Array, Hash].include?(value.class) &&
54
+ value.class == result[key].class
55
+
56
+ if object_id == result.object_id
57
+ result[key].rh_merge!(value)
58
+ else
59
+ result[key] = result[key].rh_merge(value)
60
+ end
61
+
62
+ true
63
+ end
64
+
65
+ # Internal function to determine if changing from Hash/Array to anything else
66
+ # is authorized or not.
67
+ #
68
+ # The structure is changing if `result` or `value` move from Hash/Array to any
69
+ # other type.
70
+ #
71
+ # * *Args*:
72
+ # - result: Merged Hash or Array structure.
73
+ # - key : Key in result and data.
74
+ # - data : Hash or Array structure to merge.
75
+ #
76
+ # * *returns*:
77
+ # - +true+ : if :__struct_changing == true
78
+ # - +false+ : otherwise.
79
+ def _rh_struct_changing_ok?(result, key, data)
80
+ return true unless [Array, Hash].include?(data[key].class) ||
81
+ [Array, Hash].include?(result[key].class)
82
+
83
+ # result or value are structure (Hash or Array)
84
+ if result.is_a?(Hash)
85
+ control = result[:__struct_changing]
86
+ else
87
+ control = result[0][:__struct_changing]
88
+ key -= 1
89
+ end
90
+ return true if control.is_a?(Array) && control.include?(key)
91
+
92
+ false
93
+ end
94
+
95
+ # Internal function to determine if a data merged can be updated by any
96
+ # other object like Array, String, etc...
97
+ #
98
+ # The decision is given by a :__unset setting.
99
+ #
100
+ # * *Args*:
101
+ # - Hash/Array data to replace.
102
+ # - key: string or symbol.
103
+ #
104
+ # * *returns*:
105
+ # - +false+ : if key is found in :__protected Array.
106
+ # - +true+ : otherwise.
107
+ def _rh_merge_ok?(result, key)
108
+ if result.is_a?(Hash)
109
+ control = result[:__protected]
110
+ else
111
+ control = result[0][:__protected]
112
+ key -= 1
113
+ end
114
+
115
+ return false if control.is_a?(Array) && control.include?(key)
116
+
117
+ true
118
+ end
119
+
120
+ def _rh_control_tags
121
+ [:__remove, :__remove_index, :__add, :__add_index,
122
+ :__protected, :__struct_changing, :__control]
123
+ end
124
+ end
125
+
126
+ # Recursive Hash added to the Hash class
127
+ class Hash
128
+ # Recursive Hash deep level found counter
129
+ # This function will returns the count of deep level of recursive hash.
130
+ # * *Args* :
131
+ # - +p+ : Array of string or symbols. keys tree to follow and check
132
+ # existence in yVal.
133
+ #
134
+ # * *Returns* :
135
+ # - +integer+ : Represents how many deep level was found in the recursive
136
+ # hash
137
+ #
138
+ # * *Raises* :
139
+ # No exceptions
140
+ #
141
+ # Example: (implemented in spec)
142
+ #
143
+ # yVal = { :test => {:test2 => 'value1', :test3 => 'value2'},
144
+ # :test4 => 'value3'}
145
+ #
146
+ # yVal can be represented like:
147
+ #
148
+ # yVal:
149
+ # test:
150
+ # test2 = 'value1'
151
+ # test3 = 'value2'
152
+ # test4 = 'value3'
153
+ #
154
+ # so:
155
+ # # test is found
156
+ # yVal.rh_lexist?(:test) => 1
157
+ #
158
+ # # no test5
159
+ # yVal.rh_lexist?(:test5) => 0
160
+ #
161
+ # # :test/:test2 tree is found
162
+ # yVal.rh_lexist?(:test, :test2) => 2
163
+ #
164
+ # # :test/:test2 is found (value = 2), but :test5 was not found in this tree
165
+ # yVal.rh_lexist?(:test, :test2, :test5) => 2
166
+ #
167
+ # # :test was found. but :test/:test5 tree was not found. so level 1, ok.
168
+ # yVal.rh_lexist?(:test, :test5 ) => 1
169
+ #
170
+ # # it is like searching for nothing...
171
+ # yVal.rh_lexist? => 0
172
+
173
+ def rh_lexist?(*p)
174
+ p = p.flatten
175
+
176
+ return 0 if p.length == 0
177
+
178
+ if p.length == 1
179
+ return 1 if self.key?(p[0])
180
+ return 0
181
+ end
182
+ return 0 unless self.key?(p[0])
183
+ ret = 0
184
+ ret = self[p[0]].rh_lexist?(p.drop(1)) if self[p[0]].is_a?(Hash)
185
+ 1 + ret
186
+ end
187
+
188
+ # Recursive Hash deep level existence
189
+ #
190
+ # * *Args* :
191
+ # - +p+ : Array of string or symbols. keys tree to follow and check
192
+ # existence in yVal.
193
+ #
194
+ # * *Returns* :
195
+ # - +boolean+ : Returns True if the deep level of recursive hash is found.
196
+ # false otherwise
197
+ #
198
+ # * *Raises* :
199
+ # No exceptions
200
+ #
201
+ # Example:(implemented in spec)
202
+ #
203
+ # yVal = { :test => {:test2 => 'value1', :test3 => 'value2'},
204
+ # :test4 => 'value3'}
205
+ #
206
+ # yVal can be represented like:
207
+ #
208
+ # yVal:
209
+ # test:
210
+ # test2 = 'value1'
211
+ # test3 = 'value2'
212
+ # test4 = 'value3'
213
+ #
214
+ # so:
215
+ # # test is found
216
+ # yVal.rh_exist?(:test) => True
217
+ #
218
+ # # no test5
219
+ # yVal.rh_exist?(:test5) => False
220
+ #
221
+ # # :test/:test2 tree is found
222
+ # yVal.rh_exist?(:test, :test2) => True
223
+ #
224
+ # # :test/:test2 is found (value = 2), but :test5 was not found in this tree
225
+ # yVal.rh_exist?(:test, :test2, :test5) => False
226
+ #
227
+ # # :test was found. but :test/:test5 tree was not found. so level 1, ok.
228
+ # yVal.rh_exist?(:test, :test5 ) => False
229
+ #
230
+ # # it is like searching for nothing...
231
+ # yVal.rh_exist? => nil
232
+ def rh_exist?(*p)
233
+ p = p.flatten
234
+
235
+ return nil if p.length == 0
236
+
237
+ count = p.length
238
+ (rh_lexist?(*p) == count)
239
+ end
240
+
241
+ # Recursive Hash Get
242
+ # This function will returns the level of recursive hash was found.
243
+ # * *Args* :
244
+ # - +p+ : Array of string or symbols. keys tree to follow and check
245
+ # existence in yVal.
246
+ #
247
+ # * *Returns* :
248
+ # - +value+ : Represents the data found in the tree. Can be of any type.
249
+ #
250
+ # * *Raises* :
251
+ # No exceptions
252
+ #
253
+ # Example:(implemented in spec)
254
+ #
255
+ # yVal = { :test => {:test2 => 'value1', :test3 => 'value2'},
256
+ # :test4 => 'value3'}
257
+ #
258
+ # yVal can be represented like:
259
+ #
260
+ # yVal:
261
+ # test:
262
+ # test2 = 'value1'
263
+ # test3 = 'value2'
264
+ # test4 = 'value3'
265
+ #
266
+ # so:
267
+ # yVal.rh_get(:test) => {:test2 => 'value1', :test3 => 'value2'}
268
+ # yVal.rh_get(:test5) => nil
269
+ # yVal.rh_get(:test, :test2) => 'value1'
270
+ # yVal.rh_get(:test, :test2, :test5) => nil
271
+ # yVal.rh_get(:test, :test5 ) => nil
272
+ # yVal.rh_get => { :test => {:test2 => 'value1', :test3 => 'value2'},
273
+ # :test4 => 'value3'}
274
+ def rh_get(*p)
275
+ p = p.flatten
276
+ return self if p.length == 0
277
+
278
+ if p.length == 1
279
+ return self[p[0]] if self.key?(p[0])
280
+ return nil
281
+ end
282
+ return self[p[0]].rh_get(p.drop(1)) if self[p[0]].is_a?(Hash)
283
+ nil
284
+ end
285
+
286
+ # Recursive Hash Set
287
+ # This function will build a recursive hash according to the '*p' key tree.
288
+ # if yVal is not nil, it will be updated.
289
+ #
290
+ # * *Args* :
291
+ # - +p+ : Array of string or symbols. keys tree to follow and check
292
+ # existence in yVal.
293
+ #
294
+ # * *Returns* :
295
+ # - +value+ : the value set.
296
+ #
297
+ # * *Raises* :
298
+ # No exceptions
299
+ #
300
+ # Example:(implemented in spec)
301
+ #
302
+ # yVal = {}
303
+ #
304
+ # yVal.rh_set(:test) => nil
305
+ # # yVal = {}
306
+ #
307
+ # yVal.rh_set(:test5) => nil
308
+ # # yVal = {}
309
+ #
310
+ # yVal.rh_set(:test, :test2) => :test
311
+ # # yVal = {:test2 => :test}
312
+ #
313
+ # yVal.rh_set(:test, :test2, :test5) => :test
314
+ # # yVal = {:test2 => {:test5 => :test} }
315
+ #
316
+ # yVal.rh_set(:test, :test5 ) => :test
317
+ # # yVal = {:test2 => {:test5 => :test}, :test5 => :test }
318
+ #
319
+ # yVal.rh_set('blabla', :test2, 'text') => :test
320
+ # # yVal = {:test2 => {:test5 => :test, 'text' => 'blabla'},
321
+ # :test5 => :test }
322
+ def rh_set(value, *p)
323
+ p = p.flatten
324
+ return nil if p.length == 0
325
+
326
+ if p.length == 1
327
+ self[p[0]] = value
328
+ return value
329
+ end
330
+
331
+ self[p[0]] = {} unless self[p[0]].is_a?(Hash)
332
+ self[p[0]].rh_set(value, p.drop(1))
333
+ end
334
+
335
+ # Recursive Hash delete
336
+ # This function will remove the last key defined by the key tree
337
+ #
338
+ # * *Args* :
339
+ # - +p+ : Array of string or symbols. keys tree to follow and check
340
+ # existence in yVal.
341
+ #
342
+ # * *Returns* :
343
+ # - +value+ : The Hash updated.
344
+ #
345
+ # * *Raises* :
346
+ # No exceptions
347
+ #
348
+ # Example:(implemented in spec)
349
+ #
350
+ # yVal = {{:test2 => { :test5 => :test,
351
+ # 'text' => 'blabla' },
352
+ # :test5 => :test}}
353
+ #
354
+ #
355
+ # yVal.rh_del(:test) => nil
356
+ # # yVal = no change
357
+ #
358
+ # yVal.rh_del(:test, :test2) => nil
359
+ # # yVal = no change
360
+ #
361
+ # yVal.rh_del(:test2, :test5) => {:test5 => :test}
362
+ # # yVal = {:test2 => {:test5 => :test} }
363
+ #
364
+ # yVal.rh_del(:test, :test2)
365
+ # # yVal = {:test2 => {:test5 => :test} }
366
+ #
367
+ # yVal.rh_del(:test, :test5)
368
+ # # yVal = {:test2 => {} }
369
+ #
370
+ def rh_del(*p)
371
+ p = p.flatten
372
+
373
+ return nil if p.length == 0
374
+
375
+ return delete(p[0]) if p.length == 1
376
+
377
+ return nil if self[p[0]].nil?
378
+ self[p[0]].rh_del(p.drop(1))
379
+ end
380
+
381
+ # Move levels (default level 1) of tree keys to become symbol.
382
+ #
383
+ # * *Args* :
384
+ # - +levels+: level of key tree to update.
385
+ # * *Returns* :
386
+ # - a new hash of hashes updated. Original Hash is not updated anymore.
387
+ #
388
+ # examples:
389
+ # With hdata = { :test => { :test2 => { :test5 => :test,
390
+ # 'text' => 'blabla' },
391
+ # 'test5' => 'test' }}
392
+ #
393
+ # hdata.rh_key_to_symbol(1) return no diff
394
+ # hdata.rh_key_to_symbol(2) return "test5" is replaced by :test5
395
+ # # hdata = { :test => { :test2 => { :test5 => :test,
396
+ # # 'text' => 'blabla' },
397
+ # # :test5 => 'test' }}
398
+ # rh_key_to_symbol(3) return "test5" replaced by :test5, and "text" to :text
399
+ # # hdata = { :test => { :test2 => { :test5 => :test,
400
+ # # :text => 'blabla' },
401
+ # # :test5 => 'test' }}
402
+ # rh_key_to_symbol(4) same like rh_key_to_symbol(3)
403
+
404
+ def rh_key_to_symbol(levels = 1)
405
+ result = {}
406
+ each do |key, value|
407
+ new_key = key
408
+ new_key = key.to_sym if key.is_a?(String)
409
+ if value.is_a?(Hash) && levels > 1
410
+ value = value.rh_key_to_symbol(levels - 1)
411
+ end
412
+ result[new_key] = value
413
+ end
414
+ result
415
+ end
416
+
417
+ # Check if levels of tree keys are all symbols.
418
+ #
419
+ # * *Args* :
420
+ # - +levels+: level of key tree to update.
421
+ # * *Returns* :
422
+ # - true : one key path is not symbol.
423
+ # - false : all key path are symbols.
424
+ # * *Raises* :
425
+ # Nothing
426
+ #
427
+ # examples:
428
+ # With hdata = { :test => { :test2 => { :test5 => :test,
429
+ # 'text' => 'blabla' },
430
+ # 'test5' => 'test' }}
431
+ #
432
+ # hdata.rh_key_to_symbol?(1) return false
433
+ # hdata.rh_key_to_symbol?(2) return true
434
+ # hdata.rh_key_to_symbol?(3) return true
435
+ # hdata.rh_key_to_symbol?(4) return true
436
+ def rh_key_to_symbol?(levels = 1)
437
+ each do |key, value|
438
+ return true if key.is_a?(String)
439
+
440
+ res = false
441
+ if levels > 1 && value.is_a?(Hash)
442
+ res = value.rh_key_to_symbol?(levels - 1)
443
+ end
444
+ return true if res
445
+ end
446
+ false
447
+ end
448
+
449
+ # return an exact clone of the recursive Array and Hash contents.
450
+ #
451
+ # * *Args* :
452
+ #
453
+ # * *Returns* :
454
+ # - Recursive Array/Hash cloned. Other kind of objects are kept referenced.
455
+ # * *Raises* :
456
+ # Nothing
457
+ #
458
+ # examples:
459
+ # hdata = { :test => { :test2 => { :test5 => :test,
460
+ # 'text' => 'blabla' },
461
+ # 'test5' => 'test' },
462
+ # :array => [{ :test => :value1 }, 2, { :test => :value3 }]}
463
+ #
464
+ # hclone = hdata.rh_clone
465
+ # hclone[:test] = "test"
466
+ # hdata[:test] == { :test2 => { :test5 => :test,'text' => 'blabla' }
467
+ # # => true
468
+ # hclone[:array].pop
469
+ # hdata[:array].length != hclone[:array].length
470
+ # # => true
471
+ # hclone[:array][0][:test] = "value2"
472
+ # hdata[:array][0][:test] != hclone[:array][0][:test]
473
+ # # => true
474
+ def rh_clone
475
+ result = {}
476
+ each do |key, value|
477
+ if [Array, Hash].include?(value.class)
478
+ result[key] = value.rh_clone
479
+ else
480
+ result[key] = value
481
+ end
482
+ end
483
+ result
484
+ end
485
+
486
+ # Merge the current Hash object (self) cloned with a Hash/Array tree contents
487
+ # (data).
488
+ #
489
+ # 'self' is used as original data to merge to.
490
+ # 'data' is used as data to merged to clone of 'self'. If you want to update
491
+ # 'self', use rh_merge!
492
+ #
493
+ # if 'self' or 'data' contains a Hash tree, the merge will be executed
494
+ # recursively.
495
+ #
496
+ # The current function will execute the merge of the 'self' keys with the top
497
+ # keys in 'data'
498
+ #
499
+ # The merge can be controlled by an additionnal Hash key '__*' in each
500
+ # 'self' key.
501
+ # If both a <key> exist in 'self' and 'data', the following decision is made:
502
+ # - if both 'self' and 'data' key contains an Hash or and Array, a recursive
503
+ # merge if Hash or update if Array, is started.
504
+ #
505
+ # - if 'self' <key> contains an Hash or an Array, but not 'data' <key>, then
506
+ # 'self' <key> will be set to the 'data' <key> except if 'self' <Key> has
507
+ # :__struct_changing: true
508
+ # data <key> value can set :unset value
509
+ #
510
+ # - if 'self' <key> is :unset and 'data' <key> is any value
511
+ # 'self' <key> value is set with 'data' <key> value.
512
+ # 'data' <key> value can contains a Hash with :__no_unset: true to
513
+ # protect this key against the next merge. (next config layer merge)
514
+ #
515
+ # - if 'data' <key> exist but not in 'self', 'data' <key> is just added.
516
+ #
517
+ # - if 'data' & 'self' <key> exist, 'self'<key> is updated except if key is in
518
+ # :__protected array list.
519
+ #
520
+ # * *Args* :
521
+ # - hash : Hash data to merge.
522
+ #
523
+ # * *Returns* :
524
+ # - Recursive Array/Hash merged.
525
+ #
526
+ # * *Raises* :
527
+ # Nothing
528
+ #
529
+ # examples:
530
+ #
531
+ def rh_merge(data)
532
+ _rh_merge(clone, data)
533
+ end
534
+
535
+ # Merge the current Hash object (self) with a Hash/Array tree contents (data).
536
+ #
537
+ # For details on this functions, see #rh_merge
538
+ #
539
+ def rh_merge!(data)
540
+ _rh_merge(self, data)
541
+ end
542
+ end
543
+
544
+ # Recursive Hash added to the Hash class
545
+ class Hash
546
+ private
547
+
548
+ # Internal function which do the real merge task by #rh_merge and #rh_merge!
549
+ #
550
+ # See #rh_merge for details
551
+ #
552
+ def _rh_merge(result, data)
553
+ return _rh_merge_choose_data(result, data) unless data.is_a?(Hash)
554
+
555
+ data.each do |key, _value|
556
+ next if [:__struct_changing, :__protected].include?(key)
557
+
558
+ _do_rh_merge(result, key, data)
559
+ end
560
+ [:__struct_changing, :__protected].each do |key|
561
+ # Refuse merge by default if key data type are different.
562
+ # This assume that the first layer merge has set
563
+ # :__unset as a Hash, and :__protected as an Array.
564
+
565
+ _do_rh_merge(result, key, data, true) if data.key?(key)
566
+
567
+ # Remove all control element in arrays
568
+ rh_remove_control(result[key]) if result.key?(key)
569
+ end
570
+
571
+ result
572
+ end
573
+
574
+ def _rh_merge_choose_data(result, data)
575
+ # return result as first one impose the type between Hash/Array.
576
+ return result if [Hash, Array].include?(result.class) ||
577
+ [Hash, Array].include?(data.class)
578
+
579
+ data
580
+ end
581
+ # Internal function to execute the merge on one key provided by #_rh_merge
582
+ #
583
+ # if refuse_discordance is true, then result[key] can't be updated if
584
+ # stricly not of same type.
585
+ def _do_rh_merge(result, key, data, refuse_discordance = false)
586
+ value = data[key]
587
+
588
+ return if _rh_merge_do_add_key(result, key, value)
589
+
590
+ return if _rh_merge_recursive(result, key, data)
591
+
592
+ return if refuse_discordance
593
+
594
+ return unless _rh_struct_changing_ok?(result, key, data)
595
+
596
+ return unless _rh_merge_ok?(result, key)
597
+
598
+ _rh_merge_do_upd_key(result, key, value)
599
+ end
600
+
601
+ def _rh_merge_do_add_key(result, key, value)
602
+ unless result.key?(key) || value == :unset
603
+ result[key] = value # New key added
604
+ return true
605
+ end
606
+ false
607
+ end
608
+
609
+ def _rh_merge_do_upd_key(result, key, value)
610
+ if value == :unset
611
+ result.delete(key) if result.key?(key)
612
+ return
613
+ end
614
+
615
+ result[key] = value # Key updated
616
+ end
617
+
618
+ include Rh
619
+ end
620
+
621
+ # Defines rh_clone for Array
622
+ class Array
623
+ # return an exact clone of the recursive Array and Hash contents.
624
+ #
625
+ # * *Args* :
626
+ #
627
+ # * *Returns* :
628
+ # - Recursive Array/Hash cloned.
629
+ # * *Raises* :
630
+ # Nothing
631
+ #
632
+ # examples:
633
+ # hdata = { :test => { :test2 => { :test5 => :test,
634
+ # 'text' => 'blabla' },
635
+ # 'test5' => 'test' },
636
+ # :array => [{ :test => :value1 }, 2, { :test => :value3 }]}
637
+ #
638
+ # hclone = hdata.rh_clone
639
+ # hclone[:test] = "test"
640
+ # hdata[:test] == { :test2 => { :test5 => :test,'text' => 'blabla' }
641
+ # # => true
642
+ # hclone[:array].pop
643
+ # hdata[:array].length != hclone[:array].length
644
+ # # => true
645
+ # hclone[:array][0][:test] = "value2"
646
+ # hdata[:array][0][:test] != hclone[:array][0][:test]
647
+ # # => true
648
+ def rh_clone
649
+ result = []
650
+ each do |value|
651
+ begin
652
+ result << value.rh_clone
653
+ rescue
654
+ result << value
655
+ end
656
+ end
657
+ result
658
+ end
659
+
660
+ # This function is part of the rh_merge functionnality adapted for Array.
661
+ #
662
+ # To provide Array recursivity, we uses the element index.
663
+ #
664
+ # **Warning!** If the Array order has changed (sort/random) the index changed
665
+ # and can generate unwanted result.
666
+ #
667
+ # To implement recursivity, and some specific Array management (add/remove)
668
+ # you have to create an Hash and insert it at position 0 in the 'self' Array.
669
+ #
670
+ # **Warning!** If you create an Array, where index 0 contains a Hash, this
671
+ # Hash will be considered as the Array control element.
672
+ # If the first index of your Array is not a Hash, an empty Hash will be
673
+ # inserted at position 0.
674
+ #
675
+ # 'data' has the same restriction then 'self' about the first element.
676
+ # 'data' can influence the rh_merge Array behavior, by updating the first
677
+ # element.
678
+ #
679
+ # The first Hash element has the following attributes:
680
+ #
681
+ # - :__struct_changing: Array of index which accepts to move from a structured
682
+ # data (Hash/Array) to another structure or type.
683
+ #
684
+ # Ex: Hash => Array, Array => Integer
685
+ #
686
+ # - :__protected: Array of index which protects against update from 'data'
687
+ #
688
+ # - :__remove: Array of elements to remove. each element are remove with
689
+ # Array.delete function. See Array delete function for details.
690
+ #
691
+ # - :__remove_index: Array of indexes to remove.
692
+ # Each element are removed with Array.delete_at function.
693
+ # It starts from the highest index until the lowest.
694
+ # See Array delete function for details.
695
+ #
696
+ # **NOTE**: __remove and __remove_index cannot be used together.
697
+ # if both are set, __remove is choosen
698
+ #
699
+ # **NOTE** : __remove* is executed before __add*
700
+ #
701
+ # - :__add: Array of elements to add. Those elements are systematically added
702
+ # at the end of the Array. See Array.<< for details.
703
+ #
704
+ # - :__add_index: Hash of index(key) + Array of data(value) to add.
705
+ # The index listed refer to merged 'self' Array. several elements with same
706
+ # index are grouply inserted in the index.
707
+ # ex:
708
+ # [:data3].rh_merge({:__add_index => [0 => [:data1, :data2]]})
709
+ # => [{}, :data1, :data2, :data3]
710
+ #
711
+ # **NOTE**: __add and __add_index cannot be used together.
712
+ # if both are set, __add is choosen
713
+ #
714
+ # How merge is executed:
715
+ #
716
+ # Starting at index 0, each index of 'data' and 'self' are used to compare
717
+ # indexed data.
718
+ # - If 'data' index 0 has not an Hash, the 'self' index 0 is just skipped.
719
+ # - If 'data' index 0 has the 'control' Hash, the array will be updated
720
+ # according to :__add and :__remove arrays.
721
+ # when done, those attributes are removed
722
+ #
723
+ # - For all next index (1 => 'data'.length), data are compared
724
+ #
725
+ # - If the 'data' length is > than 'self' length
726
+ # addtionnal indexed data are added to 'self'
727
+ #
728
+ # - If index element exist in both 'data' and 'self',
729
+ # 'self' indexed data is updated/merged according to control.
730
+ # 'data' indexed data can use :unset to remove the data at this index
731
+ # nil is also supported. But the index won't be removed. data will just
732
+ # be set to nil
733
+ #
734
+ # when all Arrays elements are merged, rh_merge will:
735
+ # - remove 'self' elements containing ':unset'
736
+ #
737
+ # - merge 'self' data at index 0 with 'data' found index 0
738
+ #
739
+ def rh_merge(data)
740
+ _rh_merge(clone, data)
741
+ end
742
+
743
+ def rh_merge!(data)
744
+ _rh_merge(self, data)
745
+ end
746
+
747
+ private
748
+
749
+ def _rh_merge(result, data)
750
+ data = data.clone
751
+ data_control = _rh_merge_control(data)
752
+ result_control = _rh_merge_control(result)
753
+
754
+ _rh_do_control_merge(result_control, result, data_control, data)
755
+
756
+ (1..(data.length - 1)).each do |index|
757
+ _rh_do_array_merge(result, index, data)
758
+ end
759
+
760
+ (-(result.length - 1)..-1).each do |index|
761
+ result.delete_at(index.abs) if result[index.abs] == :unset
762
+ end
763
+
764
+ _rh_do_array_merge(result, 0, [data_control])
765
+ rh_remove_control(result[0]) # Remove all control elements in tree of arrays
766
+
767
+ result
768
+ end
769
+
770
+ def _rh_do_array_merge(result, index, data)
771
+ return if _rh_merge_recursive(result, index, data)
772
+
773
+ return unless _rh_struct_changing_ok?(result, index, data)
774
+
775
+ return unless _rh_merge_ok?(result, index)
776
+
777
+ result[index] = data[index] unless data[index] == :kept
778
+ end
779
+
780
+ # Get the control element. or create it if missing.
781
+ def _rh_merge_control(array)
782
+ unless array[0].is_a?(Hash)
783
+ array.insert(0, :__control => true)
784
+ return array[0]
785
+ end
786
+
787
+ _rh_control_tags.each do |prop|
788
+ if array[0].key?(prop)
789
+ array[0][:__control] = true
790
+ return array[0]
791
+ end
792
+ end
793
+
794
+ array.insert(0, :__control => true)
795
+
796
+ array[0]
797
+ end
798
+
799
+ # Do the merge according to :__add and :__remove
800
+ def _rh_do_control_merge(_result_control, result, data_control, _data)
801
+ if data_control[:__remove].is_a?(Array)
802
+ _rh_do_control_remove(result, data_control[:__remove])
803
+ elsif data_control[:__remove_index].is_a?(Array)
804
+ index_to_remove = data_control[:__remove_index].uniq.sort.reverse
805
+ _rh_do_control_remove_index(result, index_to_remove)
806
+ end
807
+
808
+ data_control.delete(:__remove)
809
+ data_control.delete(:__remove_index)
810
+
811
+ if data_control[:__add].is_a?(Array)
812
+ data_control[:__add].each { |element| result << element }
813
+ elsif data_control[:__add_index].is_a?(Hash)
814
+ _rh_do_control_add_index(result, data_control[:__add_index].sort)
815
+ end
816
+
817
+ data_control.delete(:__add)
818
+ data_control.delete(:__add_index)
819
+ end
820
+
821
+ def _rh_do_control_add_index(result, add_index)
822
+ add_index.reverse_each do |elements_to_insert|
823
+ next unless elements_to_insert.is_a?(Array) &&
824
+ elements_to_insert[0].is_a?(Fixnum) &&
825
+ elements_to_insert[1].is_a?(Array)
826
+
827
+ index = elements_to_insert[0] + 1
828
+ elements = elements_to_insert[1]
829
+
830
+ elements.reverse_each { |element| result.insert(index, element) }
831
+ end
832
+ end
833
+
834
+ # do the element removal.
835
+ def _rh_do_control_remove(result, remove)
836
+ remove.each { |element| result.delete(element) }
837
+ end
838
+
839
+ def _rh_do_control_remove_index(result, index_to_remove)
840
+ index_to_remove.each { |index| result.delete_at(index + 1) }
841
+ end
842
+
843
+ include Rh
844
+ end
@@ -0,0 +1,4 @@
1
+ # Recursive Hash
2
+ module SubHash
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'subhash/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'subhash'
8
+ spec.version = SubHash::VERSION
9
+ spec.authors = ['Christophe Larsonneur']
10
+ spec.email = ['clarsonneur@gmail.com']
11
+
12
+ spec.summary = 'Recursive Hash of hashes/Array management'
13
+ spec.description = 'Hash and Array object enhanced to manage Hash of Hash/Array easily.'
14
+ spec.homepage = "https://github.com/forj-oss/rhash"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.9'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency "rspec", "~> 3.1.0"
24
+ spec.add_development_dependency "rubocop", "~> 0.30.0"
25
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: subhash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Christophe Larsonneur
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 3.1.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 3.1.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.30.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.30.0
69
+ description: Hash and Array object enhanced to manage Hash of Hash/Array easily.
70
+ email:
71
+ - clarsonneur@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .gitreview
78
+ - .rspec
79
+ - .rubocop.yml
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - lib/subhash.rb
86
+ - lib/subhash/version.rb
87
+ - subhash.gemspec
88
+ homepage: https://github.com/forj-oss/rhash
89
+ licenses: []
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.1.11
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Recursive Hash of hashes/Array management
111
+ test_files: []
112
+ has_rdoc: