lib/mixlib/config.rb in mixlib-config-1.1.2 vs lib/mixlib/config.rb in mixlib-config-2.0.0.rc.1
- old
+ new
@@ -6,167 +6,330 @@
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
+require 'mixlib/config/version'
+require 'mixlib/config/configurable'
+require 'mixlib/config/unknown_config_option_error'
+
module Mixlib
module Config
-
def self.extended(base)
class << base; attr_accessor :configuration; end
+ class << base; attr_accessor :configurables; end
+ class << base; attr_accessor :config_contexts; end
base.configuration = Hash.new
- end
-
- # Loads a given ruby file, and runs instance_eval against it in the context of the current
- # object.
+ base.configurables = Hash.new
+ base.config_contexts = Array.new
+ end
+
+ # Loads a given ruby file, and runs instance_eval against it in the context of the current
+ # object.
#
# Raises an IOError if the file cannot be found, or is not readable.
#
# === Parameters
- # <string>:: A filename to read from
+ # filename<String>:: A filename to read from
def from_file(filename)
self.instance_eval(IO.read(filename), filename, 1)
end
-
- # Pass Mixlib::Config.configure() a block, and it will yield self.configuration.
+
+ # Pass Mixlib::Config.configure() a block, and it will yield itself
#
# === Parameters
- # <block>:: A block that is sent self.configuration as its argument
+ # block<Block>:: A block that is called with self.configuration as the arugment.
def configure(&block)
block.call(self.configuration)
end
-
- # Get the value of a configuration option
+
+ # Get the value of a config option
#
# === Parameters
- # config_option<Symbol>:: The configuration option to return
+ # config_option<Symbol>:: The config option to return
#
# === Returns
- # value:: The value of the configuration option
+ # value:: The value of the config option
#
# === Raises
- # <ArgumentError>:: If the configuration option does not exist
+ # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on.
def [](config_option)
- self.configuration[config_option.to_sym]
+ internal_get(config_option.to_sym)
end
-
- # Set the value of a configuration option
+
+ # Set the value of a config option
#
# === Parameters
- # config_option<Symbol>:: The configuration option to set (within the [])
- # value:: The value for the configuration option
+ # config_option<Symbol>:: The config option to set (within the [])
+ # value:: The value for the config option
#
# === Returns
- # value:: The new value of the configuration option
+ # value:: The new value of the config option
+ #
+ # === Raises
+ # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on.
def []=(config_option, value)
- internal_set(config_option,value)
+ internal_set(config_option, value)
end
-
- # Check if Mixlib::Config has a configuration option.
+
+ # Check if Mixlib::Config has a config option.
#
# === Parameters
- # key<Symbol>:: The configuration option to check for
+ # key<Symbol>:: The config option to check for
#
# === Returns
- # <True>:: If the configuration option exists
- # <False>:: If the configuration option does not exist
+ # <True>:: If the config option exists
+ # <False>:: If the config option does not exist
def has_key?(key)
self.configuration.has_key?(key.to_sym)
end
+ # Resets a config option to its default.
+ #
+ # === Parameters
+ # symbol<Symbol>:: Name of the config option
+ def delete(symbol)
+ self.configuration.delete(symbol)
+ end
+
+ # Resets all config options to their defaults.
+ def reset
+ self.configuration = Hash.new
+ self.config_contexts.each { |config_context| config_context.reset }
+ end
+
# Merge an incoming hash with our config options
#
# === Parameters
# hash<Hash>:: The incoming hash
#
# === Returns
# result of Hash#merge!
def merge!(hash)
self.configuration.merge!(hash)
end
-
+
# Return the set of config hash keys
#
# === Returns
# result of Hash#keys
def keys
self.configuration.keys
end
-
+
# Creates a shallow copy of the internal hash
#
# === Returns
# result of Hash#dup
def hash_dup
self.configuration.dup
end
-
- # Internal dispatch setter, calling either the real defined method or setting the
- # hash value directly
+
+ # metaprogramming to ensure that the slot for method_symbol
+ # gets set to value after any other logic is run
#
# === Parameters
# method_symbol<Symbol>:: Name of the method (variable setter)
+ # blk<Block>:: logic block to run in setting slot method_symbol to value
# value<Object>:: Value to be set in config hash
- #
- def internal_set(method_symbol,value)
- method_name = method_symbol.id2name
- if self.respond_to?("#{method_name}=".to_sym)
- self.send("#{method_name}=", value)
+ #
+ def config_attr_writer(method_symbol, &block)
+ configurable(method_symbol).writes_value(&block)
+ end
+
+ # metaprogramming to set the default value for the given config option
+ #
+ # === Parameters
+ # symbol<Symbol>:: Name of the config option
+ # default_value<Object>:: Default value (can be unspecified)
+ # block<Block>:: Logic block that calculates default value
+ def default(symbol, default_value = nil, &block)
+ configurable(symbol).defaults_to(default_value, &block)
+ end
+
+ # metaprogramming to set information about a config option. This may be
+ # used in one of two ways:
+ #
+ # 1. Block-based:
+ # configurable(:attr) do
+ # defaults_to 4
+ # writes_value { |value| 10 }
+ # end
+ #
+ # 2. Chain-based:
+ # configurable(:attr).defaults_to(4).writes_value { |value| 10 }
+ #
+ # Currently supported configuration:
+ #
+ # defaults_to(value): value returned when configurable has no explicit value
+ # defaults_to BLOCK: block is run when the configurable has no explicit value
+ # writes_value BLOCK: block that is run to filter a value when it is being set
+ #
+ # === Parameters
+ # symbol<Symbol>:: Name of the config option
+ # default_value<Object>:: Default value [optional]
+ # block<Block>:: Logic block that calculates default value [optional]
+ #
+ # === Returns
+ # The value of the config option.
+ def configurable(symbol, &block)
+ unless configurables[symbol]
+ configurables[symbol] = Configurable.new(symbol)
+ end
+ if block
+ block.call(configurables[symbol])
+ end
+ configurables[symbol]
+ end
+
+ # Allows you to create a new config context where you can define new
+ # options with default values.
+ #
+ # For example:
+ #
+ # config_context :server_info do
+ # configurable(:url).defaults_to("http://localhost")
+ # end
+ #
+ # === Parameters
+ # symbol<Symbol>: the name of the context
+ # block<Block>: a block that will be run in the context of this new config
+ # class.
+ def config_context(symbol, &block)
+ context = Class.new
+ context.extend(::Mixlib::Config)
+ config_contexts << context
+ if block
+ context.instance_eval(&block)
+ end
+ configurable(symbol).defaults_to(context).writes_value do |value|
+ raise "config context #{symbol} cannot be modified"
+ end
+ end
+
+ NOT_PASSED = Object.new
+
+ # Gets or sets strict mode. When strict mode is on, only values which
+ # were specified with configurable(), default() or writes_with() may be
+ # retrieved or set. Getting or setting anything else will cause
+ # Mixlib::Config::UnknownConfigOptionError to be thrown.
+ #
+ # If this is set to :warn, unknown values may be get or set, but a warning
+ # will be printed with Chef::Log.warn if this occurs.
+ #
+ # === Parameters
+ # value<String>:: pass this value to set strict mode [optional]
+ #
+ # === Returns
+ # Current value of config_strict_mode
+ #
+ # === Raises
+ # <ArgumentError>:: if value is set to something other than true, false, or :warn
+ #
+ def config_strict_mode(value = NOT_PASSED)
+ if value == NOT_PASSED
+ @config_strict_mode || false
else
- self.configuration[method_symbol] = value
+ self.config_strict_mode = value
end
end
- protected :internal_set
-
- # metaprogramming to ensure that the slot for method_symbol
- # gets set to value after any other logic is run
+ # Sets strict mode. When strict mode is on, only values which
+ # were specified with configurable(), default() or writes_with() may be
+ # retrieved or set. All other values
+ #
+ # If this is set to :warn, unknown values may be get or set, but a warning
+ # will be printed with Chef::Log.warn if this occurs.
+ #
# === Parameters
- # method_symbol<Symbol>:: Name of the method (variable setter)
- # blk<Block>:: logic block to run in setting slot method_symbol to value
- # value<Object>:: Value to be set in config hash
- #
- def config_attr_writer(method_symbol, &blk)
- meta = class << self; self; end
- method_name = "#{method_symbol.to_s}=".to_sym
- meta.send :define_method, method_name do |value|
- self.configuration[method_symbol] = blk.call(value)
+ # value<String>:: pass this value to set strict mode [optional]
+ #
+ # === Raises
+ # <ArgumentError>:: if value is set to something other than true, false, or :warn
+ #
+ def config_strict_mode=(value)
+ if ![ true, false, :warn ].include?(value)
+ raise ArgumentError, "config_strict_mode must be true, false or :warn"
end
+ @config_strict_mode = value
end
- # Allows for simple lookups and setting of configuration options via method calls
+ # Allows for simple lookups and setting of config options via method calls
# on Mixlib::Config. If there any arguments to the method, they are used to set
- # the value of the configuration option. Otherwise, it's a simple get operation.
+ # the value of the config option. Otherwise, it's a simple get operation.
#
# === Parameters
- # method_symbol<Symbol>:: The method called. Must match a configuration option.
+ # method_symbol<Symbol>:: The method called. Must match a config option.
# *args:: Any arguments passed to the method
#
# === Returns
- # value:: The value of the configuration option.
+ # value:: The value of the config option.
#
# === Raises
- # <ArgumentError>:: If the method_symbol does not match a configuration option.
+ # <UnknownConfigOptionError>:: If the config option does not exist and strict mode is on.
def method_missing(method_symbol, *args)
num_args = args.length
# Setting
if num_args > 0
- method_symbol = $1.to_sym unless (method_symbol.to_s =~ /(.+)=$/).nil?
- internal_set method_symbol, (num_args == 1 ? args[0] : args)
+ method_symbol = $1.to_sym if method_symbol.to_s =~ /(.+)=$/
+ internal_set(method_symbol, num_args == 1 ? args[0] : args)
end
-
+
# Returning
- self.configuration[method_symbol]
+ internal_get(method_symbol)
+ end
+ # Internal dispatch setter, calls the setter (def myvar=) if it is defined,
+ # otherwise calls configurable(method_symbol).set(value)
+ #
+ # === Parameters
+ # method_symbol<Symbol>:: Name of the method (variable setter)
+ # value<Object>:: Value to be set in config hash
+ #
+ def internal_set(method_symbol,value)
+ # It would be nice not to have to
+ method_name = method_symbol.id2name
+
+ if self.respond_to?("#{method_name}=".to_sym)
+ self.send("#{method_name}=", value)
+ else
+ if configurables.has_key?(method_symbol)
+ configurables[method_symbol].set(self.configuration, value)
+ else
+ if config_strict_mode == :warn
+ Chef::Log.warn("Setting unsupported config value #{method_name}..")
+ elsif self.config_strict_mode
+ raise UnknownConfigOptionError, "Cannot set unsupported config value #{method_name}."
+ end
+ configuration[method_symbol] = value
+ end
+ end
+ end
+
+ protected :internal_set
+
+ private
+
+ def internal_get(symbol)
+ if configurables.has_key?(symbol)
+ configurables[symbol].get(self.configuration)
+ else
+ if config_strict_mode == :warn
+ Chef::Log.warn("Reading unsupported config value #{symbol}.")
+ elsif config_strict_mode
+ raise UnknownConfigOptionError, "Reading unsupported config value #{symbol}."
+ end
+ configuration[symbol]
+ end
end
end
end