module Configurability
A unified, unintrusive, assume-nothing configuration system for Ruby
Constants
- REVISION
Version-control revision constant
- VERSION
Library version constant
Attributes
Public Class Methods
Register a callback to be run after the config is loaded.
# File lib/configurability.rb, line 61 def self::after_configure( &block ) raise LocalJumpError, "no block given" unless block self.after_configure_hooks << block # Call the block immediately if the hooks have already been called or are in # the process of being called. block.call if self.after_configure_hooks_run? end
Set the flag that indicates that the after-configure hooks have run at least once.
# File lib/configurability.rb, line 55 def self::after_configure_hooks_run=( new_value ) @after_configure_hooks_run = new_value ? true : false end
Returns true
if the after-configuration hooks have run at least once.
# File lib/configurability.rb, line 48 def self::after_configure_hooks_run? return @after_configure_hooks_run ? true : false end
Call the post-configuration callbacks.
# File lib/configurability.rb, line 73 def self::call_after_configure_hooks self.log.debug " calling %d post-config hooks" % [ self.after_configure_hooks.length ] @after_configure_hooks_run = true self.after_configure_hooks.to_a.each do |hook| # self.log.debug " %s line %s..." % hook.source_location hook.call end end
Configure objects that have had Configurability
added to them with the sections of the specified config
that correspond to their config_key
. If the config
doesn't respond_to the object's config_key
, the object's configure
method is called with nil
instead.
# File lib/configurability.rb, line 135 def self::configure_objects( config ) self.log.debug "Splitting up config %p between %d objects with configurability." % [ config, self.configurable_objects.length ] self.reset self.loaded_config = config self.configurable_objects.each do |obj| self.install_config( config, obj ) end self.call_after_configure_hooks end
Gather the default configuration in a Configurability::Config
object and return it.
# File lib/configurability.rb, line 244 def self::default_config return self.gather_defaults( Configurability::Config.new ) end
Nest the specified hash
inside subhashes for each subsection of the given key
and return the result.
# File lib/configurability.rb, line 204 def self::expand_config_hash( key, hash ) return key.to_s.split( '__' ).reverse.inject( hash ) do |inner_hash, subkey| { subkey.to_sym => inner_hash } end end
Add configurability to the given object
.
# File lib/configurability.rb, line 94 def self::extend_object( object ) self.log.debug "Adding Configurability to %p" % [ object ] super self.configurable_objects << object # If the config has already been propagated, add deferred configuration to the extending # object in case it overrides #configure later. if (( config = self.loaded_config )) self.install_config( config, object ) object.extend( Configurability::DeferredConfig ) end end
Find the section of the specified config
object that corresponds to the given key
.
# File lib/configurability.rb, line 172 def self::find_config_section( config, key ) return key.to_s.split( '__' ).inject( config ) do |section, subkey| next nil if section.nil? self.get_config_subsection( section, subkey.to_sym ) end end
Gather defaults from objects with Configurability
in the given collection
object. Objects that wish to add a section to the defaults should implement a defaults
method in the same scope as configure
that returns the Hash of default, or set one of the constants in the default implementation of defaults
. The hash for each object will be merged into the collection
via merge!.
# File lib/configurability.rb, line 217 def self::gather_defaults( collection={} ) mergefunc = Configurability::Config.method( :merge_complex_hashes ) self.configurable_objects.each do |obj| next unless obj.respond_to?( :defaults ) if defaults_hash = obj.defaults nested_hash = self.expand_config_hash( obj.config_key, defaults_hash ) Configurability.log.debug "Defaults for %p (%p): %p" % [ obj, obj.config_key, nested_hash ] collection.merge!( nested_hash, &mergefunc ) else Configurability.log.warn "No defaults for %p; skipping" % [ obj ] end end return collection end
Return the subsection of the specified config
that corresponds to key
, trying both struct-like and hash-like interfaces.
# File lib/configurability.rb, line 182 def self::get_config_subsection( config, key ) if config.respond_to?( key ) self.log.debug " config has a #%s method; using that" % [ key ] return config.send( key ) elsif config.respond_to?( :[] ) && config.respond_to?( :key? ) self.log.debug " config has a hash-ish interface..." if config.key?( key.to_sym ) || config.key?( key.to_s ) self.log.debug " and has a %s member; using that" % [ key ] return config[ key.to_sym ] || config[ key.to_s ] else self.log.debug " but no `%s` member." % [ key ] return nil end else self.log.debug " no %p section in %p; configuring with nil" % [ key, config ] return nil end end
Mixin hook: extend including classes instead
# File lib/configurability.rb, line 109 def self::included( mod ) mod.extend( self ) end
Install the appropriate section of the config
into the given object
.
# File lib/configurability.rb, line 158 def self::install_config( config, object ) self.log.debug "Configuring %p with the %s section of the config." % [ object, object.config_key ] section = self.find_config_section( config, object.config_key ) configure_method = object.method( :configure ) self.log.debug " calling %p" % [ configure_method ] configure_method.call( section ) end
Try to generate a config key from the given object. If it responds_to name, the result will be stringified and stripped of non-word characters. If the object itself doesn't have a name, the name of its class will be used instead.
# File lib/configurability.rb, line 117 def self::make_key_from_object( object ) if object.respond_to?( :name ) name = object.name name = 'anonymous' if name.nil? || name.empty? return name.sub( /.*::/, '' ).gsub( /\W+/, '_' ).downcase.to_sym elsif object.class.name && !object.class.name.empty? return object.class.name.sub( /.*::/, '' ).gsub( /\W+/, '_' ).downcase.to_sym else return :anonymous end end
Return the specified key
normalized into a valid Symbol config key.
# File lib/configurability.rb, line 238 def self::normalize_config_key( key ) return key.to_s.gsub( /\./, '__' ).to_sym end
If a configuration has been loaded (via {#configure_objects}), clear it.
# File lib/configurability.rb, line 151 def self::reset self.loaded_config = nil self.after_configure_hooks_run = false end
Get the library version. If include_buildnum
is true, the version string will include the VCS rev ID.
# File lib/configurability.rb, line 86 def self::version_string( include_buildnum=false ) vstring = "%s %s" % [ self.name, VERSION ] vstring << " (build %s)" % [ REVISION[/: ([[:xdigit:]]+)/, 1] || '0' ] if include_buildnum return vstring end
Public Instance Methods
the loaded configuration (after ::configure_objects
has been called at least once)
# File lib/configurability.rb, line 36 singleton_class.attr_accessor :loaded_config
Configuration API
↑ topPublic Instance Methods
Get (and optionally set) the config_key
(a Symbol).
# File lib/configurability.rb, line 258 def config_key( sym=nil ) self.config_key = sym unless sym.nil? @config_key ||= Configurability.make_key_from_object( self ) @config_key end
Set the config key of the object.
# File lib/configurability.rb, line 266 def config_key=( sym ) Configurability.configurable_objects |= [ self ] @config_key = Configurability.normalize_config_key( sym ) end
Default configuration method. This will merge the provided config
with the defaults if there are any and the config
responds to #to_h
. If the config
responds to #each_pair
, any writable attributes of the calling object with the same name as a key of the config
will be called with the corresponding value. E.g.,
class MyClass extend Configurability CONFIG_DEFAULTS = { environment: 'develop', apikey: 'testing-key' } config_key :my_class class << self attr_accessor :environment, :apikey end end config = { my_class: {apikey: 'demo-key'} } Configurability.configure_objects( config ) MyClass.apikey # => 'demo-key' MyClass.environment # => 'develop'
# File lib/configurability.rb, line 294 def configure( config=nil ) config = self.defaults( {} ).merge( config.to_h || {} ) if config.nil? || config.respond_to?( :to_h ) @config = config if @config.respond_to?( :each_pair ) @config.each_pair do |key, value| Configurability.log.debug "Looking for %p config attribute" % [ key ] next unless self.respond_to?( "#{key}=" ) Configurability.log.debug " setting %p to %p" % [ key, value ] self.public_send( "#{key}=", value ) end else Configurability.log. debug "config object (%p) isn't iterable; skipping config attributes" % [ @config ] end return @config end
Configuration Defaults API
↑ topPublic Instance Methods
Return a Configurability::Config
object that contains the configuration defaults for the receiver.
# File lib/configurability.rb, line 374 def default_config default_values = self.defaults or return Configurability::Config::Struct.new( {} ) return Configurability::Config::Struct.new( default_values ) end
The default implementation of the method called by ::gather_defaults
when gathering configuration defaults. This method expects either a DEFAULT_CONFIG
or a CONFIG_DEFAULTS
constant to contain the configuration defaults, and will just return the fallback
value if neither exists.
# File lib/configurability.rb, line 354 def defaults( fallback=nil ) return fallback unless respond_to?( :const_defined? ) Configurability.log.debug "Looking for defaults in %p's constants." % [ self ] if self.const_defined?( :DEFAULT_CONFIG, false ) Configurability.log.debug " found DEFAULT_CONFIG" return self.const_get( :DEFAULT_CONFIG, false ).dup elsif self.const_defined?( :CONFIG_DEFAULTS, false ) Configurability.log.debug " found CONFIG_DEFAULTS" return self.const_get( :CONFIG_DEFAULTS, false ).dup else Configurability.log.debug " no default constants." return fallback end end
Configuration settings block
↑ topPublic Instance Methods
Declare configuration settings and defaults. In the provided block
, you can create a configuration setting using the following syntax:
configurability( :my_config_key ) do # Declare a setting with a `nil` default setting :a_config_key # Declare one with a default value setting :another_config_key, default: 18 end
# File lib/configurability.rb, line 330 def configurability( config_key=nil, &block ) self.config_key = config_key if config_key if block Configurability.log.debug "Applying config declaration block using a SettingInstaller" installer = Configurability::SettingInstaller.new( self ) installer.instance_eval( &block ) end if (( config = Configurability.loaded_config )) Configurability.install_config( config, self ) end end