config_layers 0.1.0

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.
@@ -0,0 +1,1218 @@
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
+ require 'yaml'
18
+
19
+ module PRC
20
+ # Internal CoreConfig functions
21
+ class CoreConfig
22
+ private
23
+
24
+ # Function to initialize a predefined layer
25
+ # Used internally by initialize_layers.
26
+ def _initialize_layer(layer)
27
+ newlayer = { :config => layer[:config], :name => layer[:name] }
28
+ newlayer[:set] = layer[:set].boolean? ? layer[:set] : true
29
+ newlayer[:load] = layer[:load].boolean? ? layer[:load] : false
30
+ newlayer[:save] = layer[:save].boolean? ? layer[:save] : false
31
+ newlayer[:file_set] = layer[:file_set].boolean? ? layer[:file_set] : false
32
+ newlayer[:init] = true
33
+ newlayer
34
+ end
35
+
36
+ # Check and returns values of options required and optionnal.
37
+ #
38
+ # * *Args*
39
+ # - +options+ : options to extract information.
40
+ # - +required+ : Array of required option keys.
41
+ # - +optionnal+ : Array of optionnal option keys.
42
+ #
43
+ # * *Returns*
44
+ # - nil if at least one required keys doesn't exist
45
+ # - Array of combined required and optionnql values.
46
+ #
47
+ def _valid_options(options, required, optionnal = [])
48
+ return nil unless options.is_a?(Hash)
49
+ return nil unless required.is_a?(Array)
50
+ optionnal = [] unless optionnal.is_a?(Array)
51
+
52
+ result = [[], []]
53
+
54
+ required.each do |key|
55
+ return nil unless options.key?(key)
56
+ result[0] << options[key]
57
+ end
58
+
59
+ optionnal.each { |key| result[1] << options[key] }
60
+
61
+ result
62
+ end
63
+
64
+ # Internal function to call _common_options_get
65
+ # It ensures and cleanup caller setting
66
+ # :names and :indexes as those must be single values instead of
67
+ # array. ie :name and :index is the only one supported.
68
+ #
69
+ def _nameindex_common_options_get(options, required = [], optionnal = [])
70
+ options = {} if options.nil?
71
+
72
+ # ensure :names or :indexes are not set. and clean it up if exists
73
+ options.delete(:names) if options.key?(:names)
74
+ options.delete(:indexes) if options.key?(:indexes)
75
+
76
+ _common_options_get(options, required, optionnal)
77
+ end
78
+
79
+ # Take care of keys, indexes, names, index and name.
80
+ # If names is found, it will replace indexes.
81
+ # If name is found, it will replace index.
82
+ # If index is found, it will replace indexes.
83
+ def _common_options_get(options, required = [], optionnal = [])
84
+ return nil unless options.is_a?(Hash)
85
+ required = [] unless required.is_a?(Array)
86
+ optionnal = [] unless optionnal.is_a?(Array)
87
+
88
+ _convert_nameindex_to_arrays(options)
89
+
90
+ result = _valid_options(options, required,
91
+ [:indexes, :names,
92
+ :data_options].concat(optionnal))
93
+ return nil if result.nil?
94
+ # result Array is structured as:
95
+ # required [0] => [...]
96
+ # optional [1] => [indexes, names, data_options, ...]
97
+
98
+ # Following eliminates the optional :names (1) element
99
+ _set_indexes result
100
+ # required [0] => [...]
101
+ # optional [1] => [indexes, data_options, ...]
102
+ return nil if _keys_data_missing(options, required, result)
103
+
104
+ # Following eliminates the optional :indexes (0) element
105
+ # But add it as required in the Array, at pos 0
106
+ _build_layers(result)
107
+ # required [0] => [layers, ...]
108
+ # optional [1] => [data_options, ...]
109
+
110
+ # following eliminates the optional :data_options (0) element
111
+ # But data_options is added in required Array, at pos 1.
112
+ _set_data_opts(result)
113
+ # required [0] => [layers, data_options, ...]
114
+ # optional [1] => [...]
115
+
116
+ result
117
+ end
118
+
119
+ # This internal function identify :name and :index
120
+ # To replace to respectively to :names and :indexes
121
+ def _convert_nameindex_to_arrays(options)
122
+ options[:names] = [options.delete(:name)] if options.key?(:name)
123
+ options[:indexes] = [options.delete(:index)] if options.key?(:index)
124
+ end
125
+
126
+ # This internal function checks if the :keys is required, then
127
+ # The keys value HAVE to be an Array with at least one element.
128
+ # It returns false, if :keys is not the first required field
129
+ # or if the keys data (Array not empty) is confirmed.
130
+ def _keys_data_missing(options, required, result)
131
+ return false unless required[0] == :keys
132
+
133
+ return true unless options.key?(:keys)
134
+ return true unless result[0][0].is_a?(Array)
135
+ return true if result[0][0].length == 0
136
+ false
137
+ end
138
+
139
+ # Setting indexes from names or indexes.
140
+ def _set_indexes(result)
141
+ names_indexes = layer_indexes(result[1][1])
142
+ # replaced indexes by names indexes if exists.
143
+ result[1][0] = names_indexes if names_indexes
144
+ result[1].delete_at(1)
145
+ end
146
+
147
+ def _set_data_opts(result)
148
+ data_opts = []
149
+
150
+ result[0][0].each_index do |layer_index|
151
+ data_options = result[1][0][layer_index] if result[1][0].is_a?(Array)
152
+ data_options = {} unless data_options.is_a?(Hash)
153
+ data_opts << data_options
154
+ end
155
+ result[0].insert(1, data_opts)
156
+
157
+ # And removing the optionnal :data_options
158
+ result[1].delete_at(0)
159
+ end
160
+
161
+ def _build_layers(result)
162
+ # Setting layers at required [0]
163
+ if result[1][0].nil? || result[1][0].length == 0
164
+ config_layers = @config_layers
165
+ else
166
+ config_layers = []
167
+ result[1][0].each do |index|
168
+ config_layers << @config_layers[index] if index.is_a?(Fixnum)
169
+ end
170
+ config_layers = @config_layers if config_layers.length == 0
171
+ end
172
+ result[0].insert(0, config_layers)
173
+
174
+ # And removing the optionnal indexes
175
+ result[1].delete_at(0)
176
+ result
177
+ end
178
+
179
+ # Internal function to provide a result for #p_get.
180
+ # This function is typically used by a Child Class.
181
+ #
182
+ # * *Args*
183
+ # - +*keys+ : Array of keys.
184
+ # - +layers+ : Array of config layers to get data to merge.
185
+ # - +data_opts+ : Array of data options per layers
186
+ # - +data_options+ : common data options for all layers
187
+ # - +merge+ : True if need to get a merge result.
188
+ #
189
+ # * *Returns*
190
+ # - Value, Hash merged or nil.
191
+ #
192
+ def _get_from_layers(keys, layers, data_opts, data_options, merge)
193
+ result = {}
194
+
195
+ # In merge case, we need to build from bottom to top
196
+ if merge.is_a?(TrueClass)
197
+ layers = layers.reverse
198
+ data_opts = data_opts.reverse
199
+ end
200
+
201
+ layers.each_index do |layer_index|
202
+ layer = layers[layer_index]
203
+
204
+ data_options = data_options.merge(data_opts[layer_index])
205
+
206
+ layer[:config].data_options(data_options)
207
+
208
+ next unless layer[:config].exist?(*keys)
209
+
210
+ return layer[:config][*keys] unless merge.is_a?(TrueClass)
211
+
212
+ result = result.rh_merge(layer[:config][keys[0..-2]])
213
+ end
214
+ result[keys[-1]]
215
+ end
216
+
217
+ # Return true if at least the first key value found is of type Hash/Array,
218
+ # while be_exclusive is false
219
+ # return true if at least one key value is NOT Hash or Array,
220
+ # while be_exclusive is true
221
+ #
222
+ def _check_from_layers(keys, config_layers, data_opts, data_options,
223
+ be_exclusive)
224
+ found = false
225
+ found_class = nil
226
+ config_layers.each_index do |layer_index|
227
+ layer = config_layers[layer_index]
228
+
229
+ data_options = data_options.merge(data_opts[layer_index])
230
+
231
+ layer[:config].data_options(data_options)
232
+
233
+ next unless layer[:config].exist?(*keys)
234
+
235
+ found_class = layer[:config][*keys].class
236
+ found = [Hash, Array].include?(found_class)
237
+ if be_exclusive
238
+ return false unless found
239
+ unless found_class && layer[:config][*keys].is_a?(found_class)
240
+ return false
241
+ end
242
+ else
243
+ return found
244
+ end
245
+ end
246
+ true
247
+ end
248
+ end
249
+
250
+ # Internal core functions
251
+ class CoreConfig
252
+ # *****************************************************
253
+
254
+ private
255
+
256
+ # Del function called by default by del
257
+ # This function is typically used by a Child Class.
258
+ #
259
+ # * *Args*
260
+ # - +options+ : Hash of how to get the data
261
+ # - +:keys+ : Array of key path to found
262
+ # - +:name+ : layer to get data.
263
+ # - +:index+ : layer index to get data.
264
+ # If neither :name or :index is set, set will use the
265
+ # highest layer
266
+ # - +:data_opts+ : Array or Hash. Define data options per layer.
267
+ #
268
+ # * *Returns*
269
+ # - The value attached to the key deleted.
270
+ # OR
271
+ # - nil can be returned for several reasons:
272
+ # - value is nil
273
+ # - keys is not an array
274
+ # - keys array is empty.
275
+ #
276
+ # ex:
277
+ # value = CoreConfig.New
278
+ #
279
+ # value[:level1, :level2] = 'value'
280
+ # # => {:level1 => {:level2 => 'value'}}
281
+ #
282
+ # value.del(:keys => [:level1, :level2])
283
+ # # => {:level1 => {}}
284
+ def p_del(options) #:doc:
285
+ parameters = _nameindex_common_options_get(options, [:keys])
286
+ return nil if parameters.nil?
287
+
288
+ config_layers, data_opts, keys = parameters[0]
289
+
290
+ # get data options for level 0
291
+ data_options = options.clone.merge!(data_opts[0])
292
+
293
+ return nil if keys.length == 0
294
+
295
+ data_options.delete_if do |key|
296
+ [:keys, :names, :indexes, :name, :index].include?(key)
297
+ end
298
+
299
+ return nil unless @config_layers[0][:set]
300
+
301
+ config_layers[0][:config].data_options(data_options)
302
+ config_layers[0][:config].del(keys)
303
+ end
304
+
305
+ # p_file? Core file function called by default by #file.
306
+ # This function is typically used by a Child Class.
307
+ #
308
+ # This function can be used by child class to set one layer file name
309
+ #
310
+ # * *Args*
311
+ # - +options+ : Hash parameters
312
+ # - +:name+ : layer to get data.
313
+ # - +:index+ : Array layer indexes to get data.
314
+ # If neither :name or :index is set, level 0 is used.
315
+ #
316
+ # * *Returns*
317
+ # - filename : if updated.
318
+ # OR
319
+ # - false : if not updated.
320
+ # OR
321
+ # - nil : If something went wrong.
322
+ #
323
+ # ex:
324
+ # { :test => {:titi => 'found'}}
325
+ def p_file(filename = nil, options = {}) #:doc:
326
+ parameters = _nameindex_common_options_get(options)
327
+
328
+ return nil if parameters.nil?
329
+
330
+ config_layers = parameters[0][0]
331
+
332
+ layer = config_layers[0]
333
+
334
+ return layer[:config].filename unless filename.is_a?(String)
335
+
336
+ return false if _filename_unsetable(layer)
337
+
338
+ layer[:config].filename = filename
339
+ filename
340
+ end
341
+
342
+ def _filename_unsetable(layer)
343
+ return true if !layer[:load] && !layer[:save]
344
+
345
+ !layer[:config].filename.nil? && !layer[:file_set]
346
+ end
347
+
348
+ # p_exist? Core exist function called by default by #exist?.
349
+ # This function is typically used by a Child Class.
350
+ #
351
+ # * *Args*
352
+ # - +options+ : Hash parameters
353
+ # - +:keys+ : key tree to check existence in config layers
354
+ # - +:names+ : layer to get data.
355
+ # - +:indexes+ : Array layer indexes to get data.
356
+ # If neither :name or :index is set, get will search data
357
+ # per layers priority.
358
+ # - +:data_opts+ : Array or Hash. Define data options per layer.
359
+ #
360
+ # * *Returns*
361
+ # - boolean : true if the key path was found
362
+ #
363
+ # ex:
364
+ # # if one layer data is { :test => {:titi => 'found'}}
365
+ # p_exist?(:keys => [:test]) # => true
366
+ # p_exist?(:keys => [:test, :titi]) # => true
367
+ # p_exist?(:keys => [:test1]) # => false
368
+ #
369
+ def p_exist?(options) #:doc:
370
+ parameters = _common_options_get(options, [:keys])
371
+ return nil if parameters.nil?
372
+
373
+ config_layers, data_opts, keys = parameters[0]
374
+
375
+ return nil if keys.length == 0 || keys[0].nil? || config_layers[0].nil?
376
+
377
+ config_layers.each_index do |index|
378
+ config = config_layers[index][:config]
379
+
380
+ data_options = options.clone
381
+ data_options.delete_if do |key|
382
+ [:keys, :names, :indexes, :name, :index].include?(key)
383
+ end
384
+ data_options.merge!(data_opts[index])
385
+
386
+ config.data_options(data_options)
387
+ return true if config.exist?(*keys)
388
+ end
389
+ false
390
+ end
391
+
392
+ # p_where? called by default by #where
393
+ # This function is typically used by a Child Class.
394
+ #
395
+ # * *args*
396
+ # - +options+ : Hash parameters
397
+ # - +:keys+ : key tree to check existence in config layers
398
+ # - +:names+ : layer to get data.
399
+ # - +:indexes+ : Array layer indexes to get data.
400
+ # If neither :name or :index is set, get will search data
401
+ # per layers priority.
402
+ # - +:data_opts+ : Array or Hash. Define data options per layer.
403
+ #
404
+ # * *Returns*
405
+ # - array of config name : list of first layers where the key was found.
406
+ # OR
407
+ # - nil can be returned for several reasons:
408
+ # - keys is not an array
409
+ # - keys array is empty.
410
+ def p_where?(options) #:doc:
411
+ parameters = _common_options_get(options, [:keys])
412
+ return nil if parameters.nil?
413
+
414
+ config_layers, data_opts, keys = parameters[0]
415
+
416
+ return nil if keys.length == 0 || keys[0].nil? || config_layers[0].nil?
417
+
418
+ _do_where?(config_layers, keys, options, data_opts)
419
+ end
420
+
421
+ def _do_where?(config_layers, keys, options, data_opts)
422
+ layer_indexes = []
423
+ config_layers.each_index do |index|
424
+ config = config_layers[index][:config]
425
+
426
+ data_options = options.clone
427
+ data_options.delete_if do |key|
428
+ [:keys, :names, :indexes, :name, :index].include?(key)
429
+ end
430
+ data_options.merge!(data_opts[index]) if data_opts[index].is_a?(Hash)
431
+
432
+ config.data_options(data_options)
433
+ layer_indexes << config_layers[index][:name] if config.exist?(keys)
434
+ end
435
+ return layer_indexes if layer_indexes.length > 0
436
+ false
437
+ end
438
+
439
+ # Get function called by default by #[]
440
+ # This function is typically used by a Child Class.
441
+ #
442
+ # * *Args*
443
+ # - +options+ : Hash of how to get the data
444
+ # - +:keys+ : Array of key path to found
445
+ # - +:names+ : layer to get data.
446
+ # - +:indexes+ : Array layer indexes to get data.
447
+ # If neither :name or :index is set, get will search data
448
+ # per layers priority.
449
+ # - +:data_opts+ : Array or Hash. Define data options per layer.
450
+ # - +:merge+ : Provide a Merged result instead of first found
451
+ # returned.
452
+ # The merge result depends on deep layer data type found.
453
+ #
454
+ # Ex:
455
+ #
456
+ # with 2 config layers, like 'top' and 'bottom', usually a get will
457
+ # search in 'top', then 'bottom'. Merge will search in 'bottom'
458
+ # first. Then:
459
+ # - if 'bottom' value found is of type Hash(or Array),
460
+ # p_get will return a Hash(or Array), merged accross upper layers
461
+ # So, if 'top' is Hash(or Array), the result is the merge Hash
462
+ # (or Array). Otherwise, the 'top' will be ignored.
463
+ # - if 'bottom' value found is any kind of other types
464
+ # p_get won't merge, but get the highest non Array/Hash data found
465
+ # . So with a 'bottom' data of String, and 'top' as 'Fixnum', the
466
+ # result will be Fixnum. If there is any Hash/Array in between, it
467
+ # will be ignored.
468
+ #
469
+ # * *Returns*
470
+ # value found (or Hash merged) or nil.
471
+ #
472
+ # nil can be returned for several reasons:
473
+ # - keys is not an array
474
+ # - keys array is empty.
475
+ #
476
+ def p_get(options) #:doc:
477
+ parameters = _common_options_get(options, [:keys], [:merge])
478
+ return nil if parameters.nil?
479
+
480
+ # Required options : parameters[0]
481
+ config_layers, data_opts, keys = parameters[0]
482
+ # Optional options : parameters[1]
483
+ merge = parameters[1][0]
484
+
485
+ return nil if keys.length == 0 || keys[0].nil? || config_layers[0].nil?
486
+
487
+ data_options = options.clone
488
+ data_options.delete_if do |key|
489
+ [:keys, :names, :indexes, :name, :index, :merge].include?(key)
490
+ end
491
+
492
+ _get_from_layers(keys,
493
+ config_layers, data_opts, data_options,
494
+ merge)
495
+ end
496
+
497
+ # Set function called by default by #[]=
498
+ # This function is typically used by a Child Class.
499
+ #
500
+ # * *Args*
501
+ # - +options+ : Hash of how to get the data
502
+ # - +:value+: Value to set
503
+ # - +:keys+ : Array of key path to found
504
+ # - +:name+ : layer to get data.
505
+ # - +:index+: layer index to get data.
506
+ # If neither :name or :index is set, set will use the highest layer.
507
+ # - +:data_opts+ : Array or Hash. Define data options per layer.
508
+ #
509
+ # * *Returns*
510
+ # - The value set.
511
+ # OR
512
+ # - nil can be returned for several reasons:
513
+ # - layer options :set is false
514
+ # - options defines a :data_readonly to true.
515
+ # - value is nil
516
+ # - keys is not an array
517
+ # - keys array is empty.
518
+ #
519
+ # ex:
520
+ # value = CoreConfig.New
521
+ #
522
+ # value[:level1, :level2] = 'value'
523
+ # # => {:level1 => {:level2 => 'value'}}
524
+ def p_set(options) #:doc:
525
+ parameters = _nameindex_common_options_get(options, [:keys, :value])
526
+ return nil if parameters.nil?
527
+
528
+ config_layers, data_opts, keys, value = parameters[0]
529
+
530
+ # get data options for level 0
531
+ data_options = options.clone.merge!(data_opts[0])
532
+
533
+ return nil if keys.length == 0 || keys[0].nil? || config_layers[0].nil?
534
+
535
+ data_options.delete_if do |key|
536
+ [:keys, :names, :indexes, :name, :index, :value].include?(key)
537
+ end
538
+
539
+ return nil unless config_layers[0][:set]
540
+
541
+ config_layer = config_layers[0][:config]
542
+ config_layer.data_options(data_options)
543
+ config_layer[keys] = value
544
+ end
545
+
546
+ # Load from a file called by default by load.
547
+ # This function is typically used by a Child Class.
548
+ #
549
+ # * *Args* :
550
+ # - +options+ : Supported options for load
551
+ # - +:name+ : layer name to get data.
552
+ # - +:index+ : layer index to get data.
553
+ # If neither :name or :index is set, set will use the highest layer.
554
+ #
555
+ # * *Returns* :
556
+ # - true : loaded
557
+ # - false: not loaded. There are several possible reasons:
558
+ # - input/output issue (normally raised)
559
+ # - layer option :load is false.
560
+ def p_load(options = {}) #:doc:
561
+ options = {} if options.nil?
562
+ options[:names] = [options[:name]] if options.key?(:name)
563
+ options[:indexes] = [options[:index]] if options.key?(:index)
564
+
565
+ parameters = _nameindex_common_options_get(options)
566
+ return nil if parameters.nil?
567
+
568
+ config_layers = parameters[0][0]
569
+
570
+ return nil unless config_layers[0][:load]
571
+
572
+ config_layers[0][:config].load
573
+ end
574
+
575
+ # Save to a file called by default by save
576
+ # This function is typically used by a Child Class.
577
+ #
578
+ # * *Args* :
579
+ # - +options+ : Supported options for save
580
+ # - +:name+ : layer to get data.
581
+ # - +:index+: layer index to get data.
582
+ # If neither :name or :index is set, set will use the highest layer
583
+ #
584
+ # * *Returns* :
585
+ # - true : saved
586
+ # - false: not saved. There are several possible reasons:
587
+ # - options defines a :file_readonly to true.
588
+ # - input/output issue (normally raised)
589
+ # - layer option :save is false.
590
+ def p_save(options = {}) #:doc:
591
+ options[:names] = [options[:name]] if options.key?(:name)
592
+ options[:indexes] = [options[:index]] if options.key?(:index)
593
+
594
+ parameters = _nameindex_common_options_get(options)
595
+ return nil if parameters.nil?
596
+
597
+ config_layers = parameters[0][0]
598
+
599
+ return nil unless config_layers[0][:save]
600
+
601
+ config_layers[0][:config].save
602
+ end
603
+ end
604
+
605
+ # private functions usable by child classes
606
+ class CoreConfig
607
+ # initialize CoreConfig
608
+ #
609
+ # * *Args*
610
+ # - +config_layers+ : Array config layers configuration.
611
+ # Each layer options have those options:
612
+ # - :config : optional. See `Defining Config layer instance` for
613
+ # details
614
+ # - :name : required. String. Name of the config layer.
615
+ # Warning! unique name on layers is no tested.
616
+ # - :set : boolean. True if authorized. Default is True.
617
+ # - :load : boolean. True if authorized. Default is False.
618
+ # - :save : boolean. True if authorized. Default is False.
619
+ # - :file_set : boolean. True if authorized to update a filename.
620
+ # Default is False.
621
+ #
622
+ # each layers can defines some options for the layer to behave differently
623
+ # CoreConfig call a layer data_options to set some options, before
624
+ # exist?, get or [], set or []=, save and load functions.
625
+ # See BaseConfig::data_options for predefined options.
626
+ #
627
+ # Core config provides some private additionnal functions for
628
+ # child class functions:
629
+ # - #_set_data_options(layers, options) - To set data_options on one or
630
+ # more config layers
631
+ # - #p_get(options) - core get function
632
+ # - #p_set(options) - core set function
633
+ # - #p_save(options) core save function
634
+ # - #p_load(options) - core load function
635
+ #
636
+ # if +config_layers+ is not provided, CoreConfig will instanciate a runtime
637
+ # like system:
638
+ #
639
+ # config = CoreConfig.New
640
+ # # is equivalent to :
641
+ # config_layers = [{name: 'runtime',
642
+ # config: PRC::BaseConfig.new, set: true}]
643
+ # config = CoreConfig.New(config_layers)
644
+ #
645
+ # = Defining Config layer instance:
646
+ #
647
+ # :config value requires it to be of type 'BaseConfig'
648
+ # By default, it uses `:config => PRC::BaseConfig.new`
649
+ # Instead, you can set:
650
+ # * directly BaseConfig. `:config => PRC::BaseConfig.new`
651
+ # * a child based on BaseConfig. `:config => MyConfig.new`
652
+ # * some predefined enhanced BaseConfig:
653
+ # * PRC::SectionConfig. See prc_section_config.rb.
654
+ # `:config => PRC::SectionConfig.new`
655
+ #
656
+ def initialize(config_layers = nil)
657
+ if config_layers.nil?
658
+ config_layers = []
659
+ config_layers << CoreConfig.define_layer
660
+ end
661
+ initialize_layers(config_layers)
662
+ end
663
+
664
+ # This function add a config layer at runtime.
665
+ # The new layer added at runtime, can be removed at runtime
666
+ # with layer_remove
667
+ # The name MUST be different than other existing config layer names
668
+ #
669
+ # *Args*
670
+ # - +options+ : Hash data
671
+ # - :name : Required. Name of the layer to add
672
+ # - :index : Config position to use. 0 is the default. 0 is the first
673
+ # Config layer use by get.
674
+ # - :config : A Config instance of class type PRC::BaseConfig
675
+ # - :set : Boolean. True if is authorized to set a variable.
676
+ # - :load : Boolean. True if is authorized to load from a file.
677
+ # - :save : Boolean. True if is authorized to save to a file.
678
+ # - :file_set : Boolean. True if is authorized to change the file name.
679
+ #
680
+ # *returns*
681
+ # - true if layer is added.
682
+ # OR
683
+ # - nil : if layer name already exist
684
+ def layer_add(options)
685
+ layer = CoreConfig.define_layer(options)
686
+
687
+ layer[:init] = false # Runtime layer
688
+
689
+ index = 0
690
+ index = options[:index] if options[:index].is_a?(Fixnum)
691
+ names = []
692
+ @config_layers.each { |alayer| names << alayer[:name] }
693
+
694
+ return nil if names.include?(layer[:name])
695
+ @config_layers.insert(index, layer)
696
+ true
697
+ end
698
+
699
+ # Function to remove a runtime layer.
700
+ # You cannot remove a predefined layer, created during CoreConfig
701
+ # instanciation.
702
+ # *Args*
703
+ # - +options+ : Hash data
704
+ # - +:name+ : Name of the layer to remove.
705
+ # - +:index+: Index of the layer to remove.
706
+ #
707
+ # At least, :name or :index is required.
708
+ # If both; :name and :index are set, :name is used.
709
+ # *return*
710
+ # - true if layer name is removed.
711
+ # OR
712
+ # - nil : if not found or invalid.
713
+ def layer_remove(options)
714
+ index = layer_index(options[:name])
715
+ index = options[:index] if index.nil?
716
+
717
+ return nil if index.nil?
718
+
719
+ layer = @config_layers[index]
720
+
721
+ return nil if layer.nil? || layer[:init]
722
+
723
+ @config_layers.delete_at(index)
724
+ true
725
+ end
726
+
727
+ # Function to define layer options.
728
+ # By default, :set is true and :config is attached to a new PRC::BaseConfig
729
+ # instance.
730
+ #
731
+ # Supported options:
732
+ # - :config : optional. See `Defining Config layer instance` for details
733
+ # - :name : required. String. Name of the config layer.
734
+ # Warning! unique name on layers is no tested.
735
+ # - :set : boolean. True if authorized. Default is True.
736
+ # - :load : boolean. True if authorized. Default is False.
737
+ # - :save : boolean. True if authorized. Default is False.
738
+ # - :file_set : boolean. True if authorized to update a filename.
739
+ # Default is False.
740
+ def self.define_layer(options = {})
741
+ attributes = [:name, :config, :set, :load, :save, :file_set]
742
+
743
+ layer = {}
744
+
745
+ attributes.each do |attribute|
746
+ if options.key?(attribute)
747
+ layer[attribute] = options[attribute]
748
+ else
749
+ layer[attribute] = case attribute
750
+ when :name
751
+ 'runtime'
752
+ when :config
753
+ PRC::BaseConfig.new
754
+ when :set
755
+ true
756
+ else
757
+ false
758
+ end
759
+ end
760
+ end
761
+ layer
762
+ end
763
+
764
+ # layer_indexes function
765
+ #
766
+ # * *Args*
767
+ # - +:name+ : layer to identify.
768
+ #
769
+ # * *Returns*
770
+ # first index found or nil.
771
+ #
772
+ def layer_indexes(names)
773
+ names = [names] if names.is_a?(String)
774
+ return nil unless names.is_a?(Array)
775
+
776
+ layers = []
777
+
778
+ names.each do |name|
779
+ index = layer_index(name)
780
+ layers << index unless index.nil?
781
+ end
782
+ return layers if layers.length > 0
783
+ nil
784
+ end
785
+
786
+ # layer_index function
787
+ #
788
+ # * *Args*
789
+ # - +:name+ : layer to identify.
790
+ #
791
+ # * *Returns*
792
+ # first index found or nil.
793
+ #
794
+ def layer_index(name)
795
+ return nil unless name.is_a?(String)
796
+ return nil if @config_layers.nil?
797
+
798
+ @config_layers.each_index do |index|
799
+ return index if @config_layers[index][:name] == name
800
+ end
801
+ nil
802
+ end
803
+
804
+ private
805
+
806
+ # Function to initialize Config layers.
807
+ # This function is typically used by a Child Class.
808
+ #
809
+ # * *Args*
810
+ # - +config_layers+ : Array of config layers.
811
+ # First layer is the deepest config object.
812
+ # Last layer is the first config object queried.
813
+ #
814
+ # Ex: If we define 2 config layers:
815
+ #
816
+ # class Test << PRC::CoreConfig
817
+ # def initialize
818
+ # local = PRC::BaseConfig.new(:test => :found_local)
819
+ # runtime = PRC::BaseConfig.new(:test => :found_runtime)
820
+ # layers = []
821
+ # layers << PRC::CoreConfig.define_layer(name: 'local',
822
+ # config: local )
823
+ # layers << PRC::CoreConfig.define_layer(name: 'runtime',
824
+ # config: runtime )
825
+ # initialize_layers(layers)
826
+ # end
827
+ # end
828
+ #
829
+ # config = Test.new
830
+ #
831
+ # p config[:test] # => :found_runtime
832
+ #
833
+ # config[:test] = "where?"
834
+ # p config.where?(:test) # => ["runtime", "local"]
835
+ #
836
+ # config.del(:test)
837
+ # p config.where?(:test) # => ["local"]
838
+ # p config[:test] # => :found_local
839
+ #
840
+ # config[:test] = "and now?"
841
+ # p config.where?(:test) # => ["runtime", "local"]
842
+ #
843
+ def initialize_layers(config_layers = nil) #:doc:
844
+ @config_layers = []
845
+
846
+ config_layers.each do |layer|
847
+ next unless layer.is_a?(Hash) && layer.key?(:config) &&
848
+ layer[:config].is_a?(BaseConfig)
849
+ next unless layer[:name].is_a?(String)
850
+ @config_layers << _initialize_layer(layer)
851
+ end
852
+ @config_layers.reverse!
853
+ end
854
+ end
855
+
856
+ # This class implement The CoreConfig system of lorj.
857
+ #
858
+ # * You can use it directly. See ::new.
859
+ # * you can enhance it with class heritage feature.
860
+ # See Class child discussion later in this class documentation.
861
+ #
862
+ # = Public functions implemented:
863
+ #
864
+ # It implements several layer of CoreConfig object type
865
+ # to provide several source of data in layers priorities.
866
+ # Ex: RunTime => LocalConfig => AppDefault
867
+ #
868
+ # It implements config features:
869
+ # * #[] - To get a value for a key or tree of keys
870
+ # * #[]= - To set a Config value in the highest config.
871
+ # * #del - To delete key or simply nil the value in highest config.
872
+ # * #exist? - To check the existence of a value in config levels.
873
+ # * #where? - To get the name of the level where the value was found.
874
+ # * #file - To get or set a filename to a config layer.
875
+ # * #save - To save one config data level in a yaml file.
876
+ # * #load - To load data from a yaml file to a config data layer.
877
+ # * #merge - To merge several layers data values. Values must be Hash.
878
+ #
879
+ # When the instance is initialized, it defines 3 Config layers (BaseConfig).
880
+ #
881
+ # If you need to define layers differently, consider to create your child
882
+ # class. You will be able to use SectionConfig or even any BaseConfig Child
883
+ # class as well.
884
+ #
885
+ # For details about a Config layers, See BaseConfig or SectionConfig.
886
+ #
887
+ # = Child Class implementation:
888
+ #
889
+ # This class can be enhanced with any kind of additional functionality.
890
+ #
891
+ # You can redefine following functions
892
+ # exist?, [], []=, file, save, load, del, merge.
893
+ #
894
+ # Each public functions calls pendant function, private, prefixed by _, with
895
+ # default options
896
+ #
897
+ # public => private
898
+ # * #exist? => #p_exist?
899
+ # * #[] => #p_get
900
+ # * #[]= => #p_set
901
+ # * #file => #p_file
902
+ # * #save => #p_save
903
+ # * #load => #p_load
904
+ # * #del => #p_del
905
+ # * #merge => #p_get(:merge => true).
906
+ #
907
+ # == Examples:
908
+ #
909
+ # * Your child class can limit or re-organize config layers to query.
910
+ # Use :indexes or :names options to select which layer you want to query
911
+ # and call the core function.
912
+ #
913
+ # Ex: If you have 4 config levels and want to limit to 2 top ones
914
+ #
915
+ # def [](*keys)
916
+ # options = { keys: keys}
917
+ # options[:indexes] = [0, 1]
918
+ # p_get(options)
919
+ # end
920
+ #
921
+ # Ex: If you have 4 config levels and want to limit to 2 names.
922
+ #
923
+ # def [](*keys)
924
+ # options = { keys: keys}
925
+ # options[:names] = ['local', 'default_app']
926
+ # p_get(options)
927
+ # end
928
+ #
929
+ # * Your child class can force some levels options or define some extra
930
+ # options.
931
+ #
932
+ # Use :data_options to define each of them
933
+ #
934
+ # # Ex: If your class has 4 levels. /:name is not updatable for level 1.
935
+ #
936
+ # def [](*keys)
937
+ # options = { keys: keys }
938
+ # # The following defines data_readonly for the config level 1
939
+ # if keys[0] == :name
940
+ # options[:data_options] = [nil, {data_readonly: true}]
941
+ # end
942
+ # p_get(options)
943
+ # end
944
+ #
945
+ # # Ex: if some layer takes care of option :section, and apply to each
946
+ # # layers.
947
+ # def [](section, *keys)
948
+ # options = { keys: keys, section: section }
949
+ # p_get(options)
950
+ # end
951
+ #
952
+ # # Ex: if some layer takes care of option :section, and to apply to some
953
+ # # layers, like layer 1 and 2. (Assume with 4 layers.)
954
+ #
955
+ # def [](section, *keys)
956
+ # options = { keys: keys }
957
+ # options[:data_options] = [nil, {section: section}, {section: section}]
958
+ # p_get(options)
959
+ # end
960
+ #
961
+ #
962
+ class CoreConfig
963
+ # exist?
964
+ #
965
+ # * *Args*
966
+ # - +keys+ : Array of key path to found
967
+ #
968
+ # * *Returns*
969
+ # - boolean : true if the key path was found
970
+ #
971
+ # Class child:
972
+ # A class child can redefine this function to increase default
973
+ # features.
974
+ #
975
+ def exist?(*keys)
976
+ p_exist?(:keys => keys)
977
+ end
978
+
979
+ # where?
980
+ #
981
+ # * *Args*
982
+ # - +keys+ : Array of key path to found
983
+ #
984
+ # * *Returns*
985
+ # - boolean : true if the key path was found
986
+ #
987
+ def where?(*keys)
988
+ p_where?(:keys => keys)
989
+ end
990
+
991
+ # Get function
992
+ #
993
+ # * *Args*
994
+ # - +keys+ : Array of key path to found
995
+ #
996
+ # * *Returns*
997
+ # value found or nil.
998
+ #
999
+ def [](*keys)
1000
+ p_get(:keys => keys)
1001
+ end
1002
+
1003
+ # Merge function
1004
+ # Compare to get, merge will extract all values from each layers
1005
+ # If those values are found and are type of Hash, merge will merge
1006
+ # each layers values from the bottom to the top layer.
1007
+ # ie invert of CoreConfig.layers
1008
+ #
1009
+ # Note that if a layer contains a data, but not Hash, this layer
1010
+ # will be ignored.
1011
+ #
1012
+ # * *Args*
1013
+ # - +keys+ : Array of key path to found
1014
+ #
1015
+ # * *Returns*
1016
+ # value found merged or nil.
1017
+ #
1018
+ def merge(*keys)
1019
+ p_get(:keys => keys, :merge => true)
1020
+ end
1021
+
1022
+ # Set function
1023
+ #
1024
+ # * *Args*
1025
+ # - +keys+ : Array of key path to found
1026
+ # * *Returns*
1027
+ # - The value set or nil
1028
+ #
1029
+ # ex:
1030
+ # value = CoreConfig.New
1031
+ #
1032
+ # value[:level1, :level2] = 'value'
1033
+ # # => {:level1 => {:level2 => 'value'}}
1034
+ def []=(*keys, value)
1035
+ p_set(:keys => keys, :value => value)
1036
+ end
1037
+
1038
+ # Del function
1039
+ #
1040
+ # * *Args*
1041
+ # - +keys+ : Array of key path to found and delete the last element.
1042
+ # * *Returns*
1043
+ # - The Hash updated.
1044
+ #
1045
+ # ex:
1046
+ # value = CoreConfig.New
1047
+ #
1048
+ # value[:level1, :level2] = 'value'
1049
+ # # => {:level1 => {:level2 => 'value'}}
1050
+ # {:level1 => {:level2 => 'value'}}.del(:level1, :level2)
1051
+ # # => {:level1 => {}}
1052
+ def del(*keys)
1053
+ p_del(:keys => keys)
1054
+ end
1055
+
1056
+ # Load from a file to the highest layer or a specific layer.
1057
+ #
1058
+ # * *Args* :
1059
+ # - +options+ : Supported options for load
1060
+ # - +:name+ : layer to get data.
1061
+ # - +:index+: layer index to get data.
1062
+ # If neither :name or :index is set, set will use the highest
1063
+ # layer
1064
+ #
1065
+ # * *Returns* :
1066
+ # -
1067
+ # * *Raises* :
1068
+ # - ++ ->
1069
+ def load(options = {})
1070
+ p_load(options)
1071
+ end
1072
+
1073
+ # Save to a file
1074
+ #
1075
+ # * *Args* :
1076
+ # - +options+ : Supported options for save
1077
+ # - +:name+ : layer to get data.
1078
+ # - +:index+: layer index to get data.
1079
+ # If neither :name or :index is set, set will use the highest
1080
+ # layer
1081
+ #
1082
+ # * *Returns* :
1083
+ # -
1084
+ def save(options = {})
1085
+ p_save(options)
1086
+ end
1087
+
1088
+ # Get/Set the file name.
1089
+ #
1090
+ # * *Args*
1091
+ # - +:file+ : file name for the layer identified.
1092
+ # - +options+ : Supported options for save
1093
+ # - +:index+: layer index to get data.
1094
+ # - +:name+ : layer to get data.
1095
+ # If neither :name or :index is set, nil is returned.
1096
+ #
1097
+ # * *Returns*
1098
+ # - The file name.
1099
+ def file(filename = nil, options = {})
1100
+ p_file(filename, options)
1101
+ end
1102
+
1103
+ # Function to check if merge can be used on a key.
1104
+ # merge can return data only if at least one key value accross layers
1105
+ # are of type Hash or Array.
1106
+ # * *Args*
1107
+ # - +options+ : Hash of how to get the data
1108
+ # - +:value+ : Value to set
1109
+ # - +:keys+ : Array of key path to found
1110
+ # - +:name+ : layer to get data.
1111
+ # - +:index+ : layer index to get data.
1112
+ # If neither :name or :index is set, set will use the highest layer.
1113
+ # - +:data_opts+ : Array or Hash. Define data options per layer.
1114
+ # - +:exclusive+ : true to ensure values found are exclusively Hash or
1115
+ # Array
1116
+ #
1117
+ def mergeable?(options)
1118
+ parameters = _common_options_get(options, [:keys], [:exclusive])
1119
+ return nil if parameters.nil?
1120
+
1121
+ # Required options : parameters[0]
1122
+ config_layers, data_opts, keys = parameters[0]
1123
+ # Optional options : parameters[1]
1124
+ be_exclusive = parameters[1][0]
1125
+
1126
+ # Merge is done in the reverse order. ie from deepest to top.
1127
+ config_layers = config_layers.reverse
1128
+
1129
+ return nil if keys.length == 0 || keys[0].nil? || config_layers[0].nil?
1130
+
1131
+ data_options = options.clone
1132
+ data_options.delete_if do |key|
1133
+ [:keys, :names, :indexes, :name, :index, :merge].include?(key)
1134
+ end
1135
+
1136
+ _check_from_layers(keys, config_layers, data_opts, data_options,
1137
+ be_exclusive)
1138
+ end
1139
+
1140
+ # Function to get the version of a config layer name.
1141
+ # * *Args*
1142
+ # - +:name+ : layer to get data.
1143
+ #
1144
+ def version(name)
1145
+ return nil unless name.is_a?(String)
1146
+
1147
+ index = layer_index(name)
1148
+ return nil if index.nil?
1149
+
1150
+ @config_layers[index][:config].version
1151
+ end
1152
+
1153
+ # Function to set the version of a config layer name.
1154
+ # * *Args*
1155
+ # - +:name+ : layer to set data version.
1156
+ #
1157
+ def version_set(name, version)
1158
+ return nil unless name.is_a?(String) && version.is_a?(String)
1159
+
1160
+ index = layer_index(name)
1161
+ return nil if index.nil?
1162
+
1163
+ @config_layers[index][:config].version = version
1164
+ end
1165
+
1166
+ def latest_version?(name)
1167
+ return nil unless name.is_a?(String)
1168
+
1169
+ index = layer_index(name)
1170
+ return nil if index.nil?
1171
+
1172
+ @config_layers[index][:config].latest_version?
1173
+ end
1174
+
1175
+ # List all config layers defined in this instance.
1176
+ def layers
1177
+ result = []
1178
+ @config_layers.each { |layer| result << layer[:name] }
1179
+ result
1180
+ end
1181
+
1182
+ # Display in human format.
1183
+ def to_s
1184
+ data = "Configs list ordered:\n"
1185
+ @config_layers.each do |layer|
1186
+ data += format("---- Config : %s ----\noptions: ", layer[:name])
1187
+
1188
+ data += 'predefined, ' if layer[:init].is_a?(TrueClass)
1189
+ if layer[:set]
1190
+ data += 'data RW '
1191
+ else
1192
+ data += 'data RO '
1193
+ end
1194
+ data += format(", %s\n", to_s_file_opts(layer))
1195
+
1196
+ data += format("%s\n", layer[:config].to_s)
1197
+ end
1198
+ data
1199
+ end
1200
+
1201
+ private
1202
+
1203
+ def to_s_file_opts(layer)
1204
+ data = 'File '
1205
+ if layer[:load] &&
1206
+ if layer[:save]
1207
+ data += 'RW'
1208
+ else
1209
+ data += 'RO'
1210
+ end
1211
+ data += ', filename updatable' if layer[:file_set]
1212
+ else
1213
+ data += 'None'
1214
+ end
1215
+ data
1216
+ end
1217
+ end
1218
+ end