lib/mixlib/config.rb in mixlib-config-2.0.0 vs lib/mixlib/config.rb in mixlib-config-2.1.0.rc.1
- old
+ new
@@ -19,21 +19,23 @@
#
require 'mixlib/config/version'
require 'mixlib/config/configurable'
require 'mixlib/config/unknown_config_option_error'
+require 'mixlib/config/reopened_config_context_with_configurable_error'
+require 'mixlib/config/reopened_configurable_with_config_context_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
class << base; attr_accessor :config_parent; end
base.configuration = Hash.new
base.configurables = Hash.new
- base.config_contexts = Array.new
+ base.config_contexts = Hash.new
end
# Loads a given ruby file, and runs instance_eval against it in the context of the current
# object.
#
@@ -103,38 +105,125 @@
end
# Resets all config options to their defaults.
def reset
self.configuration = Hash.new
- self.config_contexts.each { |config_context| config_context.reset }
+ self.config_contexts.values.each { |config_context| config_context.reset }
end
+ # Makes a copy of any non-default values.
+ #
+ # This returns a shallow copy of the hash; while the hash itself is
+ # duplicated a la dup, modifying data inside arrays and hashes may modify
+ # the original Config object.
+ #
+ # === Returns
+ #
+ # Hash of values the user has set.
+ #
+ # === Examples
+ #
+ # For example, this config class:
+ #
+ # class MyConfig < Mixlib::Config
+ # default :will_be_set, 1
+ # default :will_be_set_to_default, 1
+ # default :will_not_be_set, 1
+ # configurable(:computed_value) { |x| x*2 }
+ # config_context :group do
+ # default :will_not_be_set, 1
+ # end
+ # config_context :group_never_set
+ # end
+ #
+ # MyConfig.x = 2
+ # MyConfig.will_be_set = 2
+ # MyConfig.will_be_set_to_default = 1
+ # MyConfig.computed_value = 2
+ # MyConfig.group.x = 3
+ #
+ # produces this:
+ #
+ # MyConfig.save == {
+ # :x => 2,
+ # :will_be_set => 2,
+ # :will_be_set_to_default => 1,
+ # :computed_value => 4,
+ # :group => {
+ # :x => 3
+ # }
+ # }
+ #
+ def save(include_defaults = false)
+ result = self.configuration.dup
+ if include_defaults
+ (self.configurables.keys - result.keys).each do |missing_default|
+ # Ask any configurables to save themselves into the result array
+ if self.configurables[missing_default].has_default
+ result[missing_default] = self.configurables[missing_default].default
+ end
+ end
+ end
+ self.config_contexts.each_pair do |key, context|
+ context_result = context.save(include_defaults)
+ result[key] = context_result if context_result.size != 0 || include_defaults
+ end
+ result
+ end
+
+ # Restore non-default values from the given hash.
+ #
+ # This method is the equivalent of +reset+ followed by +merge!(hash)+.
+ #
+ # === Parameters
+ # hash<Hash>: a hash in the same format as output by save.
+ #
+ # === Returns
+ # self
+ def restore(hash)
+ reset
+ merge!(hash)
+ end
+
# Merge an incoming hash with our config options
#
# === Parameters
- # hash<Hash>:: The incoming hash
+ # hash<Hash>: a hash in the same format as output by save.
#
# === Returns
- # result of Hash#merge!
+ # self
def merge!(hash)
- self.configuration.merge!(hash)
+ hash.each do |key, value|
+ if self.config_contexts.has_key?(key)
+ # Grab the config context and let internal_get cache it if so desired
+ self.config_contexts[key].restore(value)
+ else
+ self.configuration[key] = value
+ end
+ end
+ self
end
- # Return the set of config hash keys
+ # Return the set of config hash keys.
+ # This *only* returns hash keys which have been set by the user. In future
+ # versions this will likely be removed in favor of something more explicit.
+ # For now though, we want this to match has_key?
#
# === Returns
# result of Hash#keys
def keys
self.configuration.keys
end
# Creates a shallow copy of the internal hash
+ # NOTE: remove this in 3.0 in favor of save. This is completely useless
+ # with default values and configuration_context.
#
# === Returns
# result of Hash#dup
def hash_dup
- self.configuration.dup
+ save
end
# metaprogramming to ensure that the slot for method_symbol
# gets set to value after any other logic is run
#
@@ -182,10 +271,13 @@
#
# === Returns
# The value of the config option.
def configurable(symbol, &block)
unless configurables[symbol]
+ if config_contexts.has_key?(symbol)
+ raise ReopenedConfigContextWithConfigurableError, "Cannot redefine config_context #{symbol} as a configurable value"
+ end
configurables[symbol] = Configurable.new(symbol)
define_attr_accessor_methods(symbol)
end
if block
block.call(configurables[symbol])
@@ -194,10 +286,12 @@
end
# Allows you to create a new config context where you can define new
# options with default values.
#
+ # This method allows you to open up the configurable more than once.
+ #
# For example:
#
# config_context :server_info do
# configurable(:url).defaults_to("http://localhost")
# end
@@ -205,20 +299,29 @@
# === 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)
- context.config_parent = self
- config_contexts << context
+ if configurables.has_key?(symbol)
+ raise ReopenedConfigurableWithConfigContextError, "Cannot redefine config value #{symbol} with a config context"
+ end
+
+ if config_contexts.has_key?(symbol)
+ context = config_contexts[symbol]
+ else
+ context = Class.new
+ context.extend(::Mixlib::Config)
+ context.config_parent = self
+ config_contexts[symbol] = context
+ define_attr_accessor_methods(symbol)
+ end
+
if block
context.instance_eval(&block)
end
- configurable(symbol).defaults_to(context).writes_value do |value|
- raise "config context #{symbol} cannot be modified"
- end
+
+ context
end
NOT_PASSED = Object.new
# Gets or sets strict mode. When strict mode is on, only values which
@@ -293,17 +396,20 @@
end
private
# Internal dispatch setter for config values.
+ #
# === Parameters
# symbol<Symbol>:: Name of the method (variable setter)
# value<Object>:: Value to be set in config hash
#
def internal_set(symbol,value)
if configurables.has_key?(symbol)
configurables[symbol].set(self.configuration, value)
+ elsif config_contexts.has_key?(symbol)
+ config_contexts[symbol].restore(value)
else
if config_strict_mode == :warn
Chef::Log.warn("Setting unsupported config value #{method_name}..")
elsif config_strict_mode
raise UnknownConfigOptionError, "Cannot set unsupported config value #{method_name}."
@@ -313,9 +419,11 @@
end
def internal_get(symbol)
if configurables.has_key?(symbol)
configurables[symbol].get(self.configuration)
+ elsif config_contexts.has_key?(symbol)
+ config_contexts[symbol]
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}."