mixlib-config 1.1.2 → 2.0.0.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
- == Mixlib::Config
1
+ ## Mixlib::Config
2
2
 
3
- Mixlib::Config provides a class-based configuration object, like the one used in Chef. To use in your project:
3
+ Mixlib::Config provides a class-based configuration object, as used in Chef. To use in your project:
4
4
 
5
5
  require 'rubygems'
6
6
  require 'mixlib/config'
7
-
7
+
8
8
  class MyConfig
9
9
  extend(Mixlib::Config)
10
10
  configure do |c|
@@ -12,30 +12,30 @@ Mixlib::Config provides a class-based configuration object, like the one used in
12
12
  c[:other_value] = 'something_else'
13
13
  end
14
14
  end
15
-
15
+
16
16
  Or...
17
17
 
18
18
  class MyConfig
19
19
  extend(Mixlib::Config)
20
-
20
+
21
21
  first_value 'something'
22
22
  other_value 'something_else'
23
23
  end
24
-
24
+
25
25
  To check a configuration variable:
26
26
 
27
27
  MyConfig.first_value # returns 'something'
28
28
  MyConfig[:first_value] # returns 'something'
29
-
29
+
30
30
  To change a configuration variable at runtime:
31
31
 
32
32
  MyConfig.first_value('foobar') # sets first_value to 'foobar'
33
- MyConfig[:first_value] = 'foobar' # sets first_value to 'foobar'
34
-
33
+ MyConfig[:first_value] = 'foobar' # sets first_value to 'foobar'
34
+
35
35
  You should populate your class with the default values for every configuration variable that might be accessed. If you try and access a variable that does not exist, Mixlib::Config will throw an <ArgumentError>.
36
36
 
37
37
  To load a ruby configuration file (which will evaluate in the context of your configuration class):
38
38
 
39
39
  MyConfig.from_file('your_config_file.rb')
40
-
40
+
41
41
  Enjoy!
data/Rakefile CHANGED
@@ -1,66 +1,23 @@
1
+ require 'bundler'
1
2
  require 'rubygems'
2
- require 'rake'
3
+ require 'rubygems/package_task'
4
+ require 'rdoc/task'
5
+ require 'rspec/core/rake_task'
3
6
 
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "mixlib-config"
8
- gem.summary = "A class based config mixin, similar to the one found in Chef."
9
- gem.email = "info@opscode.com"
10
- gem.homepage = "http://www.opscode.com"
11
- gem.authors = ["Opscode, Inc."]
7
+ Bundler::GemHelper.install_tasks
12
8
 
13
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
- end
15
- rescue LoadError
16
- puts "Jeweler (or a dependency) not available. Install from gemcutter with: sudo gem install gemcutter jeweler"
17
- end
18
-
19
- require 'spec/rake/spectask'
20
- Spec::Rake::SpecTask.new(:spec) do |spec|
21
- spec.libs << 'lib' << 'spec'
22
- spec.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
23
- spec.spec_files = FileList['spec/**/*_spec.rb']
24
- end
9
+ task :default => :spec
25
10
 
26
- Spec::Rake::SpecTask.new(:rcov) do |spec|
27
- spec.libs << 'lib' << 'spec'
11
+ desc "Run specs"
12
+ RSpec::Core::RakeTask.new(:spec) do |spec|
28
13
  spec.pattern = 'spec/**/*_spec.rb'
29
- spec.rcov = true
30
- end
31
-
32
- begin
33
- require 'cucumber/rake/task'
34
- Cucumber::Rake::Task.new(:features)
35
- rescue LoadError
36
- task :features do
37
- abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
38
- end
39
14
  end
40
15
 
41
- task :default => :spec
42
-
43
- require 'rake/rdoctask'
44
- Rake::RDocTask.new do |rdoc|
45
- if File.exist?('VERSION.yml')
46
- config = YAML.load(File.read('VERSION.yml'))
47
- version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
48
- else
49
- version = ""
50
- end
16
+ gem_spec = eval(File.read("mixlib-config.gemspec"))
51
17
 
18
+ RDoc::Task.new do |rdoc|
52
19
  rdoc.rdoc_dir = 'rdoc'
53
- rdoc.title = "mixlib-config #{version}"
20
+ rdoc.title = "mixlib-config #{gem_spec.version}"
54
21
  rdoc.rdoc_files.include('README*')
55
22
  rdoc.rdoc_files.include('lib/**/*.rb')
56
23
  end
57
-
58
- desc "remove build files"
59
- task :clean do
60
- sh %Q{ rm -f pkg/*.gem }
61
- end
62
-
63
- desc "Run the spec and features"
64
- task :test => [ :features, :spec ]
65
-
66
- Jeweler::GemcutterTasks.new
@@ -8,9 +8,9 @@
8
8
  # Licensed under the Apache License, Version 2.0 (the "License");
9
9
  # you may not use this file except in compliance with the License.
10
10
  # You may obtain a copy of the License at
11
- #
11
+ #
12
12
  # http://www.apache.org/licenses/LICENSE-2.0
13
- #
13
+ #
14
14
  # Unless required by applicable law or agreed to in writing, software
15
15
  # distributed under the License is distributed on an "AS IS" BASIS,
16
16
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,71 +18,95 @@
18
18
  # limitations under the License.
19
19
  #
20
20
 
21
+ require 'mixlib/config/version'
22
+ require 'mixlib/config/configurable'
23
+ require 'mixlib/config/unknown_config_option_error'
24
+
21
25
  module Mixlib
22
26
  module Config
23
-
24
27
  def self.extended(base)
25
28
  class << base; attr_accessor :configuration; end
29
+ class << base; attr_accessor :configurables; end
30
+ class << base; attr_accessor :config_contexts; end
26
31
  base.configuration = Hash.new
27
- end
28
-
29
- # Loads a given ruby file, and runs instance_eval against it in the context of the current
30
- # object.
32
+ base.configurables = Hash.new
33
+ base.config_contexts = Array.new
34
+ end
35
+
36
+ # Loads a given ruby file, and runs instance_eval against it in the context of the current
37
+ # object.
31
38
  #
32
39
  # Raises an IOError if the file cannot be found, or is not readable.
33
40
  #
34
41
  # === Parameters
35
- # <string>:: A filename to read from
42
+ # filename<String>:: A filename to read from
36
43
  def from_file(filename)
37
44
  self.instance_eval(IO.read(filename), filename, 1)
38
45
  end
39
-
40
- # Pass Mixlib::Config.configure() a block, and it will yield self.configuration.
46
+
47
+ # Pass Mixlib::Config.configure() a block, and it will yield itself
41
48
  #
42
49
  # === Parameters
43
- # <block>:: A block that is sent self.configuration as its argument
50
+ # block<Block>:: A block that is called with self.configuration as the arugment.
44
51
  def configure(&block)
45
52
  block.call(self.configuration)
46
53
  end
47
-
48
- # Get the value of a configuration option
54
+
55
+ # Get the value of a config option
49
56
  #
50
57
  # === Parameters
51
- # config_option<Symbol>:: The configuration option to return
58
+ # config_option<Symbol>:: The config option to return
52
59
  #
53
60
  # === Returns
54
- # value:: The value of the configuration option
61
+ # value:: The value of the config option
55
62
  #
56
63
  # === Raises
57
- # <ArgumentError>:: If the configuration option does not exist
64
+ # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on.
58
65
  def [](config_option)
59
- self.configuration[config_option.to_sym]
66
+ internal_get(config_option.to_sym)
60
67
  end
61
-
62
- # Set the value of a configuration option
68
+
69
+ # Set the value of a config option
63
70
  #
64
71
  # === Parameters
65
- # config_option<Symbol>:: The configuration option to set (within the [])
66
- # value:: The value for the configuration option
72
+ # config_option<Symbol>:: The config option to set (within the [])
73
+ # value:: The value for the config option
67
74
  #
68
75
  # === Returns
69
- # value:: The new value of the configuration option
76
+ # value:: The new value of the config option
77
+ #
78
+ # === Raises
79
+ # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on.
70
80
  def []=(config_option, value)
71
- internal_set(config_option,value)
81
+ internal_set(config_option, value)
72
82
  end
73
-
74
- # Check if Mixlib::Config has a configuration option.
83
+
84
+ # Check if Mixlib::Config has a config option.
75
85
  #
76
86
  # === Parameters
77
- # key<Symbol>:: The configuration option to check for
87
+ # key<Symbol>:: The config option to check for
78
88
  #
79
89
  # === Returns
80
- # <True>:: If the configuration option exists
81
- # <False>:: If the configuration option does not exist
90
+ # <True>:: If the config option exists
91
+ # <False>:: If the config option does not exist
82
92
  def has_key?(key)
83
93
  self.configuration.has_key?(key.to_sym)
84
94
  end
85
95
 
96
+ # Resets a config option to its default.
97
+ #
98
+ # === Parameters
99
+ # symbol<Symbol>:: Name of the config option
100
+ def delete(symbol)
101
+ self.configuration.delete(symbol)
102
+ end
103
+
104
+ # Resets all config options to their defaults.
105
+ def reset
106
+ self.configuration = Hash.new
107
+ self.config_contexts.each { |config_context| config_context.reset }
108
+ end
109
+
86
110
  # Merge an incoming hash with our config options
87
111
  #
88
112
  # === Parameters
@@ -93,7 +117,7 @@ module Mixlib
93
117
  def merge!(hash)
94
118
  self.configuration.merge!(hash)
95
119
  end
96
-
120
+
97
121
  # Return the set of config hash keys
98
122
  #
99
123
  # === Returns
@@ -101,7 +125,7 @@ module Mixlib
101
125
  def keys
102
126
  self.configuration.keys
103
127
  end
104
-
128
+
105
129
  # Creates a shallow copy of the internal hash
106
130
  #
107
131
  # === Returns
@@ -109,64 +133,203 @@ module Mixlib
109
133
  def hash_dup
110
134
  self.configuration.dup
111
135
  end
112
-
113
- # Internal dispatch setter, calling either the real defined method or setting the
114
- # hash value directly
136
+
137
+ # metaprogramming to ensure that the slot for method_symbol
138
+ # gets set to value after any other logic is run
115
139
  #
116
140
  # === Parameters
117
141
  # method_symbol<Symbol>:: Name of the method (variable setter)
142
+ # blk<Block>:: logic block to run in setting slot method_symbol to value
118
143
  # value<Object>:: Value to be set in config hash
119
- #
120
- def internal_set(method_symbol,value)
121
- method_name = method_symbol.id2name
122
- if self.respond_to?("#{method_name}=".to_sym)
123
- self.send("#{method_name}=", value)
144
+ #
145
+ def config_attr_writer(method_symbol, &block)
146
+ configurable(method_symbol).writes_value(&block)
147
+ end
148
+
149
+ # metaprogramming to set the default value for the given config option
150
+ #
151
+ # === Parameters
152
+ # symbol<Symbol>:: Name of the config option
153
+ # default_value<Object>:: Default value (can be unspecified)
154
+ # block<Block>:: Logic block that calculates default value
155
+ def default(symbol, default_value = nil, &block)
156
+ configurable(symbol).defaults_to(default_value, &block)
157
+ end
158
+
159
+ # metaprogramming to set information about a config option. This may be
160
+ # used in one of two ways:
161
+ #
162
+ # 1. Block-based:
163
+ # configurable(:attr) do
164
+ # defaults_to 4
165
+ # writes_value { |value| 10 }
166
+ # end
167
+ #
168
+ # 2. Chain-based:
169
+ # configurable(:attr).defaults_to(4).writes_value { |value| 10 }
170
+ #
171
+ # Currently supported configuration:
172
+ #
173
+ # defaults_to(value): value returned when configurable has no explicit value
174
+ # defaults_to BLOCK: block is run when the configurable has no explicit value
175
+ # writes_value BLOCK: block that is run to filter a value when it is being set
176
+ #
177
+ # === Parameters
178
+ # symbol<Symbol>:: Name of the config option
179
+ # default_value<Object>:: Default value [optional]
180
+ # block<Block>:: Logic block that calculates default value [optional]
181
+ #
182
+ # === Returns
183
+ # The value of the config option.
184
+ def configurable(symbol, &block)
185
+ unless configurables[symbol]
186
+ configurables[symbol] = Configurable.new(symbol)
187
+ end
188
+ if block
189
+ block.call(configurables[symbol])
190
+ end
191
+ configurables[symbol]
192
+ end
193
+
194
+ # Allows you to create a new config context where you can define new
195
+ # options with default values.
196
+ #
197
+ # For example:
198
+ #
199
+ # config_context :server_info do
200
+ # configurable(:url).defaults_to("http://localhost")
201
+ # end
202
+ #
203
+ # === Parameters
204
+ # symbol<Symbol>: the name of the context
205
+ # block<Block>: a block that will be run in the context of this new config
206
+ # class.
207
+ def config_context(symbol, &block)
208
+ context = Class.new
209
+ context.extend(::Mixlib::Config)
210
+ config_contexts << context
211
+ if block
212
+ context.instance_eval(&block)
213
+ end
214
+ configurable(symbol).defaults_to(context).writes_value do |value|
215
+ raise "config context #{symbol} cannot be modified"
216
+ end
217
+ end
218
+
219
+ NOT_PASSED = Object.new
220
+
221
+ # Gets or sets strict mode. When strict mode is on, only values which
222
+ # were specified with configurable(), default() or writes_with() may be
223
+ # retrieved or set. Getting or setting anything else will cause
224
+ # Mixlib::Config::UnknownConfigOptionError to be thrown.
225
+ #
226
+ # If this is set to :warn, unknown values may be get or set, but a warning
227
+ # will be printed with Chef::Log.warn if this occurs.
228
+ #
229
+ # === Parameters
230
+ # value<String>:: pass this value to set strict mode [optional]
231
+ #
232
+ # === Returns
233
+ # Current value of config_strict_mode
234
+ #
235
+ # === Raises
236
+ # <ArgumentError>:: if value is set to something other than true, false, or :warn
237
+ #
238
+ def config_strict_mode(value = NOT_PASSED)
239
+ if value == NOT_PASSED
240
+ @config_strict_mode || false
124
241
  else
125
- self.configuration[method_symbol] = value
242
+ self.config_strict_mode = value
126
243
  end
127
244
  end
128
245
 
129
- protected :internal_set
130
-
131
- # metaprogramming to ensure that the slot for method_symbol
132
- # gets set to value after any other logic is run
246
+ # Sets strict mode. When strict mode is on, only values which
247
+ # were specified with configurable(), default() or writes_with() may be
248
+ # retrieved or set. All other values
249
+ #
250
+ # If this is set to :warn, unknown values may be get or set, but a warning
251
+ # will be printed with Chef::Log.warn if this occurs.
252
+ #
133
253
  # === Parameters
134
- # method_symbol<Symbol>:: Name of the method (variable setter)
135
- # blk<Block>:: logic block to run in setting slot method_symbol to value
136
- # value<Object>:: Value to be set in config hash
137
- #
138
- def config_attr_writer(method_symbol, &blk)
139
- meta = class << self; self; end
140
- method_name = "#{method_symbol.to_s}=".to_sym
141
- meta.send :define_method, method_name do |value|
142
- self.configuration[method_symbol] = blk.call(value)
254
+ # value<String>:: pass this value to set strict mode [optional]
255
+ #
256
+ # === Raises
257
+ # <ArgumentError>:: if value is set to something other than true, false, or :warn
258
+ #
259
+ def config_strict_mode=(value)
260
+ if ![ true, false, :warn ].include?(value)
261
+ raise ArgumentError, "config_strict_mode must be true, false or :warn"
143
262
  end
263
+ @config_strict_mode = value
144
264
  end
145
265
 
146
- # Allows for simple lookups and setting of configuration options via method calls
266
+ # Allows for simple lookups and setting of config options via method calls
147
267
  # on Mixlib::Config. If there any arguments to the method, they are used to set
148
- # the value of the configuration option. Otherwise, it's a simple get operation.
268
+ # the value of the config option. Otherwise, it's a simple get operation.
149
269
  #
150
270
  # === Parameters
151
- # method_symbol<Symbol>:: The method called. Must match a configuration option.
271
+ # method_symbol<Symbol>:: The method called. Must match a config option.
152
272
  # *args:: Any arguments passed to the method
153
273
  #
154
274
  # === Returns
155
- # value:: The value of the configuration option.
275
+ # value:: The value of the config option.
156
276
  #
157
277
  # === Raises
158
- # <ArgumentError>:: If the method_symbol does not match a configuration option.
278
+ # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on.
159
279
  def method_missing(method_symbol, *args)
160
280
  num_args = args.length
161
281
  # Setting
162
282
  if num_args > 0
163
- method_symbol = $1.to_sym unless (method_symbol.to_s =~ /(.+)=$/).nil?
164
- internal_set method_symbol, (num_args == 1 ? args[0] : args)
283
+ method_symbol = $1.to_sym if method_symbol.to_s =~ /(.+)=$/
284
+ internal_set(method_symbol, num_args == 1 ? args[0] : args)
165
285
  end
166
-
286
+
167
287
  # Returning
168
- self.configuration[method_symbol]
288
+ internal_get(method_symbol)
289
+ end
290
+
291
+ # Internal dispatch setter, calls the setter (def myvar=) if it is defined,
292
+ # otherwise calls configurable(method_symbol).set(value)
293
+ #
294
+ # === Parameters
295
+ # method_symbol<Symbol>:: Name of the method (variable setter)
296
+ # value<Object>:: Value to be set in config hash
297
+ #
298
+ def internal_set(method_symbol,value)
299
+ # It would be nice not to have to
300
+ method_name = method_symbol.id2name
169
301
 
302
+ if self.respond_to?("#{method_name}=".to_sym)
303
+ self.send("#{method_name}=", value)
304
+ else
305
+ if configurables.has_key?(method_symbol)
306
+ configurables[method_symbol].set(self.configuration, value)
307
+ else
308
+ if config_strict_mode == :warn
309
+ Chef::Log.warn("Setting unsupported config value #{method_name}..")
310
+ elsif self.config_strict_mode
311
+ raise UnknownConfigOptionError, "Cannot set unsupported config value #{method_name}."
312
+ end
313
+ configuration[method_symbol] = value
314
+ end
315
+ end
316
+ end
317
+
318
+ protected :internal_set
319
+
320
+ private
321
+
322
+ def internal_get(symbol)
323
+ if configurables.has_key?(symbol)
324
+ configurables[symbol].get(self.configuration)
325
+ else
326
+ if config_strict_mode == :warn
327
+ Chef::Log.warn("Reading unsupported config value #{symbol}.")
328
+ elsif config_strict_mode
329
+ raise UnknownConfigOptionError, "Reading unsupported config value #{symbol}."
330
+ end
331
+ configuration[symbol]
332
+ end
170
333
  end
171
334
  end
172
335
  end