configtoolkit 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/FAQ.txt +113 -25
- data/Hash.txt +128 -0
- data/History.txt +45 -2
- data/KeyValue.txt +235 -0
- data/Manifest.txt +66 -4
- data/README.txt +666 -202
- data/Rakefile +3 -5
- data/Ruby.txt +172 -0
- data/YAML.txt +188 -0
- data/examples/hash_example.rb +71 -0
- data/examples/key_value_example.dump.cfg +5 -0
- data/examples/key_value_example.normal1.cfg +20 -0
- data/examples/key_value_example.normal2.cfg +15 -0
- data/examples/key_value_example.rb +72 -0
- data/examples/key_value_example.wacky.cfg +13 -0
- data/examples/load_example.rb +32 -0
- data/examples/load_example.yaml +34 -0
- data/examples/load_group_example.rb +28 -0
- data/examples/load_group_example.yaml +33 -0
- data/examples/machineconfig.rb +77 -0
- data/examples/ruby_example.rb +32 -0
- data/examples/ruby_example.rcfg +52 -0
- data/examples/usage_example.rb +93 -0
- data/examples/yaml_example.dump.yaml +12 -0
- data/examples/yaml_example.rb +48 -0
- data/examples/yaml_example.yaml +59 -0
- data/lib/configtoolkit.rb +1 -1
- data/lib/configtoolkit/baseconfig.rb +522 -418
- data/lib/configtoolkit/hasharrayvisitor.rb +242 -0
- data/lib/configtoolkit/hashreader.rb +41 -0
- data/lib/configtoolkit/hashwriter.rb +45 -0
- data/lib/configtoolkit/keyvalueconfig.rb +105 -0
- data/lib/configtoolkit/keyvaluereader.rb +597 -0
- data/lib/configtoolkit/keyvaluewriter.rb +157 -0
- data/lib/configtoolkit/prettyprintwriter.rb +167 -0
- data/lib/configtoolkit/reader.rb +62 -0
- data/lib/configtoolkit/rubyreader.rb +270 -0
- data/lib/configtoolkit/types.rb +42 -26
- data/lib/configtoolkit/writer.rb +116 -0
- data/lib/configtoolkit/yamlreader.rb +10 -6
- data/lib/configtoolkit/yamlwriter.rb +113 -71
- data/test/bad_array_index.rcfg +1 -0
- data/test/bad_containing_object_assignment.rcfg +2 -0
- data/test/bad_directive.cfg +1 -0
- data/test/bad_include1.cfg +2 -0
- data/test/bad_include2.cfg +3 -0
- data/test/bad_parameter_reference.rcfg +1 -0
- data/test/contained_sample.cfg +10 -0
- data/test/contained_sample.pretty_print +30 -0
- data/test/contained_sample.rcfg +22 -0
- data/test/contained_sample.yaml +22 -21
- data/test/containers.cfg +6 -0
- data/test/exta_string_after_container.cfg +2 -0
- data/test/extra_container_closing.cfg +2 -0
- data/test/extra_string_after_container.cfg +2 -0
- data/test/extra_string_after_nested_container.cfg +1 -0
- data/test/missing_array_closing.cfg +2 -0
- data/test/missing_array_element.cfg +2 -0
- data/test/missing_directive.cfg +1 -0
- data/test/missing_hash_closing.cfg +2 -0
- data/test/missing_hash_element.cfg +2 -0
- data/test/missing_hash_value.cfg +1 -0
- data/test/missing_include_argument.cfg +1 -0
- data/test/missing_key_value_delimiter.cfg +1 -0
- data/test/readerwritertest.rb +28 -7
- data/test/sample.cfg +10 -0
- data/test/sample.pretty_print +30 -0
- data/test/sample.rcfg +26 -0
- data/test/test_baseconfig.rb +152 -38
- data/test/test_hash.rb +82 -0
- data/test/test_keyvalue.rb +185 -0
- data/test/test_prettyprint.rb +28 -0
- data/test/test_ruby.rb +50 -0
- data/test/test_yaml.rb +33 -26
- data/test/wacky_sample1.cfg +16 -0
- data/test/wacky_sample2.cfg +5 -0
- data/test/wacky_sample3.cfg +4 -0
- data/test/wacky_sample4.cfg +1 -0
- metadata +101 -10
- data/lib/configtoolkit/toolkit.rb +0 -20
- data/test/common.rb +0 -5
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/
|
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
|
-
|
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,
|
15
|
-
and a to_s method
|
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
|
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
|
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
|
48
|
-
|
49
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
196
|
+
ConfigToolkit::BaseConfig#raise_error.
|
115
197
|
|
116
198
|
A ConfigToolkit::Boolean class is specified for the
|
117
|
-
|
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
|
203
|
+
of a parameter specified with ConfigToolkit::Boolean either is +true+ or
|
204
|
+
+false+.
|
122
205
|
|
123
|
-
A
|
124
|
-
specified for the
|
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
|
130
|
-
|
131
|
-
|
132
|
-
The following
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
274
|
-
|
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
|
-
|
742
|
+
== SHARE AND ENJOY!
|