## # = Configuration # # require 'dionysus/configuration' # # A simple configuration system that can work in a "hard-keys" or "soft-keys" # mode. In the "hard-keys" mode, the configuration will only allow a specific # set of keys given on initialization. In "soft-keys" mode, the configuration # will accept any keys, at any time, as long as they are valid. # # == Soft Keys Example: # # config = Configuration.new(false, :a_key => 'some value') # config.soft_keys? #=> true # config.hard_keys? #=> false # config.a_key = 'new value' # config.a_key #=> 'new value' # config.another_key = 'some value' # config.anoter_key #=> 'some value' # # == Hard Keys Example: # # config = Configuration.new(:foo, :bar, {:wowsers => 'wow', :foo => 'fooey'}) # config.hard_keys? #=> true # config.soft_keys? #=> false # config.a_key #=> raises NoMethodError # config[:a_key] #=> raises ArgumentError # config.foo = 'fooey' # config.bar #=> nil # config.wowsers #=> 'wow' # # == Multi Access # # Values are accessible through the "magic" methods, get() and set() methods, # and hash brackets. These will all give the same result: # config.a_key # config[:a_key] # config['a_key'] # config.get(:a_key) # config.get('a_key') # # == Object Keys # # All keys are converted to strings and then to symbols: # o = Object.new # config.set(o, 'foo') # config.get(o) #=> 'foo' # config.keys #=> [:'#'] class Configuration # Regex to define a valid key VALID_KEY = '[A-Za-z0-9\-_]+' ## # Create the configuration. # # Soft Keys mode:: Pass +false+ as the first argument. # Hard Keys mode:: Default, or pass +true+ as the first argument. def initialize( *args ) defaults = (args.last.is_a?(Hash) ? args.pop : {}) if _hard_keys?(defaults, args) @keys = _normalize_hard_keys(defaults, args) raise ArgumentError, 'Cannot have a hard keys configuration with no keys' if @keys.empty? else @keys = nil raise ArgumentError, 'Cannot define hard keys in soft keys mode' if args.any? end _initialize_config(defaults) end ## # +true+ if the configuration is limited to a specific set of keys. def hard_keys?() !!@keys; end ## # +true+ if the configuration is NOT limited to a specific set of keys. # (Inverse) of +hard_keys?+ def soft_keys?() !@keys; end ## # The set of keys for the configuration. If this is a hard keys # configuration, it returns the possible keys. Otherwise, it returns the # set keys. def keys() @keys || @config.keys; end ## # +false+ if there are any values set. def empty?() @config.empty?; end ## # The number of configurations set. def size() @config.size; end ## # Set the configuration key to the given value. def set(key, value) validate_key(key); @config[_normalize_key(key)] = value; end alias_method :'[]=', :set ## # Get the configuration value. def get(key) validate_key(key); @config[_normalize_key(key)]; end alias_method :'[]', :get ## # Delete the configuration value. def delete(key) @config.delete(key); end ## # Convert the configuration to a hash. def to_hash() @config.to_hash; end ## # +true+ if the key is valid. def valid_key?( key ) return false if key.blank? key = _normalize_key(key).to_s key =~ /^#{VALID_KEY}$/ and !self.methods.include?(key) end ## # +true+ if the key is allowed. def allowed_key?( key ) @keys.nil? ? true : @keys.include?(_normalize_key(key)) end private ## # If the key is invalid, it raises an error. def validate_key( key ) return true if valid_key?(key) and allowed_key?(key) raise ArgumentError, "Invalid key: #{key}" end ## # Determine if the set of arguments is requesting a hard keys configuration. # # This is a hard key configuration set if # 1) the first argument is NOT false # 2) there are explicit keys defined # 3) there is a default set of arguments def _hard_keys?( defaults, args ) if [false, true].include?(args.first) args.shift else args.any? or (defaults and defaults.any?) end end ## # Normalize the keys and keys from the defaults hash into the hard keys # array. def _normalize_hard_keys( defaults, keys ) returning [] do |keys_| (keys + defaults.keys).uniq.collect {|key_| key_.to_s}.sort.each do |key| raise ArgumentError, "Invalid key: '#{key}'" unless valid_key?(key) keys_ << _normalize_key(key) end end.freeze end ## # Normalize a key by converting it to a string and then a symbol. def _normalize_key( key ) key.to_s.to_sym end ## # Initialize the default config hash def _initialize_config( defaults ) @config = {} defaults.each { |key, value| set(key, value) } @config end ## # The magic. Enables +config.foo+ and +config.foo=+ to map to the getters # and setters. def method_missing( method_name, *args, &block ) match = method_name.to_s.match(/^(#{VALID_KEY})(=)??\Z/) key, setter = _normalize_key(match[1]), (match[2] == '=') if allowed_key?(key) and !block_given? if setter and args.length == 1 return set(key, args.first) elsif key and !setter and args.empty? return get(key) end end super end end