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/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
|