lib/glue/configuration.rb in glue-0.29.0 vs lib/glue/configuration.rb in glue-0.30.0
- old
+ new
@@ -1,17 +1,25 @@
require 'yaml'
require 'facet/kernel/constant'
require 'facet/synchash'
+require 'facet/dictionary'
+require 'facet/string/capitalized'
require 'glue/attribute'
require 'glue/flexob'
module Glue
# A Configuration holds a group of Settings organized by
-# Owners.
+# Owners. An owner is a class that represents the system to be
+# configured. An alias for this class is Settings.
+#
+# You can pass strings, constants or symbols as keys for the
+# classes to be configured. Passing symbols you can configure
+# classes even before they are defined.
+#
#--
# TODO: implement with annotations.
#++
class Configuration
@@ -23,97 +31,169 @@
#++
@@owners = SyncHash.new
# A datastructure to store Settings metadata.
-
+ #
+ # Please note the difference between :default and :value,
+ # :default does NOT override :value.
+
class Setting
attr_accessor :owner, :name, :type, :value, :options
def initialize(owner, name, options)
- raise ArgumentError.new('A default value is required') unless options.key?(:default)
+ if options.key? :value
+ @value = options[:value]
+ elsif options.key? :default
+ @value = options[:default]
+ else
+ raise ArgumentError.new('A value is required')
+ end
+
@owner, @name = owner, name
- @options = options
- @value = options[:default]
+ @options = options
@type = options[:type] = options[:type] || @value.class
end
- def value=(value)
- @value = value
- constant(@owner).module_eval %{
- @@#{@name} = #{value.inspect}
- }
- end
+ # Update the setting from an options hash. The update does
+ # NOT take default values into account!
+
+ def update(hash)
+ if hash.key? :value
+ @value = hash[:value]
+ @type = @value.class
+ end
+
+ if hash.key? :type
+ @type = hash[:type]
+ end
+ @options.update(hash)
+ end
+
+ # Text representation of this setting.
+
def to_s
@value.to_s
end
end
+
+ # A collection of Settings. This helper enables intuitive
+ # settings initialization like this:
+ #
+ # Configuration.Compiler.template_root = 'public'
+ # instead of
+ # Configuration.setting :compiler, :template_root, :value => 'public'
+ class SettingCollection < Hash
+ attr_accessor :owner
+
+ # Handles setting readers and writers.
+
+ def method_missing(sym, *args)
+ if sym.to_s =~ /=$/
+ # Remove trailing
+ sym = sym.to_s.gsub(/=/, '').to_sym
+ Configuration.setting @owner, sym, :value => args.first
+ else
+ self[sym]
+ end
+ end
+ end
+
class << self
- # Inject the configuration parameters to configuration
- # classes.
+ # Inject the configuration parameters provided as a hash
+ # (dictionary, ordered) to classes to be configured.
+ #
+ # === Warning: Pass an ordered hash (dictionary)
def setup(options)
options.each do |owner, ss|
next unless ss
- begin
- owner = constant(owner)
- rescue NameError
- next
- end
ss.each do |name, s|
- @@owners[owner][name.to_sym].value = s
- owner.module_eval %{
- @@#{name} = #{s.inspect}
- }
+ add_setting(owner, name.to_sym, :value => s)
end
end
end
# Parse configuration parameters in yaml format.
def parse(options)
temp = YAML::load(options)
- options = {}
+ options = Dictionary.new
temp.each do |k, v|
begin
- options[constant(k.gsub(/\./, '::').to_sym)] = v
+ options[k.gsub(/\./, '::').to_sym] = v
rescue Object
options[k] = v
end
end
+
setup(options)
end
# Load and parse an external yaml configuration file.
def load(filename)
parse(File.read(filename))
end
-
+
+ # Manually add a configuration setting. The class key can
+ # be the actual class name constant or a symbol. If the
+ # setting is already defined it updates it.
+ #
+ # === Examples
+ #
+ # Configuration.add_setting Compiler, :verification, :value => 12, :doc => '...'
+ # Configuration.setting :IdPart, :verify_registration_email, :value => false
+ # s = Configuration.Compiler.verification.value
+
def add_setting(owner, name, options)
- s = @@owners[owner] || {}
- s[name] = Setting.new(owner, name, options)
- @@owners[owner] = s
+ owner = owner.to_s.to_sym
+ @@owners[owner] ||= {}
+ if s = @@owners[owner][name]
+ # The setting already exists, update it.
+ s.update(options)
+ else
+ # The setting does not exist, create it.
+ @@owners[owner][name] = Setting.new(owner, name, options)
+ end
end
-
+ alias_method :setting, :add_setting
+
+ # Return the settings for the given owner. The owner is
+ # typically the Class that represents the system to be
+ # configured. If no class is provided, it returns all the
+ # registered settings.
+
def settings(owner = nil)
if owner
+ owner = owner.to_s.to_sym
@@owners[owner]
else
@@owners.values.inject([]) { |memo, obj| memo.concat(obj.values) }
end
end
alias_method :all, :settings
alias_method :[], :settings
+
+ #--
+ # FIXME: this does not work as expected.
+ #++
+
def method_missing(sym)
- if sym.to_s =~ /[A-Z]/ # FIXME: facets's capitalized? is buggy at the moment.
- Flexob.new(self[constant(sym)])
+ if sym.to_s.capitalized?
+# Flexob.new(self[sym])
+ bdl = SettingCollection.new
+ bdl.owner = sym
+ if hash = self[sym]
+ bdl.update(hash)
+ end
+ return bdl
end
end
end
end
@@ -124,26 +204,30 @@
end
class Module
- # Defines a configuration setting.
- #--
- # TODO: implement with annotations.
- #++
+ # Defines a configuration setting for the enclosing class.
+ #
+ # === Example
+ #
+ # class Compiler
+ # setting :template_root, :default => 'src/template', :doc => 'The template root dir'
+ # end
def setting(sym, options = {})
Glue::Configuration.add_setting(self, sym, options)
+
module_eval %{
- mattr_accessor sym, options[:default]
+ def self.#{sym}
+ Glue::Configuration[#{self}][:#{sym}].value
+ end
- def self.#{sym.id2name}=(obj)
- @@#{sym.id2name} = obj
- Glue::Configuration[#{self}][:#{sym}].value = obj
+ def self.#{sym}=(obj)
+ Glue::Configuration.setting #{self}, :#{sym}, :value => obj
end
}
end
end
-
# * George Moschovitis <gm@navel.gr>