configtoolkit 1.2.0 → 2.0.0

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