configtoolkit 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/FAQ.txt +113 -25
- data/Hash.txt +128 -0
- data/History.txt +45 -2
- data/KeyValue.txt +235 -0
- data/Manifest.txt +66 -4
- data/README.txt +666 -202
- data/Rakefile +3 -5
- data/Ruby.txt +172 -0
- data/YAML.txt +188 -0
- data/examples/hash_example.rb +71 -0
- data/examples/key_value_example.dump.cfg +5 -0
- data/examples/key_value_example.normal1.cfg +20 -0
- data/examples/key_value_example.normal2.cfg +15 -0
- data/examples/key_value_example.rb +72 -0
- data/examples/key_value_example.wacky.cfg +13 -0
- data/examples/load_example.rb +32 -0
- data/examples/load_example.yaml +34 -0
- data/examples/load_group_example.rb +28 -0
- data/examples/load_group_example.yaml +33 -0
- data/examples/machineconfig.rb +77 -0
- data/examples/ruby_example.rb +32 -0
- data/examples/ruby_example.rcfg +52 -0
- data/examples/usage_example.rb +93 -0
- data/examples/yaml_example.dump.yaml +12 -0
- data/examples/yaml_example.rb +48 -0
- data/examples/yaml_example.yaml +59 -0
- data/lib/configtoolkit.rb +1 -1
- data/lib/configtoolkit/baseconfig.rb +522 -418
- data/lib/configtoolkit/hasharrayvisitor.rb +242 -0
- data/lib/configtoolkit/hashreader.rb +41 -0
- data/lib/configtoolkit/hashwriter.rb +45 -0
- data/lib/configtoolkit/keyvalueconfig.rb +105 -0
- data/lib/configtoolkit/keyvaluereader.rb +597 -0
- data/lib/configtoolkit/keyvaluewriter.rb +157 -0
- data/lib/configtoolkit/prettyprintwriter.rb +167 -0
- data/lib/configtoolkit/reader.rb +62 -0
- data/lib/configtoolkit/rubyreader.rb +270 -0
- data/lib/configtoolkit/types.rb +42 -26
- data/lib/configtoolkit/writer.rb +116 -0
- data/lib/configtoolkit/yamlreader.rb +10 -6
- data/lib/configtoolkit/yamlwriter.rb +113 -71
- data/test/bad_array_index.rcfg +1 -0
- data/test/bad_containing_object_assignment.rcfg +2 -0
- data/test/bad_directive.cfg +1 -0
- data/test/bad_include1.cfg +2 -0
- data/test/bad_include2.cfg +3 -0
- data/test/bad_parameter_reference.rcfg +1 -0
- data/test/contained_sample.cfg +10 -0
- data/test/contained_sample.pretty_print +30 -0
- data/test/contained_sample.rcfg +22 -0
- data/test/contained_sample.yaml +22 -21
- data/test/containers.cfg +6 -0
- data/test/exta_string_after_container.cfg +2 -0
- data/test/extra_container_closing.cfg +2 -0
- data/test/extra_string_after_container.cfg +2 -0
- data/test/extra_string_after_nested_container.cfg +1 -0
- data/test/missing_array_closing.cfg +2 -0
- data/test/missing_array_element.cfg +2 -0
- data/test/missing_directive.cfg +1 -0
- data/test/missing_hash_closing.cfg +2 -0
- data/test/missing_hash_element.cfg +2 -0
- data/test/missing_hash_value.cfg +1 -0
- data/test/missing_include_argument.cfg +1 -0
- data/test/missing_key_value_delimiter.cfg +1 -0
- data/test/readerwritertest.rb +28 -7
- data/test/sample.cfg +10 -0
- data/test/sample.pretty_print +30 -0
- data/test/sample.rcfg +26 -0
- data/test/test_baseconfig.rb +152 -38
- data/test/test_hash.rb +82 -0
- data/test/test_keyvalue.rb +185 -0
- data/test/test_prettyprint.rb +28 -0
- data/test/test_ruby.rb +50 -0
- data/test/test_yaml.rb +33 -26
- data/test/wacky_sample1.cfg +16 -0
- data/test/wacky_sample2.cfg +5 -0
- data/test/wacky_sample3.cfg +4 -0
- data/test/wacky_sample4.cfg +1 -0
- metadata +101 -10
- data/lib/configtoolkit/toolkit.rb +0 -20
- data/test/common.rb +0 -5
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# These example programs use the 'relative' gem in order to
|
5
|
+
# express paths relative to __FILE__ cleanly, allowing them to be run from
|
6
|
+
# any directory. In particular, they use the require_relative
|
7
|
+
# method to load examples/machineconfig.rb and
|
8
|
+
# File.expand_path_relative_to_caller in order to refer to
|
9
|
+
# configuration files within the examples directory.
|
10
|
+
#
|
11
|
+
require 'rubygems'
|
12
|
+
require 'relative'
|
13
|
+
require_relative 'machineconfig'
|
14
|
+
|
15
|
+
require 'configtoolkit/yamlreader'
|
16
|
+
require 'configtoolkit/yamlwriter'
|
17
|
+
|
18
|
+
CONFIGURATION_FILE = File.expand_path_relative_to_caller("yaml_example.yaml")
|
19
|
+
|
20
|
+
#
|
21
|
+
# The first configuration has no containing object name.
|
22
|
+
#
|
23
|
+
reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
|
24
|
+
first_config = MachineConfig.load(reader)
|
25
|
+
print("The first config:\n#{first_config}\n")
|
26
|
+
|
27
|
+
#
|
28
|
+
# The second configuration has "production.www" as a containing
|
29
|
+
# object name. Note that the second configuration
|
30
|
+
# reprents the URI elements of the addresses parameter array
|
31
|
+
# as Ruby URI classes, leveraging Ruby's ability to unserialize
|
32
|
+
# objects from YAML.
|
33
|
+
#
|
34
|
+
reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
|
35
|
+
second_config = MachineConfig.load(reader, "production.www")
|
36
|
+
print("The second config:\n#{second_config}\n")
|
37
|
+
|
38
|
+
#
|
39
|
+
# Write second_config to a string stream, which then is written to
|
40
|
+
# stdout. Pass true as the second argument to new in order to specify that
|
41
|
+
# the writer only should write native YAML types (Strings, integers, floats,
|
42
|
+
# booleans, etc.).
|
43
|
+
#
|
44
|
+
string_stream = StringIO.new()
|
45
|
+
writer = ConfigToolkit::YAMLWriter.new(string_stream, true)
|
46
|
+
second_config.dump(writer)
|
47
|
+
print("The second configuration written in YAML with native YAML types:\n")
|
48
|
+
print("#{string_stream.string}")
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# This file contains MachineConfig configurations
|
3
|
+
# (see examples/machineconfig.rb for the configuration's specification).
|
4
|
+
#
|
5
|
+
|
6
|
+
############################################################
|
7
|
+
# First configuration
|
8
|
+
num_cpus: 32
|
9
|
+
os:
|
10
|
+
name: AIX
|
11
|
+
version: 5.3
|
12
|
+
behind_firewall: no
|
13
|
+
contains_sensitive_data: no
|
14
|
+
addresses:
|
15
|
+
- http://default.designingpatterns.com
|
16
|
+
- http://apple.designingpatterns.com
|
17
|
+
############################################################
|
18
|
+
|
19
|
+
production:
|
20
|
+
############################################################
|
21
|
+
# Second configuration (nested in the production.www
|
22
|
+
# containing object). Note that the elements of the
|
23
|
+
# addresses parameter array are expressed
|
24
|
+
# not as strings, as in the first configuration, but rather
|
25
|
+
# as serialized Ruby URI instances. The YAMLReader class
|
26
|
+
# can read both, and the YAMLWriter class can
|
27
|
+
# output values of either only YAML native types or of serialized Ruby
|
28
|
+
# classes.
|
29
|
+
www:
|
30
|
+
contains_sensitive_data: true
|
31
|
+
addresses:
|
32
|
+
- !ruby/object:URI::HTTP
|
33
|
+
fragment:
|
34
|
+
host: www.designingpatterns.com
|
35
|
+
opaque:
|
36
|
+
password:
|
37
|
+
path: ""
|
38
|
+
port: 80
|
39
|
+
query:
|
40
|
+
registry:
|
41
|
+
scheme: http
|
42
|
+
user:
|
43
|
+
- !ruby/object:URI::HTTP
|
44
|
+
fragment:
|
45
|
+
host: tokyo.designingpatterns.com
|
46
|
+
opaque:
|
47
|
+
password:
|
48
|
+
path: ""
|
49
|
+
port: 80
|
50
|
+
query:
|
51
|
+
registry:
|
52
|
+
scheme: http
|
53
|
+
user:
|
54
|
+
os:
|
55
|
+
name: Solaris
|
56
|
+
version: 10.0
|
57
|
+
num_cpus: 64
|
58
|
+
behind_firewall: true
|
59
|
+
############################################################
|
data/lib/configtoolkit.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require 'configtoolkit/
|
2
|
+
require 'configtoolkit/baseconfig'
|
@@ -1,42 +1,63 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'configtoolkit/types'
|
4
|
+
require 'configtoolkit/prettyprintwriter.rb'
|
5
|
+
|
3
6
|
require 'pathname'
|
4
7
|
require 'set'
|
8
|
+
require 'stringio'
|
5
9
|
require 'uri'
|
6
10
|
|
7
11
|
module ConfigToolkit
|
8
12
|
|
9
13
|
#
|
10
|
-
# This class
|
11
|
-
# specification, loading, and dumping functionality.
|
12
|
-
# configuration can contain "
|
13
|
-
# nested configuration class),
|
14
|
-
# BaseConfig child classes (which can
|
15
|
-
# Note that
|
16
|
-
#
|
17
|
-
# so nesting
|
14
|
+
# This class can be subclassed to create a class representing a configuration.
|
15
|
+
# It provides configuration specification, loading, and dumping functionality.
|
16
|
+
# A BaseConfig configuration can contain "scalar" parameters (anything except
|
17
|
+
# an array or nested configuration class), ConstrainedArray
|
18
|
+
# parameters (arrays), and other BaseConfig child classes parameters (which can
|
19
|
+
# be thought of as hashes). Note that nested ConstrainedArray
|
20
|
+
# and BaseConfig config parameters recursively can contain anything the parent
|
21
|
+
# BaseConfig can contain, so nesting arrays and configuration classes to any
|
22
|
+
# depth is supported.
|
23
|
+
#
|
24
|
+
# A BaseConfig child instance has getter, setter, and predicate methods for each
|
25
|
+
# parameter. The predicate method for a parameter returns whether or not the
|
26
|
+
# parameter has a value and is named +param_name?+. An optional parameter
|
27
|
+
# also has a method to clear its value from the configuration (after this the
|
28
|
+
# predicate method will return +false+); the clear method is named
|
29
|
+
# +clear_param_name+.
|
18
30
|
#
|
19
|
-
# BaseConfig neither parses nor writes to files directly
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
31
|
+
# BaseConfig neither parses from nor writes to configuration files directly
|
32
|
+
# but instead does so through reader (Reader) and
|
33
|
+
# writer (Writer) classes specified by the programmer.
|
34
|
+
# This allows BaseConfig to support virtually any underlying
|
35
|
+
# configuration file format (including YAML, key/value pairs, etc).
|
36
|
+
# BaseConfig expects reader classes to contain a +read+ method that sources
|
37
|
+
# configuration information and returns a Hash that maps Symbols or Strings
|
25
38
|
# (parameter names) to values, which can themselves
|
26
|
-
# be
|
27
|
-
#
|
39
|
+
# be Hashes or Arrays. It expects writer classes to support a +write+ method
|
40
|
+
# that in turn accepts such a Hash and writes it to some underlying stream.
|
28
41
|
# The reader and writer implementations used have no effect on BaseConfig's
|
29
|
-
# validation functionality; data from
|
30
|
-
# programatically via setters) always
|
42
|
+
# validation functionality; data from any source (even specified
|
43
|
+
# programatically via setters) always is validated fully.
|
31
44
|
#
|
32
45
|
# Programmers wishing to create their own configuration classes
|
33
46
|
# should extend BaseConfig and, in the body of their class' definition,
|
34
47
|
# call add_required_param and add_optional_param for each parameter
|
35
48
|
# they wish to specify. If they wish to enforce a relationship between
|
36
|
-
# parameters,
|
49
|
+
# parameters, they also can override validate_all_values.
|
37
50
|
#
|
38
51
|
class BaseConfig
|
39
|
-
|
52
|
+
#
|
53
|
+
# This constant can be passed to add_optional_param in order to
|
54
|
+
# indicate that the parameter has no default value. The actual value of
|
55
|
+
# the constant does not matter in the least, so long as it
|
56
|
+
# never could be a real default value (a gemsym() function would be
|
57
|
+
# handy here to generate a unique symbol).
|
58
|
+
#
|
59
|
+
NO_DEFAULT_VALUE = :baseconfig_no_default_value
|
60
|
+
|
40
61
|
#
|
41
62
|
# This class represents the specification for a parameter. Right now,
|
42
63
|
# a parameter specification consists of:
|
@@ -46,14 +67,15 @@ class BaseConfig
|
|
46
67
|
# * Whether or not the parameter is required
|
47
68
|
# * If the parameter is not required, a default value
|
48
69
|
#
|
49
|
-
class ParamSpec
|
70
|
+
class ParamSpec #:nodoc:
|
50
71
|
attr_reader :name
|
51
72
|
attr_reader :value_class
|
52
|
-
attr_reader :default_value #
|
73
|
+
attr_reader :default_value # Optionally present; default_value? is the
|
74
|
+
# predicate
|
53
75
|
|
54
76
|
#
|
55
77
|
# ====Description:
|
56
|
-
# This constructs a
|
78
|
+
# This constructs a ParamSpec for parameter name and value
|
57
79
|
# of class value_class. If is_required, then the parameter is
|
58
80
|
# required; otherwise, the parameter is optional with a default
|
59
81
|
# value of default_value.
|
@@ -68,15 +90,22 @@ class BaseConfig
|
|
68
90
|
# [default_value]
|
69
91
|
# If the parameter need not be set in a configuration file
|
70
92
|
# (!is_required), the value it defaults to if it's not specified.
|
93
|
+
# A default_value of BaseConfig::NO_DEFAULT_VALUE means that
|
94
|
+
# there is no default_value attribute.
|
71
95
|
#
|
72
96
|
def initialize(name, value_class, is_required, default_value)
|
73
97
|
@name = name
|
74
98
|
@value_class = value_class
|
75
99
|
@is_required = is_required
|
100
|
+
@default_value = default_value
|
101
|
+
end
|
76
102
|
|
77
|
-
|
78
|
-
|
79
|
-
|
103
|
+
#
|
104
|
+
# ====Returns:
|
105
|
+
# true if and only if the parameter has a default value
|
106
|
+
#
|
107
|
+
def default_value?
|
108
|
+
return !@default_value.equal?(BaseConfig::NO_DEFAULT_VALUE)
|
80
109
|
end
|
81
110
|
|
82
111
|
#
|
@@ -84,16 +113,10 @@ class BaseConfig
|
|
84
113
|
# true if and only if the parameter must be set in a configuration
|
85
114
|
# file; false otherwise.
|
86
115
|
#
|
87
|
-
def
|
116
|
+
def required?
|
88
117
|
return @is_required
|
89
118
|
end
|
90
119
|
end
|
91
|
-
|
92
|
-
#
|
93
|
-
# The indentation used to indicate nesting when printing out
|
94
|
-
# a BaseConfig.
|
95
|
-
#
|
96
|
-
NESTING_INDENT = " " * 4
|
97
120
|
|
98
121
|
#
|
99
122
|
# Create accessors for some class instance variables;
|
@@ -113,20 +136,13 @@ class BaseConfig
|
|
113
136
|
# specifications (ParamSpecs) in the order in which they were specified
|
114
137
|
# in the BaseConfig child class.
|
115
138
|
#
|
116
|
-
attr_reader :param_spec_list
|
139
|
+
attr_reader :param_spec_list #:nodoc:
|
117
140
|
|
118
141
|
#
|
119
142
|
# This is a Hash (class instance variable) mapping
|
120
143
|
# paramater names (Symbols) to parameter specifications (ParamSpecs)
|
121
144
|
#
|
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
|
-
#
|
129
|
-
attr_reader :longest_param_name_length
|
145
|
+
attr_reader :param_spec_lookup_table #:nodoc:
|
130
146
|
end
|
131
147
|
|
132
148
|
#
|
@@ -140,29 +156,126 @@ class BaseConfig
|
|
140
156
|
# [child_class]
|
141
157
|
# The new child class
|
142
158
|
#
|
143
|
-
def self.inherited(child_class)
|
159
|
+
def self.inherited(child_class) # :nodoc:
|
144
160
|
#
|
145
161
|
# Initialize the class instance variables.
|
146
162
|
#
|
147
163
|
child_class.class_eval do
|
148
164
|
@param_spec_list = []
|
149
165
|
@param_spec_lookup_table = {}
|
150
|
-
@longest_param_name_length = 0
|
151
166
|
end
|
152
167
|
end
|
153
168
|
|
154
169
|
#
|
155
170
|
# ====Description:
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
171
|
+
# Because the to_s in Ruby 1.8 for the Array and Hash classes
|
172
|
+
# are horrible... This method returns a readable String representation
|
173
|
+
# of value, recursively expanding the contents of Array and Hash
|
174
|
+
# value arguments (the String representation of the expansion is
|
175
|
+
# written on a single line, however) and otherwise returning value.to_s.
|
176
|
+
# This is called by construct_error_message in order to construct a nicely
|
177
|
+
# formatted error message.
|
178
|
+
#
|
179
|
+
# ====Parameters:
|
180
|
+
# [value]
|
181
|
+
# The object for which to return a String representation
|
182
|
+
#
|
183
|
+
# ====Returns:
|
184
|
+
# A nicely formatted String representation of value
|
185
|
+
#
|
186
|
+
def self.construct_value_str(value)
|
187
|
+
if(value == nil)
|
188
|
+
return "nil"
|
189
|
+
elsif(value.is_a?(Array))
|
190
|
+
#
|
191
|
+
# Recursively call construct_value_str for each Array element,
|
192
|
+
# surrounding the element strings in brackets.
|
193
|
+
#
|
194
|
+
str = "["
|
195
|
+
|
196
|
+
value.each_with_index do |element, index|
|
197
|
+
str << construct_value_str(element)
|
198
|
+
|
199
|
+
if(index < (value.size - 1))
|
200
|
+
str << ", "
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
str << "]"
|
205
|
+
return str
|
206
|
+
elsif(value.is_a?(Hash))
|
207
|
+
#
|
208
|
+
# Recursively call construct_value_str for each Hash element,
|
209
|
+
# surrounding the element strings in braces.
|
210
|
+
#
|
211
|
+
str = "{"
|
212
|
+
|
213
|
+
value.each_with_index do |key_and_value, index|
|
214
|
+
str << "#{construct_value_str(key_and_value[0])}=>#{construct_value_str(key_and_value[1])}"
|
215
|
+
|
216
|
+
if(index < (value.size - 1))
|
217
|
+
str << ", "
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
str << "}"
|
222
|
+
return str
|
223
|
+
else
|
224
|
+
return value.to_s
|
225
|
+
end
|
226
|
+
end
|
227
|
+
private_class_method :construct_value_str
|
228
|
+
|
229
|
+
#
|
230
|
+
# ====Description:
|
231
|
+
# This method returns an error message for an error (described by
|
232
|
+
# reason) that occurred while setting parameter param_name with
|
233
|
+
# value value.
|
234
|
+
#
|
235
|
+
# ====Parameters:
|
236
|
+
# [containing_object_name]
|
237
|
+
# The containing object name
|
238
|
+
# [param_name]
|
239
|
+
# The parameter being set when the error occurred (String)
|
240
|
+
# This need not be a "real" parameter value; it also can be
|
241
|
+
# an element in an Array value (i.e., the 3rd element of Array
|
242
|
+
# parameter servers will be passed as "servers[2]").
|
243
|
+
# [value]
|
244
|
+
# The parameter value that caused the error
|
245
|
+
# [reason]
|
246
|
+
# The reason for the error (String)
|
247
|
+
#
|
248
|
+
# ====Returns:
|
249
|
+
# An error message that includes the parameter name, the problem
|
250
|
+
# parameter value, and the error description.
|
251
|
+
#
|
252
|
+
def self.construct_error_message(containing_object_name,
|
253
|
+
param_name,
|
254
|
+
value,
|
255
|
+
reason)
|
256
|
+
if(containing_object_name.empty?)
|
257
|
+
full_param_name = param_name
|
258
|
+
else
|
259
|
+
full_param_name = "#{containing_object_name}.#{param_name}"
|
260
|
+
end
|
261
|
+
|
262
|
+
return "error setting #{full_param_name} with value #{construct_value_str(value)}: #{reason}."
|
263
|
+
end
|
264
|
+
private_class_method :construct_error_message
|
265
|
+
|
266
|
+
#
|
267
|
+
# ====Description:
|
268
|
+
# This class method adds a parameter to the BaseConfig child class
|
269
|
+
# (it should be called from within the body of the child class'
|
270
|
+
# definition). The new parameter is called +name+ and has values
|
271
|
+
# of class +value_class+ (or one of +value_class+' child classes).
|
160
272
|
# The parameter must be set in a configuration file if
|
161
|
-
# is_required
|
162
|
-
# value of the parameter if
|
273
|
+
# +is_required+; if not +is_required+, then +default_value+ is the
|
274
|
+
# value of the parameter if the parameter is not set in a configuration file
|
275
|
+
# and if +default_value+ is not +NO_DEFAULT_VALUE+.
|
163
276
|
#
|
164
|
-
# This method optionally accepts a block (validate_value_block), which,
|
165
|
-
# if present, is called with
|
277
|
+
# This method optionally accepts a block (+validate_value_block+), which,
|
278
|
+
# if present, is called with a new value for the parameter before setting
|
166
279
|
# the parameter with the new value, allowing custom validation
|
167
280
|
# code to be executed (if this validation fails, then raise_error should
|
168
281
|
# be called inside of the block).
|
@@ -172,16 +285,18 @@ class BaseConfig
|
|
172
285
|
#
|
173
286
|
# ====Parameters:
|
174
287
|
# [name]
|
175
|
-
# The name of the parameter
|
288
|
+
# The name of the parameter (Symbol)
|
176
289
|
# [value_class]
|
177
|
-
# The parent class of the parameter's values
|
290
|
+
# The class (or parent class) of the parameter's values
|
178
291
|
# [is_required]
|
179
292
|
# Whether or not the parameter must be specified in a
|
180
|
-
# configuration file
|
293
|
+
# configuration file
|
181
294
|
# [default_value]
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
295
|
+
# The default value of the parameter if it's not specified in
|
296
|
+
# a configuration file (which only makes sense if not +is_required+)
|
297
|
+
# If this argument is +NO_DEFAULT_VALUE+, then it is treated
|
298
|
+
# treated as absent (the parameter will have no default value)
|
299
|
+
# [&validate_value_block]
|
185
300
|
# An optional block that, if present, is called to validate a new
|
186
301
|
# value for the parameter before setting the parameter to the new
|
187
302
|
# value.
|
@@ -191,10 +306,6 @@ class BaseConfig
|
|
191
306
|
is_required,
|
192
307
|
default_value,
|
193
308
|
&validate_value_block) #:doc:
|
194
|
-
param_spec = ParamSpec.new(name, value_class, is_required, default_value)
|
195
|
-
@param_spec_list.push(param_spec)
|
196
|
-
@param_spec_lookup_table[name] = param_spec
|
197
|
-
|
198
309
|
#
|
199
310
|
# Convert Array value classes into ConstrainedArrays with
|
200
311
|
# no constraints. This makes supporting Arrays much easier, since
|
@@ -204,10 +315,26 @@ class BaseConfig
|
|
204
315
|
value_class = ConstrainedArray.new(Object, nil, nil)
|
205
316
|
end
|
206
317
|
|
318
|
+
if(!default_value.equal?(NO_DEFAULT_VALUE))
|
319
|
+
#
|
320
|
+
# Try loading the default value, given the specified value class,
|
321
|
+
# in order to verify that the default value is a valid value
|
322
|
+
# for the parameter.
|
323
|
+
#
|
324
|
+
value = load_value_impl("",
|
325
|
+
"default value for #{name}",
|
326
|
+
value_class,
|
327
|
+
default_value)
|
328
|
+
end
|
329
|
+
|
330
|
+
param_spec = ParamSpec.new(name, value_class, is_required, default_value)
|
331
|
+
@param_spec_list.push(param_spec)
|
332
|
+
@param_spec_lookup_table[name] = param_spec
|
333
|
+
|
207
334
|
#
|
208
335
|
# Define the getter and setter methods with a string passed to
|
209
336
|
# class_eval rather than a block, so as to avoid the need to access
|
210
|
-
# the
|
337
|
+
# the reflectionn APIs when the setter actually is called.
|
211
338
|
#
|
212
339
|
# Note that the setter will call load_value_impl() on any value
|
213
340
|
# that it is passed; this ensures that data specified programatically
|
@@ -222,37 +349,51 @@ class BaseConfig
|
|
222
349
|
# variable storing this class from the param_spec_lookup_table.
|
223
350
|
#
|
224
351
|
methods = String.new()
|
225
|
-
methods << "
|
352
|
+
methods << "def #{name}\n"
|
353
|
+
methods << " if(@__#{name}_supplied_p__)\n"
|
354
|
+
methods << " return @#{name}\n"
|
355
|
+
methods << " else\n"
|
356
|
+
methods << " return nil\n"
|
357
|
+
methods << " end\n"
|
358
|
+
methods << "end\n"
|
226
359
|
methods << "\n"
|
227
360
|
methods << "def #{name}=(value)\n"
|
228
361
|
methods << " value_class = self.class.param_spec_lookup_table[:#{name}].value_class\n"
|
229
|
-
methods << " loaded_value = load_value_impl
|
362
|
+
methods << " loaded_value = self.class.send(:load_value_impl, @containing_object_name, \"#{name}\", value_class, value)\n"
|
230
363
|
if(validate_value_block != nil)
|
231
364
|
validate_value_method = "validate_#{name}_value".to_sym()
|
232
365
|
define_method(validate_value_method, &validate_value_block)
|
233
366
|
methods << " begin\n"
|
234
|
-
methods << " #{validate_value_method}(
|
367
|
+
methods << " #{validate_value_method}(loaded_value)\n"
|
235
368
|
methods << " rescue Error => e\n"
|
236
|
-
methods << " raise Error, construct_error_message
|
369
|
+
methods << " raise Error, self.class.send(:construct_error_message, @containing_object_name, \"#{name}\", value, e.message), e.backtrace()\n"
|
237
370
|
methods << " end\n"
|
238
371
|
end
|
239
372
|
|
240
373
|
methods << " @#{name} = loaded_value\n"
|
241
|
-
methods << " @
|
374
|
+
methods << " @__#{name}_supplied_p__ = true\n"
|
375
|
+
methods << "end\n"
|
376
|
+
methods << "\n"
|
377
|
+
methods << "def #{name}?\n"
|
378
|
+
methods << " return @__#{name}_supplied_p__\n"
|
242
379
|
methods << "end\n"
|
380
|
+
|
381
|
+
if(!is_required)
|
382
|
+
methods << "\n"
|
383
|
+
methods << "def clear_#{name}\n"
|
384
|
+
methods << " @__#{name}_supplied_p__ = false\n"
|
385
|
+
methods << "end\n"
|
386
|
+
end
|
243
387
|
|
244
388
|
class_eval(methods)
|
245
|
-
|
246
|
-
name_str = name.to_s
|
247
|
-
if(name_str.length > @longest_param_name_length)
|
248
|
-
@longest_param_name_length = name_str.length
|
249
|
-
end
|
250
389
|
end
|
251
390
|
private_class_method :add_param
|
252
391
|
|
253
392
|
#
|
254
393
|
# ====Description:
|
255
|
-
# This is a wrapper around add_param that passes
|
394
|
+
# This is a wrapper around add_param that passes +true+ for the
|
395
|
+
# +is_required+ argument. Values must be specified for required
|
396
|
+
# parameters in configuration files.
|
256
397
|
#
|
257
398
|
# ====Parameters:
|
258
399
|
# See add_param's parameter list.
|
@@ -260,71 +401,124 @@ class BaseConfig
|
|
260
401
|
def self.add_required_param(name,
|
261
402
|
value_class,
|
262
403
|
&validate_value_block) #:doc:
|
263
|
-
add_param(name, value_class, true,
|
404
|
+
add_param(name, value_class, true, NO_DEFAULT_VALUE, &validate_value_block)
|
264
405
|
end
|
265
406
|
private_class_method :add_required_param
|
266
407
|
|
267
408
|
#
|
268
409
|
# ====Description:
|
269
|
-
# This is a wrapper around add_param that passes
|
270
|
-
#
|
410
|
+
# This is a wrapper around add_param that passes +false+ for the
|
411
|
+
# +is_required+ argument. Values need not be specified for optional
|
412
|
+
# parameters in configuration files (optional parameters can be
|
413
|
+
# set to default values when a value is not specified in a configuration
|
414
|
+
# file, if desired).
|
271
415
|
#
|
272
416
|
# ====Parameters:
|
273
417
|
# See add_param's parameter list.
|
274
418
|
#
|
275
419
|
def self.add_optional_param(name,
|
276
420
|
value_class,
|
277
|
-
default_value,
|
421
|
+
default_value = NO_DEFAULT_VALUE,
|
278
422
|
&validate_value_block) #:doc:
|
279
423
|
add_param(name, value_class, false, default_value, &validate_value_block)
|
280
424
|
end
|
281
425
|
private_class_method :add_optional_param
|
282
426
|
|
283
427
|
#
|
284
|
-
# This is a String
|
428
|
+
# This is a String containing the name of the object that
|
285
429
|
# contains this instance's configuration. For example, if
|
286
|
-
# all of this instance's configuration is under production and
|
287
|
-
# webserver, then the containing_object_name
|
288
|
-
#
|
430
|
+
# all of this instance's configuration is under +production+ and
|
431
|
+
# +webserver+ in the configuration file, then the +containing_object_name+
|
432
|
+
# would be +production.webserver+.
|
289
433
|
#
|
290
434
|
attr_reader :containing_object_name
|
291
435
|
|
292
436
|
#
|
293
437
|
# ====Description:
|
294
|
-
# This constructs a BaseConfig with no parameters set (
|
295
|
-
# initially
|
438
|
+
# This constructs a BaseConfig with no parameters set (each parameter
|
439
|
+
# initially has a +nil+ value).
|
296
440
|
#
|
297
|
-
|
441
|
+
# If a block is specified, then it will be passed the new instance and
|
442
|
+
# should set the BaseConfig's parameters fully (as if load had been called);
|
443
|
+
# the BaseConfig's specifications will be verified by this method after
|
444
|
+
# it executes the block (with a call to enforce_specs).
|
445
|
+
#
|
446
|
+
# ====Example:
|
447
|
+
# class Config < ConfigToolkit::BaseConfig
|
448
|
+
# add_required_param(:age, Fixnum)
|
449
|
+
# end
|
450
|
+
#
|
451
|
+
# empty_config = Config.new()
|
452
|
+
#
|
453
|
+
# initialized_config = Config.new() do |config|
|
454
|
+
# config.age = 5
|
455
|
+
# end
|
456
|
+
#
|
457
|
+
# # This will raise an exception, because the age parameter is
|
458
|
+
# # required but is not being set by the block passed to new.
|
459
|
+
# initalized_config = Config.new() do |config|
|
460
|
+
# end
|
461
|
+
#
|
462
|
+
# ====Parameters:
|
463
|
+
# [&initialization_block]
|
464
|
+
# A block that fully initializes the parameters of the new
|
465
|
+
# instance
|
466
|
+
#
|
467
|
+
def initialize(&initialization_block)
|
298
468
|
@containing_object_name = ""
|
299
|
-
@params_with_values = Set.new()
|
300
469
|
|
470
|
+
clear_all_values()
|
471
|
+
|
472
|
+
if(block_given?())
|
473
|
+
yield self
|
474
|
+
enforce_specs()
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
#
|
479
|
+
# ====Description:
|
480
|
+
# This method clears all parameter values.
|
481
|
+
#
|
482
|
+
def clear_all_values
|
301
483
|
self.class.param_spec_list.each do |param_spec|
|
302
|
-
instance_variable_set("
|
484
|
+
instance_variable_set("@__#{param_spec.name}_supplied_p__", false)
|
303
485
|
end
|
304
486
|
end
|
487
|
+
private :clear_all_values
|
488
|
+
|
489
|
+
#
|
490
|
+
# ====Description:
|
491
|
+
# This method returns true if and only if a value for parameter
|
492
|
+
# param_name is present in the configuration.
|
493
|
+
#
|
494
|
+
# ====Parameters:
|
495
|
+
# [param_name]
|
496
|
+
# The parameter for which to check for the presence of a value
|
497
|
+
#
|
498
|
+
# ====Returns:
|
499
|
+
# true if and only if a value for parameter param_name is present in
|
500
|
+
# the configuration
|
501
|
+
#
|
502
|
+
def has_value(param_name)
|
503
|
+
return send("#{param_name}?".to_sym())
|
504
|
+
end
|
505
|
+
private :has_value
|
305
506
|
|
306
507
|
#
|
307
508
|
# ====Description:
|
308
509
|
# This method enforces the BaseConfig's specifications. In particular,
|
309
510
|
# it:
|
310
|
-
# * Checks
|
511
|
+
# * Checks that all required parameters have been set. If a required
|
311
512
|
# parameter is missing, then this method raises an Error.
|
312
|
-
# * Sets all optional parameters without values to
|
313
|
-
# default values.
|
314
|
-
# * Calls validate_all_values
|
513
|
+
# * Sets all optional parameters without values and with default values to
|
514
|
+
# their default values.
|
515
|
+
# * Calls validate_all_values.
|
315
516
|
#
|
316
517
|
# This method is called at the end of a load operation (verifying that
|
317
518
|
# a valid configuration was loaded from a reader) and before a dump operation
|
318
|
-
# (verifying that a valid configuration will be dumped).
|
319
|
-
# parameter is a String containing the name of the operation calling
|
320
|
-
# this method; this String is used in Error messages.
|
321
|
-
#
|
322
|
-
# ====Parameters:
|
323
|
-
# [operation_name]
|
324
|
-
# A String containing the name of the operation calling this
|
325
|
-
# method ("load" or "dump", for example)
|
519
|
+
# (verifying that a valid configuration will be dumped).
|
326
520
|
#
|
327
|
-
def enforce_specs
|
521
|
+
def enforce_specs
|
328
522
|
#
|
329
523
|
# Iterate through the parameters without values. If any are
|
330
524
|
# required parameters, then raise (after completing the
|
@@ -335,10 +529,10 @@ class BaseConfig
|
|
335
529
|
missing_params = []
|
336
530
|
|
337
531
|
self.class.param_spec_list.each do |param_spec|
|
338
|
-
if(
|
339
|
-
if(param_spec.
|
532
|
+
if(!has_value(param_spec.name))
|
533
|
+
if(param_spec.required?)
|
340
534
|
missing_params.push(param_spec.name)
|
341
|
-
|
535
|
+
elsif(param_spec.default_value?)
|
342
536
|
#
|
343
537
|
# Even the default values are set through the setter.
|
344
538
|
#
|
@@ -354,128 +548,37 @@ class BaseConfig
|
|
354
548
|
param_prefix = "#{@containing_object_name}."
|
355
549
|
end
|
356
550
|
|
357
|
-
|
551
|
+
missing_params_str = missing_params.map do |param_name|
|
358
552
|
"#{param_prefix}#{param_name}"
|
359
|
-
end
|
360
|
-
|
361
|
-
raise Error, "missing parameter(s): #{missing_param_spec_list.join(", ")}"
|
362
|
-
end
|
363
|
-
|
364
|
-
if(respond_to?(:validate_all_values))
|
365
|
-
begin
|
366
|
-
validate_all_values()
|
367
|
-
rescue Error => e
|
368
|
-
#
|
369
|
-
# Rescue the error, add some information to the error message, and
|
370
|
-
# throw a repackaged error.
|
371
|
-
#
|
372
|
-
message = "#{self.class}#validate_all_values #{operation_name} error"
|
373
|
-
|
374
|
-
if(!@containing_object_name.empty?)
|
375
|
-
message << " for #{@containing_object_name}"
|
376
|
-
end
|
553
|
+
end.join(", ")
|
377
554
|
|
378
|
-
|
379
|
-
raise Error, message, e.backtrace()
|
380
|
-
end
|
555
|
+
raise Error, "missing parameter(s): #{missing_params_str}"
|
381
556
|
end
|
382
|
-
end
|
383
|
-
private :enforce_specs
|
384
|
-
|
385
|
-
#
|
386
|
-
# ====Description:
|
387
|
-
# Because the to_s in Ruby 1.8 for the Array and Hash classes
|
388
|
-
# are horrible... This method returns a readable String representation
|
389
|
-
# of value, recursively expanding the contents of Array and Hash
|
390
|
-
# value arguments and otherwise returning value.to_s. This is
|
391
|
-
# called by construct_error_message in order to construct a nicely formatted
|
392
|
-
# error message.
|
393
|
-
#
|
394
|
-
# ====Parameters:
|
395
|
-
# [value]
|
396
|
-
# The object for which to return a String representation
|
397
|
-
#
|
398
|
-
# ====Returns:
|
399
|
-
# A nicely formatted String representation of value
|
400
|
-
#
|
401
|
-
def construct_value_str(value)
|
402
|
-
if(value.is_a?(Array))
|
403
|
-
#
|
404
|
-
# Recursively call construct_value_str for each Array element,
|
405
|
-
# surrounding the element strings in brackets.
|
406
|
-
#
|
407
|
-
str = "["
|
408
|
-
|
409
|
-
value.each_with_index do |element, index|
|
410
|
-
str << construct_value_str(element)
|
411
|
-
|
412
|
-
if(index < (value.size - 1))
|
413
|
-
str << ", "
|
414
|
-
end
|
415
|
-
end
|
416
557
|
|
417
|
-
|
418
|
-
|
419
|
-
|
558
|
+
begin
|
559
|
+
validate_all_values()
|
560
|
+
rescue Error => e
|
420
561
|
#
|
421
|
-
#
|
422
|
-
#
|
562
|
+
# Rescue the error, add some information to the error message, and
|
563
|
+
# throw a repackaged error.
|
423
564
|
#
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
if(index < (value.size - 1))
|
430
|
-
str << ", "
|
431
|
-
end
|
565
|
+
message = "#{self.class}#validate_all_values error"
|
566
|
+
|
567
|
+
if(!@containing_object_name.empty?)
|
568
|
+
message << " for #{@containing_object_name}"
|
432
569
|
end
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
else
|
437
|
-
return value.to_s
|
438
|
-
end
|
439
|
-
end
|
440
|
-
private :construct_value_str
|
441
|
-
|
442
|
-
#
|
443
|
-
# ====Description:
|
444
|
-
# This method returns an error message for an error (described by
|
445
|
-
# reason) that occurred while setting parameter param_name with
|
446
|
-
# value value.
|
447
|
-
#
|
448
|
-
# ====Parameters:
|
449
|
-
# [param_name]
|
450
|
-
# The parameter being set when the error occurred (String)
|
451
|
-
# This need not be a "real" parameter value; it also can be
|
452
|
-
# an element in an Array value (i.e., the 3rd element of Array
|
453
|
-
# parameter servers will be passed as "servers[2]").
|
454
|
-
# [value]
|
455
|
-
# The parameter value that caused the error
|
456
|
-
# [reason]
|
457
|
-
# The reason for the error (String)
|
458
|
-
#
|
459
|
-
# ====Returns:
|
460
|
-
# An error message that includes the parameter name, the problem
|
461
|
-
# parameter value, and the error description.
|
462
|
-
#
|
463
|
-
def construct_error_message(param_name, value, reason)
|
464
|
-
if(@containing_object_name.empty?)
|
465
|
-
full_param_name = "#{param_name}"
|
466
|
-
else
|
467
|
-
full_param_name = "#{@containing_object_name}.#{param_name}"
|
570
|
+
|
571
|
+
message << ": #{e.message}"
|
572
|
+
raise Error, message, e.backtrace()
|
468
573
|
end
|
469
|
-
|
470
|
-
return "error setting parameter #{full_param_name} with value #{construct_value_str(value)}: #{reason}."
|
471
574
|
end
|
472
|
-
private :construct_error_message
|
473
575
|
|
474
576
|
#
|
475
577
|
# ====Description:
|
476
|
-
# This method raises an Error with message reason
|
477
|
-
# be called from user-defined parameter validation blocks
|
478
|
-
# a Hotel California type of call;
|
578
|
+
# This method raises an Error with message +reason+. It is meant to
|
579
|
+
# be called from user-defined parameter validation blocks (see the
|
580
|
+
# arguments to add_param). This is a Hotel California type of call;
|
581
|
+
# it does not return.
|
479
582
|
#
|
480
583
|
# ====Parameters:
|
481
584
|
# [reason]
|
@@ -487,16 +590,16 @@ class BaseConfig
|
|
487
590
|
|
488
591
|
#
|
489
592
|
# ====Description:
|
490
|
-
# Equality operator for BaseConfig; this iterates through the
|
491
|
-
#
|
492
|
-
# self and rhs using the equality operator
|
593
|
+
# Equality operator for BaseConfig; this method iterates through the
|
594
|
+
# configuration's parameters and, for each parameter, compares the value
|
595
|
+
# of +self+ and +rhs+ using the value's equality operator.
|
493
596
|
#
|
494
597
|
# ====Parameters:
|
495
598
|
# [rhs]
|
496
|
-
# The instance to compare with self
|
599
|
+
# The instance to compare with +self+
|
497
600
|
#
|
498
601
|
# ====Returns:
|
499
|
-
# True if and only if the values of self and rhs are equal
|
602
|
+
# True if and only if the parameter values of self and rhs are equal
|
500
603
|
#
|
501
604
|
def ==(rhs)
|
502
605
|
if(rhs == nil)
|
@@ -521,17 +624,23 @@ class BaseConfig
|
|
521
624
|
# ====Parameters:
|
522
625
|
# [value]
|
523
626
|
# The parameter value to dump
|
627
|
+
# [use_symbol_parameter_names]
|
628
|
+
# Whether dump_value_impl should use Symbols or Strings for parameter
|
629
|
+
# names (needed in order to recursively call dump_impl)
|
524
630
|
#
|
525
631
|
# ====Returns:
|
526
632
|
# A dump of value
|
527
633
|
#
|
528
|
-
def dump_value_impl(value)
|
634
|
+
def dump_value_impl(value, use_symbol_parameter_names)
|
529
635
|
if(value.class < BaseConfig)
|
530
|
-
|
636
|
+
#
|
637
|
+
# Use send to call the private dump_impl method.
|
638
|
+
#
|
639
|
+
return value.send(:dump_impl, {}, use_symbol_parameter_names)
|
531
640
|
elsif(value.class == Array)
|
532
641
|
dumped_array = []
|
533
642
|
value.each do |element|
|
534
|
-
dumped_array.push(dump_value_impl(element))
|
643
|
+
dumped_array.push(dump_value_impl(element, use_symbol_parameter_names))
|
535
644
|
end
|
536
645
|
return dumped_array
|
537
646
|
else
|
@@ -542,61 +651,79 @@ class BaseConfig
|
|
542
651
|
|
543
652
|
#
|
544
653
|
# ====Description:
|
545
|
-
# This method dumps self into containing_object_hash
|
546
|
-
# it adds entries to containing_object_hash for each parameter.
|
547
|
-
# An entry's key is the parameter name (as a
|
548
|
-
# the
|
654
|
+
# This method dumps +self+ into +containing_object_hash+; that is,
|
655
|
+
# it adds entries to +containing_object_hash+ for each parameter.
|
656
|
+
# An entry's key is the parameter name (as a Symbol or a String,
|
657
|
+
# depending on the value of +use_symbol_parameter_names+), and the value is
|
658
|
+
# the parameter value. The method returns +containing_object_hash+
|
549
659
|
# after the dump. Before dumping everything, this method calls
|
550
|
-
# enforce_specs in order to ensure that valid data is being
|
660
|
+
# +enforce_specs+ in order to ensure that valid data is being
|
551
661
|
# dumped.
|
552
662
|
#
|
553
663
|
# ====Parameters:
|
554
664
|
# [containing_object_hash]
|
555
665
|
# The hash into which to dump self
|
666
|
+
# [use_symbol_parameter_names]
|
667
|
+
# Whether dump_impl should use Symbols or Strings for parameter names
|
556
668
|
#
|
557
669
|
# ====Returns:
|
558
|
-
# containing_object_hash after the dump
|
670
|
+
# +containing_object_hash+ after the dump
|
559
671
|
#
|
560
|
-
def dump_impl(containing_object_hash)
|
672
|
+
def dump_impl(containing_object_hash, use_symbol_parameter_names)
|
561
673
|
#
|
562
674
|
# Configuration never will be dumped without the
|
563
675
|
# specifications first having been enforced.
|
564
676
|
#
|
565
|
-
enforce_specs(
|
677
|
+
enforce_specs()
|
566
678
|
|
567
679
|
self.class.param_spec_list.each do |param_spec|
|
568
|
-
|
680
|
+
#
|
681
|
+
# Don't dump parameters without values.
|
682
|
+
#
|
683
|
+
if(!has_value(param_spec.name))
|
684
|
+
next
|
685
|
+
end
|
686
|
+
|
687
|
+
value = dump_value_impl(send(param_spec.name), use_symbol_parameter_names)
|
688
|
+
|
689
|
+
if(use_symbol_parameter_names)
|
690
|
+
containing_object_hash[param_spec.name] = value
|
691
|
+
else
|
692
|
+
containing_object_hash[param_spec.name.to_s()] = value
|
693
|
+
end
|
694
|
+
|
569
695
|
end
|
570
696
|
|
571
697
|
return containing_object_hash
|
572
698
|
end
|
573
|
-
|
699
|
+
private :dump_impl
|
574
700
|
|
575
701
|
#
|
576
702
|
# ====Description:
|
577
|
-
# This method
|
578
|
-
#
|
579
|
-
#
|
580
|
-
#
|
581
|
-
#
|
582
|
-
#
|
703
|
+
# This method writes +self+ to a configuration file. It does this by
|
704
|
+
# passing the contents of +self+ to +writer+. That is, it constructs
|
705
|
+
# a Hash containing entries for the parameters and passes the Hash to
|
706
|
+
# the +write+ method of +writer+, which should write the information to
|
707
|
+
# some underlying medium (most likely writing a configuration file). The
|
708
|
+
# method checks the configuration against its specifications (via a
|
709
|
+
# call to enforce_specs) before dumping anything.
|
583
710
|
#
|
584
711
|
# ====Parameters:
|
585
712
|
# [writer]
|
586
|
-
# The writer to which to dump
|
713
|
+
# The writer to which to dump (see Writer)
|
587
714
|
#
|
588
715
|
def dump(writer)
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
716
|
+
#
|
717
|
+
# If the require_symbol_parameter_names? method returns true,
|
718
|
+
# then Symbol parameter names will be passed to the writer;
|
719
|
+
# otherwise, String parameter names will be passed to the writer.
|
720
|
+
#
|
721
|
+
use_symbol_parameter_names = writer.require_symbol_parameter_names?()
|
722
|
+
|
723
|
+
config_hash = {}
|
724
|
+
dump_impl(config_hash, use_symbol_parameter_names)
|
597
725
|
|
598
|
-
|
599
|
-
writer.write(dump_hash)
|
726
|
+
writer.write(config_hash, @containing_object_name)
|
600
727
|
end
|
601
728
|
|
602
729
|
#
|
@@ -615,6 +742,8 @@ class BaseConfig
|
|
615
742
|
# types from files and so BaseConfig has to do some lexical casting.
|
616
743
|
#
|
617
744
|
# ====Parameters:
|
745
|
+
# [containing_object_name]
|
746
|
+
# The containing object name
|
618
747
|
# [param_name]
|
619
748
|
# The name of the parameter whose value is being loaded (String).
|
620
749
|
# This need not be a "real" parameter value; it also can be
|
@@ -628,14 +757,24 @@ class BaseConfig
|
|
628
757
|
# ====Returns:
|
629
758
|
# A parameter value of class value_class for param_name loaded from raw_value
|
630
759
|
#
|
631
|
-
def load_value_impl(
|
760
|
+
def self.load_value_impl(containing_object_name,
|
761
|
+
param_name,
|
762
|
+
value_class,
|
763
|
+
raw_value)
|
632
764
|
if(raw_value.class <= value_class)
|
633
765
|
#
|
634
766
|
# If the reader has returned a value of the same class as
|
635
767
|
# was specified for that parameter (or a child class of the specified
|
636
|
-
# class), then just return the value.
|
768
|
+
# class), then just return a copy of the value. Erros need
|
769
|
+
# to be rescued because Fixnums, symbols, and the rest of
|
770
|
+
# the Ruby immediate values do not support dup() (they will
|
771
|
+
# throw).
|
637
772
|
#
|
638
|
-
|
773
|
+
begin
|
774
|
+
return raw_value.dup()
|
775
|
+
rescue
|
776
|
+
return raw_value
|
777
|
+
end
|
639
778
|
elsif((value_class < BaseConfig) && (raw_value.class == Hash))
|
640
779
|
#
|
641
780
|
# If we're looking for a BaseConfig child and have a Hash, then
|
@@ -644,13 +783,16 @@ class BaseConfig
|
|
644
783
|
#
|
645
784
|
child_config = value_class.new()
|
646
785
|
|
647
|
-
if(
|
786
|
+
if(containing_object_name.empty?)
|
648
787
|
nested_containing_object_name = param_name
|
649
788
|
else
|
650
|
-
nested_containing_object_name = "#{
|
789
|
+
nested_containing_object_name = "#{containing_object_name}.#{param_name}"
|
651
790
|
end
|
652
791
|
|
653
|
-
|
792
|
+
#
|
793
|
+
# Use send to call the private load_impl method.
|
794
|
+
#
|
795
|
+
child_config.send(:load_impl, raw_value, nested_containing_object_name)
|
654
796
|
return child_config
|
655
797
|
elsif((value_class < ConstrainedArray) && (raw_value.class == Array))
|
656
798
|
#
|
@@ -661,21 +803,32 @@ class BaseConfig
|
|
661
803
|
value = []
|
662
804
|
|
663
805
|
raw_value.each_with_index do |element, index|
|
664
|
-
value.push(load_value_impl(
|
806
|
+
value.push(load_value_impl(containing_object_name,
|
807
|
+
"#{param_name}[#{index}]",
|
808
|
+
value_class.element_class,
|
809
|
+
element))
|
665
810
|
end
|
666
811
|
|
667
812
|
if((value_class.min_num_elements != nil) &&
|
668
813
|
(value.length < value_class.min_num_elements))
|
669
|
-
message = "the number of elements (#{value.length}) is less than the
|
670
|
-
message << "minimum number of elements
|
671
|
-
|
814
|
+
message = "the number of elements (#{value.length}) is less than the "
|
815
|
+
message << "specified minimum number of elements "
|
816
|
+
message << "(#{value_class.min_num_elements})"
|
817
|
+
raise Error, construct_error_message(containing_object_name,
|
818
|
+
param_name,
|
819
|
+
raw_value,
|
820
|
+
message)
|
672
821
|
end
|
673
822
|
|
674
823
|
if((value_class.max_num_elements != nil) &&
|
675
824
|
(value.length > value_class.max_num_elements))
|
676
|
-
message = "the number of elements (#{value.length}) is greater than the
|
677
|
-
message << "maximum number of elements
|
678
|
-
|
825
|
+
message = "the number of elements (#{value.length}) is greater than the "
|
826
|
+
message << "specified maximum number of elements "
|
827
|
+
message << "(#{value_class.max_num_elements})"
|
828
|
+
raise Error, construct_error_message(containing_object_name,
|
829
|
+
param_name,
|
830
|
+
raw_value,
|
831
|
+
message)
|
679
832
|
end
|
680
833
|
|
681
834
|
return value
|
@@ -717,67 +870,25 @@ class BaseConfig
|
|
717
870
|
end
|
718
871
|
end
|
719
872
|
|
720
|
-
message = "cannot convert from value class #{raw_value.class.name} to
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
# ====Description:
|
727
|
-
# This method loads self from containing_object_hash, which
|
728
|
-
# must contain key/value pairs, where the keys are
|
729
|
-
# parameter names (Strings) and the values are parameter
|
730
|
-
# values (to be passed to load_value_impl). The configuration's
|
731
|
-
# specifications are enforced by this method.
|
732
|
-
#
|
733
|
-
# ====Parameters:
|
734
|
-
# [containing_object_hash]
|
735
|
-
# The Hash containing the configuration's parameters
|
736
|
-
# [containing_object_name]
|
737
|
-
# The containing object name
|
738
|
-
#
|
739
|
-
def load_impl(containing_object_hash, containing_object_name)
|
740
|
-
@containing_object_name = containing_object_name
|
741
|
-
@params_with_values.clear()
|
742
|
-
|
743
|
-
if(containing_object_hash != nil)
|
744
|
-
containing_object_hash.each do |name, value|
|
745
|
-
#
|
746
|
-
# Lookup the spec by name. If the parameter is not found, the
|
747
|
-
# config file must have extra parameters in it, which is not
|
748
|
-
# an error (should it be?); we just skip the unsupported
|
749
|
-
# parameter.
|
750
|
-
#
|
751
|
-
param_spec = self.class.param_spec_lookup_table.fetch(name.to_sym(), nil)
|
752
|
-
|
753
|
-
if(param_spec == nil)
|
754
|
-
next
|
755
|
-
end
|
756
|
-
|
757
|
-
#
|
758
|
-
# Invoke the parameter's setter.
|
759
|
-
#
|
760
|
-
send("#{param_spec.name}=", value)
|
761
|
-
end
|
762
|
-
end
|
763
|
-
|
764
|
-
enforce_specs("load")
|
873
|
+
message = "cannot convert from value class #{raw_value.class.name} to "
|
874
|
+
message << "specified class #{value_class.name}"
|
875
|
+
raise Error, construct_error_message(containing_object_name,
|
876
|
+
param_name,
|
877
|
+
raw_value,
|
878
|
+
message)
|
765
879
|
end
|
766
|
-
|
880
|
+
private_class_method :load_value_impl
|
767
881
|
|
768
882
|
#
|
769
883
|
# ====Description:
|
770
|
-
# This method loads the configuration hash from reader for containing object
|
771
|
-
# containing_object_name
|
772
|
-
# public API but, unfortunately, cannot be made private (since it is
|
773
|
-
# called from an instance method; there are no "protected" class methods,
|
774
|
-
# unfortunately).
|
884
|
+
# This method loads the configuration hash from +reader+ for containing object
|
885
|
+
# +containing_object_name+.
|
775
886
|
#
|
776
887
|
# ====Parameters:
|
777
888
|
# See the parameter list for load.
|
778
889
|
#
|
779
890
|
# ====Returns:
|
780
|
-
# The configuration hash for containing_object_name (or nil
|
891
|
+
# The configuration hash for +containing_object_name+ (or +nil+, if none
|
781
892
|
# is found)
|
782
893
|
#
|
783
894
|
def self.load_containing_object_hash(reader, containing_object_name)
|
@@ -791,8 +902,8 @@ class BaseConfig
|
|
791
902
|
|
792
903
|
#
|
793
904
|
# Work through the param_hash until the most nested object in
|
794
|
-
# containing_object_name is found by
|
795
|
-
#
|
905
|
+
# containing_object_name is found by splitting
|
906
|
+
# up containing_object_name into an object list and
|
796
907
|
# hashing for each. For example, production.webserver.tokyo would
|
797
908
|
# result in a lookup for production, the results of which them would be
|
798
909
|
# searched for webserver, the results of which finally would be searched
|
@@ -805,7 +916,14 @@ class BaseConfig
|
|
805
916
|
#
|
806
917
|
containing_object_hash = param_hash
|
807
918
|
containing_object_name.split(".").each do |object_name|
|
808
|
-
|
919
|
+
#
|
920
|
+
# Do the lookup with a String and with a Symbol (if the String lookup
|
921
|
+
# fails), as a containing object could be specified with either in the
|
922
|
+
# hash.
|
923
|
+
#
|
924
|
+
containing_object_hash =
|
925
|
+
containing_object_hash[object_name] ||
|
926
|
+
containing_object_hash[object_name.to_sym()]
|
809
927
|
|
810
928
|
if(containing_object_hash == nil)
|
811
929
|
break
|
@@ -824,16 +942,70 @@ class BaseConfig
|
|
824
942
|
|
825
943
|
#
|
826
944
|
# ====Description:
|
827
|
-
# This method
|
828
|
-
#
|
945
|
+
# This method clears all of the configuration's parameter values and
|
946
|
+
# loads self from +containing_object_hash+, which
|
947
|
+
# must contain key/value pairs, where the keys are
|
948
|
+
# parameter names (Strings or Symbols) and the values are parameter
|
949
|
+
# values (to be passed to load_value_impl). The configuration's
|
950
|
+
# specifications are enforced by this method.
|
951
|
+
#
|
952
|
+
# ====Parameters:
|
953
|
+
# [containing_object_hash]
|
954
|
+
# The Hash containing the configuration's parameters
|
955
|
+
# [containing_object_name]
|
956
|
+
# The containing object name (String)
|
957
|
+
#
|
958
|
+
def load_impl(containing_object_hash, containing_object_name)
|
959
|
+
@containing_object_name = containing_object_name
|
960
|
+
|
961
|
+
#
|
962
|
+
# A load operation clears all prior parameter values.
|
963
|
+
#
|
964
|
+
clear_all_values()
|
965
|
+
|
966
|
+
if(containing_object_hash != nil)
|
967
|
+
containing_object_hash.each do |name, value|
|
968
|
+
#
|
969
|
+
# Lookup the parameter specification by name. If the parameter is
|
970
|
+
# not found, the config file must have extra parameters in it,
|
971
|
+
# which is not an error (should it be?); we just skip the unsupported
|
972
|
+
# parameter.
|
973
|
+
#
|
974
|
+
# The parameter name in the containing object hash either could be a
|
975
|
+
# Symbol or a String; in either case to_sym() will normalize
|
976
|
+
# it as a Symbol (the keys in param_spec_lookup_table all are
|
977
|
+
# Symbols)
|
978
|
+
#
|
979
|
+
param_spec = self.class.param_spec_lookup_table[name.to_sym()]
|
980
|
+
|
981
|
+
if(param_spec == nil)
|
982
|
+
next
|
983
|
+
end
|
984
|
+
|
985
|
+
#
|
986
|
+
# Invoke the parameter's setter.
|
987
|
+
#
|
988
|
+
send("#{param_spec.name}=", value)
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
enforce_specs()
|
993
|
+
end
|
994
|
+
private :load_impl
|
995
|
+
|
996
|
+
#
|
997
|
+
# ====Description:
|
998
|
+
# This method loads the contents of a configuration file into
|
999
|
+
# +self+. It loads +self+ from +reader+ (with containing object
|
1000
|
+
# +containing_object_name+), deleting all prior parameter values.
|
829
1001
|
#
|
830
1002
|
# ====Parameters:
|
831
1003
|
# [reader]
|
832
|
-
# The reader from which to load the parameter
|
833
|
-
# will call the read method of
|
834
|
-
# a Hash containing, in the most nested
|
835
|
-
# the configuration parameter values for self
|
836
|
-
# [containing_object_name
|
1004
|
+
# The reader (see Reader) from which to load the parameter
|
1005
|
+
# values. This method will call the +read+ method of
|
1006
|
+
# +reader+, which will return a Hash containing, in the most nested
|
1007
|
+
# containing object entry, the configuration parameter values for +self+.
|
1008
|
+
# [containing_object_name]
|
837
1009
|
# The containing object name
|
838
1010
|
#
|
839
1011
|
def load(reader, containing_object_name = "")
|
@@ -849,7 +1021,7 @@ class BaseConfig
|
|
849
1021
|
#
|
850
1022
|
# ====Description:
|
851
1023
|
# This class method creates a new config instance and
|
852
|
-
# calls load on it with the specified
|
1024
|
+
# calls load on it with the specified arguments. This
|
853
1025
|
# is a second "constructor" for the class.
|
854
1026
|
#
|
855
1027
|
# ====Parameters:
|
@@ -867,17 +1039,16 @@ class BaseConfig
|
|
867
1039
|
#
|
868
1040
|
# ====Description:
|
869
1041
|
# This class method loads arbitrarily many configs from
|
870
|
-
# reader. This should be used when containing_object_name's
|
871
|
-
# elements
|
872
|
-
#
|
873
|
-
#
|
874
|
-
# instance.
|
1042
|
+
# reader. This should be used when each of +containing_object_name+'s
|
1043
|
+
# elements is a different instance of this class' configuration.
|
1044
|
+
# This call returns a Hash containing elements mapping configuration names
|
1045
|
+
# (Symbols) to configuration instance.
|
875
1046
|
#
|
876
1047
|
# ====Parameters:
|
877
1048
|
# See the parameter list for load.
|
878
1049
|
#
|
879
1050
|
# ====Returns:
|
880
|
-
# A Hash of names (
|
1051
|
+
# A Hash of names (Symbols) mapping to loaded configuration instances
|
881
1052
|
#
|
882
1053
|
def self.load_group(reader, containing_object_name = "")
|
883
1054
|
containing_object_hash = load_containing_object_hash(reader, containing_object_name)
|
@@ -898,109 +1069,42 @@ class BaseConfig
|
|
898
1069
|
instance = new()
|
899
1070
|
|
900
1071
|
#
|
901
|
-
# Have to use send in order to call
|
902
|
-
# I think that not being able to call
|
1072
|
+
# Have to use send in order to call private method.
|
1073
|
+
# I think that not being able to call private instance methods
|
903
1074
|
# from class methods is a bug.
|
904
1075
|
#
|
905
1076
|
instance.send(:load_impl, value, value_containing_object_name)
|
906
|
-
config_group[key] = instance
|
1077
|
+
config_group[key.to_sym()] = instance
|
907
1078
|
end
|
908
1079
|
end
|
909
1080
|
|
910
1081
|
return config_group
|
911
1082
|
end
|
912
|
-
|
913
|
-
#
|
914
|
-
# ====Description:
|
915
|
-
# This method writes parameter value value to str, with
|
916
|
-
# indentation indent.
|
917
|
-
#
|
918
|
-
# ====Parameters:
|
919
|
-
# [str]
|
920
|
-
# The String to which to write
|
921
|
-
# [indent]
|
922
|
-
# The indentation at which to write value
|
923
|
-
# [value]
|
924
|
-
# The parameter value to write
|
925
|
-
#
|
926
|
-
def write_value_impl(str, indent, value)
|
927
|
-
nesting_indent = indent + NESTING_INDENT
|
928
|
-
if(value != nil)
|
929
|
-
if(value.class < BaseConfig)
|
930
|
-
#
|
931
|
-
# Writing a nested config requires calling its write(),
|
932
|
-
# just at one further indentation level.
|
933
|
-
#
|
934
|
-
str << "{\n"
|
935
|
-
value.write_impl(str, nesting_indent)
|
936
|
-
str << indent << "}"
|
937
|
-
elsif(value.is_a?(Array))
|
938
|
-
#
|
939
|
-
# Writing an array requires calling write_value_impl for each element,
|
940
|
-
# just at one further indentation level.
|
941
|
-
#
|
942
|
-
str << "[\n"
|
943
|
-
|
944
|
-
value.each_with_index do |element, index|
|
945
|
-
str << nesting_indent
|
946
|
-
write_value_impl(str, nesting_indent, element)
|
947
|
-
|
948
|
-
if(index < (value.length - 1))
|
949
|
-
str << ","
|
950
|
-
end
|
951
|
-
|
952
|
-
str << "\n"
|
953
|
-
end
|
954
|
-
|
955
|
-
str << indent << "]"
|
956
|
-
else
|
957
|
-
str << "#{value}"
|
958
|
-
end
|
959
|
-
end
|
960
|
-
end
|
961
|
-
private :write_value_impl
|
962
|
-
|
963
|
-
#
|
964
|
-
# ====Description:
|
965
|
-
# This method writes self to str, at indentation indent.
|
966
|
-
#
|
967
|
-
# ====Parameters:
|
968
|
-
# [str]
|
969
|
-
# The String to which to write
|
970
|
-
# [indent]
|
971
|
-
# The indentation at which to write self
|
972
|
-
#
|
973
|
-
def write_impl(str, indent)
|
974
|
-
name_field_length = self.class.longest_param_name_length
|
975
|
-
self.class.param_spec_list.each do |param_spec|
|
976
|
-
str << indent << param_spec.name.to_s.ljust(name_field_length) << ": "
|
977
|
-
value = send(param_spec.name)
|
978
|
-
write_value_impl(str, indent, value)
|
979
|
-
str << "\n"
|
980
|
-
end
|
981
|
-
end
|
982
|
-
protected :write_impl
|
983
1083
|
|
984
1084
|
#
|
985
1085
|
# ====Returns:
|
986
|
-
# String representation of self
|
1086
|
+
# String representation of +self+
|
987
1087
|
#
|
988
1088
|
def to_s
|
989
|
-
|
990
|
-
# Print out config file name and the containing object_name and then
|
991
|
-
# call write.
|
992
|
-
#
|
993
|
-
str = String.new()
|
1089
|
+
string_stream = StringIO.new()
|
994
1090
|
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
write_impl(str, NESTING_INDENT)
|
1001
|
-
str << "}\n"
|
1091
|
+
writer = PrettyPrintWriter.new(string_stream)
|
1092
|
+
dump(writer)
|
1093
|
+
|
1094
|
+
return string_stream.string()
|
1095
|
+
end
|
1002
1096
|
|
1003
|
-
|
1097
|
+
#
|
1098
|
+
# ====Description:
|
1099
|
+
# This method enforces constraints between parameters; unless overriden,
|
1100
|
+
# it is a no-op. It is called after all values have been loaded
|
1101
|
+
# during a load operation or at the start of a dump operation. If a child
|
1102
|
+
# class wishes to enforce a particular constraint, it should re-implement
|
1103
|
+
# this method; if a constraint is violated, the method should call
|
1104
|
+
# raise_error in order to raise an Error.
|
1105
|
+
#
|
1106
|
+
def validate_all_values
|
1107
|
+
# This must be re-implemented in child classes in order to do anything
|
1004
1108
|
end
|
1005
1109
|
end
|
1006
1110
|
|