lib/pluginfactory.rb in pluginfactory-1.0.2 vs lib/pluginfactory.rb in pluginfactory-1.0.3
- old
+ new
@@ -1,7 +1,10 @@
#!/usr/bin/env ruby -w
-#
+
+### An exception class for PluginFactory specific errors.
+class FactoryError < RuntimeError; end
+
# This module contains the PluginFactory mixin. Including PluginFactory in your
# class turns it into a factory for its derivatives, capable of searching for
# and loading them by name. This is useful when you have an abstract base class
# which defines an interface and basic functionality for a part of a larger
# system, and a collection of subclasses which implement the interface for
@@ -16,11 +19,13 @@
# them in a directory and using the Driver's <tt>create</tt> method to
# instantiate them:
#
# == Creation Argument Variants
#
-# The +create+ class method added to your class by PluginFactory searches for your module using
+# The +create+ class method added to your class by PluginFactory searches for
+# your module using the $LOAD_PATH that require uses. See the README for a
+# detailed explanation.
#
# == Synopsis
#
# in driver.rb:
#
@@ -58,68 +63,54 @@
# driver.class #=> MysqlDriver
# pgdriver = Driver.create( "PostGresDriver" )
#
# == Subversion ID
#
-# $Id: pluginfactory.rb 38 2007-03-13 18:20:58Z deveiant $
+# $Id: pluginfactory.rb 52 2008-08-13 21:58:41Z deveiant $
#
# == Authors
#
# * Martin Chase <stillflame@FaerieMUD.org>
# * Michael Granger <ged@FaerieMUD.org>
#
-#:include: COPYRIGHT
+# :include: LICENSE
#
#---
#
-# Please see the file docs/COPYRIGHT for licensing details.
+# Please see the file LICENSE for licensing details.
#
-
-
-### An exception class for PluginFactory specific errors.
-class FactoryError < RuntimeError
- def initialize( *args )
- if ! args.empty?
- msg = args.collect {|a| a.to_s}.join
- super( msg )
- else
- super( message )
- end
- end
-end # class FactoryError
-
-
-### A mixin that adds PluginFactory class methods to a base class, so that
-### subclasses may be instantiated by name.
module PluginFactory
+ VERSION = '1.0.3'
+
+
### A callback for logging the various debug and information this module
### has to log. Should take two arguments, the log level, possibly as a
### symbol, and the log message itself.
@logger_callback = nil
class << self
attr_accessor :logger_callback
end
- ### If the logger callback is set, use it to pass on a log entry. First argument is
- def self::log(level, *msg)
- @logger_callback.call(level, msg.join) if @logger_callback
+ ### If the logger callback is set, use it to pass on a log entry. First
+ ### argument is a 'level' which is passed to the logging callback. Any
+ ### remaining arguments will be joined and passed as a single second
+ ### argument to the callback.
+ def self::log( level, *msg )
+ @logger_callback.call( level, msg.join ) if @logger_callback
end
- ### Inclusion callback -- extends the including class.
+ ### Inclusion callback -- extends the including class. This is here so you can
+ ### either 'include' or 'extend'.
def self::included( klass )
klass.extend( self )
end
- ### Raise an exception if the object being extended is anything but a
- ### class.
+ ### Add the @derivatives instance variable to including classes.
def self::extend_object( obj )
- unless obj.is_a?( Class )
- raise TypeError, "Cannot extend a #{obj.class.name}", caller(1)
- end
obj.instance_variable_set( :@derivatives, {} )
super
end
@@ -128,27 +119,27 @@
#############################################################
### Return the Hash of derivative classes, keyed by various versions of
### the class name.
def derivatives
- ancestors.each {|klass|
+ ancestors.each do |klass|
if klass.instance_variables.include?( "@derivatives" )
- break klass.instance_variable_get( :@derivatives )
+ return klass.instance_variable_get( :@derivatives )
end
- }
+ end
end
### Returns the type name used when searching for a derivative.
def factory_type
base = nil
- self.ancestors.each {|klass|
+ self.ancestors.each do |klass|
if klass.instance_variables.include?( "@derivatives" )
base = klass
break
end
- }
+ end
raise FactoryError, "Couldn't find factory base for #{self.name}" if
base.nil?
if base.name =~ /^.*::(.*)/
@@ -170,15 +161,16 @@
keys << Regexp.last_match[1].downcase
else
keys << subclass.name.sub( /.*::/, '' ).downcase
end
- keys.uniq.each {|key|
- #PluginFactory::log :info, "Registering %s derivative of %s as %p" %
- # [ subclass.name, self.name, key ]
+ keys.uniq.each do |key|
+ PluginFactory.log :info, "Registering %s derivative of %s as %p" %
+ [ subclass.name, self.name, key ]
self.derivatives[ key ] = subclass
- }
+ end
+
super
end
### Returns an Array of registered derivatives
@@ -186,60 +178,61 @@
self.derivatives.values.uniq
end
alias_method :derivativeClasses, :derivative_classes
- ### Given the <tt>className</tt> of the class to instantiate, and other
+ ### Given the <tt>class_name</tt> of the class to instantiate, and other
### arguments bound for the constructor of the new object, this method
### loads the derivative class if it is not loaded already (raising a
### LoadError if an appropriately-named file cannot be found), and
- ### instantiates it with the given <tt>args</tt>. The <tt>className</tt>
+ ### instantiates it with the given <tt>args</tt>. The <tt>class_name</tt>
### may be the the fully qualified name of the class, the class object
### itself, or the unique part of the class name. The following examples
### would all try to load and instantiate a class called "FooListener"
### if Listener included Factory
- ### obj = Listener::create( 'FooListener' )
- ### obj = Listener::create( FooListener )
- ### obj = Listener::create( 'Foo' )
- def create( subType, *args, &block )
- subclass = get_subclass( subType )
+ ### obj = Listener.create( 'FooListener' )
+ ### obj = Listener.create( FooListener )
+ ### obj = Listener.create( 'Foo' )
+ def create( class_name, *args, &block )
+ subclass = get_subclass( class_name )
- return subclass.new( *args, &block )
- rescue => err
- nicetrace = err.backtrace.reject {|frame| /#{__FILE__}/ =~ frame}
- msg = "When creating '#{subType}': " + err.message
- Kernel::raise( err.class, msg, nicetrace )
+ begin
+ return subclass.new( *args, &block )
+ rescue => err
+ nicetrace = err.backtrace.reject {|frame| /#{__FILE__}/ =~ frame}
+ msg = "When creating '#{class_name}': " + err.message
+ Kernel.raise( err, msg, nicetrace )
+ end
end
- ### Given a <tt>className</tt> like that of the first argument to
+ ### Given a <tt>class_name</tt> like that of the first argument to
### #create, attempt to load the corresponding class if it is not
### already loaded and return the class object.
- def get_subclass( className )
- return self if ( self.name == className || className == '' )
- return className if className.is_a?( Class ) && className >= self
+ def get_subclass( class_name )
+ return self if ( self.name == class_name || class_name == '' )
+ if class_name.is_a?( Class )
+ return class_name if class_name <= self
+ raise ArgumentError, "%s is not a descendent of %s" % [class_name, self]
+ end
- unless self.derivatives.has_key?( className.downcase )
- self.load_derivative( className )
+ class_name = class_name.to_s
- unless self.derivatives.has_key?( className.downcase )
- raise FactoryError,
- "load_derivative(%s) didn't add a '%s' key to the "\
- "registry for %s" %
- [ className, className.downcase, self.name ]
- end
+ # If the derivatives hash doesn't already contain the class, try to load it
+ unless self.derivatives.has_key?( class_name.downcase )
+ self.load_derivative( class_name )
- subclass = self.derivatives[ className.downcase ]
+ subclass = self.derivatives[ class_name.downcase ]
unless subclass.is_a?( Class )
raise FactoryError,
"load_derivative(%s) added something other than a class "\
"to the registry for %s: %p" %
- [ className, self.name, subclass ]
+ [ class_name, self.name, subclass ]
end
end
- return self.derivatives[ className.downcase ]
+ return self.derivatives[ class_name.downcase ]
end
alias_method :getSubclass, :get_subclass
### Calculates an appropriate filename for the derived class using the
@@ -249,54 +242,51 @@
### array of Strings) is added to the list of prefix directories to try
### when attempting to require a modules. Eg., if
### <tt>class.derivativeDirs</tt> returns <tt>['foo','bar']</tt> the
### require line is tried with both <tt>'foo/'</tt> and <tt>'bar/'</tt>
### prepended to it.
- def load_derivative( className )
- className = className.to_s
+ def load_derivative( class_name )
+ PluginFactory.log :debug, "Loading derivative #{class_name}"
- #PluginFactory::log :debug, "Loading derivative #{className}"
-
# Get the unique part of the derived class name and try to
# load it from one of the derivative subdirs, if there are
# any.
- mod_name = self.get_module_name( className )
- self.require_derivative( mod_name )
+ mod_name = self.get_module_name( class_name )
+ result = self.require_derivative( mod_name )
# Check to see if the specified listener is now loaded. If it
# is not, raise an error to that effect.
- unless self.derivatives[ className.downcase ]
- raise FactoryError,
- "Couldn't find a %s named '%s'. Loaded derivatives are: %p" % [
+ unless self.derivatives[ class_name.downcase ]
+ errmsg = "Require of '%s' succeeded, but didn't load a %s named '%s' for some reason." % [
+ result,
self.factory_type,
- className.downcase,
- self.derivatives.keys,
- ], caller(3)
+ class_name.downcase,
+ ]
+ PluginFactory.log :error, errmsg
+ raise FactoryError, errmsg, caller(3)
end
-
- return true
end
alias_method :loadDerivative, :load_derivative
- ### Build and return the unique part of the given <tt>className</tt>
+ ### Build and return the unique part of the given <tt>class_name</tt>
### either by stripping leading namespaces if the name already has the
### name of the factory type in it (eg., 'My::FooService' for Service,
### or by appending the factory type if it doesn't.
- def get_module_name( className )
- if className =~ /\w+#{self.factory_type}/
- mod_name = className.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" )
+ def get_module_name( class_name )
+ if class_name =~ /\w+#{self.factory_type}/
+ mod_name = class_name.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" )
else
- mod_name = className
+ mod_name = class_name
end
return mod_name
end
alias_method :getModuleName, :get_module_name
- ### If the factory responds to the #derivativeDirs method, call
+ ### If the factory responds to the #derivative_dirs method, call
### it and use the returned array as a list of directories to
### search for the module with the specified <tt>mod_name</tt>.
def require_derivative( mod_name )
# See if we have a list of special subdirs that derivatives
@@ -311,52 +301,57 @@
else
subdirs = ['']
end
subdirs = [ subdirs ] unless subdirs.is_a?( Array )
- PluginFactory::log :debug, "Subdirs are: %p" % [subdirs]
+ PluginFactory.log :debug, "Subdirs are: %p" % [subdirs]
fatals = []
+ tries = []
# Iterate over the subdirs until we successfully require a
# module.
- catch( :found ) {
- subdirs.collect {|dir| dir.strip}.each do |subdir|
- self.make_require_path( mod_name, subdir ).each {|path|
- PluginFactory::log :debug, "Trying #{path}..."
+ subdirs.collect {|dir| dir.strip}.each do |subdir|
+ self.make_require_path( mod_name, subdir ).each do |path|
+ PluginFactory.log :debug, "Trying #{path}..."
+ tries << path
- # Try to require the module, saving errors and jumping
- # out of the catch block on success.
- begin
- require( path.untaint )
- rescue LoadError => err
- PluginFactory::log :debug,
- "No module at '%s', trying the next alternative: '%s'" %
- [ path, err.message ]
- rescue ScriptError,StandardError => err
- fatals << err
- PluginFactory::log :error,
- "Found '#{path}', but encountered an error: %s\n\t%s" %
- [ err.message, err.backtrace.join("\n\t") ]
- else
- #PluginFactory::log :debug,
- # "Found '#{path}'. Throwing :found"
- throw :found
- end
- }
+ # Try to require the module, saving errors and jumping
+ # out of the catch block on success.
+ begin
+ require( path.untaint )
+ rescue LoadError => err
+ PluginFactory.log :debug,
+ "No module at '%s', trying the next alternative: '%s'" %
+ [ path, err.message ]
+ rescue Exception => err
+ fatals << err
+ PluginFactory.log :error,
+ "Found '#{path}', but encountered an error: %s\n\t%s" %
+ [ err.message, err.backtrace.join("\n\t") ]
+ else
+ PluginFactory.log :info, "Loaded '#{path}' without error."
+ return path
+ end
end
+ end
- #PluginFactory::log :debug, "fatals = %p" % [ fatals ]
+ PluginFactory.log :debug, "fatals = %p" % [ fatals ]
- # Re-raise is there was a file found, but it didn't load for
- # some reason.
- if ! fatals.empty?
- #PluginFactory::log :debug, "Re-raising first fatal error"
- Kernel::raise( fatals.first )
- end
-
- nil
- }
+ # Re-raise is there was a file found, but it didn't load for
+ # some reason.
+ if fatals.empty?
+ errmsg = "Couldn't find a %s named '%s': tried %p" % [
+ self.factory_type,
+ mod_name,
+ tries
+ ]
+ PluginFactory.log :error, errmsg
+ raise FactoryError, errmsg
+ else
+ PluginFactory.log :debug, "Re-raising first fatal error"
+ Kernel.raise( fatals.first )
+ end
end
alias_method :requireDerivative, :require_derivative
### Make a list of permutations of the given +modname+ for the given
@@ -369,20 +364,23 @@
myname = self.factory_type
# Make permutations of the two parts
path << modname
path << modname.downcase
- path << modname + myname
- path << modname.downcase + myname
- path << modname.downcase + myname.downcase
+ path << modname + myname
+ path << modname.downcase + myname
+ path << modname.downcase + myname.downcase
+ path << modname + '_' + myname
+ path << modname.downcase + '_' + myname
+ path << modname.downcase + '_' + myname.downcase
# If a non-empty subdir was given, prepend it to all the items in the
# path
unless subdir.nil? or subdir.empty?
- path.collect! {|m| File::join(subdir, m)}
+ path.collect! {|m| File.join(subdir, m)}
end
- PluginFactory::log :debug, "Path is: #{path.uniq.reverse.inspect}..."
+ PluginFactory.log :debug, "Path is: #{path.uniq.reverse.inspect}..."
return path.uniq.reverse
end
alias_method :makeRequirePath, :make_require_path
end # module Factory