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 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:0xb7e8b018 URL:http://www.designingpatterns.com>, #<URI::HTTP:0xb7e8aed8 URL:http://tokyo.designingpatterns.com>], :num_cpus=>64, :os=>{:version=>10.0, :name=>"Solaris"}, :behind_firewall=>true, :contains_sensitive_data=>true}}}
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 configurations to Hashes.
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
@@ -3,7 +3,7 @@
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
5
 
6
- Hoe.new('configtoolkit', "2.1.0") do |p|
6
+ Hoe.new('configtoolkit', "2.2.0") do |p|
7
7
  p.extra_deps << ['relative', '>= 1.0.0']
8
8
  p.extra_deps << ['assertions', '>= 1.0.0']
9
9
  p.remote_rdoc_dir = ''
@@ -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,9 @@
1
+ #
2
+ # This file contains a subset of a MachineConfig configuration and
3
+ # is meant to be an override of the MachineConfig specified in
4
+ # examples/override_example.yaml.
5
+ #
6
+
7
+ num_cpus: 64
8
+ os:
9
+ version: 5.4
@@ -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.1.0
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-25 00:00:00 -04:00
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