configtoolkit 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/FAQ.txt +22 -0
- data/Hash.txt +1 -1
- data/History.txt +6 -0
- data/Manifest.txt +9 -0
- data/Override.txt +249 -0
- data/README.txt +11 -3
- data/Rakefile +1 -1
- data/examples/override_example.yaml +14 -0
- data/examples/override_example_1.rb +27 -0
- data/examples/override_example_2.rb +48 -0
- data/examples/override_example_override.yaml +9 -0
- data/lib/configtoolkit/overridereader.rb +190 -0
- data/test/override_sample_1.yaml +21 -0
- data/test/override_sample_2.yaml +20 -0
- data/test/test_override.rb +115 -0
- metadata +14 -3
data/FAQ.txt
CHANGED
@@ -170,3 +170,25 @@ I realize that this is an unsatisfactory solution and have it on the
|
|
170
170
|
+configtoolkit+ TODO list (see README.txt) to craft a better one (one that
|
171
171
|
does not require patching rdoc). If this is particularly important to you,
|
172
172
|
please let me know so that I can prioritize this work.
|
173
|
+
|
174
|
+
=== How can I override the contents of one configuration file with the contents of another?
|
175
|
+
One common pattern in UNIX applications is to have a system-wide configuration
|
176
|
+
file in /etc and to allow users to override the system-wide settings with
|
177
|
+
a configuration file in their home directories. The
|
178
|
+
ConfigToolkit::OverrideReader supports this by allowing the contents of
|
179
|
+
one configuration file to override those of another (the files even can be
|
180
|
+
different formats). See Override.txt for more details.
|
181
|
+
|
182
|
+
=== Configuration files are great, but how can I populate a configuration from the command-line?
|
183
|
+
The ConfigToolkit currently does not support sourcing configuration from the
|
184
|
+
command-line directly. This is an enhancement that we are
|
185
|
+
investigating, however, so if you think it desirable, please let us know so
|
186
|
+
that we can prioritize it appropriately.
|
187
|
+
|
188
|
+
In the meantime, configuration classes still can be populated with command-line
|
189
|
+
arguments. The easiest way to do this is to populate a Hash with the arguments
|
190
|
+
and, after reading all the command-line options, to use the
|
191
|
+
ConfigToolkit::HashReader to load the Hash into a configuration instance.
|
192
|
+
If the application also sources configuration from a file, the
|
193
|
+
ConfigToolkit::OverrideReader allows configuration to be sourced from more
|
194
|
+
than one source.
|
data/Hash.txt
CHANGED
@@ -125,4 +125,4 @@ When run, the program produces:
|
|
125
125
|
}
|
126
126
|
|
127
127
|
The Hash dumped from the second config:
|
128
|
-
{:production=>{:www=>{:addresses=>[#<URI::HTTP:
|
128
|
+
{:production=>{:www=>{:addresses=>[#<URI::HTTP:0xb7e6046c URL:http://www.designingpatterns.com>, #<URI::HTTP:0xb7e60408 URL:http://tokyo.designingpatterns.com>], :num_cpus=>64, :os=>{:version=>10.0, :name=>"Solaris"}, :behind_firewall=>true, :contains_sensitive_data=>true}}}
|
data/History.txt
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
=== 2.2.0 / 2008-07-29
|
2
|
+
* Add support for overriding configurations through the
|
3
|
+
ConfigToolkit::OverrideReader, allowing the contents of one configuration file
|
4
|
+
to override the contents of another configuration file. See Override.txt
|
5
|
+
for more details.
|
6
|
+
|
1
7
|
=== 2.1.0 / 2008-07-25
|
2
8
|
* Small documentation enhancements
|
3
9
|
* Add a patch for properly documenting configuration parameters for rdoc
|
data/Manifest.txt
CHANGED
@@ -4,6 +4,7 @@ History.txt
|
|
4
4
|
FAQ.txt
|
5
5
|
Hash.txt
|
6
6
|
KeyValue.txt
|
7
|
+
Override.txt
|
7
8
|
README.txt
|
8
9
|
Ruby.txt
|
9
10
|
YAML.txt
|
@@ -13,11 +14,15 @@ examples/key_value_example.rb
|
|
13
14
|
examples/load_example.rb
|
14
15
|
examples/load_group_example.rb
|
15
16
|
examples/machineconfig.rb
|
17
|
+
examples/override_example_1.rb
|
18
|
+
examples/override_example_2.rb
|
16
19
|
examples/ruby_example.rb
|
17
20
|
examples/usage_example.rb
|
18
21
|
examples/yaml_example.rb
|
19
22
|
examples/load_example.yaml
|
20
23
|
examples/load_group_example.yaml
|
24
|
+
examples/override_example_override.yaml
|
25
|
+
examples/override_example.yaml
|
21
26
|
examples/yaml_example.dump.yaml
|
22
27
|
examples/yaml_example.yaml
|
23
28
|
examples/key_value_example.dump.cfg
|
@@ -33,6 +38,7 @@ lib/configtoolkit/hashwriter.rb
|
|
33
38
|
lib/configtoolkit/keyvalueconfig.rb
|
34
39
|
lib/configtoolkit/keyvaluereader.rb
|
35
40
|
lib/configtoolkit/keyvaluewriter.rb
|
41
|
+
lib/configtoolkit/overridereader.rb
|
36
42
|
lib/configtoolkit/prettyprintwriter.rb
|
37
43
|
lib/configtoolkit/reader.rb
|
38
44
|
lib/configtoolkit/rubyreader.rb
|
@@ -46,6 +52,7 @@ test/readerwritertest.rb
|
|
46
52
|
test/test_baseconfig.rb
|
47
53
|
test/test_hash.rb
|
48
54
|
test/test_keyvalue.rb
|
55
|
+
test/test_override.rb
|
49
56
|
test/test_prettyprint.rb
|
50
57
|
test/test_ruby.rb
|
51
58
|
test/test_yaml.rb
|
@@ -53,6 +60,8 @@ test/bad_config.yaml
|
|
53
60
|
test/contained_sample.yaml
|
54
61
|
test/firewall.yaml
|
55
62
|
test/machines.yaml
|
63
|
+
test/override_sample_1.yaml
|
64
|
+
test/override_sample_2.yaml
|
56
65
|
test/sample.ruby_yaml_classes.yaml
|
57
66
|
test/sample.standard_yaml_classes.yaml
|
58
67
|
test/webserver.yaml
|
data/Override.txt
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
== OVERRIDE CONFIGURATION FILES:
|
2
|
+
Do you find yourself needing to override specific configuration
|
3
|
+
parameters in your configuration? For example, does your application
|
4
|
+
require a configuration file and also allow the user to override
|
5
|
+
configuration parameters via the command line or a per-user
|
6
|
+
configuration file? Do you want to postpone validating this
|
7
|
+
configuration until after the override has been performed? The
|
8
|
+
ConfigToolkit::OverrideReader allows you to override a Reader instance
|
9
|
+
with other Reader instance(s). Validation is performed by your custom
|
10
|
+
config class (derived from ConfigToolkit::BaseConfig) after the
|
11
|
+
override has been performed.
|
12
|
+
|
13
|
+
For more specific details see the ConfigToolkit::OverrideReader class.
|
14
|
+
|
15
|
+
== EXAMPLE:
|
16
|
+
|
17
|
+
Following are two YAML configurations that will be passed to the
|
18
|
+
override reader. Configuration in the second file will override that
|
19
|
+
of the first.
|
20
|
+
======+examples/override_example.yaml+:
|
21
|
+
#
|
22
|
+
# This file contains MachineConfig configurations
|
23
|
+
# (see examples/machineconfig.rb for the configuration's specification).
|
24
|
+
#
|
25
|
+
|
26
|
+
num_cpus: 32
|
27
|
+
os:
|
28
|
+
name: AIX
|
29
|
+
version: 5.3
|
30
|
+
behind_firewall: no
|
31
|
+
contains_sensitive_data: no
|
32
|
+
addresses:
|
33
|
+
- http://default.designingpatterns.com
|
34
|
+
- http://apple.designingpatterns.com
|
35
|
+
|
36
|
+
======+examples/override_example_override.yaml+:
|
37
|
+
#
|
38
|
+
# This file contains a subset of a MachineConfig configuration and
|
39
|
+
# is meant to be an override of the MachineConfig specified in
|
40
|
+
# examples/override_example.yaml.
|
41
|
+
#
|
42
|
+
|
43
|
+
num_cpus: 64
|
44
|
+
os:
|
45
|
+
version: 5.4
|
46
|
+
|
47
|
+
This is the program that reads the two above configurations and performs the override.
|
48
|
+
======+examples/override_example_1.rb+:
|
49
|
+
#!/usr/bin/env ruby
|
50
|
+
|
51
|
+
#
|
52
|
+
# These example programs use the 'relative' gem in order to
|
53
|
+
# express paths relative to __FILE__ cleanly, allowing them to be run from
|
54
|
+
# any directory. In particular, they use the require_relative
|
55
|
+
# method to load examples/machineconfig.rb and
|
56
|
+
# File.expand_path_relative_to_caller in order to refer to
|
57
|
+
# configuration files within the examples directory.
|
58
|
+
#
|
59
|
+
|
60
|
+
require 'rubygems'
|
61
|
+
require 'relative'
|
62
|
+
require_relative 'machineconfig'
|
63
|
+
|
64
|
+
require 'configtoolkit/yamlreader'
|
65
|
+
require 'configtoolkit/overridereader'
|
66
|
+
|
67
|
+
CONFIGURATION_FILE = File.expand_path_relative_to_caller("override_example.yaml")
|
68
|
+
OVERRIDE_FILE = File.expand_path_relative_to_caller("override_example_override.yaml")
|
69
|
+
|
70
|
+
reader = ConfigToolkit::OverrideReader.new(ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE),
|
71
|
+
ConfigToolkit::YAMLReader.new(OVERRIDE_FILE))
|
72
|
+
|
73
|
+
# Let's load the overriden configuration.
|
74
|
+
config = MachineConfig.load(reader)
|
75
|
+
print("The config:\n#{config}\n")
|
76
|
+
|
77
|
+
When run, it produces the following output:
|
78
|
+
======The output of <code>examples/override_example_1.rb</code>:
|
79
|
+
The config:
|
80
|
+
{
|
81
|
+
os : {
|
82
|
+
version: 5.4
|
83
|
+
name : AIX
|
84
|
+
}
|
85
|
+
behind_firewall : false
|
86
|
+
num_cpus : 64
|
87
|
+
addresses : [
|
88
|
+
http://default.designingpatterns.com,
|
89
|
+
http://apple.designingpatterns.com
|
90
|
+
]
|
91
|
+
contains_sensitive_data: false
|
92
|
+
}
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
In the following example, a machine configuration will be added to a
|
97
|
+
list of machine configurations via the ConfigToolkit::OverrideReader.
|
98
|
+
|
99
|
+
The following YAML configuration specifies a list of machine
|
100
|
+
configurations (+db1+, +db2+, ..).
|
101
|
+
======+examples/load_group_example.yaml+:
|
102
|
+
#
|
103
|
+
# This file contains MachineConfig configurations
|
104
|
+
# (see examples/machineconfig.rb for the configuration's specification).
|
105
|
+
#
|
106
|
+
|
107
|
+
db_cluster:
|
108
|
+
db1:
|
109
|
+
num_cpus: 16
|
110
|
+
os:
|
111
|
+
name: Solaris
|
112
|
+
version: 10.0
|
113
|
+
contains_sensitive_data: yes
|
114
|
+
behind_firewall: yes
|
115
|
+
addresses:
|
116
|
+
- http://db1.designingpatterns.com
|
117
|
+
db2:
|
118
|
+
num_cpus: 12
|
119
|
+
os:
|
120
|
+
name: AIX
|
121
|
+
version: 5.3
|
122
|
+
contains_sensitive_data: yes
|
123
|
+
behind_firewall: yes
|
124
|
+
addresses:
|
125
|
+
- http://db2.designingpatterns.com
|
126
|
+
db3:
|
127
|
+
num_cpus: 24
|
128
|
+
os:
|
129
|
+
name: Solaris
|
130
|
+
version: 10.0
|
131
|
+
contains_sensitive_data: yes
|
132
|
+
behind_firewall: yes
|
133
|
+
addresses:
|
134
|
+
- http://db3.designingpatterns.com
|
135
|
+
|
136
|
+
|
137
|
+
This program specifies a MachineConfig via HashReader and adds it to
|
138
|
+
the list of MachineConfig configurations specified in the YAML file
|
139
|
+
above.
|
140
|
+
|
141
|
+
======+examples/override_example_2.rb+:
|
142
|
+
#!/usr/bin/env ruby
|
143
|
+
|
144
|
+
#
|
145
|
+
# These example programs use the 'relative' gem in order to
|
146
|
+
# express paths relative to __FILE__ cleanly, allowing them to be run from
|
147
|
+
# any directory. In particular, they use the require_relative
|
148
|
+
# method to load examples/machineconfig.rb and
|
149
|
+
# File.expand_path_relative_to_caller in order to refer to
|
150
|
+
# configuration files within the examples directory.
|
151
|
+
#
|
152
|
+
require 'rubygems'
|
153
|
+
require 'relative'
|
154
|
+
require_relative 'machineconfig'
|
155
|
+
|
156
|
+
require 'configtoolkit/yamlreader'
|
157
|
+
require 'configtoolkit/hashreader'
|
158
|
+
require 'configtoolkit/overridereader'
|
159
|
+
|
160
|
+
CONFIGURATION_FILE =
|
161
|
+
File.expand_path_relative_to_caller("load_group_example.yaml")
|
162
|
+
|
163
|
+
hash_configuration = {
|
164
|
+
"db_cluster" => {
|
165
|
+
"db4" => {
|
166
|
+
"num_cpus" => 48,
|
167
|
+
"os" => {
|
168
|
+
"name" => "Solaris",
|
169
|
+
"version" => 10.0
|
170
|
+
},
|
171
|
+
"contains_sensitive_data" => "yes",
|
172
|
+
"behind_firewall" => "yes",
|
173
|
+
"addresses" => [ "http://db4.designingpatterns.com" ]
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
#
|
179
|
+
# The group of configurations is under the db_cluster containing
|
180
|
+
# object name.
|
181
|
+
#
|
182
|
+
reader = ConfigToolkit::OverrideReader.new(ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE),
|
183
|
+
ConfigToolkit::HashReader.new(hash_configuration))
|
184
|
+
|
185
|
+
configs = MachineConfig.load_group(reader, "db_cluster")
|
186
|
+
configs.each do |name, config|
|
187
|
+
print "The #{name} configuration:\n#{config}\n"
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
When run, it produces the following output:
|
192
|
+
======The output of <code>examples/override_example_2.rb</code>:
|
193
|
+
The db3 configuration:
|
194
|
+
db_cluster.db3: {
|
195
|
+
num_cpus : 24
|
196
|
+
addresses : [
|
197
|
+
http://db3.designingpatterns.com
|
198
|
+
]
|
199
|
+
contains_sensitive_data: true
|
200
|
+
os : {
|
201
|
+
version: 10.0
|
202
|
+
name : Solaris
|
203
|
+
}
|
204
|
+
behind_firewall : true
|
205
|
+
}
|
206
|
+
|
207
|
+
The db4 configuration:
|
208
|
+
db_cluster.db4: {
|
209
|
+
num_cpus : 48
|
210
|
+
addresses : [
|
211
|
+
http://db4.designingpatterns.com
|
212
|
+
]
|
213
|
+
contains_sensitive_data: true
|
214
|
+
os : {
|
215
|
+
version: 10.0
|
216
|
+
name : Solaris
|
217
|
+
}
|
218
|
+
behind_firewall : true
|
219
|
+
}
|
220
|
+
|
221
|
+
The db1 configuration:
|
222
|
+
db_cluster.db1: {
|
223
|
+
num_cpus : 16
|
224
|
+
addresses : [
|
225
|
+
http://db1.designingpatterns.com
|
226
|
+
]
|
227
|
+
contains_sensitive_data: true
|
228
|
+
os : {
|
229
|
+
version: 10.0
|
230
|
+
name : Solaris
|
231
|
+
}
|
232
|
+
behind_firewall : true
|
233
|
+
}
|
234
|
+
|
235
|
+
The db2 configuration:
|
236
|
+
db_cluster.db2: {
|
237
|
+
num_cpus : 12
|
238
|
+
addresses : [
|
239
|
+
http://db2.designingpatterns.com
|
240
|
+
]
|
241
|
+
contains_sensitive_data: true
|
242
|
+
os : {
|
243
|
+
version: 5.3
|
244
|
+
name : AIX
|
245
|
+
}
|
246
|
+
behind_firewall : true
|
247
|
+
}
|
248
|
+
|
249
|
+
|
data/README.txt
CHANGED
@@ -20,6 +20,9 @@ robust and easy! It:
|
|
20
20
|
* Provides classes that can load from (parse) and dump to YAML and key-value
|
21
21
|
configuration files.
|
22
22
|
* Provides classes that can load from and dump to Hashes.
|
23
|
+
* Provides a class that allows the contents of one configuration source to
|
24
|
+
override the contents of another (this works with configuration files of any
|
25
|
+
format or Hashes).
|
23
26
|
* Is very extensible, allowing the engine to be used with custom format
|
24
27
|
configuration files and with custom data validation rules.
|
25
28
|
|
@@ -81,6 +84,9 @@ robust and easy! It:
|
|
81
84
|
* The ConfigToolkit::KeyValueReader and ConfigToolkit::KeyValueWriter classes
|
82
85
|
can be configured to work with many different formats of key-value
|
83
86
|
configuration files (via ConfigToolkit::KeyValueConfig).
|
87
|
+
* A reader class to source one configuration from multiple configuration files,
|
88
|
+
allowing one configuration file to override another
|
89
|
+
(ConfigToolkit::OverrideReader)
|
84
90
|
* The ConfigToolkit includes a full unit testing suite.
|
85
91
|
* The ConfigToolkit code has detailed comments.
|
86
92
|
* The ConfigToolkit code has many example programs (in the +examples+
|
@@ -605,7 +611,11 @@ When run, it produces:
|
|
605
611
|
* YAML.txt describes working with YAML configuration files.
|
606
612
|
* KeyValue.txt describes working with key-value configuration files.
|
607
613
|
* Ruby.txt describes working with Ruby configuration files.
|
608
|
-
* Hash.txt describes loading configurations from Hashes and dumping
|
614
|
+
* Hash.txt describes loading configurations from Hashes and dumping
|
615
|
+
configurations to Hashes.
|
616
|
+
* Override.txt describes loading configurations from multiple sources (files),
|
617
|
+
allowing the configuration from one source to override that from another
|
618
|
+
source.
|
609
619
|
|
610
620
|
=== NEW FORMATS:
|
611
621
|
The ConfigToolkit easily can be extended to support new file formats. A new
|
@@ -673,8 +683,6 @@ Here are some *possible* ideas:
|
|
673
683
|
any format) containing embedded Ruby (+EmbeddedRubyReader+ class, which
|
674
684
|
would process the embedded Ruby and then hand the resulting file off
|
675
685
|
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
686
|
* Allow more powerful parameter specifications to be specified. For instance,
|
679
687
|
it would be nice if users could specify that the parameter is a Pathname
|
680
688
|
to a readable file. Possibly the ConfigToolkit should provide a Rules
|
data/Rakefile
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
#
|
2
|
+
# This file contains MachineConfig configurations
|
3
|
+
# (see examples/machineconfig.rb for the configuration's specification).
|
4
|
+
#
|
5
|
+
|
6
|
+
num_cpus: 32
|
7
|
+
os:
|
8
|
+
name: AIX
|
9
|
+
version: 5.3
|
10
|
+
behind_firewall: no
|
11
|
+
contains_sensitive_data: no
|
12
|
+
addresses:
|
13
|
+
- http://default.designingpatterns.com
|
14
|
+
- http://apple.designingpatterns.com
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# These example programs use the 'relative' gem in order to
|
5
|
+
# express paths relative to __FILE__ cleanly, allowing them to be run from
|
6
|
+
# any directory. In particular, they use the require_relative
|
7
|
+
# method to load examples/machineconfig.rb and
|
8
|
+
# File.expand_path_relative_to_caller in order to refer to
|
9
|
+
# configuration files within the examples directory.
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'relative'
|
14
|
+
require_relative 'machineconfig'
|
15
|
+
|
16
|
+
require 'configtoolkit/yamlreader'
|
17
|
+
require 'configtoolkit/overridereader'
|
18
|
+
|
19
|
+
CONFIGURATION_FILE = File.expand_path_relative_to_caller("override_example.yaml")
|
20
|
+
OVERRIDE_FILE = File.expand_path_relative_to_caller("override_example_override.yaml")
|
21
|
+
|
22
|
+
reader = ConfigToolkit::OverrideReader.new(ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE),
|
23
|
+
ConfigToolkit::YAMLReader.new(OVERRIDE_FILE))
|
24
|
+
|
25
|
+
# Let's load the overriden configuration.
|
26
|
+
config = MachineConfig.load(reader)
|
27
|
+
print("The config:\n#{config}\n")
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# These example programs use the 'relative' gem in order to
|
5
|
+
# express paths relative to __FILE__ cleanly, allowing them to be run from
|
6
|
+
# any directory. In particular, they use the require_relative
|
7
|
+
# method to load examples/machineconfig.rb and
|
8
|
+
# File.expand_path_relative_to_caller in order to refer to
|
9
|
+
# configuration files within the examples directory.
|
10
|
+
#
|
11
|
+
require 'rubygems'
|
12
|
+
require 'relative'
|
13
|
+
require_relative 'machineconfig'
|
14
|
+
|
15
|
+
require 'configtoolkit/yamlreader'
|
16
|
+
require 'configtoolkit/hashreader'
|
17
|
+
require 'configtoolkit/overridereader'
|
18
|
+
|
19
|
+
CONFIGURATION_FILE =
|
20
|
+
File.expand_path_relative_to_caller("load_group_example.yaml")
|
21
|
+
|
22
|
+
hash_configuration = {
|
23
|
+
"db_cluster" => {
|
24
|
+
"db4" => {
|
25
|
+
"num_cpus" => 48,
|
26
|
+
"os" => {
|
27
|
+
"name" => "Solaris",
|
28
|
+
"version" => 10.0
|
29
|
+
},
|
30
|
+
"contains_sensitive_data" => "yes",
|
31
|
+
"behind_firewall" => "yes",
|
32
|
+
"addresses" => [ "http://db4.designingpatterns.com" ]
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
#
|
38
|
+
# The group of configurations is under the db_cluster containing
|
39
|
+
# object name.
|
40
|
+
#
|
41
|
+
reader = ConfigToolkit::OverrideReader.new(ConfigToolkit::YAMLReader.new(CONFIGURATION_FILE),
|
42
|
+
ConfigToolkit::HashReader.new(hash_configuration))
|
43
|
+
|
44
|
+
configs = MachineConfig.load_group(reader, "db_cluster")
|
45
|
+
configs.each do |name, config|
|
46
|
+
print "The #{name} configuration:\n#{config}\n"
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,190 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'configtoolkit/reader'
|
4
|
+
|
5
|
+
module ConfigToolkit
|
6
|
+
|
7
|
+
#
|
8
|
+
# This class allows the caller to override configuration paramaters in
|
9
|
+
# a given Reader instance with configuration parameters in a second
|
10
|
+
# Reader instance.
|
11
|
+
#
|
12
|
+
# The override process is similar to a union without duplicate keys.
|
13
|
+
# When processing two readers, if a given configuration parameter's
|
14
|
+
# key is present in both readers, the second reader's parameter
|
15
|
+
# replaces the first. The union performed is a "deep" union. Hashes
|
16
|
+
# nested in the configuration are unioned as well. Handling arrays is
|
17
|
+
# a tad more complicated. If an array specified in the first reader
|
18
|
+
# contains no containers (arrays or hashes), and is present in the
|
19
|
+
# second reader as well, the second array will completely replace the
|
20
|
+
# first. If the array specified in the first reader contains a
|
21
|
+
# container, the array will be unioned, and nested containers will be
|
22
|
+
# unioned (except for arrays with no containers which are always
|
23
|
+
# replaced not unioned.)
|
24
|
+
#
|
25
|
+
# See Override.txt for more details.
|
26
|
+
#
|
27
|
+
# === Additional Note on the Handling of Arrays
|
28
|
+
# The Array handling behavior was chosen from a usage standpoint.
|
29
|
+
# Arrays with no container elements are most often considered as
|
30
|
+
# one configuration parameter (not a list of configuration parameters).
|
31
|
+
# As such, arrays should remain intact during the union (be replaced as
|
32
|
+
# opposed to unioned). However, if configurations are nested inside an array,
|
33
|
+
# the array is no longer (from a usage standpoint) one configuration parameter,
|
34
|
+
# but a container of configuration parameters. Thus, when arrays contain
|
35
|
+
# non-container elements, they are unioned instead of simply replaced. If different
|
36
|
+
# behavior is required, please feel free to contact us in order to request it.
|
37
|
+
#
|
38
|
+
|
39
|
+
class OverrideReader < Reader
|
40
|
+
#
|
41
|
+
# ====Description:
|
42
|
+
# This constructs a OverrideReader instance for the list of readers passed in.
|
43
|
+
#
|
44
|
+
# ====Parameters:
|
45
|
+
# [readers]
|
46
|
+
# A list of objects whose class(es) implements the Reader interface.
|
47
|
+
#
|
48
|
+
def initialize(*readers)
|
49
|
+
if (readers.length == 0)
|
50
|
+
raise ArgumentError, "At least one reader must be specified"
|
51
|
+
end
|
52
|
+
|
53
|
+
@readers = *readers
|
54
|
+
end
|
55
|
+
|
56
|
+
# The Visitor allows the caller to override a configuration
|
57
|
+
# (specified by a hash) with a second configuration (specified by a
|
58
|
+
# hash). The first hash is passed in through the new method, the
|
59
|
+
# second via the visit method. The hash passed in through the new
|
60
|
+
# method is directly edited, and after the visit method finishes,
|
61
|
+
# contains the resulting overriden hash.
|
62
|
+
#
|
63
|
+
# The Visitor algorithm works as follows. The visit method visits
|
64
|
+
# all elements in the second "overriding" hash. While doing so, the
|
65
|
+
# Visitor keeps track of the matching containing element in the
|
66
|
+
# first hash (if such containing element exists), by storing this
|
67
|
+
# information on the stack. When an element (in the second hash) is
|
68
|
+
# visited, it is added to the containing element specified on the
|
69
|
+
# stack (replaces the element in the first hash if the parameter's
|
70
|
+
# key already exists in the first hash).
|
71
|
+
class Visitor < HashArrayVisitor # :nodoc:
|
72
|
+
#
|
73
|
+
# The structure currently being built.
|
74
|
+
#
|
75
|
+
attr_reader :config_hash
|
76
|
+
|
77
|
+
#
|
78
|
+
# A StackFrame contains the container currently being
|
79
|
+
# built.
|
80
|
+
#
|
81
|
+
StackFrame = Struct.new(:container)
|
82
|
+
|
83
|
+
def initialize(config_hash)
|
84
|
+
@config_hash = config_hash
|
85
|
+
@next_container = @config_hash
|
86
|
+
|
87
|
+
super()
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# ====Description:
|
92
|
+
# This method clears the container if a container is
|
93
|
+
# passed in.
|
94
|
+
#
|
95
|
+
def convert_value(value)
|
96
|
+
if(value.is_a?(Hash))
|
97
|
+
return {}
|
98
|
+
elsif(value.is_a?(Array))
|
99
|
+
return []
|
100
|
+
else
|
101
|
+
return value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
private :convert_value
|
105
|
+
|
106
|
+
def enter_hash(hash)
|
107
|
+
return StackFrame.new(@next_container)
|
108
|
+
end
|
109
|
+
|
110
|
+
def visit_hash_element(key, value, index, is_container)
|
111
|
+
converted_value = convert_value(value)
|
112
|
+
|
113
|
+
if(is_container)
|
114
|
+
existing_value = stack_top().container[key]
|
115
|
+
|
116
|
+
if ((existing_value.class == value.class) &&
|
117
|
+
(existing_value.class == Hash ||
|
118
|
+
(existing_value.class == Array &&
|
119
|
+
array_contains_container(existing_value))))
|
120
|
+
@next_container = existing_value
|
121
|
+
return
|
122
|
+
end
|
123
|
+
|
124
|
+
@next_container = converted_value
|
125
|
+
end
|
126
|
+
|
127
|
+
stack_top().container[key] = converted_value
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# ====Description:
|
132
|
+
# This method returns whether the container passed in contains a container.
|
133
|
+
#
|
134
|
+
def array_contains_container(container)
|
135
|
+
container.index() {|value| value.class == Hash || value.class == Array }
|
136
|
+
end
|
137
|
+
private :array_contains_container
|
138
|
+
|
139
|
+
def enter_array(array)
|
140
|
+
return StackFrame.new(@next_container)
|
141
|
+
end
|
142
|
+
|
143
|
+
def visit_array_element(value, index, is_container)
|
144
|
+
converted_value = convert_value(value)
|
145
|
+
|
146
|
+
|
147
|
+
if(is_container)
|
148
|
+
existing_value = stack_top().container[index]
|
149
|
+
if ((existing_value.class == value.class) &&
|
150
|
+
(existing_value.class == Hash ||
|
151
|
+
(existing_value.class == Array &&
|
152
|
+
array_contains_container(existing_value))))
|
153
|
+
@next_container = existing_value
|
154
|
+
return
|
155
|
+
end
|
156
|
+
|
157
|
+
@next_container = converted_value
|
158
|
+
end
|
159
|
+
|
160
|
+
if (index < stack_top().container.length())
|
161
|
+
stack_top().container[index] = converted_value
|
162
|
+
else
|
163
|
+
stack_top().container.push(converted_value)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.override(left, right)
|
169
|
+
visitor = Visitor.new(left)
|
170
|
+
visitor.visit(right)
|
171
|
+
return visitor.config_hash
|
172
|
+
end
|
173
|
+
private_class_method :override
|
174
|
+
|
175
|
+
#
|
176
|
+
# ====Returns:
|
177
|
+
# A hash representing the configuration after all overrides are performed.
|
178
|
+
#
|
179
|
+
def read
|
180
|
+
aggregate_hash = {}
|
181
|
+
|
182
|
+
@readers.each do |reader|
|
183
|
+
aggregate_hash = self.class.send(:override, aggregate_hash, reader.read())
|
184
|
+
end
|
185
|
+
|
186
|
+
aggregate_hash
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
# missing req_integer
|
3
|
+
opt_integer: 56
|
4
|
+
req_uri: http://blogs.designingpatterns.com
|
5
|
+
req_float: 1.23
|
6
|
+
req_pathname: /usr/designingpatterns/bin
|
7
|
+
req_string: sample string2
|
8
|
+
req_boolean: true
|
9
|
+
req_symbol: sample_symbol5
|
10
|
+
req_big_integer: 90000000000
|
11
|
+
nested_config:
|
12
|
+
req_nested_array:
|
13
|
+
- req_second_nested_array:
|
14
|
+
- 6
|
15
|
+
- 20
|
16
|
+
- req_second_nested_pathname: /usr
|
17
|
+
req_second_nested_array:
|
18
|
+
- 305
|
19
|
+
- 306
|
20
|
+
- 307
|
21
|
+
req_nested_integer: 23
|
@@ -0,0 +1,20 @@
|
|
1
|
+
---
|
2
|
+
# missing req_big_integer
|
3
|
+
opt_integer: 56
|
4
|
+
req_uri: http://blogs.designingpatterns.com
|
5
|
+
req_float: 3.14 # override
|
6
|
+
req_pathname: /usr/designingpatterns/bin
|
7
|
+
req_integer: 6
|
8
|
+
req_string: sample string2
|
9
|
+
req_boolean: true
|
10
|
+
req_symbol: sample_symbol2
|
11
|
+
nested_config:
|
12
|
+
req_nested_array:
|
13
|
+
- req_second_nested_pathname: /usr/designingpatterns/Applications/pattmake # override
|
14
|
+
req_second_nested_array:
|
15
|
+
- 6
|
16
|
+
- 20
|
17
|
+
- req_second_nested_array: # override
|
18
|
+
- 205
|
19
|
+
- 206
|
20
|
+
req_nested_integer: 34 # override
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'readerwritertest'
|
2
|
+
|
3
|
+
require 'configtoolkit/overridereader'
|
4
|
+
require 'configtoolkit/yamlreader'
|
5
|
+
require 'configtoolkit/hashreader'
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'assertions'
|
9
|
+
require 'relative'
|
10
|
+
|
11
|
+
class OverrideTest < Test::Unit::TestCase
|
12
|
+
include ReaderWriterTest
|
13
|
+
|
14
|
+
# Test basic override operations.
|
15
|
+
def basic_reader_operations()
|
16
|
+
# non-container -> non-container
|
17
|
+
run_override({ "first" => 5 },
|
18
|
+
{ "first" => 6})
|
19
|
+
# non-container -> array
|
20
|
+
run_override({ "first" => 5 },
|
21
|
+
{ "first" => [ 1, 2, 3 ] })
|
22
|
+
# array -> non-container
|
23
|
+
run_override({ "first" => [ 1, 2, 3 ] },
|
24
|
+
{ "first" => 5 })
|
25
|
+
# non-container -> hash
|
26
|
+
run_override({ "first" => 5 },
|
27
|
+
{ "first" => { "second" => 3 }})
|
28
|
+
# hash -> non-container
|
29
|
+
run_override({ "first" => { "second" => 3 }},
|
30
|
+
{ "first" => 5 } )
|
31
|
+
# hash -> array
|
32
|
+
run_override({ "first" => { "second" => 3 }},
|
33
|
+
{ "first" => [1, 2, 3] })
|
34
|
+
# array -> hash
|
35
|
+
run_override({ "first" => [1, 2, 3] },
|
36
|
+
{ "first" => { "second" => 3 }})
|
37
|
+
# simple union of 2 hash values
|
38
|
+
run_override({ "first" => 1 },
|
39
|
+
{"second" => 2 },
|
40
|
+
{ "first" => 1, "second" => 2 })
|
41
|
+
# union of nested hash values
|
42
|
+
run_override({ "config" => { "first" => 1 }},
|
43
|
+
{ "config" => { "second" => 2 }},
|
44
|
+
{ "config" => { "first" => 1, "second" => 2 }})
|
45
|
+
# array -> array - replace entire array
|
46
|
+
run_override({ "first" => [1, 2, 3] },
|
47
|
+
{ "first" => [4, 5]})
|
48
|
+
# array -> array - union hash inside array
|
49
|
+
run_override({ "first" => [1, { "second" => 2 }] },
|
50
|
+
{ "first" => [3, { "fourth" => 5 }] },
|
51
|
+
{ "first" => [3, { "second" => 2, "fourth" => 5 }] })
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_override(hash_1, hash_2, expected = nil)
|
55
|
+
reader_1 = ConfigToolkit::HashReader.new(hash_1)
|
56
|
+
reader_2 = ConfigToolkit::HashReader.new(hash_2)
|
57
|
+
|
58
|
+
if (!expected) then expected = hash_2 end
|
59
|
+
|
60
|
+
assert_equal(expected,
|
61
|
+
ConfigToolkit::OverrideReader.new(reader_1, reader_2).read())
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_reader
|
65
|
+
basic_reader_operations()
|
66
|
+
|
67
|
+
# Override a reader with an identical reader.
|
68
|
+
file = File.expand_path_relative_to_caller("sample.standard_yaml_classes.yaml")
|
69
|
+
run_reader_test(ConfigToolkit::OverrideReader,
|
70
|
+
"",
|
71
|
+
REFERENCE_SAMPLE_CONFIG,
|
72
|
+
ConfigToolkit::YAMLReader.new(file),
|
73
|
+
ConfigToolkit::YAMLReader.new(file))
|
74
|
+
|
75
|
+
# Override an almost empty reader with the standard sample reader.
|
76
|
+
file = File.expand_path_relative_to_caller("sample.standard_yaml_classes.yaml")
|
77
|
+
hash_reader = ConfigToolkit::HashReader.new({ "opt_integer" => 10 })
|
78
|
+
yaml_reader = ConfigToolkit::YAMLReader.new(file)
|
79
|
+
|
80
|
+
run_reader_test(ConfigToolkit::OverrideReader,
|
81
|
+
"",
|
82
|
+
REFERENCE_SAMPLE_CONFIG,
|
83
|
+
hash_reader,
|
84
|
+
yaml_reader)
|
85
|
+
|
86
|
+
# Perform an override on two different configs together to produce the sample config.
|
87
|
+
# - replace a hash value
|
88
|
+
# - replace a hash value nested in an array
|
89
|
+
# - replace an array containing only non-containers
|
90
|
+
file_1 = File.expand_path_relative_to_caller("override_sample_1.yaml")
|
91
|
+
file_2 = File.expand_path_relative_to_caller("override_sample_2.yaml")
|
92
|
+
run_reader_test(ConfigToolkit::OverrideReader,
|
93
|
+
"",
|
94
|
+
REFERENCE_SAMPLE_CONFIG,
|
95
|
+
ConfigToolkit::YAMLReader.new(file_1),
|
96
|
+
ConfigToolkit::YAMLReader.new(file_2))
|
97
|
+
|
98
|
+
# The contained sample, pass more than 2 readers.
|
99
|
+
file = File.expand_path_relative_to_caller("contained_sample.yaml")
|
100
|
+
yaml_reader = ConfigToolkit::YAMLReader.new(file)
|
101
|
+
|
102
|
+
hash_1 = { "first" => [ 1, 2, 3 ] }
|
103
|
+
hash_2 = { "first" => { "second" => 3 }}
|
104
|
+
hash_reader_1 = ConfigToolkit::HashReader.new(hash_1)
|
105
|
+
hash_reader_2 = ConfigToolkit::HashReader.new(hash_2)
|
106
|
+
|
107
|
+
run_reader_test(ConfigToolkit::OverrideReader,
|
108
|
+
"first.second",
|
109
|
+
REFERENCE_CONTAINED_SAMPLE_CONFIG,
|
110
|
+
hash_reader_1,
|
111
|
+
hash_reader_2,
|
112
|
+
yaml_reader)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: configtoolkit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DesigningPatterns
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-07-
|
12
|
+
date: 2008-07-29 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: 1.7.0
|
44
44
|
version:
|
45
|
-
description: "This package makes sourcing information from (parsing) configuration files robust and easy! It: * Allows programmers to specify the type of data that should be loaded from a configuration file. The toolkit automatically will validate the file's data against this specification when loading the file, ensuring that the specification always is obeyed and saving the programmer the tedious chore of writing validation code. * Automagically generates parameter accessor methods (getters, setters, and predicates to test for presence), an equality operator, and a +to_s+ method from the configuration's specification. * Allows programmers to create configuration files, easily and programatically. * Provides a class that can load (parse) Ruby configuration files (allowing the full power of Ruby to be used within configuration files). * Provides classes that can load from (parse) and dump to YAML and key-value configuration files. * Provides classes that can load from and dump to Hashes. * Is very extensible, allowing the engine to be used with custom format configuration files and with custom data validation rules."
|
45
|
+
description: "This package makes sourcing information from (parsing) configuration files robust and easy! It: * Allows programmers to specify the type of data that should be loaded from a configuration file. The toolkit automatically will validate the file's data against this specification when loading the file, ensuring that the specification always is obeyed and saving the programmer the tedious chore of writing validation code. * Automagically generates parameter accessor methods (getters, setters, and predicates to test for presence), an equality operator, and a +to_s+ method from the configuration's specification. * Allows programmers to create configuration files, easily and programatically. * Provides a class that can load (parse) Ruby configuration files (allowing the full power of Ruby to be used within configuration files). * Provides classes that can load from (parse) and dump to YAML and key-value configuration files. * Provides classes that can load from and dump to Hashes. * Provides a class that allows the contents of one configuration source to override the contents of another (this works with configuration files of any format or Hashes). * Is very extensible, allowing the engine to be used with custom format configuration files and with custom data validation rules."
|
46
46
|
email:
|
47
47
|
- technical.inquiries@designingpatterns.com
|
48
48
|
executables: []
|
@@ -55,6 +55,7 @@ extra_rdoc_files:
|
|
55
55
|
- FAQ.txt
|
56
56
|
- Hash.txt
|
57
57
|
- KeyValue.txt
|
58
|
+
- Override.txt
|
58
59
|
- README.txt
|
59
60
|
- Ruby.txt
|
60
61
|
- YAML.txt
|
@@ -65,6 +66,7 @@ files:
|
|
65
66
|
- FAQ.txt
|
66
67
|
- Hash.txt
|
67
68
|
- KeyValue.txt
|
69
|
+
- Override.txt
|
68
70
|
- README.txt
|
69
71
|
- Ruby.txt
|
70
72
|
- YAML.txt
|
@@ -74,11 +76,15 @@ files:
|
|
74
76
|
- examples/load_example.rb
|
75
77
|
- examples/load_group_example.rb
|
76
78
|
- examples/machineconfig.rb
|
79
|
+
- examples/override_example_1.rb
|
80
|
+
- examples/override_example_2.rb
|
77
81
|
- examples/ruby_example.rb
|
78
82
|
- examples/usage_example.rb
|
79
83
|
- examples/yaml_example.rb
|
80
84
|
- examples/load_example.yaml
|
81
85
|
- examples/load_group_example.yaml
|
86
|
+
- examples/override_example_override.yaml
|
87
|
+
- examples/override_example.yaml
|
82
88
|
- examples/yaml_example.dump.yaml
|
83
89
|
- examples/yaml_example.yaml
|
84
90
|
- examples/key_value_example.dump.cfg
|
@@ -94,6 +100,7 @@ files:
|
|
94
100
|
- lib/configtoolkit/keyvalueconfig.rb
|
95
101
|
- lib/configtoolkit/keyvaluereader.rb
|
96
102
|
- lib/configtoolkit/keyvaluewriter.rb
|
103
|
+
- lib/configtoolkit/overridereader.rb
|
97
104
|
- lib/configtoolkit/prettyprintwriter.rb
|
98
105
|
- lib/configtoolkit/reader.rb
|
99
106
|
- lib/configtoolkit/rubyreader.rb
|
@@ -107,6 +114,7 @@ files:
|
|
107
114
|
- test/test_baseconfig.rb
|
108
115
|
- test/test_hash.rb
|
109
116
|
- test/test_keyvalue.rb
|
117
|
+
- test/test_override.rb
|
110
118
|
- test/test_prettyprint.rb
|
111
119
|
- test/test_ruby.rb
|
112
120
|
- test/test_yaml.rb
|
@@ -114,6 +122,8 @@ files:
|
|
114
122
|
- test/contained_sample.yaml
|
115
123
|
- test/firewall.yaml
|
116
124
|
- test/machines.yaml
|
125
|
+
- test/override_sample_1.yaml
|
126
|
+
- test/override_sample_2.yaml
|
117
127
|
- test/sample.ruby_yaml_classes.yaml
|
118
128
|
- test/sample.standard_yaml_classes.yaml
|
119
129
|
- test/webserver.yaml
|
@@ -175,6 +185,7 @@ specification_version: 2
|
|
175
185
|
summary: "This package makes sourcing information from (parsing) configuration files robust and easy! It: * Allows programmers to specify the type of data that should be loaded from a configuration file"
|
176
186
|
test_files:
|
177
187
|
- test/test_baseconfig.rb
|
188
|
+
- test/test_override.rb
|
178
189
|
- test/test_ruby.rb
|
179
190
|
- test/test_yaml.rb
|
180
191
|
- test/test_keyvalue.rb
|