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
data/Manifest.txt CHANGED
@@ -1,18 +1,51 @@
1
1
  Manifest.txt
2
2
  LICENSE
3
- README.txt
4
3
  History.txt
5
- Rakefile
6
4
  FAQ.txt
5
+ Hash.txt
6
+ KeyValue.txt
7
+ README.txt
8
+ Ruby.txt
9
+ YAML.txt
10
+ Rakefile
11
+ examples/hash_example.rb
12
+ examples/key_value_example.rb
13
+ examples/load_example.rb
14
+ examples/load_group_example.rb
15
+ examples/machineconfig.rb
16
+ examples/ruby_example.rb
17
+ examples/usage_example.rb
18
+ examples/yaml_example.rb
19
+ examples/load_example.yaml
20
+ examples/load_group_example.yaml
21
+ examples/yaml_example.dump.yaml
22
+ examples/yaml_example.yaml
23
+ examples/key_value_example.dump.cfg
24
+ examples/key_value_example.normal1.cfg
25
+ examples/key_value_example.normal2.cfg
26
+ examples/key_value_example.wacky.cfg
27
+ examples/ruby_example.rcfg
7
28
  lib/configtoolkit.rb
8
29
  lib/configtoolkit/baseconfig.rb
9
- lib/configtoolkit/toolkit.rb
30
+ lib/configtoolkit/hasharrayvisitor.rb
31
+ lib/configtoolkit/hashreader.rb
32
+ lib/configtoolkit/hashwriter.rb
33
+ lib/configtoolkit/keyvalueconfig.rb
34
+ lib/configtoolkit/keyvaluereader.rb
35
+ lib/configtoolkit/keyvaluewriter.rb
36
+ lib/configtoolkit/prettyprintwriter.rb
37
+ lib/configtoolkit/reader.rb
38
+ lib/configtoolkit/rubyreader.rb
10
39
  lib/configtoolkit/types.rb
40
+ lib/configtoolkit/writer.rb
11
41
  lib/configtoolkit/yamlreader.rb
12
42
  lib/configtoolkit/yamlwriter.rb
13
- test/common.rb
14
43
  test/readerwritertest.rb
15
44
  test/test_baseconfig.rb
45
+ test/test_hash.rb
46
+ test/test_keyvalue.rb
47
+ test/test_prettyprint.rb
48
+ test/test_ruby.rb
16
49
  test/test_yaml.rb
17
50
  test/bad_config.yaml
18
51
  test/contained_sample.yaml
@@ -21,3 +54,32 @@ test/machines.yaml
21
54
  test/sample.ruby_yaml_classes.yaml
22
55
  test/sample.standard_yaml_classes.yaml
23
56
  test/webserver.yaml
57
+ test/bad_directive.cfg
58
+ test/bad_include1.cfg
59
+ test/bad_include2.cfg
60
+ test/contained_sample.cfg
61
+ test/containers.cfg
62
+ test/exta_string_after_container.cfg
63
+ test/extra_container_closing.cfg
64
+ test/extra_string_after_container.cfg
65
+ test/extra_string_after_nested_container.cfg
66
+ test/missing_array_closing.cfg
67
+ test/missing_array_element.cfg
68
+ test/missing_directive.cfg
69
+ test/missing_hash_closing.cfg
70
+ test/missing_hash_element.cfg
71
+ test/missing_hash_value.cfg
72
+ test/missing_include_argument.cfg
73
+ test/missing_key_value_delimiter.cfg
74
+ test/sample.cfg
75
+ test/wacky_sample1.cfg
76
+ test/wacky_sample2.cfg
77
+ test/wacky_sample3.cfg
78
+ test/wacky_sample4.cfg
79
+ test/contained_sample.pretty_print
80
+ test/sample.pretty_print
81
+ test/bad_array_index.rcfg
82
+ test/bad_containing_object_assignment.rcfg
83
+ test/bad_parameter_reference.rcfg
84
+ test/contained_sample.rcfg
85
+ test/sample.rcfg
data/README.txt CHANGED
@@ -1,9 +1,8 @@
1
1
  = configtoolkit
2
-
3
- Project Page: http://rubyforge.org/projects/configtoolkit/
2
+ * Project Page: http://rubyforge.org/projects/configtoolkit/
3
+ * Documentation: http://configtoolkit.rubyforge.org/
4
4
 
5
5
  == DESCRIPTION:
6
-
7
6
  This package makes sourcing information from configuration files robust
8
7
  and easy! It:
9
8
  * Allows programmers to specify the type of data that should be loaded
@@ -11,268 +10,733 @@ and easy! It:
11
10
  the file's data against this specification when loading the file, ensuring
12
11
  that the specification always is obeyed and saving the programmer the
13
12
  tedious chore of writing validation code.
14
- * Automagically generates parameter accessor methods, an equality operator,
15
- and a to_s method from the configuration's specification.
13
+ * Automagically generates parameter accessor methods (getters, setters, and
14
+ predicates to test for presence), an equality operator, and a +to_s+ method
15
+ from the configuration's specification.
16
16
  * Allows programmers to create configuration files,
17
17
  easily and programatically.
18
- * Provides classes that can load from and dump to different kinds of
18
+ * Provides a class that can load Ruby configuration files (allowing the
19
+ full power of Ruby to be used within configuration files).
20
+ * Provides classes that can load from and dump to YAML and key-value
19
21
  configuration files.
22
+ * Provides classes that can load from and dump to Hashes.
20
23
  * Is very extensible, allowing the engine to be used with custom format
21
24
  configuration files and with custom data validation rules.
22
25
 
23
26
  == FEATURES:
24
-
25
27
  * The ConfigToolkit allows programmers to define a new configuration class by
26
28
  specifying the parameters that are included in the configuration. A
27
29
  parameter specification consists of the class of the parameter's values,
28
30
  whether or not the paramater is required, and a default value if the
29
31
  parameter is not required.
30
- * Getter and setter methods automatically are added to a new configuration
31
- class for each specified parameter.
32
+ * Getter, setter, and predicate methods automatically are generated for a new
33
+ configuration class for each specified parameter to get the parameter's
34
+ value, set the parameter's value, and test whether the parameter has a value.
35
+ * A method to clear a parameter's value automatically is generated for
36
+ optional parameters.
37
+ * A block can be passed to the +new+ method of a configuration class in order
38
+ to initialize the instance (see ConfigToolkit::BaseConfig.new).
39
+ * +rdoc+ can detect and generate documentation for each parameter in a
40
+ configuration class (see FAQ.txt for how to enable this).
32
41
  * An equality operator exists for each configuration class that
33
42
  determines equality based on whether all parameter values are equal.
34
- * A to_s method that produces very pretty output exists for each
35
- configuration class.
43
+ * A +to_s+ method that produces very pretty output exists for each
44
+ configuration class (ConfigToolkit::BaseConfig#to_s, leveraging the
45
+ ConfigToolkit::PrettyPrintWriter)
36
46
  * Programmers can specify custom validation blocks for each parameter, in
37
47
  order to enforce specifications not directly supported by the engine.
38
48
  * Programmers can define a method in a configuration class that will be
39
49
  called in order to enforce relationships between the values of different
40
- parameters.
50
+ parameters (ConfigToolkit::BaseConfig#validate_all_values)
41
51
  * Programmers can create custom reader and writer classes in order to
42
52
  load from and dump to (respectively) configuration file formats not
43
53
  directly supported by the ConfigToolkit.
44
54
  * Configuration classes can be nested to any depth within each other.
45
55
  * Configuration classes have first class support for Array configuration
46
56
  parameters. Constraints can be specified for a given Array parameter that
47
- will ensure that all elements of a value are of a
48
- specified class and that there are a specified number of elements in
49
- a value.
57
+ will ensure that all elements are of a specified class and that there
58
+ are a specified number of elements present (see
59
+ ConfigToolkit::ConstrainedArray)
50
60
  * The ConfigToolkit supports multiple configurations stored
51
61
  in a single file; it is able to distinguish that different configurations
52
62
  within a file belong to different configuration objects. For example,
53
63
  "production" and "test" configuration information can live within the
54
64
  same configuration file and can be loaded into separate configuration
55
- instances.
56
- * A reader class to read YAML configuration files is included in the
57
- Configtoolkit.
58
- * A writer class to dump YAML configuration files is included in the
59
- Configtoolkit.
65
+ instances (by specifying a different "containing object name" for each
66
+ ConfigToolkit::BaseConfig#load call)
67
+ * A reader class to read Ruby configuration files
68
+ (ConfigToolkit::RubyReader)
69
+ * A reader class to read YAML configuration files
70
+ (ConfigToolkit::YAMLReader)
71
+ * A writer class to dump YAML configuration files
72
+ (ConfigToolkit::YAMLWriter)
73
+ * A reader class to load configuration directly from a Hash
74
+ (ConfigToolkit::HashReader)
75
+ * A writer class do dump configuration directly to a Hash
76
+ (ConfigToolkit::HashWriter)
77
+ * A reader class to read key-value configuration files
78
+ (ConfigToolkit::KeyValueReader)
79
+ * A writer class to dump key-value configuration files
80
+ (ConfigToolkit::KeyValueWriter)
81
+ * The ConfigToolkit::KeyValueReader and ConfigToolkit::KeyValueWriter classes
82
+ can be configured to work with many different formats of key-value
83
+ configuration files (via ConfigToolkit::KeyValueConfig).
60
84
  * The ConfigToolkit includes a full unit testing suite.
61
85
  * The ConfigToolkit code has detailed comments.
86
+ * The ConfigToolkit code has many example programs (in the +examples+
87
+ subdirectory).
88
+ * The ConfigToolkit package includesx extensive documentation, including
89
+ FAQ.txt and documentation for the support file formats.
62
90
 
63
91
  == PROBLEMS:
64
-
65
92
  None (known).
66
93
 
67
94
  == SYNOPSIS:
68
-
69
- This is a sample configuration class:
70
- require 'rubygems'
71
- require 'configtoolkit'
72
-
73
- class MachineConfig < ConfigToolkit::BaseConfig
74
- add_required_param(:name, String)
75
-
76
- add_required_param(:architecture, String)
77
-
78
- add_required_param(:os, String)
79
-
80
- add_required_param(:num_cpus, Integer) do |value|
81
- if(value <= 0)
82
- raise_error("num_cpus must be greater than zero")
83
- end
84
- end
85
-
86
- add_optional_param(:behind_firewall, ConfigToolkit::Boolean, true)
87
-
88
- add_required_param(:contains_sensitive_data, ConfigToolkit::Boolean)
89
-
90
- add_required_param(:addresses, ConfigToolkit::ConstrainedArray.new(URI, 2, 3))
91
-
92
- def validate_all_values
93
- if(contains_sensitive_data && !behind_firewall)
94
- raise_error("a machine cannot contain sensitive data and not be behind the firewall")
95
- end
96
- end
97
- end
98
-
99
- This code creates a configuration class for a company's servers. The
100
- programmer specifies each of the parameters (the parameter's class, whether
101
- the parameter is required, and a default value if the parameter is optional).
102
-
103
- The programmer also specifies a method (<code>validate_all_values</code>)
104
- that enforces an invariant between multiple parameters. This will be called
105
- by the ConfigToolkit at the end of a load or at the start of a dump operation.
106
-
107
- A custom validation block is specified for the <code>num_cpus</code> parameter;
95
+ Here is a sample configuration class:
96
+ ======+examples/machineconfig.rb+:
97
+ require 'rubygems'
98
+ require 'configtoolkit'
99
+
100
+ require 'uri'
101
+
102
+ #
103
+ # This configuration class is nested within MachineConfig (below).
104
+ # All configuration classes must descend from ConfigToolkit::BaseConfig.
105
+ #
106
+ class OSConfig < ConfigToolkit::BaseConfig
107
+ add_required_param(:name, String)
108
+ add_required_param(:version, Float)
109
+ end
110
+
111
+ #
112
+ # This configuration class is used in all of the example programs.
113
+ # Since it is a configuration class, it descends from
114
+ # ConfigToolkit::BaseConfig.
115
+ #
116
+ class MachineConfig < ConfigToolkit::BaseConfig
117
+ #
118
+ # This is a required parameter with extra user-specified validation
119
+ # (that :num_cpus > 0 must be true)
120
+ #
121
+ add_required_param(:num_cpus, Integer) do |value|
122
+ if(value <= 0)
123
+ raise_error("num_cpus must be greater than zero")
124
+ end
125
+ end
126
+
127
+ #
128
+ # This required parameter is itself a BaseConfig instance; BaseConfig
129
+ # instances can be nested within each other.
130
+ #
131
+ add_required_param(:os, OSConfig)
132
+
133
+ #
134
+ # This is a required boolean parameter. Note that Ruby does not
135
+ # have a boolean type (it has a TrueClass and a FalseClass), so use
136
+ # a marker class (ConfigToolkit::Boolean) to indicate that
137
+ # the parameter will have boolean values (true and false).
138
+ # Boolean values can be written as "true"/"false" or as
139
+ # "yes"/"no" in configuration files.
140
+ #
141
+ add_required_param(:contains_sensitive_data, ConfigToolkit::Boolean)
142
+
143
+ #
144
+ # The behind_firewall parameter is optional and has a default value of
145
+ # true, which means that it will be set to the true if not explicitly set
146
+ # by a configuration file.
147
+ #
148
+ add_optional_param(:behind_firewall, ConfigToolkit::Boolean, true)
149
+
150
+ #
151
+ # The primary_contact parameter is optional and has no default value, which
152
+ # means that it will be absent if not explicitly set by a configuration file.
153
+ #
154
+ add_optional_param(:primary_contact, String)
155
+
156
+ #
157
+ # This parameter's values are guaranteed to be Arrays with URI elements.
158
+ #
159
+ add_required_param(:addresses, ConfigToolkit::ConstrainedArray.new(URI))
160
+
161
+ #
162
+ # This method is called by load() after loading values for
163
+ # all parameters from a specified configuration and can enforce
164
+ # constraints between different parameters. In this case, this method
165
+ # ensures that all machines containing sensitive data are behind
166
+ # the firewall.
167
+ #
168
+ def validate_all_values
169
+ if(contains_sensitive_data && !behind_firewall)
170
+ raise_error("only machines behind firewalls can contain sensitive data")
171
+ end
172
+ end
173
+ end
174
+
175
+
176
+ This code creates a configuration class for a company's servers by subclassing
177
+ ConfigToolkit::BaseConfig. Each of the parameters is specified (the
178
+ parameter's class, whether the parameter is required, and a default value
179
+ if the parameter is optional) with private class methods of
180
+ ConfigToolkit::BaseConfig (ConfigToolkit::BaseConfig.add_required_param and
181
+ ConfigToolkit::BaseConfig.add_optional_param).
182
+
183
+ A +validate_all_values+ method also is defined in the +MachineConfig+
184
+ class that enforces an invariant between multiple parameters. This will be
185
+ called at the end of ConfigToolkit::BaseConfig#load or at the start of
186
+ ConfigToolkit::BaseConfig#dump (see
187
+ ConfigToolkit::BaseConfig#validate_all_values for more details).
188
+
189
+ A custom validation block is specified for the +num_cpus+ parameter;
108
190
  the ConfigToolkit engine does not have direct support for
109
191
  checking specifications like "the value must be greater than zero", but these
110
192
  conditions easily can be added by the programmer. Such blocks are
111
193
  called by the engine before the configuration parameter is set to the new
112
194
  value and are executed in the context of the configuration class instance.
113
195
  If there is a problem with the new value, the block should call
114
- <code>ConfigToolkit::BaseConfig#raise_error</code>.
196
+ ConfigToolkit::BaseConfig#raise_error.
115
197
 
116
198
  A ConfigToolkit::Boolean class is specified for the
117
- <code>behind_firewall</code> and <code>contains_sensitive_data</code>
199
+ +behind_firewall+ and +contains_sensitive_data+
118
200
  parameters. Ruby does not have a standard boolean
119
201
  class (although it does have TrueClass and FalseClass, for boolean values), and
120
202
  so the ConfigToolkit provides a marker class for boolean values. The value
121
- of a parameter specified with ConfigToolkit::Boolean either is true or false.
203
+ of a parameter specified with ConfigToolkit::Boolean either is +true+ or
204
+ +false+.
122
205
 
123
- A <code>ConfigToolkit::ConstrainedArray.new(URI, 2, 3)</code> class is
124
- specified for the <code>addresses</code> parameter. A
206
+ A ConfigToolkit::ConstrainedArray.new(URI) class is
207
+ specified for the +addresses+ parameter. A
125
208
  ConfigToolkit::ConstrainedArray class simply specifies that the
126
209
  parameter value will be an Array that obeys the
127
210
  constraints contained in the ConfigToolkit::ConstrainedArray. In this
128
211
  case, the constraints ensure that all Array elements are of class URI,
129
- that the Array has at least two elements, and that the Array has at most
130
- three elements.
131
-
132
- The following machines.yaml file contains two MachineConfig configurations:
133
- name: apple
134
- architecture: powerpc
135
- os: AIX
136
- num_cpus: 32
137
- behind_firewall: no
138
- contains_sensitive_data: no
139
- addresses:
140
- - http://default.designingpatterns.com
141
- - http://apple.designingpatterns.com
142
-
143
- production:
144
- tokyo:
145
- name: orange
146
- architecture: ultrasparc
147
- os: Solaris
148
- num_cpus: 64
149
- contains_sensitive_data: yes
150
- addresses:
151
- - http://production.designingpatterns.com
152
- - http://orange.designingpatterns.com
153
-
154
- The first configuration in the file:
155
- name: apple
156
- architecture: powerpc
157
- os: AIX
158
- num_cpus: 32
159
- behind_firewall: no
160
- contains_sensitive_data: no
161
- addresses:
162
- - http://default.designingpatterns.com
163
- - http://apple.designingpatterns.com
164
- can be loaded into a MachineConfig instance like this:
165
- require 'rubygems'
166
- require 'configtoolkit'
167
- require 'configtoolkit/yamlreader'
168
-
169
- config = MachineConfig.load(ConfigToolkit::YAMLReader.new("machines.yaml"))
170
-
171
- The second configuration:
172
- production:
173
- tokyo:
174
- name: orange
175
- architecture: ultrasparc
176
- os: Solaris
177
- num_cpus: 64
178
- contains_sensitive_data: yes
179
- addresses:
180
- - http://production.designingpatterns.com
181
- - http://orange.designingpatterns.com
182
- can be loaded by specifying a "containing object" in the load call.
183
- A containing object simply is an object in a configuration file within
184
- which a config object is stored. In this case, the containing
185
- object is "production.tokyo". So:
186
- require 'rubygems'
187
- require 'configtoolkit'
188
- require 'configtoolkit/yamlreader'
189
-
190
- config = MachineConfig.load(ConfigToolkit::YAMLReader.new("machines.yaml"), "production.tokyo")
191
- will load the second configuration in machines.yaml.
192
-
193
- A MachineConfig instance can be dumped like this:
194
- require 'rubygems'
195
- require 'configtoolkit'
196
- require 'configtoolkit/yamlreader'
197
- require 'configtoolkit/yamlwriter'
198
-
199
- config = MachineConfig.load(ConfigToolkit::YAMLReader.new("machines.yaml"))
200
- config.dump(ConfigToolkit::YAMLWriter.new("apple.yaml", true))
201
-
202
- This will produce the following apple.yaml:
203
- ---
204
- name: apple
205
- contains_sensitive_data: false
206
- addresses:
207
- - http://default.designingpatterns.com
208
- - http://apple.designingpatterns.com
209
- os: AIX
210
- architecture: powerpc
211
- num_cpus: 32
212
- behind_firewall: false
213
-
214
- To load a group of named configurations at once from this machines.yaml file:
215
- db_cluster:
216
- db1:
217
- name: cherry
218
- architecture: powerpc
219
- os: Linux
220
- num_cpus: 24
221
- contains_sensitive_data: yes
222
- addresses:
223
- - http://cherrydb.designingpatterns.com
224
- - http://db1.designingpatterns.com
225
- db2:
226
- name: strawberry
227
- architecture: powerpc
228
- os: Linux
229
- num_cpus: 24
230
- contains_sensitive_data: yes
231
- addresses:
232
- - http://strawberrydb.designingpatterns.com
233
- - http://db2.designingpatterns.com
234
- db3:
235
- name: blueberry
236
- architecture: powerpc
237
- os: Linux
238
- num_cpus: 24
239
- contains_sensitive_data: yes
240
- addresses:
241
- - http://blueberrydb.designingpatterns.com
242
- - http://db3.designingpatterns.com
243
-
244
- do:
245
- require 'rubygems'
246
- require 'configtoolkit'
247
- require 'configtoolkit/yamlreader'
248
-
249
- configs = MachineConfig.load_group(ConfigToolkit::YAMLReader.new("machines.yaml"), "db_cluster")
250
- configs.each do |name, config|
251
- print "The #{name} configuration:\n#{config}\n"
252
- end
212
+ that the Array has at least two elements. Additional constraints on the
213
+ number of elements in the Array are possible
214
+
215
+ The following program manipulates a MachineConfig instance.
216
+ ======+examples/usage_example.rb+:
217
+ #!/usr/bin/env ruby
218
+
219
+ #
220
+ # These example programs use the 'relative' gem in order to
221
+ # express paths relative to __FILE__ cleanly, allowing them to be run from
222
+ # any directory. In particular, they use the require_relative
223
+ # method to load examples/machineconfig.rb and
224
+ # File.expand_path_relative_to_caller in order to refer to
225
+ # configuration files within the examples directory.
226
+ #
227
+ require 'rubygems'
228
+ require 'relative'
229
+ require_relative 'machineconfig'
230
+
231
+ #
232
+ # If new() is passed a block, the block *must* initialize all
233
+ # required parameters (otherwise a ConfigToolkit::Error will
234
+ # be thrown). Optional parameters to not need to be
235
+ # explicitly initialized, however. If a default value was
236
+ # specified for an uninitialized optional parameter, the
237
+ # optional parameter will be set to the default value after
238
+ # the block (the behind_firewall parameter is an example of this below);
239
+ # otherwise, the default value will have no value
240
+ # (the primary_contact parameter is an example of this below).
241
+ #
242
+ config = MachineConfig.new() do |config|
243
+ config.num_cpus = 8
244
+ config.os = OSConfig.new() do |os_config|
245
+ os_config.name = "Solaris"
246
+ os_config.version = 10.0
247
+ end
248
+ config.contains_sensitive_data = true
249
+ config.addresses = [URI("http://www.designingpatterns.com"),
250
+ URI("http://jackfruit.designingpatterns.com")]
251
+ end
252
+
253
+ #
254
+ # Note in the output that the behind_firewall parameter is true, its
255
+ # default value, because it was not initialized in the block above.
256
+ # Also note that the primary_contact parameter is not present.
257
+ #
258
+ print "Config after initialization:\n#{config}\n"
259
+
260
+ #
261
+ # Each parameter has a predicate method (automatically generated by
262
+ # add_required_param or add_optional_param) that returns whether or not
263
+ # the parameter has a value.
264
+ #
265
+ if(!config.primary_contact?)
266
+ #
267
+ # This will be printed...
268
+ #
269
+ print "Optional primary_contact parameter is NOT set!\n"
270
+ end
271
+
272
+ #
273
+ # Each parameter has getter and setter accessor methods (automatically
274
+ # generated by add_required_param or add_optional_param), just like
275
+ # "normal" attributes.
276
+ #
277
+ config.primary_contact = "Tony"
278
+ print("primary_contact parameter set to: #{config.primary_contact}\n")
279
+
280
+ if(config.primary_contact?)
281
+ #
282
+ # This will be printed...
283
+ #
284
+ print "Optional primary_contact parameter is now IS set!\n\n"
285
+ end
286
+
287
+ #
288
+ # Note in the output that the primary_contact parameter *now* is present.
289
+ #
290
+ print "Config after setting optional primary_contact parameter:\n#{config}\n"
291
+
292
+ #
293
+ # Each optional parameter has a clear method (automatically generated by
294
+ # add_optional_param) that deletes the parameter's value.
295
+ #
296
+ config.clear_primary_contact()
297
+
298
+ if(!config.primary_contact?)
299
+ #
300
+ # This will be printed...
301
+ #
302
+ print "Optional primary_contact parameter has been deleted!\n\n"
303
+ end
304
+
305
+ #
306
+ # Note in the output that the primary_contact parameter once again
307
+ # is absent.
308
+ #
309
+ print "Config after deleting the primary_contact parameter:\n#{config}\n"
310
+
311
+ This example uses the accessor methods (a getter, a setter, and a predicate)
312
+ that are generated automatically for each configuration parameter. These
313
+ allow configuration instances to be manipulated in the same way that
314
+ instances of a hand-written configuration class could be manipulated. Note,
315
+ however, that the same strict validation is performed for values specified
316
+ programatically through setters as is done for values sourced from
317
+ configuration files. Also note that setters copy the *values* of their
318
+ arguments (with the +dup+ method) not *references* to their arguments, unlike
319
+ the setters created by the +attr_accessor+ method.
320
+ When run, it produces:
321
+ ======The output of <code>examples/usage_example.rb</code>:
322
+ Config after initialization:
323
+ {
324
+ contains_sensitive_data: true
325
+ num_cpus : 8
326
+ addresses : [
327
+ http://www.designingpatterns.com,
328
+ http://jackfruit.designingpatterns.com
329
+ ]
330
+ behind_firewall : true
331
+ os : {
332
+ version: 10.0
333
+ name : Solaris
334
+ }
335
+ }
336
+
337
+ Optional primary_contact parameter is NOT set!
338
+ primary_contact parameter set to: Tony
339
+ Optional primary_contact parameter is now IS set!
340
+
341
+ Config after setting optional primary_contact parameter:
342
+ {
343
+ contains_sensitive_data: true
344
+ num_cpus : 8
345
+ addresses : [
346
+ http://www.designingpatterns.com,
347
+ http://jackfruit.designingpatterns.com
348
+ ]
349
+ behind_firewall : true
350
+ os : {
351
+ version: 10.0
352
+ name : Solaris
353
+ }
354
+ primary_contact : Tony
355
+ }
356
+
357
+ Optional primary_contact parameter has been deleted!
358
+
359
+ Config after deleting the primary_contact parameter:
360
+ {
361
+ contains_sensitive_data: true
362
+ num_cpus : 8
363
+ addresses : [
364
+ http://www.designingpatterns.com,
365
+ http://jackfruit.designingpatterns.com
366
+ ]
367
+ behind_firewall : true
368
+ os : {
369
+ version: 10.0
370
+ name : Solaris
371
+ }
372
+ }
373
+
374
+
375
+ Of course, the most common use case will be setting configuration parameters
376
+ with the contents of configuration files, not programatically. The following YAML configuration file contains two MachineConfig configurations:
377
+ ======+examples/load_example.yaml+:
378
+ #
379
+ # This file contains MachineConfig configurations
380
+ # (see examples/machineconfig.rb for the configuration's specification).
381
+ #
382
+
383
+ ############################################################
384
+ # First configuration
385
+ num_cpus: 32
386
+ os:
387
+ name: AIX
388
+ version: 5.3
389
+ behind_firewall: no
390
+ contains_sensitive_data: no
391
+ addresses:
392
+ - http://default.designingpatterns.com
393
+ - http://apple.designingpatterns.com
394
+ ############################################################
395
+
396
+ # production is a containing object
397
+ production:
398
+ # production.www is a containing object
399
+ www:
400
+ ############################################################
401
+ # Second configuration (nested in the production.www
402
+ # containing object)
403
+ num_cpus: 64
404
+ os:
405
+ name: Solaris
406
+ version: 10.0
407
+ contains_sensitive_data: yes
408
+ addresses:
409
+ - http://www.designingpatterns.com
410
+ - http://tokyo.designingpatterns.com
411
+ ############################################################
412
+
413
+ The following program loads and prints the two configurations from
414
+ +examples/load_example.yaml+.
415
+ ======+examples/load_example.rb+:
416
+ #!/usr/bin/env ruby
417
+
418
+ #
419
+ # These example programs use the 'relative' gem in order to
420
+ # express paths relative to __FILE__ cleanly, allowing them to be run from
421
+ # any directory. In particular, they use the require_relative
422
+ # method to load examples/machineconfig.rb and
423
+ # File.expand_path_relative_to_caller in order to refer to
424
+ # configuration files within the examples directory.
425
+ #
426
+ require 'rubygems'
427
+ require 'relative'
428
+ require_relative 'machineconfig'
429
+
430
+ require 'configtoolkit/yamlreader'
431
+
432
+ CONFIGURATION_FILE = File.expand_path_relative_to_caller("load_example.yaml")
433
+
434
+ #
435
+ # The first configuration has no containing object name.
436
+ #
437
+ reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
438
+ first_config = MachineConfig.load(reader)
439
+ print("The first config:\n#{first_config}\n")
440
+
441
+ #
442
+ # The second configuration has "production.www" as a containing
443
+ # object name.
444
+ #
445
+ reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
446
+ second_config = MachineConfig.load(reader, "production.www")
447
+ print("The second config:\n#{second_config}\n")
448
+
449
+ Note how the program changes the containing object name passed to the +load+
450
+ method (first an empty string, next +production.www+, and finally
451
+ +production.mail+) in order to refer to different configurations within
452
+ the YAML file. The ConfigToolkit's support of containing objects allows it
453
+ to accomodate multiple configurations living in the same file, with each
454
+ configuration being accessible separately. When run, it produces:
455
+ ======The output of <code>examples/load_example.rb</code>:
456
+ The first config:
457
+ {
458
+ os : {
459
+ name : AIX
460
+ version: 5.3
461
+ }
462
+ behind_firewall : false
463
+ num_cpus : 32
464
+ addresses : [
465
+ http://default.designingpatterns.com,
466
+ http://apple.designingpatterns.com
467
+ ]
468
+ contains_sensitive_data: false
469
+ }
470
+
471
+ The second config:
472
+ production.www: {
473
+ os : {
474
+ name : Solaris
475
+ version: 10.0
476
+ }
477
+ behind_firewall : true
478
+ num_cpus : 64
479
+ addresses : [
480
+ http://www.designingpatterns.com,
481
+ http://tokyo.designingpatterns.com
482
+ ]
483
+ contains_sensitive_data: true
484
+ }
485
+
486
+
487
+ A group of configurations also can be loaded with the
488
+ ConfigToolkit::BaseConfig.load_group method. For instance, given
489
+ +examples/load_group_example.yaml+:
490
+ ======+examples/load_group_example.yaml+:
491
+ #
492
+ # This file contains MachineConfig configurations
493
+ # (see examples/machineconfig.rb for the configuration's specification).
494
+ #
495
+
496
+ db_cluster:
497
+ db1:
498
+ num_cpus: 16
499
+ os:
500
+ name: Solaris
501
+ version: 10.0
502
+ contains_sensitive_data: yes
503
+ behind_firewall: yes
504
+ addresses:
505
+ - http://db1.designingpatterns.com
506
+ db2:
507
+ num_cpus: 12
508
+ os:
509
+ name: AIX
510
+ version: 5.3
511
+ contains_sensitive_data: yes
512
+ behind_firewall: yes
513
+ addresses:
514
+ - http://db2.designingpatterns.com
515
+ db3:
516
+ num_cpus: 24
517
+ os:
518
+ name: Solaris
519
+ version: 10.0
520
+ contains_sensitive_data: yes
521
+ behind_firewall: yes
522
+ addresses:
523
+ - http://db3.designingpatterns.com
524
+
525
+ the following code will load the file's three configurations into a +Hash+
526
+ mapping containing object names to configurations:
527
+ ======+examples/load_group_example.rb+:
528
+ #!/usr/bin/env ruby
529
+
530
+ #
531
+ # These example programs use the 'relative' gem in order to
532
+ # express paths relative to __FILE__ cleanly, allowing them to be run from
533
+ # any directory. In particular, they use the require_relative
534
+ # method to load examples/machineconfig.rb and
535
+ # File.expand_path_relative_to_caller in order to refer to
536
+ # configuration files within the examples directory.
537
+ #
538
+ require 'rubygems'
539
+ require 'relative'
540
+ require_relative 'machineconfig'
541
+
542
+ require 'configtoolkit/yamlreader'
543
+
544
+ CONFIGURATION_FILE =
545
+ File.expand_path_relative_to_caller("load_group_example.yaml")
546
+
547
+ #
548
+ # The group of configurations is under the db_cluster containing
549
+ # object name.
550
+ #
551
+ reader = ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE)
552
+ configs = MachineConfig.load_group(reader, "db_cluster")
553
+ configs.each do |name, config|
554
+ print "The #{name} configuration:\n#{config}\n"
555
+ end
556
+
557
+ When run, it produces:
558
+ ======The output of <code>examples/load_group_example.rb</code>:
559
+ The db1 configuration:
560
+ db_cluster.db1: {
561
+ os : {
562
+ version: 10.0
563
+ name : Solaris
564
+ }
565
+ behind_firewall : true
566
+ num_cpus : 16
567
+ addresses : [
568
+ http://db1.designingpatterns.com
569
+ ]
570
+ contains_sensitive_data: true
571
+ }
572
+
573
+ The db2 configuration:
574
+ db_cluster.db2: {
575
+ os : {
576
+ version: 5.3
577
+ name : AIX
578
+ }
579
+ behind_firewall : true
580
+ num_cpus : 12
581
+ addresses : [
582
+ http://db2.designingpatterns.com
583
+ ]
584
+ contains_sensitive_data: true
585
+ }
586
+
587
+ The db3 configuration:
588
+ db_cluster.db3: {
589
+ os : {
590
+ version: 10.0
591
+ name : Solaris
592
+ }
593
+ behind_firewall : true
594
+ num_cpus : 24
595
+ addresses : [
596
+ http://db3.designingpatterns.com
597
+ ]
598
+ contains_sensitive_data: true
599
+ }
600
+
601
+
602
+
603
+ == CONFIGURATION FILE FORMATS:
604
+ === EXISTING FORMATS:
605
+ * YAML.txt describes working with YAML configuration files.
606
+ * KeyValue.txt describes working with key-value configuration files.
607
+ * Ruby.txt describes working with Ruby configuration files.
608
+ * Hash.txt describes loading configurations from Hashes and dumping configurations to Hashes.
609
+
610
+ === NEW FORMATS:
611
+ The ConfigToolkit easily can be extended to support new file formats. A new
612
+ reader class can be created to load from a new format, and a new writer class
613
+ can be created to write out a new format.
614
+
615
+ The reader interface is documented by the ConfigToolkit::Reader class.
616
+ Basically, a reader needs to have a read method that reads data from some
617
+ underlying stream and returns a Hash that represents the configuration (see
618
+ ConfigToolkit::Reader#read for the specifications;
619
+ ConfigToolkit::YAMLReader#read is a simple example of an implementation).
620
+ ConfigToolkit::HashReader is a no-op reader, so the Hash that its constructor
621
+ accepts is the same that a new reader class should return from its
622
+ +read+ method.
623
+
624
+ The writer interface is documented by the ConfigToolkit::Writer class.
625
+ Basically, a writer needs to have a write method that accepts a Hash
626
+ representing a configuration and writes it to some underlying stream (see
627
+ ConfigToolkit::Writer#write for the specifications;
628
+ ConfigToolkit::YAMLWriter#write is a relatively simple example of an
629
+ implementation). ConfigToolkit::HashWriter is a no-op writer, so the Hash
630
+ stored in its +config_hash+ attribute after a ConfigToolkit::BaseConfig#dump
631
+ call is the same Hash passed to a writer's +write+ method.
632
+
633
+ While writing a new reader or writer class, it may be necessary to iterate
634
+ over a Hash that contains arbitrarily deeply nested Hashes and Arrays. The
635
+ ConfigToolkit::HashArrayVisitor can help with this task by allowing hook
636
+ functions to be written that will be called when a particular type of node
637
+ is visited. ConfigToolkit::YAMLWriter#write,
638
+ ConfigToolkit::KeyValueWriter#write and ConfigToolkit::PrettyPrintWriter#write
639
+ all use such visitors.
640
+
641
+ Feel free also to request that we add support for a new file format to the
642
+ distribution, and we will try our best to accomodate! See below for some
643
+ ideas that we have had for new readers and writers.
644
+
645
+ == CONFIGURATION CLASS DOCUMENTATION:
646
+ Configuration classes *can* be documented properly by +rdoc+, although not by
647
+ default. See FAQ.txt for full details.
253
648
 
254
649
  == REQUIREMENTS:
255
-
256
- Hoe is required, but only for running the tests.
650
+ Hoe, assertions, and relative are required, but only for running the tests.
651
+ Relative also is required for running the examples.
257
652
 
258
653
  == INSTALL:
259
-
260
- sudo gem install configtoolkit
654
+ sudo gem install configtoolkit
655
+
656
+ == POSSIBLE FUTURE TECHNICAL DIRECTIONS:
657
+ Here are some *possible* ideas:
658
+ * Modify the ConfigToolkit and/or +rdoc+ so that +rdoc+ produces good
659
+ documentation for configuration classes (perhaps showing parameter value
660
+ classes and any default values). This enhancement should eliminate
661
+ any need to patch +rdoc+ (see FAQ.txt for the current procedure).
662
+ * Provide support for sourcing configuration from XML files
663
+ (+XMLReader+ and +XMLWriter+ classes)
664
+ * Provide support for sourcing configuration from INI files
665
+ (+INIReader+ and +INIWriter+ classes)
666
+ * Provide support for sourcing configuration from command-line parameters
667
+ (+CommandLineReader+ and +CommandLineWriter+ classes)
668
+ * Provide support for sourcing configuration from environment variables
669
+ (+EnvVarReader+ and +EnvVarWriter+ classes)
670
+ * Provide support for sourcing configuration from a database
671
+ (+DatabaseReader+ and +DatabaseWriter+ classes)
672
+ * Provide support for sourcing configuration from a configuration file (of
673
+ any format) containing embedded Ruby (+EmbeddedRubyReader+ class, which
674
+ would process the embedded Ruby and then hand the resulting file off
675
+ to another reader class)
676
+ * Provide support for sourcing configuration from multiple sources (i.e., first
677
+ source from a file and then from the command-line).
678
+ * Allow more powerful parameter specifications to be specified. For instance,
679
+ it would be nice if users could specify that the parameter is a Pathname
680
+ to a readable file. Possibly the ConfigToolkit should provide a Rules
681
+ interface to create new parameter rules.
682
+ * Allow user-specified conversions from Strings to arbitrary types for
683
+ configuration file formats that only have a limited type palette (key-value
684
+ files, for instance).
685
+ * Allow other languages to use the ConfigToolkit (possible targets could
686
+ be C++, Python, or Java)
687
+ * Provide a ConfigToolkit::BaseConfig class method "constructor" that would
688
+ allocate any nested config instances and Arrays (recursively, so that
689
+ and multiply-nested config instances and Arrays also would be initialized).
690
+ This would make programatically setting the configuration parameters
691
+ very easy, since no explicit +new+ calls would be necessary for the
692
+ nested config object and Array parameters.
693
+
694
+ We would love to receive input from users about which new features are most
695
+ desirable!
261
696
 
262
697
  == AUTHORS:
263
-
264
- Designing Patterns: http://www.designingpatterns.com
265
-
266
- Share and Enjoy!
698
+ === Designing Patterns
699
+ * Homepage: http://www.designingpatterns.com
700
+ * Blogs: http://blogs.designingpatterns.com
267
701
 
268
702
  == SUPPORT
269
-
270
703
  Please post questions, concerns, or requests for enhancement to the forums on
271
704
  the project page. Alternatively, direct contact information for
272
705
  Designing Patterns can be found on the project page for this gem.
273
- Please discuss any enhancements with the authors (Designing Patterns)
274
- before doing any work.
706
+
707
+ == ENHANCEMENTS
708
+ Please feel free to contact us with any ideas; we will try our best to
709
+ enhance the software and respond to user requests. Of course, we are more
710
+ likely to work on a particular enhancement if we know that there are users
711
+ who want it. Designing Patterns provides contracting and consulting services,
712
+ so if there is an enhancement that *must* get done (and in a specified time
713
+ frame), please inquire about retaining our services!
275
714
 
276
715
  == LICENSE:
716
+ The license text can be found in the +LICENSE+ file at the root of the
717
+ distribution.
718
+
719
+ This package is licensed with an MIT license:
720
+
721
+ Copyright (c) 2008 Designing Patterns
722
+
723
+ Permission is hereby granted, free of charge, to any person obtaining
724
+ a copy of this software and associated documentation files (the
725
+ 'Software'), to deal in the Software without restriction, including
726
+ without limitation the rights to use, copy, modify, merge, publish,
727
+ distribute, sublicense, and/or sell copies of the Software, and to
728
+ permit persons to whom the Software is furnished to do so, subject to
729
+ the following conditions:
730
+
731
+ The above copyright notice and this permission notice shall be
732
+ included in all copies or substantial portions of the Software.
733
+
734
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
735
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
736
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
737
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
738
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
739
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
740
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
277
741
 
278
- See LICENSE, at the root of the distribution.
742
+ == SHARE AND ENJOY!