subhash 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ihash.rb ADDED
@@ -0,0 +1,605 @@
1
+ # encoding: UTF-8
2
+
3
+ # (c) Copyright 2014 Hewlett-Packard Development Company, L.P.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ # Recursive Hash added to the Hash class
18
+ class Hash
19
+ # Recursive Hash deep level found counter
20
+ # This function will returns the count of deep level of recursive hash.
21
+ # * *Args* :
22
+ # - +p+ : Array of string or symbols. keys tree to follow and check
23
+ # existence in yVal.
24
+ #
25
+ # * *Returns* :
26
+ # - +integer+ : Represents how many deep level was found in the recursive
27
+ # hash
28
+ #
29
+ # * *Raises* :
30
+ # No exceptions
31
+ #
32
+ # Example: (implemented in spec)
33
+ #
34
+ # yVal = { :test => {:test2 => 'value1', :test3 => 'value2'},
35
+ # :test4 => 'value3'}
36
+ #
37
+ # yVal can be represented like:
38
+ #
39
+ # yVal:
40
+ # test:
41
+ # test2 = 'value1'
42
+ # test3 = 'value2'
43
+ # test4 = 'value3'
44
+ #
45
+ # so:
46
+ # # test is found
47
+ # yVal.rh_lexist?(:test) => 1
48
+ #
49
+ # # no test5
50
+ # yVal.rh_lexist?(:test5) => 0
51
+ #
52
+ # # :test/:test2 tree is found
53
+ # yVal.rh_lexist?(:test, :test2) => 2
54
+ #
55
+ # # :test/:test2 is found (value = 2), but :test5 was not found in this tree
56
+ # yVal.rh_lexist?(:test, :test2, :test5) => 2
57
+ #
58
+ # # :test/:test2 is found (value = 2), and 'value1' was found in this tree,
59
+ # # but not as a Hash
60
+ # yVal.rh_lexist?(:test, :test2, 'value1') => 2
61
+ #
62
+ # # :test was found. but :test/:test5 tree was not found. so level 1, ok.
63
+ # yVal.rh_lexist?(:test, :test5 ) => 1
64
+ #
65
+ # # it is like searching for nothing...
66
+ # yVal.rh_lexist? => 0
67
+
68
+ def rh_lexist?(*p)
69
+ p = p.flatten
70
+
71
+ return 0 if p.length == 0
72
+
73
+ if p.length == 1
74
+ return 1 if self.key?(p[0])
75
+ return 0
76
+ end
77
+ return 0 unless self.key?(p[0])
78
+ ret = 0
79
+ if [Hash, Array].include?(self[p[0]].class)
80
+ ret = self[p[0]].rh_lexist?(p.drop(1))
81
+ end
82
+ 1 + ret
83
+ end
84
+
85
+ # Recursive Hash deep level existence
86
+ #
87
+ # * *Args* :
88
+ # - +p+ : Array of string or symbols. keys tree to follow and check
89
+ # existence in yVal.
90
+ #
91
+ # * *Returns* :
92
+ # - +boolean+ : Returns True if the deep level of recursive hash is found.
93
+ # false otherwise
94
+ #
95
+ # * *Raises* :
96
+ # No exceptions
97
+ #
98
+ # Example:(implemented in spec)
99
+ #
100
+ # yVal = { :test => {:test2 => 'value1', :test3 => 'value2'},
101
+ # :test4 => 'value3'}
102
+ #
103
+ # yVal can be represented like:
104
+ #
105
+ # yVal:
106
+ # test:
107
+ # test2 = 'value1'
108
+ # test3 = 'value2'
109
+ # test4 = 'value3'
110
+ #
111
+ # so:
112
+ # # test is found
113
+ # yVal.rh_exist?(:test) => True
114
+ #
115
+ # # no test5
116
+ # yVal.rh_exist?(:test5) => False
117
+ #
118
+ # # :test/:test2 tree is found
119
+ # yVal.rh_exist?(:test, :test2) => True
120
+ #
121
+ # # :test/:test2 is found (value = 2), but :test5 was not found in this tree
122
+ # yVal.rh_exist?(:test, :test2, :test5) => False
123
+ #
124
+ # # :test was found. but :test/:test5 tree was not found. so level 1, ok.
125
+ # yVal.rh_exist?(:test, :test5 ) => False
126
+ #
127
+ # # it is like searching for nothing...
128
+ # yVal.rh_exist? => nil
129
+ def rh_exist?(*p)
130
+ p = p.flatten
131
+
132
+ return nil if p.length == 0
133
+
134
+ count = p.length
135
+ (rh_lexist?(*p) == count)
136
+ end
137
+
138
+ # Recursive Hash/Array Get
139
+ # This function will returns the level of recursive subhash structure found.
140
+ # Thanks to Regexp support in keys or string interpretation, rh_get can
141
+ # extract data found in the tree matching and build a simplified structure as
142
+ # result.
143
+ #
144
+ # * *Args* :
145
+ # - +p+ : Array of String/Symbol or Regexp. It contains the list of keys
146
+ # tree to follow and check existence in self.
147
+ #
148
+ # In the subhash structure, each hierachie tree level is a Hash or an
149
+ # Array.
150
+ #
151
+ # At a given level the top key will be interpreted as follow if the object
152
+ # is:
153
+ # - Hash:
154
+ # You can define key matching with Regexp or with a structured string:
155
+ # - Regexp or '/<Regexp>/' or '[<Regexp>]' :
156
+ # For each key in self tree, matching <Regexp>, the value associated
157
+ # to the key will be added as a new item in an array.
158
+ #
159
+ # - '{<regexp>}' :
160
+ # For each key in self tree, matching <Regexp>, the value and the key
161
+ # will be added as a new item (key => value) in a Hash
162
+ #
163
+ # - Array:
164
+ # If the top key type is:
165
+ # - Fixnum : The key is considered as the Array index.
166
+ # it will get in self[p[0]]
167
+ #
168
+ # - String/Symbol : loop in array to find in Hash, the key to get value
169
+ # and go on in the tree if possible. it must return an array of result
170
+ # In case of symbol matching, the symbol is converted in string with a
171
+ # ':' at pos 0 in the string, then start match process.
172
+ #
173
+ # * *Returns* :
174
+ # - +value+ : Represents the data found in the tree. Can be of any type.
175
+ #
176
+ # * *Raises* :
177
+ # No exceptions
178
+ #
179
+ # Example:(implemented in spec)
180
+ #
181
+ # data =
182
+ # :test:
183
+ # :test2 = 'value1'
184
+ # :test3 => 'value2'
185
+ # :test4 = 'value3'
186
+ # :arr1: [ 4, 'value4']
187
+ # :arr2:
188
+ # - :test5 = 'value5'
189
+ # :test6 = 'value6'
190
+ # - :test7 = 'value7'
191
+ # :test8
192
+ # :test5 = 'value8'
193
+ #
194
+ # so:
195
+ # data.rh_get(:test) => {:test2 => 'value1', :test3 => 'value2'}
196
+ # data.rh_get(:test5) => nil
197
+ # data.rh_get(:test, :test2) => 'value1'
198
+ # data.rh_get(:test, :test2, :test5) => nil
199
+ # data.rh_get(:test, :test5 ) => nil
200
+ # data.rh_get => { :test => {:test2 => 'value1', :test3 => 'value2'},
201
+ # :test4 => 'value3'}
202
+ #
203
+ # New features:
204
+ # data.rh_get(:test, /^test/) # => []
205
+ # data.rh_get(:test, /^:test/) # => ['value1', 'value2']
206
+ # data.rh_get(:test, /^:test.*/) # => ['value1', 'value2']
207
+ # data.rh_get(:test, '/:test.*/') # => ['value1', 'value2']
208
+ # data.rh_get(:test, '/:test.*/') # => ['value1', 'value2']
209
+ # data.rh_get(:test, '[/:test.*/]') # => ['value1', 'value2']
210
+ # data.rh_get(:test, '{/:test2/}') # => {:test2 => 'value1'}
211
+ # data.rh_get(:test, '{/test/}')
212
+ # # => {:test2 => 'value1', :test3 => 'value2'}
213
+ # data.rh_get(:test, '{:test2}') # => {:test2 => 'value1'}
214
+ # data.rh_get(:test, '{:test2}') # => {:test2 => 'value1'}
215
+ #
216
+ # data.rh_get(:arr2, :test6) # => ['value6']
217
+ # data.rh_get(:arr2, :test8) # => nil
218
+ # data.rh_get(:arr2, :test5) # => ['value5', 'value8']
219
+ # data.rh_get(/arr/, :test5) # => [['value5', 'value8']]
220
+ # data.rh_get('{/arr/}', :test5) # => { :arr2 => ['value5', 'value8']}
221
+ # data.rh_get('{/arr/}', '{:test5}')
222
+ # # => { :arr2 => {:test5 => ['value5', 'value8']}}
223
+ #
224
+ # data.rh_get(:arr2, 2) # => nil
225
+ # data.rh_get(:arr2, 0) # => { :test5 = 'value5',
226
+ # # :test6 = 'value6'}
227
+ # data.rh_get(:arr2, 1) # => { :test7 = 'value7',
228
+ # # :test8
229
+ # # :test5 = 'value8' }
230
+ # data.rh_get(:arr2, 1, :test7) # => 'value7'
231
+ # data.rh_get(:arr2, 0, :test7) # => nil
232
+ #
233
+ def rh_get(*p)
234
+ p = p.flatten
235
+ return self if p.length == 0
236
+
237
+ key = p[0]
238
+ sp = p.drop(1)
239
+
240
+ re, res, opts = _regexp(key)
241
+ return _keys_match(re, res, sp, opts) unless re.nil?
242
+
243
+ if sp.length == 0
244
+ return self[key] if self.key?(key)
245
+ return nil
246
+ end
247
+
248
+ return self[key].rh_get(sp) if [Array, Hash].include?(self[key].class)
249
+ nil
250
+ end
251
+
252
+ # Recursive Hash Set
253
+ # This function will build a recursive hash according to the '*p' key tree.
254
+ # if yVal is not nil, it will be updated.
255
+ #
256
+ # * *Args* :
257
+ # - +p+ : Array of string or symbols. keys tree to follow and check
258
+ # existence in yVal.
259
+ #
260
+ # * *Returns* :
261
+ # - +value+ : the value set.
262
+ #
263
+ # * *Raises* :
264
+ # No exceptions
265
+ #
266
+ # Example:(implemented in spec)
267
+ #
268
+ # data = {}.rh_set(:test) # => {}
269
+ #
270
+ # data.rh_set(:test, :test2) # => {:test2 => :test}
271
+ #
272
+ # data.rh_set(:test, :test2, :test5) # => {:test2 => {:test5 => :test} }
273
+ #
274
+ # data.rh_set(:test, :test5 ) # => {:test2 => {:test5 => :test},
275
+ # # :test5 => :test }
276
+ #
277
+ # data.rh_set('blabla', :test2, 'text')
278
+ # # => {:test2 => {:test5 => :test,
279
+ # # 'text' => 'blabla'},
280
+ # # :test5 => :test }
281
+ def rh_set(value, *p)
282
+ p = p.flatten
283
+ return nil if p.length == 0
284
+
285
+ if p.length == 1
286
+ self[p[0]] = value
287
+ return value
288
+ end
289
+
290
+ self[p[0]] = {} unless self[p[0]].is_a?(Hash)
291
+ self[p[0]].rh_set(value, p.drop(1))
292
+ end
293
+
294
+ # Recursive Hash delete
295
+ # This function will remove the last key defined by the key tree
296
+ #
297
+ # * *Args* :
298
+ # - +p+ : Array of string or symbols. keys tree to follow and check
299
+ # existence in self.
300
+ #
301
+ # * *Returns* :
302
+ # - +value+ : The Hash updated.
303
+ #
304
+ # * *Raises* :
305
+ # No exceptions
306
+ #
307
+ # Example:(implemented in spec)
308
+ #
309
+ # data = {{:test2 => { :test5 => :test,
310
+ # 'text' => 'blabla' },
311
+ # :test5 => :test}}
312
+ #
313
+ # data.rh_del(:test) # => nil
314
+ # # data = no change
315
+ #
316
+ # data.rh_del(:test, :test2) # => nil
317
+ # # data = no change
318
+ #
319
+ # data.rh_del(:test2, :test5) # => {:test5 => :test}
320
+ # # data = { :test2 => { 'text' => 'blabla' },
321
+ # # :test5 => :test} }
322
+ #
323
+ # data.rh_del(:test2, 'text') # => { 'text' => 'blabla' }
324
+ # # data = { :test2 => {},
325
+ # # :test5 => :test} }
326
+ #
327
+ # data.rh_del(:test5) # => {:test5 => :test}
328
+ # # data = { :test2 => {} }
329
+ #
330
+ def rh_del(*p)
331
+ p = p.flatten
332
+
333
+ return nil if p.length == 0
334
+
335
+ return delete(p[0]) if p.length == 1
336
+
337
+ return nil if self[p[0]].nil?
338
+ self[p[0]].rh_del(p.drop(1))
339
+ end
340
+
341
+ # Move levels (default level 1) of tree keys to become symbol.
342
+ #
343
+ # * *Args* :
344
+ # - +levels+: level of key tree to update.
345
+ # * *Returns* :
346
+ # - a new hash of hashes updated. Original Hash is not updated anymore.
347
+ #
348
+ # examples:
349
+ # With hdata = { :test => { :test2 => { :test5 => :test,
350
+ # 'text' => 'blabla' },
351
+ # 'test5' => 'test' }}
352
+ #
353
+ # hdata.rh_key_to_symbol(1) return no diff
354
+ # hdata.rh_key_to_symbol(2) return "test5" is replaced by :test5
355
+ # # hdata = { :test => { :test2 => { :test5 => :test,
356
+ # # 'text' => 'blabla' },
357
+ # # :test5 => 'test' }}
358
+ # rh_key_to_symbol(3) return "test5" replaced by :test5, and "text" to :text
359
+ # # hdata = { :test => { :test2 => { :test5 => :test,
360
+ # # :text => 'blabla' },
361
+ # # :test5 => 'test' }}
362
+ # rh_key_to_symbol(4) same like rh_key_to_symbol(3)
363
+
364
+ def rh_key_to_symbol(levels = 1)
365
+ result = {}
366
+ each do |key, value|
367
+ new_key = key
368
+ new_key = key.to_sym if key.is_a?(String)
369
+ if value.is_a?(Hash) && levels > 1
370
+ value = value.rh_key_to_symbol(levels - 1)
371
+ end
372
+ result[new_key] = value
373
+ end
374
+ result
375
+ end
376
+
377
+ # Check if levels of tree keys are all symbols.
378
+ #
379
+ # * *Args* :
380
+ # - +levels+: level of key tree to update.
381
+ # * *Returns* :
382
+ # - true : one key path is not symbol.
383
+ # - false : all key path are symbols.
384
+ # * *Raises* :
385
+ # Nothing
386
+ #
387
+ # examples:
388
+ # With hdata = { :test => { :test2 => { :test5 => :test,
389
+ # 'text' => 'blabla' },
390
+ # 'test5' => 'test' }}
391
+ #
392
+ # hdata.rh_key_to_symbol?(1) return false
393
+ # hdata.rh_key_to_symbol?(2) return true
394
+ # hdata.rh_key_to_symbol?(3) return true
395
+ # hdata.rh_key_to_symbol?(4) return true
396
+ def rh_key_to_symbol?(levels = 1)
397
+ each do |key, value|
398
+ return true if key.is_a?(String)
399
+
400
+ res = false
401
+ if levels > 1 && value.is_a?(Hash)
402
+ res = value.rh_key_to_symbol?(levels - 1)
403
+ end
404
+ return true if res
405
+ end
406
+ false
407
+ end
408
+
409
+ # return an exact clone of the recursive Array and Hash contents.
410
+ #
411
+ # * *Args* :
412
+ #
413
+ # * *Returns* :
414
+ # - Recursive Array/Hash cloned. Other kind of objects are kept referenced.
415
+ # * *Raises* :
416
+ # Nothing
417
+ #
418
+ # examples:
419
+ # hdata = { :test => { :test2 => { :test5 => :test,
420
+ # 'text' => 'blabla' },
421
+ # 'test5' => 'test' },
422
+ # :array => [{ :test => :value1 }, 2, { :test => :value3 }]}
423
+ #
424
+ # hclone = hdata.rh_clone
425
+ # hclone[:test] = "test"
426
+ # hdata[:test] == { :test2 => { :test5 => :test,'text' => 'blabla' }
427
+ # # => true
428
+ # hclone[:array].pop
429
+ # hdata[:array].length != hclone[:array].length
430
+ # # => true
431
+ # hclone[:array][0][:test] = "value2"
432
+ # hdata[:array][0][:test] != hclone[:array][0][:test]
433
+ # # => true
434
+ def rh_clone
435
+ result = {}
436
+ each do |key, value|
437
+ if [Array, Hash].include?(value.class)
438
+ result[key] = value.rh_clone
439
+ else
440
+ result[key] = value
441
+ end
442
+ end
443
+ result
444
+ end
445
+
446
+ # Merge the current Hash object (self) cloned with a Hash/Array tree contents
447
+ # (data).
448
+ #
449
+ # 'self' is used as original data to merge to.
450
+ # 'data' is used as data to merged to clone of 'self'. If you want to update
451
+ # 'self', use rh_merge!
452
+ #
453
+ # if 'self' or 'data' contains a Hash tree, the merge will be executed
454
+ # recursively.
455
+ #
456
+ # The current function will execute the merge of the 'self' keys with the top
457
+ # keys in 'data'
458
+ #
459
+ # The merge can be controlled by an additionnal Hash key '__*' in each
460
+ # 'self' key.
461
+ # If both a <key> exist in 'self' and 'data', the following decision is made:
462
+ # - if both 'self' and 'data' key contains an Hash or and Array, a recursive
463
+ # merge if Hash or update if Array, is started.
464
+ #
465
+ # - if 'self' <key> contains an Hash or an Array, but not 'data' <key>, then
466
+ # 'self' <key> will be set to the 'data' <key> except if 'self' <Key> has
467
+ # :__struct_changing: true
468
+ # data <key> value can set :unset value
469
+ #
470
+ # - if 'self' <key> is :unset and 'data' <key> is any value
471
+ # 'self' <key> value is set with 'data' <key> value.
472
+ # 'data' <key> value can contains a Hash with :__no_unset: true to
473
+ # protect this key against the next merge. (next config layer merge)
474
+ #
475
+ # - if 'data' <key> exist but not in 'self', 'data' <key> is just added.
476
+ #
477
+ # - if 'data' & 'self' <key> exist, 'self'<key> is updated except if key is in
478
+ # :__protected array list.
479
+ #
480
+ # * *Args* :
481
+ # - hash : Hash data to merge.
482
+ #
483
+ # * *Returns* :
484
+ # - Recursive Array/Hash merged.
485
+ #
486
+ # * *Raises* :
487
+ # Nothing
488
+ #
489
+ # examples:
490
+ #
491
+ def rh_merge(data)
492
+ _rh_merge(clone, data)
493
+ end
494
+
495
+ # Merge the current Hash object (self) with a Hash/Array tree contents (data).
496
+ #
497
+ # For details on this functions, see #rh_merge
498
+ #
499
+ def rh_merge!(data)
500
+ _rh_merge(self, data)
501
+ end
502
+ end
503
+
504
+ # Recursive Hash added to the Hash class
505
+ class Hash
506
+ private
507
+
508
+ def _keys_match(re, res, sp, opts)
509
+ empty = false
510
+ empty = opts.include?('e') if opts
511
+
512
+ _keys_match_loop(re, res, sp)
513
+
514
+ return res if empty || res.length > 0
515
+ nil
516
+ end
517
+
518
+ def _keys_match_loop(re, res, sp)
519
+ keys.sort.each do |k|
520
+ k_re = _key_to_s(k)
521
+ next unless re.match(k_re)
522
+
523
+ if sp.length == 0
524
+ _update_res(res, k, self[k])
525
+ else
526
+ v = self[k].rh_get(sp) if [Array, Hash].include?(self[k].class)
527
+
528
+ _update_res(res, k, v) unless v.nil?
529
+ end
530
+ end
531
+ end
532
+
533
+ # Internal function which do the real merge task by #rh_merge and #rh_merge!
534
+ #
535
+ # See #rh_merge for details
536
+ #
537
+ def _rh_merge(result, data)
538
+ return _rh_merge_choose_data(result, data) unless data.is_a?(Hash)
539
+
540
+ data.each do |key, _value|
541
+ next if [:__struct_changing, :__protected].include?(key)
542
+
543
+ _do_rh_merge(result, key, data)
544
+ end
545
+ [:__struct_changing, :__protected].each do |key|
546
+ # Refuse merge by default if key data type are different.
547
+ # This assume that the first layer merge has set
548
+ # :__unset as a Hash, and :__protected as an Array.
549
+
550
+ _do_rh_merge(result, key, data, true) if data.key?(key)
551
+
552
+ # Remove all control element in arrays
553
+ _rh_remove_control(result[key]) if result.key?(key)
554
+ end
555
+
556
+ result
557
+ end
558
+
559
+ def _rh_merge_choose_data(result, data)
560
+ # return result as first one impose the type between Hash/Array.
561
+ return result if [Hash, Array].include?(result.class) ||
562
+ [Hash, Array].include?(data.class)
563
+
564
+ data
565
+ end
566
+ # Internal function to execute the merge on one key provided by #_rh_merge
567
+ #
568
+ # if refuse_discordance is true, then result[key] can't be updated if
569
+ # stricly not of same type.
570
+ def _do_rh_merge(result, key, data, refuse_discordance = false)
571
+ value = data[key]
572
+
573
+ return if _rh_merge_do_add_key(result, key, value)
574
+
575
+ return if _rh_merge_recursive(result, key, data)
576
+
577
+ return if refuse_discordance
578
+
579
+ return unless _rh_struct_changing_ok?(result, key, data)
580
+
581
+ return unless _rh_merge_ok?(result, key)
582
+
583
+ _rh_merge_do_upd_key(result, key, value)
584
+ end
585
+
586
+ def _rh_merge_do_add_key(result, key, value)
587
+ unless result.key?(key) || value == :unset
588
+ result[key] = value # New key added
589
+ return true
590
+ end
591
+ false
592
+ end
593
+
594
+ def _rh_merge_do_upd_key(result, key, value)
595
+ if value == :unset
596
+ result.delete(key) if result.key?(key)
597
+ return
598
+ end
599
+
600
+ result[key] = value # Key updated
601
+ end
602
+
603
+ include Rh
604
+ include RhGet
605
+ end