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