configtoolkit 1.0.6 → 1.0.7
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.
- data/History.txt +3 -0
- data/Manifest.txt +2 -2
- data/Rakefile +1 -1
- data/lib/configtoolkit/baseconfig.rb +298 -234
- data/lib/configtoolkit/types.rb +13 -0
- data/lib/configtoolkit/yamlreader.rb +22 -0
- data/lib/configtoolkit/yamlwriter.rb +106 -27
- data/test/{sample.ruby_yaml_types.yaml → sample.ruby_yaml_classes.yaml} +0 -0
- data/test/{sample.standard_yaml_types.yaml → sample.standard_yaml_classes.yaml} +0 -0
- data/test/test_yaml.rb +14 -14
- metadata +3 -3
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -17,6 +17,6 @@ test/bad_config.yaml
|
|
17
17
|
test/contained_sample.yaml
|
18
18
|
test/firewall.yaml
|
19
19
|
test/machines.yaml
|
20
|
-
test/sample.
|
21
|
-
test/sample.
|
20
|
+
test/sample.ruby_yaml_classes.yaml
|
21
|
+
test/sample.standard_yaml_classes.yaml
|
22
22
|
test/webserver.yaml
|
data/Rakefile
CHANGED
@@ -40,10 +40,11 @@ class BaseConfig
|
|
40
40
|
#
|
41
41
|
# This class represents the specification for a parameter. Right now,
|
42
42
|
# a parameter specification consists of:
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
43
|
+
# * The parameter name (Symbol)
|
44
|
+
# * The parameter value class (Class); values must be instances of this
|
45
|
+
# class or a child class.
|
46
|
+
# * Whether or not the parameter is required
|
47
|
+
# * If the parameter is not required, a default value
|
47
48
|
#
|
48
49
|
class ParamSpec
|
49
50
|
attr_reader :name
|
@@ -52,20 +53,21 @@ class BaseConfig
|
|
52
53
|
|
53
54
|
#
|
54
55
|
# ====Description:
|
55
|
-
#
|
56
|
+
# This constructs a Paramspec for parameter name and value
|
57
|
+
# of class value_class. If is_required, then the parameter is
|
58
|
+
# required; otherwise, the parameter is optional with a default
|
59
|
+
# value of default_value.
|
56
60
|
#
|
57
61
|
# ====Parameters:
|
58
62
|
# [name]
|
59
|
-
#
|
63
|
+
# The name of the parameter
|
60
64
|
# [value_class]
|
61
|
-
#
|
65
|
+
# The class of the parameter's values
|
62
66
|
# [is_required]
|
63
|
-
#
|
67
|
+
# Whether or not the parameter must be set in a configuration file
|
64
68
|
# [default_value]
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# ====Returns:
|
68
|
-
#
|
69
|
+
# If the parameter need not be set in a configuration file
|
70
|
+
# (!is_required), the value it defaults to if it's not specified.
|
69
71
|
#
|
70
72
|
def initialize(name, value_class, is_required, default_value)
|
71
73
|
@name = name
|
@@ -77,12 +79,10 @@ class BaseConfig
|
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
80
|
-
#
|
81
|
-
# ====Description:
|
82
|
-
#
|
83
82
|
#
|
84
83
|
# ====Returns:
|
85
|
-
#
|
84
|
+
# true if and only if the parameter must be set in a configuration
|
85
|
+
# file; false otherwise.
|
86
86
|
#
|
87
87
|
def is_required?
|
88
88
|
return @is_required
|
@@ -93,37 +93,52 @@ class BaseConfig
|
|
93
93
|
# The indentation used to indicate nesting when printing out
|
94
94
|
# a BaseConfig.
|
95
95
|
#
|
96
|
-
NESTING_INDENT = "
|
96
|
+
NESTING_INDENT = " " * 4
|
97
97
|
|
98
98
|
#
|
99
99
|
# Create accessors for some class instance variables;
|
100
|
-
# these variables will contain the
|
101
|
-
# BaseConfig child class (
|
102
|
-
#
|
100
|
+
# these variables will contain the specifications for a given
|
101
|
+
# BaseConfig child class (what parameters are specified, which
|
102
|
+
# parameters are required, etc).
|
103
103
|
#
|
104
|
-
# The
|
105
|
-
# access (we want to print them out in the order in which they
|
106
|
-
# were
|
107
|
-
#
|
108
|
-
# is supported by the class).
|
104
|
+
# The parameter specifications are stored in a list (@param_spec_list)
|
105
|
+
# for ordered access (we want to print them out in the order in which they
|
106
|
+
# were specified in the BaseConfig child class, for instance) and
|
107
|
+
# in a hash (@param_hash), for fast lookups (as we want to check whether
|
108
|
+
# a parameter that we read from a file is supported by the class).
|
109
109
|
#
|
110
110
|
class << self
|
111
|
+
#
|
112
|
+
# This is an Array (class instance variable) containing the parameter
|
113
|
+
# specifications (ParamSpecs) in the order in which they were specified
|
114
|
+
# in the BaseConfig child class.
|
115
|
+
#
|
111
116
|
attr_reader :param_spec_list
|
117
|
+
|
118
|
+
#
|
119
|
+
# This is a Hash (class instance variable) mapping
|
120
|
+
# paramater names (Symbols) to parameter specifications (ParamSpecs)
|
121
|
+
#
|
112
122
|
attr_reader :param_spec_lookup_table
|
123
|
+
|
124
|
+
#
|
125
|
+
# This is an Integer (class instance variable) containing the length
|
126
|
+
# of the longest parameter name. This is used to line up the output
|
127
|
+
# when printing out an instance.
|
128
|
+
#
|
113
129
|
attr_reader :longest_param_name_length
|
114
|
-
attr_reader :required_params
|
115
130
|
end
|
116
131
|
|
117
132
|
#
|
118
133
|
# ====Description:
|
119
|
-
#
|
134
|
+
# This is BaseConfig's implementation of the inherited hook, which is
|
135
|
+
# called by the Ruby interpreter whenever BaseConfig is extended. This
|
136
|
+
# function initializes the class instance variables of the new
|
137
|
+
# child class.
|
120
138
|
#
|
121
139
|
# ====Parameters:
|
122
140
|
# [child_class]
|
123
|
-
#
|
124
|
-
#
|
125
|
-
# ====Returns:
|
126
|
-
#
|
141
|
+
# The new child class
|
127
142
|
#
|
128
143
|
def self.inherited(child_class)
|
129
144
|
#
|
@@ -133,52 +148,68 @@ class BaseConfig
|
|
133
148
|
@param_spec_list = []
|
134
149
|
@param_spec_lookup_table = {}
|
135
150
|
@longest_param_name_length = 0
|
136
|
-
@required_params = Set.new()
|
137
151
|
end
|
138
152
|
end
|
139
153
|
|
140
|
-
#
|
154
|
+
#
|
141
155
|
# ====Description:
|
142
|
-
#
|
156
|
+
# This class method adds a new parameter to a BaseConfig child
|
157
|
+
# class (it should be called within the body of the child class'
|
158
|
+
# definition). The new parameter has name name and has values
|
159
|
+
# of class value_class (or one of value_class' child classes).
|
160
|
+
# The parameter must be set in a configuration file if
|
161
|
+
# is_required is true; if !is_required, then default_value is the
|
162
|
+
# value of the parameter if it's not set in a configuration file.
|
163
|
+
#
|
164
|
+
# This method optionally accepts a block (validate_value_block), which,
|
165
|
+
# if present, is called with the new value for the parameter before setting
|
166
|
+
# the parameter with the new value, allowing custom validation
|
167
|
+
# code to be executed (if this validation fails, then raise_error should
|
168
|
+
# be called inside of the block).
|
169
|
+
#
|
170
|
+
# This method is not meant to be called directly; users instead should
|
171
|
+
# call add_required_param or add_optional_param.
|
143
172
|
#
|
144
173
|
# ====Parameters:
|
145
174
|
# [name]
|
146
|
-
#
|
175
|
+
# The name of the parameter
|
147
176
|
# [value_class]
|
148
|
-
#
|
177
|
+
# The parent class of the parameter's values
|
149
178
|
# [is_required]
|
150
|
-
#
|
179
|
+
# Whether or not the parameter must be specified in a
|
180
|
+
# configuration file.
|
151
181
|
# [default_value]
|
152
|
-
#
|
182
|
+
# If !is_required, then the default value of the parameter if it's
|
183
|
+
# not specified in a configuration file.
|
153
184
|
# [validate_value_block]
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
185
|
+
# An optional block that, if present, is called to validate a new
|
186
|
+
# value for the parameter before setting the parameter to the new
|
187
|
+
# value.
|
158
188
|
#
|
159
189
|
def self.add_param(name,
|
160
190
|
value_class,
|
161
191
|
is_required,
|
162
192
|
default_value,
|
163
|
-
&validate_value_block)
|
193
|
+
&validate_value_block) #:doc:
|
164
194
|
param_spec = ParamSpec.new(name, value_class, is_required, default_value)
|
165
195
|
@param_spec_list.push(param_spec)
|
166
196
|
@param_spec_lookup_table[name] = param_spec
|
167
197
|
|
168
198
|
#
|
169
|
-
# Define the setter
|
170
|
-
# than a block, so as to avoid the need to access
|
171
|
-
# when the setter actually is called.
|
199
|
+
# Define the getter and setter methods with a string passed to
|
200
|
+
# class_eval rather than a block, so as to avoid the need to access
|
201
|
+
# the reflectoin APIs when the setter actually is called.
|
172
202
|
#
|
173
203
|
# Note that the setter will call load_value_impl() on any value
|
174
204
|
# that it is passed; this ensures that data specified programatically
|
175
|
-
# gets processed the same way as that sourced from files.
|
205
|
+
# gets processed in the same way as that sourced from files.
|
176
206
|
#
|
177
207
|
# There is a bit of hackiness to get the specified value class; the
|
178
208
|
# value class is retrieved from a lookup table rather
|
179
|
-
# than being sourced from value_class.
|
180
|
-
#
|
181
|
-
#
|
209
|
+
# than being sourced from the value_class argument.
|
210
|
+
# The reason for this is that the value_class argument could be
|
211
|
+
# an anonymous class without a valid name and so cannot safely
|
212
|
+
# be expanded in a string. Instead, we'll access a
|
182
213
|
# variable storing this class from the param_spec_lookup_table.
|
183
214
|
#
|
184
215
|
methods = String.new()
|
@@ -189,6 +220,7 @@ class BaseConfig
|
|
189
220
|
methods << " loaded_value = load_value_impl(\"#{name}\", value_class, value)\n"
|
190
221
|
if(validate_value_block != nil)
|
191
222
|
validate_value_method = "validate_#{name}_value".to_sym()
|
223
|
+
define_method(validate_value_method, &validate_value_block)
|
192
224
|
methods << " begin\n"
|
193
225
|
methods << " #{validate_value_method}(value)\n"
|
194
226
|
methods << " rescue Error => e\n"
|
@@ -202,76 +234,57 @@ class BaseConfig
|
|
202
234
|
|
203
235
|
class_eval(methods)
|
204
236
|
|
205
|
-
if(validate_value_block != nil)
|
206
|
-
define_method(validate_value_method, &validate_value_block)
|
207
|
-
end
|
208
|
-
|
209
237
|
name_str = name.to_s
|
210
238
|
if(name_str.length > @longest_param_name_length)
|
211
239
|
@longest_param_name_length = name_str.length
|
212
240
|
end
|
213
|
-
|
214
|
-
if(is_required)
|
215
|
-
@required_params.add(name)
|
216
|
-
end
|
217
241
|
end
|
218
242
|
private_class_method :add_param
|
219
243
|
|
220
244
|
#
|
221
245
|
# ====Description:
|
222
|
-
#
|
246
|
+
# This is a wrapper around add_param that passes is_required = true.
|
223
247
|
#
|
224
248
|
# ====Parameters:
|
225
|
-
#
|
226
|
-
# ?
|
227
|
-
# [value_class]
|
228
|
-
# ?
|
229
|
-
# [validate_value_block]
|
230
|
-
# ?
|
231
|
-
#
|
232
|
-
# ====Returns:
|
233
|
-
#
|
249
|
+
# See add_param's parameter list.
|
234
250
|
#
|
235
251
|
def self.add_required_param(name,
|
236
252
|
value_class,
|
237
|
-
&validate_value_block)
|
253
|
+
&validate_value_block) #:doc:
|
238
254
|
add_param(name, value_class, true, nil, &validate_value_block)
|
239
255
|
end
|
240
256
|
private_class_method :add_required_param
|
241
257
|
|
242
258
|
#
|
243
259
|
# ====Description:
|
244
|
-
#
|
260
|
+
# This is a wrapper around add_param that passes is_required = false and
|
261
|
+
# default_value = default_value.
|
245
262
|
#
|
246
263
|
# ====Parameters:
|
247
|
-
#
|
248
|
-
# ?
|
249
|
-
# [value_class]
|
250
|
-
# ?
|
251
|
-
# [default_value]
|
252
|
-
# ?
|
253
|
-
# [validate_value_block]
|
254
|
-
# ?
|
255
|
-
#
|
256
|
-
# ====Returns:
|
257
|
-
#
|
264
|
+
# See add_param's parameter list.
|
258
265
|
#
|
259
266
|
def self.add_optional_param(name,
|
260
267
|
value_class,
|
261
268
|
default_value,
|
262
|
-
&validate_value_block)
|
269
|
+
&validate_value_block) #:doc:
|
263
270
|
add_param(name, value_class, false, default_value, &validate_value_block)
|
264
271
|
end
|
265
272
|
private_class_method :add_optional_param
|
266
273
|
|
267
|
-
|
274
|
+
#
|
275
|
+
# This is a String (instance variable) containing the name of the object that
|
276
|
+
# contains this instance's configuration. For example, if
|
277
|
+
# all of this instance's configuration is under production and
|
278
|
+
# webserver, then the containing_object_name would be
|
279
|
+
# 'production.webserver'.
|
280
|
+
#
|
281
|
+
attr_accessor :containing_object_name
|
282
|
+
protected :containing_object_name= # This is NOT part of the public interface
|
268
283
|
|
269
284
|
#
|
270
285
|
# ====Description:
|
271
|
-
#
|
272
|
-
#
|
273
|
-
# ====Returns:
|
274
|
-
#
|
286
|
+
# This constructs a BaseConfig with no parameters set (all parameters
|
287
|
+
# initially have a nil value).
|
275
288
|
#
|
276
289
|
def initialize
|
277
290
|
@containing_object_name = ""
|
@@ -284,19 +297,29 @@ class BaseConfig
|
|
284
297
|
|
285
298
|
#
|
286
299
|
# ====Description:
|
287
|
-
#
|
300
|
+
# This method enforces the BaseConfig's specifications. In particular,
|
301
|
+
# it:
|
302
|
+
# * Checks hat all required parameters have been set. If a required
|
303
|
+
# parameter is missing, then this method raises an Error.
|
304
|
+
# * Sets all optional parameters without values to their
|
305
|
+
# default values.
|
306
|
+
# * Calls validate_all_values, if this exists.
|
307
|
+
#
|
308
|
+
# This method is called at the end of a load operation (verifying that
|
309
|
+
# a valid configuration was loaded from a reader) and before a dump operation
|
310
|
+
# (verifying that a valid configuration will be dumped). The operation_name
|
311
|
+
# parameter is a String containing the name of the operation calling
|
312
|
+
# this method; this String is used in Error messages.
|
288
313
|
#
|
289
314
|
# ====Parameters:
|
290
315
|
# [operation_name]
|
291
|
-
#
|
292
|
-
#
|
293
|
-
# ====Returns:
|
294
|
-
#
|
316
|
+
# A String containing the name of the operation calling this
|
317
|
+
# method ("load" or "dump", for example)
|
295
318
|
#
|
296
319
|
def enforce_specs(operation_name)
|
297
320
|
#
|
298
321
|
# Iterate through the parameters without values. If any are
|
299
|
-
# required parameters, then
|
322
|
+
# required parameters, then raise (after completing the
|
300
323
|
# iteration in order to get the full list of missing
|
301
324
|
# parameters). Otherwise, set all optional, missing
|
302
325
|
# parameters to their default values.
|
@@ -308,18 +331,23 @@ class BaseConfig
|
|
308
331
|
if(param_spec.is_required?)
|
309
332
|
missing_params.push(param_spec.name)
|
310
333
|
else
|
334
|
+
#
|
335
|
+
# Even the default values are set through the setter.
|
336
|
+
#
|
311
337
|
send("#{param_spec.name}=", param_spec.default_value)
|
312
338
|
end
|
313
339
|
end
|
314
340
|
end
|
315
341
|
|
316
342
|
if(!missing_params.empty?)
|
343
|
+
if(@containing_object_name.empty?)
|
344
|
+
param_prefix = ""
|
345
|
+
else
|
346
|
+
param_prefix = "#{@containing_object_name}."
|
347
|
+
end
|
348
|
+
|
317
349
|
missing_param_spec_list = missing_params.map do |param_name|
|
318
|
-
|
319
|
-
next "#{param_name}"
|
320
|
-
else
|
321
|
-
next "#{@containing_object_name}.#{param_name}"
|
322
|
-
end
|
350
|
+
"#{param_prefix}#{param_name}"
|
323
351
|
end
|
324
352
|
|
325
353
|
raise Error, "missing parameter(s): #{missing_param_spec_list.join(", ")}"
|
@@ -329,6 +357,10 @@ class BaseConfig
|
|
329
357
|
begin
|
330
358
|
validate_all_values()
|
331
359
|
rescue Error => e
|
360
|
+
#
|
361
|
+
# Rescue the error, add some information to the error message, and
|
362
|
+
# throw a repackaged error.
|
363
|
+
#
|
332
364
|
message = "#{self.class}#validate_all_values #{operation_name} error"
|
333
365
|
|
334
366
|
if(!@containing_object_name.empty?)
|
@@ -345,23 +377,31 @@ class BaseConfig
|
|
345
377
|
#
|
346
378
|
# ====Description:
|
347
379
|
# Because the to_s in Ruby 1.8 for the Array and Hash classes
|
348
|
-
# are horrible...
|
380
|
+
# are horrible... This method returns a readable String representation
|
381
|
+
# of value, recursively expanding the contents of Array and Hash
|
382
|
+
# value arguments and otherwise returning value.to_s. This is
|
383
|
+
# called by construct_error_message in order to construct a nicely formatted
|
384
|
+
# error message.
|
349
385
|
#
|
350
386
|
# ====Parameters:
|
351
387
|
# [value]
|
352
|
-
#
|
388
|
+
# The object for which to return a String representation
|
353
389
|
#
|
354
390
|
# ====Returns:
|
355
|
-
#
|
391
|
+
# A nicely formatted String representation of value
|
356
392
|
#
|
357
393
|
def construct_value_str(value)
|
358
394
|
if(value.is_a?(Array))
|
395
|
+
#
|
396
|
+
# Recursively call construct_value_str for each Array element,
|
397
|
+
# surrounding the element strings in brackets.
|
398
|
+
#
|
359
399
|
str = "["
|
360
400
|
|
361
401
|
value.each_with_index do |element, index|
|
362
402
|
str << construct_value_str(element)
|
363
403
|
|
364
|
-
if(index
|
404
|
+
if(index < (value.size - 1))
|
365
405
|
str << ", "
|
366
406
|
end
|
367
407
|
end
|
@@ -369,12 +409,16 @@ class BaseConfig
|
|
369
409
|
str << "]"
|
370
410
|
return str
|
371
411
|
elsif(value.is_a?(Hash))
|
412
|
+
#
|
413
|
+
# Recursively call construct_value_str for each Hash element,
|
414
|
+
# surrounding the element strings in braces.
|
415
|
+
#
|
372
416
|
str = "{"
|
373
417
|
|
374
418
|
value.each_with_index do |key_value, index|
|
375
419
|
str << "#{construct_value_str(key_value[0])}=>#{construct_value_str(key_value[1])}"
|
376
420
|
|
377
|
-
if(index
|
421
|
+
if(index < (value.size - 1))
|
378
422
|
str << ", "
|
379
423
|
end
|
380
424
|
end
|
@@ -389,57 +433,62 @@ class BaseConfig
|
|
389
433
|
|
390
434
|
#
|
391
435
|
# ====Description:
|
392
|
-
#
|
436
|
+
# This method returns an error message for an error (described by
|
437
|
+
# reason) that occurred while setting parameter param_name with
|
438
|
+
# value value.
|
393
439
|
#
|
394
440
|
# ====Parameters:
|
395
441
|
# [param_name]
|
396
|
-
#
|
442
|
+
# The parameter being set when the error occurred (String)
|
443
|
+
# This need not be a "real" parameter value; it also can be
|
444
|
+
# an element in an Array value (i.e., the 3rd element of Array
|
445
|
+
# parameter servers will be passed as "servers[2]").
|
397
446
|
# [value]
|
398
|
-
#
|
399
|
-
# [
|
400
|
-
#
|
447
|
+
# The parameter value that caused the error
|
448
|
+
# [reason]
|
449
|
+
# The reason for the error (String)
|
401
450
|
#
|
402
451
|
# ====Returns:
|
452
|
+
# An error message that includes the parameter name, the problem
|
453
|
+
# parameter value, and the error description.
|
403
454
|
#
|
404
|
-
|
405
|
-
def construct_error_message(param_name, value, message)
|
455
|
+
def construct_error_message(param_name, value, reason)
|
406
456
|
if(@containing_object_name.empty?)
|
407
|
-
full_param_name = ""
|
457
|
+
full_param_name = "#{param_name}"
|
408
458
|
else
|
409
|
-
full_param_name = "#{@containing_object_name}
|
459
|
+
full_param_name = "#{@containing_object_name}.#{param_name}"
|
410
460
|
end
|
411
461
|
|
412
|
-
full_param_name
|
413
|
-
|
414
|
-
return "error setting parameter #{full_param_name} with value #{construct_value_str(value)}: #{message}."
|
462
|
+
return "error setting parameter #{full_param_name} with value #{construct_value_str(value)}: #{reason}."
|
415
463
|
end
|
416
464
|
private :construct_error_message
|
417
465
|
|
418
466
|
#
|
419
467
|
# ====Description:
|
420
|
-
#
|
468
|
+
# This method raises an Error with message reason. It is meant to
|
469
|
+
# be called from user-defined parameter validation blocks. This is
|
470
|
+
# a Hotel California type of call; it does not return.
|
421
471
|
#
|
422
472
|
# ====Parameters:
|
423
|
-
# [
|
424
|
-
#
|
425
|
-
#
|
426
|
-
# ====Returns:
|
473
|
+
# [reason]
|
474
|
+
# The reason for the error
|
427
475
|
#
|
428
|
-
|
429
|
-
|
430
|
-
raise Error, message, caller()
|
476
|
+
def raise_error(reason)
|
477
|
+
raise Error, reason, caller()
|
431
478
|
end
|
432
479
|
|
433
480
|
#
|
434
481
|
# ====Description:
|
435
|
-
#
|
482
|
+
# Equality operator for BaseConfig; this iterates through the
|
483
|
+
# specified parameters and compares the parameter values of
|
484
|
+
# self and rhs using the equality operator of the parameter values.
|
436
485
|
#
|
437
486
|
# ====Parameters:
|
438
487
|
# [rhs]
|
439
|
-
#
|
488
|
+
# The instance to compare with self
|
440
489
|
#
|
441
490
|
# ====Returns:
|
442
|
-
#
|
491
|
+
# True if and only if the values of self and rhs are equal
|
443
492
|
#
|
444
493
|
def ==(rhs)
|
445
494
|
if(rhs == nil)
|
@@ -457,24 +506,24 @@ class BaseConfig
|
|
457
506
|
|
458
507
|
#
|
459
508
|
# ====Description:
|
460
|
-
#
|
509
|
+
# This method returns a dump of parameter value value.
|
510
|
+
# It recursively calls itself and dump_impl to handle
|
511
|
+
# Array elements and BaseConfig instances.
|
461
512
|
#
|
462
513
|
# ====Parameters:
|
463
|
-
# [value_class]
|
464
|
-
# ?
|
465
514
|
# [value]
|
466
|
-
#
|
515
|
+
# The parameter value to dump
|
467
516
|
#
|
468
517
|
# ====Returns:
|
518
|
+
# A dump of value
|
469
519
|
#
|
470
|
-
|
471
|
-
|
472
|
-
if(value_class < BaseConfig)
|
520
|
+
def dump_value_impl(value)
|
521
|
+
if(value.class < BaseConfig)
|
473
522
|
return value.dump_impl({})
|
474
|
-
elsif(
|
523
|
+
elsif(value.class == Array)
|
475
524
|
dumped_array = []
|
476
525
|
value.each do |element|
|
477
|
-
dumped_array.push(dump_value_impl(
|
526
|
+
dumped_array.push(dump_value_impl(element))
|
478
527
|
end
|
479
528
|
return dumped_array
|
480
529
|
else
|
@@ -485,14 +534,20 @@ class BaseConfig
|
|
485
534
|
|
486
535
|
#
|
487
536
|
# ====Description:
|
488
|
-
#
|
537
|
+
# This method dumps self into containing_object_hash; that is,
|
538
|
+
# it adds entries to containing_object_hash for each parameter.
|
539
|
+
# An entry's key is the parameter name (as a String), and the value is
|
540
|
+
# the parameter value. The method returns containing_object_hash
|
541
|
+
# after the dump. Before dumping everything, this method calls
|
542
|
+
# enforce_specs in order to ensure that valid data is being
|
543
|
+
# dumped.
|
489
544
|
#
|
490
545
|
# ====Parameters:
|
491
546
|
# [containing_object_hash]
|
492
|
-
#
|
547
|
+
# The hash into which to dump self
|
493
548
|
#
|
494
549
|
# ====Returns:
|
495
|
-
#
|
550
|
+
# containing_object_hash after the dump
|
496
551
|
#
|
497
552
|
def dump_impl(containing_object_hash)
|
498
553
|
#
|
@@ -502,7 +557,7 @@ class BaseConfig
|
|
502
557
|
enforce_specs("dump")
|
503
558
|
|
504
559
|
self.class.param_spec_list.each do |param_spec|
|
505
|
-
containing_object_hash[param_spec.name.to_s] = dump_value_impl(
|
560
|
+
containing_object_hash[param_spec.name.to_s] = dump_value_impl(send(param_spec.name))
|
506
561
|
end
|
507
562
|
|
508
563
|
return containing_object_hash
|
@@ -511,14 +566,16 @@ class BaseConfig
|
|
511
566
|
|
512
567
|
#
|
513
568
|
# ====Description:
|
514
|
-
#
|
569
|
+
# This method dumps self to writer. That is, it constructs
|
570
|
+
# a Hash containing entries for the containing object (and,
|
571
|
+
# in the the most nested containing object entry, entries for the
|
572
|
+
# paramters) and passes the Hash to the write method of writer. The
|
573
|
+
# configuration's data is checked against its specifications
|
574
|
+
# before anything is dumped.
|
515
575
|
#
|
516
576
|
# ====Parameters:
|
517
577
|
# [writer]
|
518
|
-
#
|
519
|
-
#
|
520
|
-
# ====Returns:
|
521
|
-
#
|
578
|
+
# The writer to which to dump
|
522
579
|
#
|
523
580
|
def dump(writer)
|
524
581
|
dump_hash = {}
|
@@ -536,31 +593,46 @@ class BaseConfig
|
|
536
593
|
|
537
594
|
#
|
538
595
|
# ====Description:
|
539
|
-
#
|
596
|
+
# This method validates and returns a parameter value of class value_class
|
597
|
+
# for parameter param_name (String) from raw_value. The logic in this
|
598
|
+
# method is a bit gnarly. In particular, it needs to:
|
599
|
+
# * Construct BaseConfig child instances from Hash values, recursively
|
600
|
+
# loading these with load_impl.
|
601
|
+
# * Recursively call itself on Array elements in order to construct
|
602
|
+
# Array parameter values.
|
603
|
+
# * Load true and false values for Boolean parameters
|
604
|
+
# * Convert String values to a small number of non-String parameter
|
605
|
+
# types, including Integers, Floats, Booleans, Pathnames, and URIs.
|
606
|
+
# This is done because not all reader classes will read different
|
607
|
+
# types from files and so BaseConfig has to do some lexical casting.
|
540
608
|
#
|
541
609
|
# ====Parameters:
|
542
610
|
# [param_name]
|
543
|
-
#
|
611
|
+
# The name of the parameter whose value is being loaded (String).
|
612
|
+
# This need not be a "real" parameter value; it also can be
|
613
|
+
# an element in an Array value (i.e., the 3rd element of Array
|
614
|
+
# parameter servers will be passed as "servers[2]").
|
544
615
|
# [value_class]
|
545
|
-
#
|
546
|
-
# [
|
547
|
-
#
|
616
|
+
# The class of the parameter's values (Class)
|
617
|
+
# [raw_value]
|
618
|
+
# The raw value from which to load the parameter value
|
548
619
|
#
|
549
620
|
# ====Returns:
|
621
|
+
# A parameter value of class value_class for param_name loaded from raw_value
|
550
622
|
#
|
551
|
-
|
552
|
-
|
553
|
-
if(value.class <= value_class)
|
623
|
+
def load_value_impl(param_name, value_class, raw_value)
|
624
|
+
if(raw_value.class <= value_class)
|
554
625
|
#
|
555
|
-
# If the
|
626
|
+
# If the reader has returned a value of the same class as
|
556
627
|
# was specified for that parameter (or a child class of the specified
|
557
628
|
# class), then just return the value.
|
558
629
|
#
|
559
|
-
return
|
560
|
-
elsif((value_class < BaseConfig) && (
|
630
|
+
return raw_value
|
631
|
+
elsif((value_class < BaseConfig) && (raw_value.class == Hash))
|
561
632
|
#
|
562
|
-
# If we're looking for a BaseConfig child and have a
|
563
|
-
# construct the child with the
|
633
|
+
# If we're looking for a BaseConfig child and have a Hash, then
|
634
|
+
# construct the BaseConfig child with the Hash by (recursively) calling
|
635
|
+
# load_impl.
|
564
636
|
#
|
565
637
|
child_config = value_class.new()
|
566
638
|
|
@@ -570,91 +642,92 @@ class BaseConfig
|
|
570
642
|
nested_containing_object_name = "#{@containing_object_name}.#{param_name}"
|
571
643
|
end
|
572
644
|
|
573
|
-
child_config.
|
645
|
+
child_config.containing_object_name = nested_containing_object_name
|
646
|
+
child_config.load_impl(raw_value)
|
574
647
|
return child_config
|
575
|
-
elsif((value_class < ConstrainedArray) && (
|
648
|
+
elsif((value_class < ConstrainedArray) && (raw_value.class == Array))
|
576
649
|
#
|
577
650
|
# If we're looking for a ConstrainedArray and have a Ruby Array, then
|
578
651
|
# iterate over each element of the Ruby Array and recursively
|
579
|
-
# load it.
|
652
|
+
# load it with load_value_impl.
|
580
653
|
#
|
581
|
-
value
|
582
|
-
|
654
|
+
value = []
|
655
|
+
|
656
|
+
raw_value.each_with_index do |element, index|
|
657
|
+
value.push(load_value_impl("#{param_name}[#{index}]", value_class.element_class, element))
|
583
658
|
end
|
584
659
|
|
585
660
|
if((value_class.min_num_elements != nil) &&
|
586
661
|
(value.length < value_class.min_num_elements))
|
587
662
|
message = "the number of elements (#{value.length}) is less than the specified "
|
588
663
|
message << "minimum number of elements (#{value_class.min_num_elements})"
|
589
|
-
raise Error, construct_error_message(param_name,
|
664
|
+
raise Error, construct_error_message(param_name, raw_value, message)
|
590
665
|
end
|
591
666
|
|
592
667
|
if((value_class.max_num_elements != nil) &&
|
593
668
|
(value.length > value_class.max_num_elements))
|
594
669
|
message = "the number of elements (#{value.length}) is greater than the specified "
|
595
670
|
message << "maximum number of elements (#{value_class.max_num_elements})"
|
596
|
-
raise Error, construct_error_message(param_name,
|
671
|
+
raise Error, construct_error_message(param_name, raw_value, message)
|
597
672
|
end
|
598
673
|
|
599
674
|
return value
|
600
675
|
elsif((value_class == Boolean) &&
|
601
|
-
((
|
676
|
+
((raw_value.class == TrueClass) || (raw_value.class == FalseClass)))
|
602
677
|
#
|
603
678
|
# If we're looking for a Boolean, then we just can return
|
604
|
-
# the
|
679
|
+
# the reader's value if it's true or false.
|
605
680
|
#
|
606
|
-
return
|
607
|
-
elsif(
|
681
|
+
return raw_value
|
682
|
+
elsif(raw_value.class == String)
|
608
683
|
#
|
609
|
-
# Some
|
684
|
+
# Some readers only may return Strings and leave it up to
|
610
685
|
# the caller to figure out the proper types for the parameters.
|
611
|
-
#
|
686
|
+
# We'll levarage Ruby's built-in conversion functions
|
612
687
|
# to support the most common parameter types. Should this be
|
613
688
|
# made extensible?
|
614
689
|
#
|
615
690
|
case
|
616
691
|
when (value_class <= Integer)
|
617
|
-
return Integer(
|
692
|
+
return Integer(raw_value)
|
618
693
|
when (value_class <= Float)
|
619
|
-
return Float(
|
694
|
+
return Float(raw_value)
|
620
695
|
when (value_class <= Pathname)
|
621
|
-
return Pathname(
|
696
|
+
return Pathname(raw_value)
|
622
697
|
when (value_class <= Symbol)
|
623
|
-
return
|
698
|
+
return raw_value.to_sym()
|
624
699
|
when (value_class <= URI)
|
625
|
-
return URI(
|
700
|
+
return URI(raw_value)
|
626
701
|
when (value_class == Boolean)
|
627
702
|
#
|
628
703
|
# Support both yes/no and true/false boolean values.
|
629
704
|
#
|
630
|
-
if((
|
705
|
+
if((raw_value.casecmp("yes") == 0) || (raw_value.casecmp("true") == 0))
|
631
706
|
return true
|
632
|
-
elsif((
|
707
|
+
elsif((raw_value.casecmp("no") == 0)|| (raw_value.casecmp("false") == 0))
|
633
708
|
return false
|
634
709
|
end
|
635
710
|
end
|
636
711
|
end
|
637
712
|
|
638
|
-
message = "cannot convert from value class #{
|
639
|
-
raise Error, construct_error_message(param_name,
|
713
|
+
message = "cannot convert from value class #{raw_value.class.name} to specified class #{value_class.name}"
|
714
|
+
raise Error, construct_error_message(param_name, raw_value, message)
|
640
715
|
end
|
641
716
|
private :load_value_impl
|
642
717
|
|
643
718
|
#
|
644
719
|
# ====Description:
|
645
|
-
#
|
720
|
+
# This method loads self from containing_object_hash, which
|
721
|
+
# must contain key/value pairs, where the keys are
|
722
|
+
# parameter names (Strings) and the values are parameter
|
723
|
+
# values (to be passed to load_value_impl). The configuration's
|
724
|
+
# specifications are enforced by this method.
|
646
725
|
#
|
647
726
|
# ====Parameters:
|
648
727
|
# [containing_object_hash]
|
649
|
-
#
|
650
|
-
# [containing_object_name]
|
651
|
-
# ?
|
728
|
+
# The Hash containing the configuration's parameters
|
652
729
|
#
|
653
|
-
|
654
|
-
#
|
655
|
-
#
|
656
|
-
def load_impl(containing_object_hash, containing_object_name)
|
657
|
-
@containing_object_name = containing_object_name
|
730
|
+
def load_impl(containing_object_hash)
|
658
731
|
@params_with_values.clear()
|
659
732
|
|
660
733
|
if(containing_object_hash != nil)
|
@@ -684,16 +757,17 @@ class BaseConfig
|
|
684
757
|
|
685
758
|
#
|
686
759
|
# ====Description:
|
687
|
-
#
|
760
|
+
# This method loads self from reader (with containing object
|
761
|
+
# containing_object_name).
|
688
762
|
#
|
689
763
|
# ====Parameters:
|
690
764
|
# [reader]
|
691
|
-
#
|
765
|
+
# The reader from which to load the parameter values. This method
|
766
|
+
# will call the read method of reader, which will return
|
767
|
+
# a Hash containing, in the most nested containing object entry,
|
768
|
+
# the configuration parameter values for self.
|
692
769
|
# [containing_object_name = ""]
|
693
|
-
#
|
694
|
-
#
|
695
|
-
# ====Returns:
|
696
|
-
#
|
770
|
+
# The containing object name
|
697
771
|
#
|
698
772
|
def load(reader, containing_object_name = "")
|
699
773
|
param_hash = reader.read
|
@@ -705,11 +779,12 @@ class BaseConfig
|
|
705
779
|
end
|
706
780
|
|
707
781
|
#
|
708
|
-
# Work through the param_hash until
|
782
|
+
# Work through the param_hash until the most nested object in
|
783
|
+
# containing_object_name is found by
|
709
784
|
# splitting up containing_object_name into an object list and
|
710
785
|
# hashing for each. For example, production.webserver.tokyo would
|
711
786
|
# result in a lookup for production, the results of which them would be
|
712
|
-
# searched for webserver, the results of which finally would be
|
787
|
+
# searched for webserver, the results of which finally would be searched
|
713
788
|
# for tokyo. Not being able to find one of the objects is not
|
714
789
|
# (necessarily) an error; that just means that none of the parameters will
|
715
790
|
# be set from param_hash, which is allowable if all of the parameters
|
@@ -717,8 +792,9 @@ class BaseConfig
|
|
717
792
|
# is empty). On the other hand, if an object is found but it is not
|
718
793
|
# really an object (a Hash), then raise an exception.
|
719
794
|
#
|
795
|
+
@containing_object_name = containing_object_name
|
720
796
|
containing_object_hash = param_hash
|
721
|
-
containing_object_name.split(".").each do |object_name|
|
797
|
+
@containing_object_name.split(".").each do |object_name|
|
722
798
|
containing_object_hash = containing_object_hash.fetch(object_name, nil)
|
723
799
|
|
724
800
|
if(containing_object_hash == nil)
|
@@ -727,26 +803,25 @@ class BaseConfig
|
|
727
803
|
message = "error: #{self.class}#load found "
|
728
804
|
message << "#{containing_object_hash.class} "
|
729
805
|
message << "rather than Hash when reading the "
|
730
|
-
message << "#{
|
806
|
+
message << "#{object_name} containing object"
|
731
807
|
raise Error, message
|
732
808
|
end
|
733
809
|
end
|
734
810
|
|
735
|
-
load_impl(containing_object_hash
|
811
|
+
load_impl(containing_object_hash)
|
736
812
|
end
|
737
813
|
|
738
814
|
#
|
739
815
|
# ====Description:
|
740
|
-
#
|
816
|
+
# This class method creates a new config instance and
|
817
|
+
# calls load on it with the specified parameters. This
|
818
|
+
# is a second "constructor" for the class.
|
741
819
|
#
|
742
820
|
# ====Parameters:
|
743
|
-
#
|
744
|
-
# ?
|
745
|
-
# [containing_object_name = ""]
|
746
|
-
# ?
|
821
|
+
# See the parameter list for load.
|
747
822
|
#
|
748
823
|
# ====Returns:
|
749
|
-
#
|
824
|
+
# The new instance, preloaded!
|
750
825
|
#
|
751
826
|
def self.load(reader, containing_object_name = "")
|
752
827
|
instance = new()
|
@@ -756,25 +831,21 @@ class BaseConfig
|
|
756
831
|
|
757
832
|
#
|
758
833
|
# ====Description:
|
759
|
-
#
|
834
|
+
# This method writes parameter value value to str, with
|
835
|
+
# indentation indent.
|
760
836
|
#
|
761
837
|
# ====Parameters:
|
762
838
|
# [str]
|
763
|
-
#
|
839
|
+
# The String to which to write
|
764
840
|
# [indent]
|
765
|
-
#
|
766
|
-
# [value_class]
|
767
|
-
# ?
|
841
|
+
# The indentation at which to write value
|
768
842
|
# [value]
|
769
|
-
#
|
843
|
+
# The parameter value to write
|
770
844
|
#
|
771
|
-
|
772
|
-
#
|
773
|
-
#
|
774
|
-
def write_value_impl(str, indent, value_class, value)
|
845
|
+
def write_value_impl(str, indent, value)
|
775
846
|
nesting_indent = indent + NESTING_INDENT
|
776
847
|
if(value != nil)
|
777
|
-
if(
|
848
|
+
if(value.class < BaseConfig)
|
778
849
|
#
|
779
850
|
# Writing a nested config requires calling its write(),
|
780
851
|
# just at one further indentation level.
|
@@ -782,19 +853,18 @@ class BaseConfig
|
|
782
853
|
str << "{\n"
|
783
854
|
value.write_impl(str, nesting_indent)
|
784
855
|
str << indent << "}"
|
785
|
-
elsif(
|
856
|
+
elsif(value.class < Array)
|
786
857
|
#
|
787
|
-
# Writing an array requires
|
788
|
-
#
|
789
|
-
# level.
|
858
|
+
# Writing an array requires calling write_value_impl for each element,
|
859
|
+
# just at one further indentation level.
|
790
860
|
#
|
791
861
|
str << "[\n"
|
792
862
|
|
793
863
|
value.each_with_index do |element, index|
|
794
864
|
str << nesting_indent
|
795
|
-
write_value_impl(str, nesting_indent,
|
865
|
+
write_value_impl(str, nesting_indent, element)
|
796
866
|
|
797
|
-
if(index
|
867
|
+
if(index < (value.length - 1))
|
798
868
|
str << ","
|
799
869
|
end
|
800
870
|
|
@@ -811,34 +881,28 @@ class BaseConfig
|
|
811
881
|
|
812
882
|
#
|
813
883
|
# ====Description:
|
814
|
-
#
|
884
|
+
# This method writes self to str, at indentation indent.
|
815
885
|
#
|
816
886
|
# ====Parameters:
|
817
887
|
# [str]
|
818
|
-
#
|
888
|
+
# The String to which to write
|
819
889
|
# [indent]
|
820
|
-
#
|
821
|
-
#
|
822
|
-
# ====Returns:
|
823
|
-
#
|
890
|
+
# The indentation at which to write self
|
824
891
|
#
|
825
892
|
def write_impl(str, indent)
|
893
|
+
name_field_length = self.class.longest_param_name_length
|
826
894
|
self.class.param_spec_list.each do |param_spec|
|
827
|
-
name_field_length = self.class.longest_param_name_length
|
828
895
|
str << indent << param_spec.name.to_s.ljust(name_field_length) << ": "
|
829
896
|
value = send(param_spec.name)
|
830
|
-
write_value_impl(str, indent,
|
897
|
+
write_value_impl(str, indent, value)
|
831
898
|
str << "\n"
|
832
899
|
end
|
833
900
|
end
|
834
901
|
protected :write_impl
|
835
902
|
|
836
|
-
#
|
837
|
-
# ====Description:
|
838
|
-
#
|
839
903
|
#
|
840
904
|
# ====Returns:
|
841
|
-
#
|
905
|
+
# String representation of self
|
842
906
|
#
|
843
907
|
def to_s
|
844
908
|
#
|