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>