lib/configurable.rb in configurable-0.7.0 vs lib/configurable.rb in configurable-1.0.0

- old
+ new

@@ -1,198 +1,23 @@ -require 'configurable/version' require 'configurable/module_methods' # Configurable enables the specification of configurations within a class -# definition. +# definition. Include and declare configs as below. # # class ConfigClass # include Configurable # config :one, 'one' # config :two, 'two' # config :three, 'three' # end # # c = ConfigClass.new # c.config.class # => Configurable::ConfigHash -# c.config # => {:one => 'one', :two => 'two', :three => 'three'} +# c.config.to_hash # => {:one => 'one', :two => 'two', :three => 'three'} # -# Instances have a <tt>config</tt> object that acts like a forwarding hash; -# configuration keys delegate to accessors while undeclared key-value pairs -# 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 accessible through the class method 'c': -# -# class ValidationClass -# include Configurable -# config(:one, 'one') {|v| v.upcase } -# config :two, 2, &c.integer -# end -# -# c = ValidationClass.new -# c.config # => {:one => 'ONE', :two => 2} -# -# c.one = 'aNothER' -# c.one # => 'ANOTHER' -# -# c.two = -2 -# c.two # => -2 -# c.two = "3" -# c.two # => 3 -# c.two = nil # !> ValidationError -# c.two = 'str' # !> ValidationError -# -# Note that config blocks are defined in class-context and will have access -# to variables outside the block (as you would expect). For instance, these -# are analagous declarations: -# -# class ExampleClass -# config :key, 'value' do |input| -# input.upcase -# end -# end -# -# class AnalagousClass -# block = lambda {|input| input.upcase} -# -# define_method(:key=) do |input| -# @key = block.call(input) -# end -# end -# -# To have the block literally define the writer, use the config_attr method. -# Blocks provided to config_attr will have instance context and must set -# the instance variable themselves. -# -# class LiteralClass -# config_attr :key, 'value' do |input| -# @key = input.upcase -# end -# end -# -# Configurations are inherited and may be overridden in subclasses. They may -# also be included from a module: -# -# module A -# include Configurable -# config :a, 'a' -# config :b, 'b' -# end -# -# class B -# include A -# end -# -# class C < B -# config :b, 'B' -# config :c, 'C' -# end -# -# B.new.config.to_hash # => {:a => 'a', :b => 'b'} -# C.new.config.to_hash # => {:a => 'a', :b => 'B', :c => 'C'} -# -# Lastly, configurable classes may be nested through the nest method. Nesting -# creates a configurable class with the configs defined in the nest block; -# nested configs may be accessed by chaining method calls, or through nested -# calls to config. -# -# class NestingClass -# include Configurable -# config :one, 'one' -# nest :two do -# config :three, 'three' -# end -# end -# -# c = NestingClass.new -# c.config.to_hash # => {:one => 'one', :two => {:three => 'three'}} -# -# c.two.three = 'THREE' -# c.config[:two][:three] # => 'THREE' -# -# === Attributes -# -# Alternative reader and writer methods may be specified as config attributes. -# When alternate methods are specified, Configurable assumes the methods are -# declared elsewhere and will not define accessors. -# -# class AlternativeClass -# include Configurable -# -# config_attr :sym, 'value', :reader => :get_sym, :writer => :set_sym -# -# def get_sym -# @sym -# end -# -# def set_sym(input) -# @sym = input.to_sym -# end -# end -# -# alt = AlternativeClass.new -# alt.respond_to?(:sym) # => false -# alt.respond_to?(:sym=) # => false -# -# alt.config[:sym] = 'one' -# alt.get_sym # => :one -# -# alt.set_sym('two') -# alt.config[:sym] # => :two -# -# Idiosyncratically, true and false may also be provided as reader/writer -# values. -# -# true:: Same as using the defaults, accessors are defined. -# false:: Sets the default reader/writer but does not define -# the accessors (think 'define reader/writer' => false). -# -# Nil is not allowed as a value. -# -# ==== Non-reader/writer attributes -# -# Attributes provide metadata for how to use configurations in various contexts. -# In general, attributes can be used to set any metadata an application -# needs. A few attributes are used internally by Configurable. -# -# Attribute:: Use:: -# init:: When set to false, the config will not initialize itself. -# Specify when you manually initialize a config. -# type:: Specifies the type of option ConfigParser generates for this -# Config (ex: :switch, :flag, :list, :hidden) -# desc:: The description string used in the ConfigParser help -# long:: The long option (default: key) -# short:: The short option. -# -# Validation blocks have default attributes already assigned to them (ex type). -# In cases where a user-defined block gets used multiple times, it may be useful -# to register default attributes for that block. To do so, use this pattern: -# -# class AttributesClass -# include Configurable -# block = c.register(:type => :upcase) {|v| v.upcase } -# -# config :a, 'A', &block -# config :b, 'B', &block -# end -# -# AttributesClass.configurations[:a][:type] # => :upcase -# AttributesClass.configurations[:b][:type] # => :upcase -# module Configurable - autoload(:Utils, 'configurable/utils') - + # A ConfigHash bound to self. Accessing configurations through config # is much slower (although sometimes more convenient) than through the # config accessors. attr_reader :config @@ -201,108 +26,20 @@ # manually within the new initialize method. def initialize(*args) initialize_config unless instance_variable_defined?(:@config) super end - - # Reconfigures self with the given overrides. Only the specified configs - # are modified. - # - # Returns self. - def reconfigure(overrides={}) - config.merge!(overrides) - 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 - @config = ConfigHash.new(self, orig.config.store.dup, false) + @config = ConfigHash.new(orig.config.store.dup, self) end - + protected - # Opens the file specified by io and yield it to the block. If io is an - # IO, it will be yielded immediately, and the mode is ignored. Nil io are - # simply ignored. - # - # === Usage - # - # open_io is used to compliment the io validation, to ensure that if a file - # is specified, it will be closed. - # - # class IoSample - # include Configurable - # config :output, $stdout, &c.io # can be an io or filepath - # - # def say_hello - # open_io(output, 'w') do |io| - # io << 'hello!' - # end - # end - # end - # - # In short, this method provides a way to responsibly handle IO and file - # configurations. - def open_io(io, mode='r') - case io - when String - dir = File.dirname(io) - FileUtils.mkdir_p(dir) unless File.directory?(dir) - File.open(io, mode) {|file| yield(file) } - when Integer - # note this does not close the io because, as far as I understand, - # valid integer file descriptors point to files that are already - # open and presumably managed elsewhere - yield IO.open(io, mode) - when nil then nil - else yield(io) - end - end - # Initializes config. Default config values are overridden as specified. def initialize_config(overrides={}) - @config = ConfigHash.new(self, overrides, false) - - # cache as configs (equivalent to self.class.configurations) - # as an optimization - configs = @config.configs - - # hash overrides by delegate so they may be set - # in the correct order below - initial_values = {} - overrides.each_key do |key| - if config = configs[key] - - # check that the config may be initialized - unless config.init? - key = configs.keys.find {|k| configs[k] == config } - raise "initialization values are not allowed for: #{key.inspect}" - end - - # check that multiple overrides are not specified for a - # single config, as may happen with indifferent access - # (ex 'key' and :key) - if initial_values.has_key?(config) - key = configs.keys.find {|k| configs[k] == config } - raise "multiple values map to config: #{key.inspect}" - end - - # since overrides are used as the ConfigHash store, - # the overriding values must be removed, not read - initial_values[config] = overrides.delete(key) - end - end - - # now initialize configs in order - configs.each_pair do |key, config| - next unless config.init? - - if initial_values.has_key?(config) - config.set(self, initial_values[config]) - else - config.init(self) - end - end + @config = ConfigHash.new(overrides).bind(self) end end \ No newline at end of file