A configuration object class for systems with Configurability
Michael Granger <ged@FaerieMUD.org>
Mahlon E. Smith <mahlon@martini.nu>
This class also delegates some of its methods to the underlying struct:
Configurability::Config::Struct#to_hash
to_hash (delegated to its internal Struct
)
Configurability::Config::Struct#member?
member? (delegated to its internal Struct
)
Configurability::Config::Struct#members
members (delegated to its internal Struct
)
Configurability::Config::Struct#merge
merge (delegated to its internal Struct
)
Configurability::Config::Struct#merge!
merge! (delegated to its internal Struct
)
each (delegated to its internal Struct
)
Configurability::Config::Struct#[]
[] (delegated to its internal Struct
)
Configurability::Config::Struct#[]=
[]= (delegated to its internal Struct
)
the path to the config file, if loaded from a file
The underlying config data structure
The time the configuration was loaded
Read and return a Configurability::Config
object from the file at the given path
.
# File lib/configurability/config.rb, line 51
def self::load( path, defaults=nil, &block )
path = Pathname( path ).expand_path
source = path.read
Configurability.log.debug "Read %d bytes from %s" % [ source.length, path ]
return new( source, path, defaults, &block )
end
Recursive hash-merge function. Used as the block argument to a Hash#merge.
# File lib/configurability/config.rb, line 60
def self::merge_complex_hashes( key, oldval, newval )
return oldval.merge( newval, &method(:merge_complex_hashes) ) if
oldval.respond_to?( :merge ) && newval.respond_to?( :merge )
return newval
end
Create a new Configurability::Config
object. If the optional source
argument is specified, parse the config from it. If one is given, the block will be evaluated in the context of the config object after the config is loaded, unless it accepts an argument, in which case the config object is passed as the argument.
# File lib/configurability/config.rb, line 77
def initialize( source=nil, path=nil, defaults=nil, &block )
# Shift the hash parameter if it shows up as the path
if path.is_a?( Hash )
defaults = path
path = nil
end
# Make a deep copy of the defaults before loading so we don't modify
# the argument
@defaults = defaults ? Marshal.load( Marshal.dump(defaults) ) : nil
@time_created = Time.now
@path = path
if source
@struct = self.make_configstruct_from_source( source, @defaults )
else
@struct = Configurability::Config::Struct.new( @defaults )
end
if block
Configurability.log.debug "Block arity is: %p" % [ block.arity ]
# A block with an argument is called with the config as the argument
# instead of instance_evaled
case block.arity
when 0, -1 # 1.9 and 1.8, respectively
Configurability.log.debug "Instance evaling in the context of %p" % [ self ]
self.instance_eval( &block )
else
block.call( self )
end
end
end
Returns true
if the configuration has changed since it was last loaded, either by setting one of its members or changing the file from which it was loaded.
# File lib/configurability/config.rb, line 174
def changed?
return self.changed_reason ? true : false
end
If the configuration has changed, return the reason. If it hasn't, returns nil.
# File lib/configurability/config.rb, line 181
def changed_reason
if @struct.dirty?
Configurability.log.debug "Changed_reason: struct was modified"
return "Struct was modified"
end
if self.path && self.is_older_than?( self.path )
Configurability.log.debug "Source file (%s) has changed." % [ self.path ]
return "Config source (%s) has been updated since %s" %
[ self.path, self.time_created ]
end
return nil
end
Return the config object as a YAML hash
# File lib/configurability/config.rb, line 139
def dump
strhash = stringify_keys( self.to_h )
return YAML.dump( strhash )
end
Return a human-readable, compact representation of the configuration suitable for debugging.
# File lib/configurability/config.rb, line 228
def inspect
return "#<%s:0x%0x16 loaded from %s; %d sections: %s>" % [
self.class.name,
self.object_id * 2,
self.path ? self.path : "memory",
self.struct.members.length,
self.struct.members.join( ', ' )
]
end
Install this config object in any objects that have added Configurability
.
# File lib/configurability/config.rb, line 133
def install
Configurability.configure_objects( self )
end
Return true
if the specified file
is newer than the time the receiver was created.
# File lib/configurability/config.rb, line 199
def is_older_than?( path )
return false unless path.exist?
st = path.stat
Configurability.log.debug "File mtime is: %s, comparison time is: %s" %
[ st.mtime, @time_created ]
return st.mtime > @time_created
end
Reload the configuration from the original source if it has changed. Returns true
if it was reloaded and false
otherwise.
# File lib/configurability/config.rb, line 210
def reload
raise "can't reload from an in-memory source" unless self.path
if self.changed?
self.time_created = Time.now
source = self.path.read
@struct = self.make_configstruct_from_source( source, @defaults )
self.install
return true
else
return false
end
end
Returns true
for methods which can be autoloaded
# File lib/configurability/config.rb, line 165
def respond_to?( sym )
return true if @struct.member?( sym.to_s.sub(/(=|\?)$/, '').to_sym )
super
end
Write the configuration object using the specified name and any additional args
.
# File lib/configurability/config.rb, line 147
def write( path=@path, *args )
unless path.is_a?( String ) || path.is_a?( Pathname )
args.unshift( path )
path = @path
end
raise ArgumentError,
"No name associated with this config." unless path
self.log.info "Writing config to %s with args: %p" % [ path, args ]
path = Pathname( path )
path.open( File::WRONLY|File::CREAT|File::TRUNC, *args ) do |ofh|
ofh.print( self.dump )
end
end
Delegate logging to the module's Logger.
# File lib/configurability/config.rb, line 282
def log
Configurability.logger
end
Read in the specified filename
and return a config struct.
# File lib/configurability/config.rb, line 244
def make_configstruct_from_source( source, defaults=nil )
defaults ||= {}
mergefunc = Configurability::Config.method( :merge_complex_hashes )
hash = nil
if source.is_a?( Hash )
hash = source
else
hash = if defined?( SafeYAML ) then
YAML.load( source, :safe => true )
else
YAML.load( source )
end
end
ihash = symbolify_keys( untaint_hash(hash) )
idefaults = symbolify_keys( defaults )
mergedhash = idefaults.merge( ihash, &mergefunc )
return Configurability::Config::Struct.new( mergedhash )
end
Handle calls to struct-members
# File lib/configurability/config.rb, line 268
def method_missing( sym, *args )
key = sym.to_s.sub( /(=|\?)$/, '' ).to_sym
self.class.class_eval %{
def #{key}; @struct.#{key}; end
def #{key}=(arg); @struct.#{key} = arg; end
def #{key}?; @struct.#{key}?; end
}
return self.method( sym ).call( *args )
end