subhash 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +19 -0
- data/Rakefile +45 -8
- data/build/build_with_proxy.sh +53 -0
- data/files/bundle.sh +19 -0
- data/files/proxy.sh +23 -0
- data/lib/iarray.rb +365 -0
- data/lib/ihash.rb +605 -0
- data/lib/rh.rb +157 -0
- data/lib/subhash/version.rb +2 -2
- data/lib/subhash.rb +9 -825
- data/subhash.gemspec +10 -1
- metadata +37 -3
data/lib/subhash.rb
CHANGED
@@ -17,6 +17,9 @@
|
|
17
17
|
|
18
18
|
require 'rubygems'
|
19
19
|
require 'yaml'
|
20
|
+
require 'rh'
|
21
|
+
require 'ihash'
|
22
|
+
require 'iarray'
|
20
23
|
|
21
24
|
# Adding rh_clone at object level. This be able to use a generic rh_clone
|
22
25
|
# redefined per object Hash and Array.
|
@@ -24,831 +27,12 @@ class Object
|
|
24
27
|
alias_method :rh_clone, :clone
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
def merge_cleanup
|
36
|
-
_rh_remove_control(rh_clone)
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
# Function which will parse arrays in hierarchie and will remove any control
|
42
|
-
# element (index 0)
|
43
|
-
def _rh_remove_control(result)
|
44
|
-
return unless [Hash, Array].include?(result.class)
|
45
|
-
|
46
|
-
if result.is_a?(Hash)
|
47
|
-
result.each { |elem| _rh_remove_control(elem) }
|
48
|
-
else
|
49
|
-
result.delete_at(0) if result[0].is_a?(Hash) && result[0].key?(:__control)
|
50
|
-
result.each_index { |index| _rh_remove_control(result[index]) }
|
51
|
-
end
|
52
|
-
result
|
53
|
-
end
|
54
|
-
|
55
|
-
# Internal function to determine if result and data key contains both Hash or
|
56
|
-
# Array and if so, do the merge task on those sub Hash/Array
|
57
|
-
#
|
58
|
-
def _rh_merge_recursive(result, key, data)
|
59
|
-
return false unless [Array, Hash].include?(data.class)
|
60
|
-
|
61
|
-
value = data[key]
|
62
|
-
return false unless [Array, Hash].include?(value.class) &&
|
63
|
-
value.class == result[key].class
|
64
|
-
|
65
|
-
if object_id == result.object_id
|
66
|
-
result[key].rh_merge!(value)
|
67
|
-
else
|
68
|
-
result[key] = result[key].rh_merge(value)
|
69
|
-
end
|
70
|
-
|
71
|
-
true
|
72
|
-
end
|
73
|
-
|
74
|
-
# Internal function to determine if changing from Hash/Array to anything else
|
75
|
-
# is authorized or not.
|
76
|
-
#
|
77
|
-
# The structure is changing if `result` or `value` move from Hash/Array to any
|
78
|
-
# other type.
|
79
|
-
#
|
80
|
-
# * *Args*:
|
81
|
-
# - result: Merged Hash or Array structure.
|
82
|
-
# - key : Key in result and data.
|
83
|
-
# - data : Hash or Array structure to merge.
|
84
|
-
#
|
85
|
-
# * *returns*:
|
86
|
-
# - +true+ : if :__struct_changing == true
|
87
|
-
# - +false+ : otherwise.
|
88
|
-
def _rh_struct_changing_ok?(result, key, data)
|
89
|
-
return true unless [Array, Hash].include?(data[key].class) ||
|
90
|
-
[Array, Hash].include?(result[key].class)
|
91
|
-
|
92
|
-
# result or value are structure (Hash or Array)
|
93
|
-
if result.is_a?(Hash)
|
94
|
-
control = result[:__struct_changing]
|
95
|
-
else
|
96
|
-
control = result[0][:__struct_changing]
|
97
|
-
key -= 1
|
98
|
-
end
|
99
|
-
return true if control.is_a?(Array) && control.include?(key)
|
100
|
-
|
101
|
-
false
|
102
|
-
end
|
103
|
-
|
104
|
-
# Internal function to determine if a data merged can be updated by any
|
105
|
-
# other object like Array, String, etc...
|
106
|
-
#
|
107
|
-
# The decision is given by a :__unset setting.
|
108
|
-
#
|
109
|
-
# * *Args*:
|
110
|
-
# - Hash/Array data to replace.
|
111
|
-
# - key: string or symbol.
|
112
|
-
#
|
113
|
-
# * *returns*:
|
114
|
-
# - +false+ : if key is found in :__protected Array.
|
115
|
-
# - +true+ : otherwise.
|
116
|
-
def _rh_merge_ok?(result, key)
|
117
|
-
if result.is_a?(Hash)
|
118
|
-
control = result[:__protected]
|
119
|
-
else
|
120
|
-
control = result[0][:__protected]
|
121
|
-
key -= 1
|
122
|
-
end
|
123
|
-
|
124
|
-
return false if control.is_a?(Array) && control.include?(key)
|
125
|
-
|
126
|
-
true
|
127
|
-
end
|
128
|
-
|
129
|
-
def _rh_control_tags
|
130
|
-
[:__remove, :__remove_index, :__add, :__add_index,
|
131
|
-
:__protected, :__struct_changing, :__control]
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Recursive Hash added to the Hash class
|
136
|
-
class Hash
|
137
|
-
# Recursive Hash deep level found counter
|
138
|
-
# This function will returns the count of deep level of recursive hash.
|
139
|
-
# * *Args* :
|
140
|
-
# - +p+ : Array of string or symbols. keys tree to follow and check
|
141
|
-
# existence in yVal.
|
142
|
-
#
|
143
|
-
# * *Returns* :
|
144
|
-
# - +integer+ : Represents how many deep level was found in the recursive
|
145
|
-
# hash
|
146
|
-
#
|
147
|
-
# * *Raises* :
|
148
|
-
# No exceptions
|
149
|
-
#
|
150
|
-
# Example: (implemented in spec)
|
151
|
-
#
|
152
|
-
# yVal = { :test => {:test2 => 'value1', :test3 => 'value2'},
|
153
|
-
# :test4 => 'value3'}
|
154
|
-
#
|
155
|
-
# yVal can be represented like:
|
156
|
-
#
|
157
|
-
# yVal:
|
158
|
-
# test:
|
159
|
-
# test2 = 'value1'
|
160
|
-
# test3 = 'value2'
|
161
|
-
# test4 = 'value3'
|
162
|
-
#
|
163
|
-
# so:
|
164
|
-
# # test is found
|
165
|
-
# yVal.rh_lexist?(:test) => 1
|
166
|
-
#
|
167
|
-
# # no test5
|
168
|
-
# yVal.rh_lexist?(:test5) => 0
|
169
|
-
#
|
170
|
-
# # :test/:test2 tree is found
|
171
|
-
# yVal.rh_lexist?(:test, :test2) => 2
|
172
|
-
#
|
173
|
-
# # :test/:test2 is found (value = 2), but :test5 was not found in this tree
|
174
|
-
# yVal.rh_lexist?(:test, :test2, :test5) => 2
|
175
|
-
#
|
176
|
-
# # :test was found. but :test/:test5 tree was not found. so level 1, ok.
|
177
|
-
# yVal.rh_lexist?(:test, :test5 ) => 1
|
178
|
-
#
|
179
|
-
# # it is like searching for nothing...
|
180
|
-
# yVal.rh_lexist? => 0
|
181
|
-
|
182
|
-
def rh_lexist?(*p)
|
183
|
-
p = p.flatten
|
184
|
-
|
185
|
-
return 0 if p.length == 0
|
186
|
-
|
187
|
-
if p.length == 1
|
188
|
-
return 1 if self.key?(p[0])
|
189
|
-
return 0
|
30
|
+
if RUBY_VERSION.match(/1\.8/)
|
31
|
+
# Added missing <=> function in Symbol of ruby 1.8
|
32
|
+
class Symbol
|
33
|
+
def <=>(other)
|
34
|
+
return to_s <=> other if other.is_a?(String)
|
35
|
+
to_s <=> other.to_s
|
190
36
|
end
|
191
|
-
return 0 unless self.key?(p[0])
|
192
|
-
ret = 0
|
193
|
-
ret = self[p[0]].rh_lexist?(p.drop(1)) if self[p[0]].is_a?(Hash)
|
194
|
-
1 + ret
|
195
|
-
end
|
196
|
-
|
197
|
-
# Recursive Hash deep level existence
|
198
|
-
#
|
199
|
-
# * *Args* :
|
200
|
-
# - +p+ : Array of string or symbols. keys tree to follow and check
|
201
|
-
# existence in yVal.
|
202
|
-
#
|
203
|
-
# * *Returns* :
|
204
|
-
# - +boolean+ : Returns True if the deep level of recursive hash is found.
|
205
|
-
# false otherwise
|
206
|
-
#
|
207
|
-
# * *Raises* :
|
208
|
-
# No exceptions
|
209
|
-
#
|
210
|
-
# Example:(implemented in spec)
|
211
|
-
#
|
212
|
-
# yVal = { :test => {:test2 => 'value1', :test3 => 'value2'},
|
213
|
-
# :test4 => 'value3'}
|
214
|
-
#
|
215
|
-
# yVal can be represented like:
|
216
|
-
#
|
217
|
-
# yVal:
|
218
|
-
# test:
|
219
|
-
# test2 = 'value1'
|
220
|
-
# test3 = 'value2'
|
221
|
-
# test4 = 'value3'
|
222
|
-
#
|
223
|
-
# so:
|
224
|
-
# # test is found
|
225
|
-
# yVal.rh_exist?(:test) => True
|
226
|
-
#
|
227
|
-
# # no test5
|
228
|
-
# yVal.rh_exist?(:test5) => False
|
229
|
-
#
|
230
|
-
# # :test/:test2 tree is found
|
231
|
-
# yVal.rh_exist?(:test, :test2) => True
|
232
|
-
#
|
233
|
-
# # :test/:test2 is found (value = 2), but :test5 was not found in this tree
|
234
|
-
# yVal.rh_exist?(:test, :test2, :test5) => False
|
235
|
-
#
|
236
|
-
# # :test was found. but :test/:test5 tree was not found. so level 1, ok.
|
237
|
-
# yVal.rh_exist?(:test, :test5 ) => False
|
238
|
-
#
|
239
|
-
# # it is like searching for nothing...
|
240
|
-
# yVal.rh_exist? => nil
|
241
|
-
def rh_exist?(*p)
|
242
|
-
p = p.flatten
|
243
|
-
|
244
|
-
return nil if p.length == 0
|
245
|
-
|
246
|
-
count = p.length
|
247
|
-
(rh_lexist?(*p) == count)
|
248
37
|
end
|
249
|
-
|
250
|
-
# Recursive Hash Get
|
251
|
-
# This function will returns the level of recursive hash was found.
|
252
|
-
# * *Args* :
|
253
|
-
# - +p+ : Array of string or symbols. keys tree to follow and check
|
254
|
-
# existence in yVal.
|
255
|
-
#
|
256
|
-
# * *Returns* :
|
257
|
-
# - +value+ : Represents the data found in the tree. Can be of any type.
|
258
|
-
#
|
259
|
-
# * *Raises* :
|
260
|
-
# No exceptions
|
261
|
-
#
|
262
|
-
# Example:(implemented in spec)
|
263
|
-
#
|
264
|
-
# yVal = { :test => {:test2 => 'value1', :test3 => 'value2'},
|
265
|
-
# :test4 => 'value3'}
|
266
|
-
#
|
267
|
-
# yVal can be represented like:
|
268
|
-
#
|
269
|
-
# yVal:
|
270
|
-
# test:
|
271
|
-
# test2 = 'value1'
|
272
|
-
# test3 = 'value2'
|
273
|
-
# test4 = 'value3'
|
274
|
-
#
|
275
|
-
# so:
|
276
|
-
# yVal.rh_get(:test) => {:test2 => 'value1', :test3 => 'value2'}
|
277
|
-
# yVal.rh_get(:test5) => nil
|
278
|
-
# yVal.rh_get(:test, :test2) => 'value1'
|
279
|
-
# yVal.rh_get(:test, :test2, :test5) => nil
|
280
|
-
# yVal.rh_get(:test, :test5 ) => nil
|
281
|
-
# yVal.rh_get => { :test => {:test2 => 'value1', :test3 => 'value2'},
|
282
|
-
# :test4 => 'value3'}
|
283
|
-
def rh_get(*p)
|
284
|
-
p = p.flatten
|
285
|
-
return self if p.length == 0
|
286
|
-
|
287
|
-
if p.length == 1
|
288
|
-
return self[p[0]] if self.key?(p[0])
|
289
|
-
return nil
|
290
|
-
end
|
291
|
-
return self[p[0]].rh_get(p.drop(1)) if self[p[0]].is_a?(Hash)
|
292
|
-
nil
|
293
|
-
end
|
294
|
-
|
295
|
-
# Recursive Hash Set
|
296
|
-
# This function will build a recursive hash according to the '*p' key tree.
|
297
|
-
# if yVal is not nil, it will be updated.
|
298
|
-
#
|
299
|
-
# * *Args* :
|
300
|
-
# - +p+ : Array of string or symbols. keys tree to follow and check
|
301
|
-
# existence in yVal.
|
302
|
-
#
|
303
|
-
# * *Returns* :
|
304
|
-
# - +value+ : the value set.
|
305
|
-
#
|
306
|
-
# * *Raises* :
|
307
|
-
# No exceptions
|
308
|
-
#
|
309
|
-
# Example:(implemented in spec)
|
310
|
-
#
|
311
|
-
# yVal = {}
|
312
|
-
#
|
313
|
-
# yVal.rh_set(:test) => nil
|
314
|
-
# # yVal = {}
|
315
|
-
#
|
316
|
-
# yVal.rh_set(:test5) => nil
|
317
|
-
# # yVal = {}
|
318
|
-
#
|
319
|
-
# yVal.rh_set(:test, :test2) => :test
|
320
|
-
# # yVal = {:test2 => :test}
|
321
|
-
#
|
322
|
-
# yVal.rh_set(:test, :test2, :test5) => :test
|
323
|
-
# # yVal = {:test2 => {:test5 => :test} }
|
324
|
-
#
|
325
|
-
# yVal.rh_set(:test, :test5 ) => :test
|
326
|
-
# # yVal = {:test2 => {:test5 => :test}, :test5 => :test }
|
327
|
-
#
|
328
|
-
# yVal.rh_set('blabla', :test2, 'text') => :test
|
329
|
-
# # yVal = {:test2 => {:test5 => :test, 'text' => 'blabla'},
|
330
|
-
# :test5 => :test }
|
331
|
-
def rh_set(value, *p)
|
332
|
-
p = p.flatten
|
333
|
-
return nil if p.length == 0
|
334
|
-
|
335
|
-
if p.length == 1
|
336
|
-
self[p[0]] = value
|
337
|
-
return value
|
338
|
-
end
|
339
|
-
|
340
|
-
self[p[0]] = {} unless self[p[0]].is_a?(Hash)
|
341
|
-
self[p[0]].rh_set(value, p.drop(1))
|
342
|
-
end
|
343
|
-
|
344
|
-
# Recursive Hash delete
|
345
|
-
# This function will remove the last key defined by the key tree
|
346
|
-
#
|
347
|
-
# * *Args* :
|
348
|
-
# - +p+ : Array of string or symbols. keys tree to follow and check
|
349
|
-
# existence in yVal.
|
350
|
-
#
|
351
|
-
# * *Returns* :
|
352
|
-
# - +value+ : The Hash updated.
|
353
|
-
#
|
354
|
-
# * *Raises* :
|
355
|
-
# No exceptions
|
356
|
-
#
|
357
|
-
# Example:(implemented in spec)
|
358
|
-
#
|
359
|
-
# yVal = {{:test2 => { :test5 => :test,
|
360
|
-
# 'text' => 'blabla' },
|
361
|
-
# :test5 => :test}}
|
362
|
-
#
|
363
|
-
#
|
364
|
-
# yVal.rh_del(:test) => nil
|
365
|
-
# # yVal = no change
|
366
|
-
#
|
367
|
-
# yVal.rh_del(:test, :test2) => nil
|
368
|
-
# # yVal = no change
|
369
|
-
#
|
370
|
-
# yVal.rh_del(:test2, :test5) => {:test5 => :test}
|
371
|
-
# # yVal = {:test2 => {:test5 => :test} }
|
372
|
-
#
|
373
|
-
# yVal.rh_del(:test, :test2)
|
374
|
-
# # yVal = {:test2 => {:test5 => :test} }
|
375
|
-
#
|
376
|
-
# yVal.rh_del(:test, :test5)
|
377
|
-
# # yVal = {:test2 => {} }
|
378
|
-
#
|
379
|
-
def rh_del(*p)
|
380
|
-
p = p.flatten
|
381
|
-
|
382
|
-
return nil if p.length == 0
|
383
|
-
|
384
|
-
return delete(p[0]) if p.length == 1
|
385
|
-
|
386
|
-
return nil if self[p[0]].nil?
|
387
|
-
self[p[0]].rh_del(p.drop(1))
|
388
|
-
end
|
389
|
-
|
390
|
-
# Move levels (default level 1) of tree keys to become symbol.
|
391
|
-
#
|
392
|
-
# * *Args* :
|
393
|
-
# - +levels+: level of key tree to update.
|
394
|
-
# * *Returns* :
|
395
|
-
# - a new hash of hashes updated. Original Hash is not updated anymore.
|
396
|
-
#
|
397
|
-
# examples:
|
398
|
-
# With hdata = { :test => { :test2 => { :test5 => :test,
|
399
|
-
# 'text' => 'blabla' },
|
400
|
-
# 'test5' => 'test' }}
|
401
|
-
#
|
402
|
-
# hdata.rh_key_to_symbol(1) return no diff
|
403
|
-
# hdata.rh_key_to_symbol(2) return "test5" is replaced by :test5
|
404
|
-
# # hdata = { :test => { :test2 => { :test5 => :test,
|
405
|
-
# # 'text' => 'blabla' },
|
406
|
-
# # :test5 => 'test' }}
|
407
|
-
# rh_key_to_symbol(3) return "test5" replaced by :test5, and "text" to :text
|
408
|
-
# # hdata = { :test => { :test2 => { :test5 => :test,
|
409
|
-
# # :text => 'blabla' },
|
410
|
-
# # :test5 => 'test' }}
|
411
|
-
# rh_key_to_symbol(4) same like rh_key_to_symbol(3)
|
412
|
-
|
413
|
-
def rh_key_to_symbol(levels = 1)
|
414
|
-
result = {}
|
415
|
-
each do |key, value|
|
416
|
-
new_key = key
|
417
|
-
new_key = key.to_sym if key.is_a?(String)
|
418
|
-
if value.is_a?(Hash) && levels > 1
|
419
|
-
value = value.rh_key_to_symbol(levels - 1)
|
420
|
-
end
|
421
|
-
result[new_key] = value
|
422
|
-
end
|
423
|
-
result
|
424
|
-
end
|
425
|
-
|
426
|
-
# Check if levels of tree keys are all symbols.
|
427
|
-
#
|
428
|
-
# * *Args* :
|
429
|
-
# - +levels+: level of key tree to update.
|
430
|
-
# * *Returns* :
|
431
|
-
# - true : one key path is not symbol.
|
432
|
-
# - false : all key path are symbols.
|
433
|
-
# * *Raises* :
|
434
|
-
# Nothing
|
435
|
-
#
|
436
|
-
# examples:
|
437
|
-
# With hdata = { :test => { :test2 => { :test5 => :test,
|
438
|
-
# 'text' => 'blabla' },
|
439
|
-
# 'test5' => 'test' }}
|
440
|
-
#
|
441
|
-
# hdata.rh_key_to_symbol?(1) return false
|
442
|
-
# hdata.rh_key_to_symbol?(2) return true
|
443
|
-
# hdata.rh_key_to_symbol?(3) return true
|
444
|
-
# hdata.rh_key_to_symbol?(4) return true
|
445
|
-
def rh_key_to_symbol?(levels = 1)
|
446
|
-
each do |key, value|
|
447
|
-
return true if key.is_a?(String)
|
448
|
-
|
449
|
-
res = false
|
450
|
-
if levels > 1 && value.is_a?(Hash)
|
451
|
-
res = value.rh_key_to_symbol?(levels - 1)
|
452
|
-
end
|
453
|
-
return true if res
|
454
|
-
end
|
455
|
-
false
|
456
|
-
end
|
457
|
-
|
458
|
-
# return an exact clone of the recursive Array and Hash contents.
|
459
|
-
#
|
460
|
-
# * *Args* :
|
461
|
-
#
|
462
|
-
# * *Returns* :
|
463
|
-
# - Recursive Array/Hash cloned. Other kind of objects are kept referenced.
|
464
|
-
# * *Raises* :
|
465
|
-
# Nothing
|
466
|
-
#
|
467
|
-
# examples:
|
468
|
-
# hdata = { :test => { :test2 => { :test5 => :test,
|
469
|
-
# 'text' => 'blabla' },
|
470
|
-
# 'test5' => 'test' },
|
471
|
-
# :array => [{ :test => :value1 }, 2, { :test => :value3 }]}
|
472
|
-
#
|
473
|
-
# hclone = hdata.rh_clone
|
474
|
-
# hclone[:test] = "test"
|
475
|
-
# hdata[:test] == { :test2 => { :test5 => :test,'text' => 'blabla' }
|
476
|
-
# # => true
|
477
|
-
# hclone[:array].pop
|
478
|
-
# hdata[:array].length != hclone[:array].length
|
479
|
-
# # => true
|
480
|
-
# hclone[:array][0][:test] = "value2"
|
481
|
-
# hdata[:array][0][:test] != hclone[:array][0][:test]
|
482
|
-
# # => true
|
483
|
-
def rh_clone
|
484
|
-
result = {}
|
485
|
-
each do |key, value|
|
486
|
-
if [Array, Hash].include?(value.class)
|
487
|
-
result[key] = value.rh_clone
|
488
|
-
else
|
489
|
-
result[key] = value
|
490
|
-
end
|
491
|
-
end
|
492
|
-
result
|
493
|
-
end
|
494
|
-
|
495
|
-
# Merge the current Hash object (self) cloned with a Hash/Array tree contents
|
496
|
-
# (data).
|
497
|
-
#
|
498
|
-
# 'self' is used as original data to merge to.
|
499
|
-
# 'data' is used as data to merged to clone of 'self'. If you want to update
|
500
|
-
# 'self', use rh_merge!
|
501
|
-
#
|
502
|
-
# if 'self' or 'data' contains a Hash tree, the merge will be executed
|
503
|
-
# recursively.
|
504
|
-
#
|
505
|
-
# The current function will execute the merge of the 'self' keys with the top
|
506
|
-
# keys in 'data'
|
507
|
-
#
|
508
|
-
# The merge can be controlled by an additionnal Hash key '__*' in each
|
509
|
-
# 'self' key.
|
510
|
-
# If both a <key> exist in 'self' and 'data', the following decision is made:
|
511
|
-
# - if both 'self' and 'data' key contains an Hash or and Array, a recursive
|
512
|
-
# merge if Hash or update if Array, is started.
|
513
|
-
#
|
514
|
-
# - if 'self' <key> contains an Hash or an Array, but not 'data' <key>, then
|
515
|
-
# 'self' <key> will be set to the 'data' <key> except if 'self' <Key> has
|
516
|
-
# :__struct_changing: true
|
517
|
-
# data <key> value can set :unset value
|
518
|
-
#
|
519
|
-
# - if 'self' <key> is :unset and 'data' <key> is any value
|
520
|
-
# 'self' <key> value is set with 'data' <key> value.
|
521
|
-
# 'data' <key> value can contains a Hash with :__no_unset: true to
|
522
|
-
# protect this key against the next merge. (next config layer merge)
|
523
|
-
#
|
524
|
-
# - if 'data' <key> exist but not in 'self', 'data' <key> is just added.
|
525
|
-
#
|
526
|
-
# - if 'data' & 'self' <key> exist, 'self'<key> is updated except if key is in
|
527
|
-
# :__protected array list.
|
528
|
-
#
|
529
|
-
# * *Args* :
|
530
|
-
# - hash : Hash data to merge.
|
531
|
-
#
|
532
|
-
# * *Returns* :
|
533
|
-
# - Recursive Array/Hash merged.
|
534
|
-
#
|
535
|
-
# * *Raises* :
|
536
|
-
# Nothing
|
537
|
-
#
|
538
|
-
# examples:
|
539
|
-
#
|
540
|
-
def rh_merge(data)
|
541
|
-
_rh_merge(clone, data)
|
542
|
-
end
|
543
|
-
|
544
|
-
# Merge the current Hash object (self) with a Hash/Array tree contents (data).
|
545
|
-
#
|
546
|
-
# For details on this functions, see #rh_merge
|
547
|
-
#
|
548
|
-
def rh_merge!(data)
|
549
|
-
_rh_merge(self, data)
|
550
|
-
end
|
551
|
-
end
|
552
|
-
|
553
|
-
# Recursive Hash added to the Hash class
|
554
|
-
class Hash
|
555
|
-
private
|
556
|
-
|
557
|
-
# Internal function which do the real merge task by #rh_merge and #rh_merge!
|
558
|
-
#
|
559
|
-
# See #rh_merge for details
|
560
|
-
#
|
561
|
-
def _rh_merge(result, data)
|
562
|
-
return _rh_merge_choose_data(result, data) unless data.is_a?(Hash)
|
563
|
-
|
564
|
-
data.each do |key, _value|
|
565
|
-
next if [:__struct_changing, :__protected].include?(key)
|
566
|
-
|
567
|
-
_do_rh_merge(result, key, data)
|
568
|
-
end
|
569
|
-
[:__struct_changing, :__protected].each do |key|
|
570
|
-
# Refuse merge by default if key data type are different.
|
571
|
-
# This assume that the first layer merge has set
|
572
|
-
# :__unset as a Hash, and :__protected as an Array.
|
573
|
-
|
574
|
-
_do_rh_merge(result, key, data, true) if data.key?(key)
|
575
|
-
|
576
|
-
# Remove all control element in arrays
|
577
|
-
_rh_remove_control(result[key]) if result.key?(key)
|
578
|
-
end
|
579
|
-
|
580
|
-
result
|
581
|
-
end
|
582
|
-
|
583
|
-
def _rh_merge_choose_data(result, data)
|
584
|
-
# return result as first one impose the type between Hash/Array.
|
585
|
-
return result if [Hash, Array].include?(result.class) ||
|
586
|
-
[Hash, Array].include?(data.class)
|
587
|
-
|
588
|
-
data
|
589
|
-
end
|
590
|
-
# Internal function to execute the merge on one key provided by #_rh_merge
|
591
|
-
#
|
592
|
-
# if refuse_discordance is true, then result[key] can't be updated if
|
593
|
-
# stricly not of same type.
|
594
|
-
def _do_rh_merge(result, key, data, refuse_discordance = false)
|
595
|
-
value = data[key]
|
596
|
-
|
597
|
-
return if _rh_merge_do_add_key(result, key, value)
|
598
|
-
|
599
|
-
return if _rh_merge_recursive(result, key, data)
|
600
|
-
|
601
|
-
return if refuse_discordance
|
602
|
-
|
603
|
-
return unless _rh_struct_changing_ok?(result, key, data)
|
604
|
-
|
605
|
-
return unless _rh_merge_ok?(result, key)
|
606
|
-
|
607
|
-
_rh_merge_do_upd_key(result, key, value)
|
608
|
-
end
|
609
|
-
|
610
|
-
def _rh_merge_do_add_key(result, key, value)
|
611
|
-
unless result.key?(key) || value == :unset
|
612
|
-
result[key] = value # New key added
|
613
|
-
return true
|
614
|
-
end
|
615
|
-
false
|
616
|
-
end
|
617
|
-
|
618
|
-
def _rh_merge_do_upd_key(result, key, value)
|
619
|
-
if value == :unset
|
620
|
-
result.delete(key) if result.key?(key)
|
621
|
-
return
|
622
|
-
end
|
623
|
-
|
624
|
-
result[key] = value # Key updated
|
625
|
-
end
|
626
|
-
|
627
|
-
include Rh
|
628
|
-
end
|
629
|
-
|
630
|
-
# Defines rh_clone for Array
|
631
|
-
class Array
|
632
|
-
# return an exact clone of the recursive Array and Hash contents.
|
633
|
-
#
|
634
|
-
# * *Args* :
|
635
|
-
#
|
636
|
-
# * *Returns* :
|
637
|
-
# - Recursive Array/Hash cloned.
|
638
|
-
# * *Raises* :
|
639
|
-
# Nothing
|
640
|
-
#
|
641
|
-
# examples:
|
642
|
-
# hdata = { :test => { :test2 => { :test5 => :test,
|
643
|
-
# 'text' => 'blabla' },
|
644
|
-
# 'test5' => 'test' },
|
645
|
-
# :array => [{ :test => :value1 }, 2, { :test => :value3 }]}
|
646
|
-
#
|
647
|
-
# hclone = hdata.rh_clone
|
648
|
-
# hclone[:test] = "test"
|
649
|
-
# hdata[:test] == { :test2 => { :test5 => :test,'text' => 'blabla' }
|
650
|
-
# # => true
|
651
|
-
# hclone[:array].pop
|
652
|
-
# hdata[:array].length != hclone[:array].length
|
653
|
-
# # => true
|
654
|
-
# hclone[:array][0][:test] = "value2"
|
655
|
-
# hdata[:array][0][:test] != hclone[:array][0][:test]
|
656
|
-
# # => true
|
657
|
-
def rh_clone
|
658
|
-
result = []
|
659
|
-
each do |value|
|
660
|
-
begin
|
661
|
-
result << value.rh_clone
|
662
|
-
rescue
|
663
|
-
result << value
|
664
|
-
end
|
665
|
-
end
|
666
|
-
result
|
667
|
-
end
|
668
|
-
|
669
|
-
# This function is part of the rh_merge functionnality adapted for Array.
|
670
|
-
#
|
671
|
-
# To provide Array recursivity, we uses the element index.
|
672
|
-
#
|
673
|
-
# **Warning!** If the Array order has changed (sort/random) the index changed
|
674
|
-
# and can generate unwanted result.
|
675
|
-
#
|
676
|
-
# To implement recursivity, and some specific Array management (add/remove)
|
677
|
-
# you have to create an Hash and insert it at position 0 in the 'self' Array.
|
678
|
-
#
|
679
|
-
# **Warning!** If you create an Array, where index 0 contains a Hash, this
|
680
|
-
# Hash will be considered as the Array control element.
|
681
|
-
# If the first index of your Array is not a Hash, an empty Hash will be
|
682
|
-
# inserted at position 0.
|
683
|
-
#
|
684
|
-
# 'data' has the same restriction then 'self' about the first element.
|
685
|
-
# 'data' can influence the rh_merge Array behavior, by updating the first
|
686
|
-
# element.
|
687
|
-
#
|
688
|
-
# The first Hash element has the following attributes:
|
689
|
-
#
|
690
|
-
# - :__struct_changing: Array of index which accepts to move from a structured
|
691
|
-
# data (Hash/Array) to another structure or type.
|
692
|
-
#
|
693
|
-
# Ex: Hash => Array, Array => Integer
|
694
|
-
#
|
695
|
-
# - :__protected: Array of index which protects against update from 'data'
|
696
|
-
#
|
697
|
-
# - :__remove: Array of elements to remove. each element are remove with
|
698
|
-
# Array.delete function. See Array delete function for details.
|
699
|
-
#
|
700
|
-
# - :__remove_index: Array of indexes to remove.
|
701
|
-
# Each element are removed with Array.delete_at function.
|
702
|
-
# It starts from the highest index until the lowest.
|
703
|
-
# See Array delete function for details.
|
704
|
-
#
|
705
|
-
# **NOTE**: __remove and __remove_index cannot be used together.
|
706
|
-
# if both are set, __remove is choosen
|
707
|
-
#
|
708
|
-
# **NOTE** : __remove* is executed before __add*
|
709
|
-
#
|
710
|
-
# - :__add: Array of elements to add. Those elements are systematically added
|
711
|
-
# at the end of the Array. See Array.<< for details.
|
712
|
-
#
|
713
|
-
# - :__add_index: Hash of index(key) + Array of data(value) to add.
|
714
|
-
# The index listed refer to merged 'self' Array. several elements with same
|
715
|
-
# index are grouply inserted in the index.
|
716
|
-
# ex:
|
717
|
-
# [:data3].rh_merge({:__add_index => [0 => [:data1, :data2]]})
|
718
|
-
# => [{}, :data1, :data2, :data3]
|
719
|
-
#
|
720
|
-
# **NOTE**: __add and __add_index cannot be used together.
|
721
|
-
# if both are set, __add is choosen
|
722
|
-
#
|
723
|
-
# How merge is executed:
|
724
|
-
#
|
725
|
-
# Starting at index 0, each index of 'data' and 'self' are used to compare
|
726
|
-
# indexed data.
|
727
|
-
# - If 'data' index 0 has not an Hash, the 'self' index 0 is just skipped.
|
728
|
-
# - If 'data' index 0 has the 'control' Hash, the array will be updated
|
729
|
-
# according to :__add and :__remove arrays.
|
730
|
-
# when done, those attributes are removed
|
731
|
-
#
|
732
|
-
# - For all next index (1 => 'data'.length), data are compared
|
733
|
-
#
|
734
|
-
# - If the 'data' length is > than 'self' length
|
735
|
-
# addtionnal indexed data are added to 'self'
|
736
|
-
#
|
737
|
-
# - If index element exist in both 'data' and 'self',
|
738
|
-
# 'self' indexed data is updated/merged according to control.
|
739
|
-
# 'data' indexed data can use :unset to remove the data at this index
|
740
|
-
# nil is also supported. But the index won't be removed. data will just
|
741
|
-
# be set to nil
|
742
|
-
#
|
743
|
-
# when all Arrays elements are merged, rh_merge will:
|
744
|
-
# - remove 'self' elements containing ':unset'
|
745
|
-
#
|
746
|
-
# - merge 'self' data at index 0 with 'data' found index 0
|
747
|
-
#
|
748
|
-
def rh_merge(data)
|
749
|
-
_rh_merge(clone, data)
|
750
|
-
end
|
751
|
-
|
752
|
-
def rh_merge!(data)
|
753
|
-
_rh_merge(self, data)
|
754
|
-
end
|
755
|
-
|
756
|
-
private
|
757
|
-
|
758
|
-
def _rh_merge(result, data)
|
759
|
-
data = data.clone
|
760
|
-
data_control = _rh_merge_control(data)
|
761
|
-
result_control = _rh_merge_control(result)
|
762
|
-
|
763
|
-
_rh_do_control_merge(result_control, result, data_control, data)
|
764
|
-
|
765
|
-
(1..(data.length - 1)).each do |index|
|
766
|
-
_rh_do_array_merge(result, index, data)
|
767
|
-
end
|
768
|
-
|
769
|
-
(-(result.length - 1)..-1).each do |index|
|
770
|
-
result.delete_at(index.abs) if result[index.abs] == :unset
|
771
|
-
end
|
772
|
-
|
773
|
-
_rh_do_array_merge(result, 0, [data_control])
|
774
|
-
# Remove all control elements in tree of arrays
|
775
|
-
_rh_remove_control(result[0])
|
776
|
-
|
777
|
-
result
|
778
|
-
end
|
779
|
-
|
780
|
-
def _rh_do_array_merge(result, index, data)
|
781
|
-
return if _rh_merge_recursive(result, index, data)
|
782
|
-
|
783
|
-
return unless _rh_struct_changing_ok?(result, index, data)
|
784
|
-
|
785
|
-
return unless _rh_merge_ok?(result, index)
|
786
|
-
|
787
|
-
result[index] = data[index] unless data[index] == :kept
|
788
|
-
end
|
789
|
-
|
790
|
-
# Get the control element. or create it if missing.
|
791
|
-
def _rh_merge_control(array)
|
792
|
-
unless array[0].is_a?(Hash)
|
793
|
-
array.insert(0, :__control => true)
|
794
|
-
return array[0]
|
795
|
-
end
|
796
|
-
|
797
|
-
_rh_control_tags.each do |prop|
|
798
|
-
if array[0].key?(prop)
|
799
|
-
array[0][:__control] = true
|
800
|
-
return array[0]
|
801
|
-
end
|
802
|
-
end
|
803
|
-
|
804
|
-
array.insert(0, :__control => true)
|
805
|
-
|
806
|
-
array[0]
|
807
|
-
end
|
808
|
-
|
809
|
-
# Do the merge according to :__add and :__remove
|
810
|
-
def _rh_do_control_merge(_result_control, result, data_control, _data)
|
811
|
-
if data_control[:__remove].is_a?(Array)
|
812
|
-
_rh_do_control_remove(result, data_control[:__remove])
|
813
|
-
elsif data_control[:__remove_index].is_a?(Array)
|
814
|
-
index_to_remove = data_control[:__remove_index].uniq.sort.reverse
|
815
|
-
_rh_do_control_remove_index(result, index_to_remove)
|
816
|
-
end
|
817
|
-
|
818
|
-
data_control.delete(:__remove)
|
819
|
-
data_control.delete(:__remove_index)
|
820
|
-
|
821
|
-
if data_control[:__add].is_a?(Array)
|
822
|
-
data_control[:__add].each { |element| result << element }
|
823
|
-
elsif data_control[:__add_index].is_a?(Hash)
|
824
|
-
_rh_do_control_add_index(result, data_control[:__add_index].sort)
|
825
|
-
end
|
826
|
-
|
827
|
-
data_control.delete(:__add)
|
828
|
-
data_control.delete(:__add_index)
|
829
|
-
end
|
830
|
-
|
831
|
-
def _rh_do_control_add_index(result, add_index)
|
832
|
-
add_index.reverse_each do |elements_to_insert|
|
833
|
-
next unless elements_to_insert.is_a?(Array) &&
|
834
|
-
elements_to_insert[0].is_a?(Fixnum) &&
|
835
|
-
elements_to_insert[1].is_a?(Array)
|
836
|
-
|
837
|
-
index = elements_to_insert[0] + 1
|
838
|
-
elements = elements_to_insert[1]
|
839
|
-
|
840
|
-
elements.reverse_each { |element| result.insert(index, element) }
|
841
|
-
end
|
842
|
-
end
|
843
|
-
|
844
|
-
# do the element removal.
|
845
|
-
def _rh_do_control_remove(result, remove)
|
846
|
-
remove.each { |element| result.delete(element) }
|
847
|
-
end
|
848
|
-
|
849
|
-
def _rh_do_control_remove_index(result, index_to_remove)
|
850
|
-
index_to_remove.each { |index| result.delete_at(index + 1) }
|
851
|
-
end
|
852
|
-
|
853
|
-
include Rh
|
854
38
|
end
|