configtoolkit 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|