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.
Files changed (81) hide show
  1. data/FAQ.txt +113 -25
  2. data/Hash.txt +128 -0
  3. data/History.txt +45 -2
  4. data/KeyValue.txt +235 -0
  5. data/Manifest.txt +66 -4
  6. data/README.txt +666 -202
  7. data/Rakefile +3 -5
  8. data/Ruby.txt +172 -0
  9. data/YAML.txt +188 -0
  10. data/examples/hash_example.rb +71 -0
  11. data/examples/key_value_example.dump.cfg +5 -0
  12. data/examples/key_value_example.normal1.cfg +20 -0
  13. data/examples/key_value_example.normal2.cfg +15 -0
  14. data/examples/key_value_example.rb +72 -0
  15. data/examples/key_value_example.wacky.cfg +13 -0
  16. data/examples/load_example.rb +32 -0
  17. data/examples/load_example.yaml +34 -0
  18. data/examples/load_group_example.rb +28 -0
  19. data/examples/load_group_example.yaml +33 -0
  20. data/examples/machineconfig.rb +77 -0
  21. data/examples/ruby_example.rb +32 -0
  22. data/examples/ruby_example.rcfg +52 -0
  23. data/examples/usage_example.rb +93 -0
  24. data/examples/yaml_example.dump.yaml +12 -0
  25. data/examples/yaml_example.rb +48 -0
  26. data/examples/yaml_example.yaml +59 -0
  27. data/lib/configtoolkit.rb +1 -1
  28. data/lib/configtoolkit/baseconfig.rb +522 -418
  29. data/lib/configtoolkit/hasharrayvisitor.rb +242 -0
  30. data/lib/configtoolkit/hashreader.rb +41 -0
  31. data/lib/configtoolkit/hashwriter.rb +45 -0
  32. data/lib/configtoolkit/keyvalueconfig.rb +105 -0
  33. data/lib/configtoolkit/keyvaluereader.rb +597 -0
  34. data/lib/configtoolkit/keyvaluewriter.rb +157 -0
  35. data/lib/configtoolkit/prettyprintwriter.rb +167 -0
  36. data/lib/configtoolkit/reader.rb +62 -0
  37. data/lib/configtoolkit/rubyreader.rb +270 -0
  38. data/lib/configtoolkit/types.rb +42 -26
  39. data/lib/configtoolkit/writer.rb +116 -0
  40. data/lib/configtoolkit/yamlreader.rb +10 -6
  41. data/lib/configtoolkit/yamlwriter.rb +113 -71
  42. data/test/bad_array_index.rcfg +1 -0
  43. data/test/bad_containing_object_assignment.rcfg +2 -0
  44. data/test/bad_directive.cfg +1 -0
  45. data/test/bad_include1.cfg +2 -0
  46. data/test/bad_include2.cfg +3 -0
  47. data/test/bad_parameter_reference.rcfg +1 -0
  48. data/test/contained_sample.cfg +10 -0
  49. data/test/contained_sample.pretty_print +30 -0
  50. data/test/contained_sample.rcfg +22 -0
  51. data/test/contained_sample.yaml +22 -21
  52. data/test/containers.cfg +6 -0
  53. data/test/exta_string_after_container.cfg +2 -0
  54. data/test/extra_container_closing.cfg +2 -0
  55. data/test/extra_string_after_container.cfg +2 -0
  56. data/test/extra_string_after_nested_container.cfg +1 -0
  57. data/test/missing_array_closing.cfg +2 -0
  58. data/test/missing_array_element.cfg +2 -0
  59. data/test/missing_directive.cfg +1 -0
  60. data/test/missing_hash_closing.cfg +2 -0
  61. data/test/missing_hash_element.cfg +2 -0
  62. data/test/missing_hash_value.cfg +1 -0
  63. data/test/missing_include_argument.cfg +1 -0
  64. data/test/missing_key_value_delimiter.cfg +1 -0
  65. data/test/readerwritertest.rb +28 -7
  66. data/test/sample.cfg +10 -0
  67. data/test/sample.pretty_print +30 -0
  68. data/test/sample.rcfg +26 -0
  69. data/test/test_baseconfig.rb +152 -38
  70. data/test/test_hash.rb +82 -0
  71. data/test/test_keyvalue.rb +185 -0
  72. data/test/test_prettyprint.rb +28 -0
  73. data/test/test_ruby.rb +50 -0
  74. data/test/test_yaml.rb +33 -26
  75. data/test/wacky_sample1.cfg +16 -0
  76. data/test/wacky_sample2.cfg +5 -0
  77. data/test/wacky_sample3.cfg +4 -0
  78. data/test/wacky_sample4.cfg +1 -0
  79. metadata +101 -10
  80. data/lib/configtoolkit/toolkit.rb +0 -20
  81. data/test/common.rb +0 -5
@@ -0,0 +1,12 @@
1
+ ---
2
+ production:
3
+ www:
4
+ contains_sensitive_data: true
5
+ addresses:
6
+ - http://www.designingpatterns.com
7
+ - http://tokyo.designingpatterns.com
8
+ os:
9
+ name: Solaris
10
+ version: 10.0
11
+ num_cpus: 64
12
+ behind_firewall: true
@@ -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/toolkit'
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 represents a configuration. It provides configuration
11
- # specification, loading, and dumping functionality. A BaseConfig
12
- # configuration can contain "scalars" (anything except an array or
13
- # nested configuration class), ConstrainedArrays (arrays), and other
14
- # BaseConfig child classes (which can be thought of as hashes).
15
- # Note that ConstrainedArrays and other BaseConfig
16
- # classes recursively can contain anything the parent BaseConfig can contain,
17
- # so nesting to any depth is supported.
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, but instead does so
20
- # through reader and writer classes (respectively) specified by the
21
- # programmer. This allows BaseConfig to support virtually any underlying
22
- # configuration file format (including YAML, UNIX key/value pairs, etc).
23
- # BaseConfig expects reader classes to contain a read method that sources
24
- # configuration information and returns a hash that maps strings
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 hashes or arrays. It expects writer classes to in turn support a write
27
- # method that accepts such a hash and writes it to some underlying stream.
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 whatever source (even specified
30
- # programatically via setters) always fully is validated.
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, then they also can define validate_all_values.
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 # Only meaningful if is_required?
73
+ attr_reader :default_value # Optionally present; default_value? is the
74
+ # predicate
53
75
 
54
76
  #
55
77
  # ====Description:
56
- # This constructs a Paramspec for parameter name and value
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
- if(!is_required)
78
- @default_value = default_value
79
- end
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 is_required?
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
- # This class method adds a new parameter to a BaseConfig child
157
- # class (it should be called within the body of the child class'
158
- # definition). The new parameter has name name and has values
159
- # of class value_class (or one of value_class' child classes).
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 is true; if !is_required, then default_value is the
162
- # value of the parameter if it's not set in a configuration file.
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 the new value for the parameter before setting
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
- # If !is_required, then the default value of the parameter if it's
183
- # not specified in a configuration file.
184
- # [validate_value_block]
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 reflectoin APIs when the setter actually is called.
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 << "attr_reader :#{name}\n"
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(\"#{name}\", value_class, value)\n"
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}(value)\n"
367
+ methods << " #{validate_value_method}(loaded_value)\n"
235
368
  methods << " rescue Error => e\n"
236
- methods << " raise Error, construct_error_message(\"#{name}\", value, e.message), e.backtrace()\n"
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 << " @params_with_values.add(:#{name})\n"
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 is_required = true.
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, nil, &validate_value_block)
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 is_required = false and
270
- # default_value = default_value.
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 (instance variable) containing the name of the object that
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 would be
288
- # 'production.webserver'.
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 (all parameters
295
- # initially have a nil value).
438
+ # This constructs a BaseConfig with no parameters set (each parameter
439
+ # initially has a +nil+ value).
296
440
  #
297
- def initialize
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("@#{param_spec.name}", nil)
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 hat all required parameters have been set. If a required
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 their
313
- # default values.
314
- # * Calls validate_all_values, if this exists.
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). The operation_name
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(operation_name)
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(!@params_with_values.include?(param_spec.name))
339
- if(param_spec.is_required?)
532
+ if(!has_value(param_spec.name))
533
+ if(param_spec.required?)
340
534
  missing_params.push(param_spec.name)
341
- else
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
- missing_param_spec_list = missing_params.map do |param_name|
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
- message << ": #{e.message}"
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
- str << "]"
418
- return str
419
- elsif(value.is_a?(Hash))
558
+ begin
559
+ validate_all_values()
560
+ rescue Error => e
420
561
  #
421
- # Recursively call construct_value_str for each Hash element,
422
- # surrounding the element strings in braces.
562
+ # Rescue the error, add some information to the error message, and
563
+ # throw a repackaged error.
423
564
  #
424
- str = "{"
425
-
426
- value.each_with_index do |key_value, index|
427
- str << "#{construct_value_str(key_value[0])}=>#{construct_value_str(key_value[1])}"
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
- str << "}"
435
- return str
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. It is meant to
477
- # be called from user-defined parameter validation blocks. This is
478
- # a Hotel California type of call; it does not return.
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
- # specified parameters and compares the parameter values of
492
- # self and rhs using the equality operator of the parameter values.
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
- return value.dump_impl({})
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; that is,
546
- # it adds entries to containing_object_hash for each parameter.
547
- # An entry's key is the parameter name (as a String), and the value is
548
- # the parameter value. The method returns containing_object_hash
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("dump")
677
+ enforce_specs()
566
678
 
567
679
  self.class.param_spec_list.each do |param_spec|
568
- containing_object_hash[param_spec.name.to_s] = dump_value_impl(send(param_spec.name))
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
- protected :dump_impl
699
+ private :dump_impl
574
700
 
575
701
  #
576
702
  # ====Description:
577
- # This method dumps self to writer. That is, it constructs
578
- # a Hash containing entries for the containing object (and,
579
- # in the the most nested containing object entry, entries for the
580
- # paramters) and passes the Hash to the write method of writer. The
581
- # configuration's data is checked against its specifications
582
- # before anything is dumped.
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
- dump_hash = {}
590
- containing_object_hash = dump_hash
591
-
592
- @containing_object_name.split(".").each do |object_name|
593
- object_hash = {}
594
- containing_object_hash[object_name] = object_hash
595
- containing_object_hash = object_hash
596
- end
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
- dump_impl(containing_object_hash)
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(param_name, value_class, raw_value)
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
- return raw_value
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(@containing_object_name.empty?)
786
+ if(containing_object_name.empty?)
648
787
  nested_containing_object_name = param_name
649
788
  else
650
- nested_containing_object_name = "#{@containing_object_name}.#{param_name}"
789
+ nested_containing_object_name = "#{containing_object_name}.#{param_name}"
651
790
  end
652
791
 
653
- child_config.load_impl(raw_value, nested_containing_object_name)
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("#{param_name}[#{index}]", value_class.element_class, element))
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 specified "
670
- message << "minimum number of elements (#{value_class.min_num_elements})"
671
- raise Error, construct_error_message(param_name, raw_value, message)
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 specified "
677
- message << "maximum number of elements (#{value_class.max_num_elements})"
678
- raise Error, construct_error_message(param_name, raw_value, message)
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 specified class #{value_class.name}"
721
- raise Error, construct_error_message(param_name, raw_value, message)
722
- end
723
- private :load_value_impl
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
- protected :load_impl
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. This method is not really part of BaseConfig's
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, if none
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
- # splitting up containing_object_name into an object list and
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
- containing_object_hash = containing_object_hash.fetch(object_name, nil)
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 loads self from reader (with containing object
828
- # containing_object_name).
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 values. This method
833
- # will call the read method of reader, which will return
834
- # a Hash containing, in the most nested containing object entry,
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 parameters. This
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 all are hashes, each of which is a different instance of
872
- # this class' configuration. This call returns a Hash containing
873
- # elements mapping configuration name to configuration
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 (Strings) mapping to loaded configuration instances
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 protected method.
902
- # I think that not being able to call protected instance methods
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
- if(!@containing_object_name.empty?)
996
- str << "#{@containing_object_name}: "
997
- end
998
-
999
- str << "{\n"
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
- return str
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