require 'tap/support/configurable_class' module Tap module Support # Configurable enables the specification of configurations within a class definition. # # class ConfigClass # include Configurable # # config :one, 'one' # config :two, 'two' # config :three, 'three' # # def initialize(overrides={}) # initialize_config(overrides) # end # end # # c = ConfigClass.new # c.config.class # => InstanceConfiguration # c.config # => {:one => 'one', :two => 'two', :three => 'three'} # # The config object acts as a forwarding hash; declared configurations # map to accessors while undeclared configurations are stored internally: # # c.config[:one] = 'ONE' # c.one # => 'ONE' # # c.one = 1 # c.config # => {:one => 1, :two => 'two', :three => 'three'} # # c.config[:undeclared] = 'value' # c.config.store # => {:undeclared => 'value'} # # The writer for a configuration can be defined by providing a block to config. # The Validation module provides a number of common validation/transform # blocks which can be accessed through the class method 'c': # # class SubClass < ConfigClass # config(:one, 'one') {|v| v.upcase } # config :two, 2, &c.integer # end # # s = SubClass.new # s.config # => {:one => 'ONE', :two => 2, :three => 'three'} # # s.one = 'aNothER' # s.one # => 'ANOTHER' # # s.two = -2 # s.two # => -2 # s.two = "3" # s.two # => 3 # s.two = nil # !> ValidationError # s.two = 'str' # !> ValidationError # # As shown above, configurations are inherited from the parent and may be # overridden in subclasses. See ConfigurableClass for more details. # module Configurable # Extends including classes with ConfigurableClass def self.included(mod) # :nodoc: mod.extend Support::ConfigurableClass if mod.kind_of?(Class) end # An InstanceConfiguration with configurations for self attr_reader :config # Reconfigures self with the given overrides. Only the specified configs # are modified. Keys are symbolized. # # Returns self. def reconfigure(overrides={}) keys = (config.class_config.ordered_keys + overrides.keys) & overrides.keys keys.each do |key| config[key.to_sym] = overrides[key] end self end # Reinitializes configurations in the copy such that # the new object has it's own set of configurations, # separate from the original object. def initialize_copy(orig) super initialize_config(orig.config) end protected # Initializes config to an InstanceConfiguration. Default config values # are overridden as specified by overrides. Keys are symbolized. def initialize_config(overrides={}) class_config = self.class.configurations @config = class_config.instance_config overrides.each_pair do |key, value| config[key.to_sym] = value end class_config.each_pair do |key, value| next if config.has_key?(key) config[key] = value.default end config.bind(self) end end end end