config_layers 0.1.0

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