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