Commits

Michael Granger committed 25ff09f

Start conversion to Pluggability

  • Participants
  • Parent commits dc4d376

Comments (0)

Files changed (11)

 
 # Modify prompt to do highlighting unless we're running in an inferior shell.
 unless ENV['EMACS']
-	IRB.conf[:PROMPT][:PluginFactory] = { # name of prompt mode
+	IRB.conf[:PROMPT][:Pluggability] = { # name of prompt mode
 		:PROMPT_I => colored( "%N(%m):%03n:%i>", %w{bold white on_blue} ) + " ",
 		:PROMPT_S => colored( "%N(%m):%03n:%i%l", %w{white on_blue} ) + " ",
 		:PROMPT_C => colored( "%N(%m):%03n:%i*", %w{white on_blue} ) + " ",
 		:RETURN => "    ==> %s\n\n"      # format to return value
 	}
-	IRB.conf[:PROMPT_MODE] = :PluginFactory
+	IRB.conf[:PROMPT_MODE] = :Pluggability
 end
 
 # Try to require the 'mues' library
 begin
-	puts "Requiring pluginfactory..."
-	require "pluginfactory"
+	puts "Requiring pluggability..."
+	require "pluggability"
 
 	if $DEBUG
 		puts "Setting up the logging callback..."
-		PluginFactory::logger_callback = lambda {|lvl, msg|
+		Pluggability::logger_callback = lambda {|lvl, msg|
 			debugMsg "[%s] %s" % [ lvl.to_s, msg ]
 		}
 	end	
 rescue => e
-	$stderr.puts "Ack! PluginFactory library failed to load: #{e.message}\n\t" +
+	$stderr.puts "Ack! Pluggability library failed to load: #{e.message}\n\t" +
 		e.backtrace.join( "\n\t" )
 end
 
 # This is an RVM Project .rvmrc file, used to automatically load the ruby
 # development environment upon cd'ing into the directory
 
-environment_id="ruby-1.9.3@pluginfactory"
+environment_id="ruby-1.9.3@pluggability"
 rvmdir=${rvm_path:-$HOME/.rvm}
 gemset_file=".rvm.gems"
 

File Manifest.txt

 Manifest.txt
 README.rdoc
 Rakefile
-lib/pluginfactory.rb
+lib/pluggability.rb
 spec/lib/helpers.rb
-spec/pluginfactory_spec.rb
+spec/pluggability_spec.rb
-= pluginfactory
+= pluggability
 
-* http://deveiate.org/projects/PluginFactory
+* http://deveiate.org/projects/Pluggability
 
 
 == Description
 
-PluginFactory is a mixin module that turns an including class into a factory for
+Pluggability is a mixin module that turns an including class 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 encapsulates your program's interaction with the database behind a useful
 interface. Now you can create a concrete implementation of the Driver class for
 each kind of database you wish to talk to. If you make the base Driver class a
-PluginFactory, too, you can add new drivers simply by dropping them in a
+Pluggability, too, you can add new drivers simply by dropping them in a
 directory and using the Driver's `create` method to instantiate them:
 
 === Synopsis
 
 in driver.rb:
 
-	require "PluginFactory"
+	require "Pluggability"
 	
 	class Driver
-		include PluginFactory
+		include Pluggability
 		def self::derivative_dirs
 		   ["drivers"]
 		end
 
 === How Plugins Are Loaded
 
-The +create+ class method added to your class by PluginFactory searches for your
+The +create+ class method added to your class by Pluggability searches for your
 module using several different strategies. It tries various permutations of the
 base class's name in combination with the derivative requested. For example,
 assume we want to make a +DataDriver+ base class, and then use plugins to define
 drivers for different kinds of data sources:
 
-	require 'pluginfactory'
+	require 'pluggability'
 	
 	class DataDriver
-	  include PluginFactory
+	  include Pluggability
 	end
 
 When you attempt to load the 'socket' data-driver class like so:
 
 	DataDriver.create( 'socket' )
 
-PluginFactory searches for modules with the following names:
+Pluggability searches for modules with the following names:
 
 	'socketdatadriver'
 	'socket_datadriver'
 	'Socket'
 
 Obviously the last one will load something other than what is intended, so you
-can also tell PluginFactory that plugins should be loaded from a subdirectory by
+can also tell Pluggability that plugins should be loaded from a subdirectory by
 declaring a class method called `derivative_dirs` in the base class. It should
 return an Array that contains a list of subdirectories to try:
 
 	class DataDriver
-	  include PluginFactory
+	  include Pluggability
 	
 	  def self::derivative_dirs
 	    ['drivers']
 If you return more than one subdirectory, each of them will be tried in turn:
 
 	class DataDriver
-	  include PluginFactory
+	  include Pluggability
 	
 	  def self::derivative_dirs
 	    ['drivers', 'datadriver']
 
 === Logging
 
-If you need a little more insight into what's going on, PluginFactory uses
+If you need a little more insight into what's going on, Pluggability uses
 'Logger' from the standard library. Just set its logger to your own to include
 log messages about plugins being loaded:
 
 
-	require 'pluginfactory'
+	require 'pluggability'
 	require 'logger'
 	
 	class DataDriver
-	  include PluginFactory
+	  include Pluggability
 	
 	end
 	
 	$logger = Logger.new( $stderr )
 	$logger.level = Logger::DEBUG
-	PluginFactory.logger = $logger
+	Pluggability.logger = $logger
 	
 	DataDriver.create( 'ringbuffer' )
 
 
 == Installation
 
-    gem install pluginfactory
+    gem install pluggability
 
 
 == Contributing
 
 You can check out the current development source with Mercurial via its
-{Mercurial repo}[http://repo.deveiate.org/PluginFactory]. Or if you prefer
-Git, via {its Github mirror}[https://github.com/ged/pluginfactory].
+{Mercurial repo}[http://repo.deveiate.org/Pluggability]. Or if you prefer
+Git, via {its Github mirror}[https://github.com/ged/pluggability].
 
 After checking out the source, run:
 
 
 Hoe.plugins.delete :rubyforge
 
-hoespec = Hoe.spec 'pluginfactory' do
+hoespec = Hoe.spec 'pluggability' do
 	self.readme_file = 'README.rdoc'
 	self.history_file = 'History.rdoc'
 	self.extra_rdoc_files = Rake::FileList[ '*.rdoc' ]

File experiments/logger_output.rb

 #!/usr/bin/env ruby
 
-# This is a short demo of the logging output generated by PluginFactory's logger_callback.
+# This is a short demo of the logging output generated by Pluggability's logger_callback.
 
 BEGIN {
 	require 'pathname'
 	$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
 }
 
-require 'pluginfactory'
+require 'pluggability'
 require 'logger'
 
 class DataDriver
-	include PluginFactory
+	include Pluggability
 	
 	@logger = Logger.new( $stderr )
 	@logger.level = Logger::DEBUG
-	PluginFactory.logger_callback = lambda do |severity, msg|
+	Pluggability.logger_callback = lambda do |severity, msg|
 		@logger.send(severity, msg)
 	end
 
 # D, [2008-03-19T18:16:29.862707 #70418] DEBUG -- : No module at 'ringbuffer', trying the next alternative: 'no such file to load -- ringbuffer'
 # D, [2008-03-19T18:16:29.862746 #70418] DEBUG -- : fatals = []
 # E, [2008-03-19T18:16:29.862793 #70418] ERROR -- : Couldn't find a DataDriver named 'ringbuffer': tried ["ringbufferdatadriver", "ringbufferDataDriver", "ringbuffer"]
-# ./lib/pluginfactory.rb:348:in `require_derivative': Couldn't find a DataDriver named 'ringbuffer': tried ["ringbufferdatadriver", "ringbufferDataDriver", "ringbuffer"] (FactoryError)
-# 	from ./lib/pluginfactory.rb:252:in `load_derivative'
-# 	from ./lib/pluginfactory.rb:220:in `get_subclass'
-# 	from ./lib/pluginfactory.rb:194:in `create'
+# ./lib/pluggability.rb:348:in `require_derivative': Couldn't find a DataDriver named 'ringbuffer': tried ["ringbufferdatadriver", "ringbufferDataDriver", "ringbuffer"] (FactoryError)
+# 	from ./lib/pluggability.rb:252:in `load_derivative'
+# 	from ./lib/pluggability.rb:220:in `get_subclass'
+# 	from ./lib/pluggability.rb:194:in `create'
 # 	from experiments/logger_output.rb:27

File lib/pluggability.rb

+#!/usr/bin/env ruby -w
+
+require 'loggability' unless defined?( Loggability )
+
+
+### An exception class for Pluggability specific errors.
+class FactoryError < RuntimeError; end
+
+# The Pluggability module
+module Pluggability
+	extend Loggability
+
+
+	# Loggability API -- Set up a logger
+	log_as :pluggability
+
+
+	# Library version
+	VERSION = '0.0.1'
+
+
+	### Add the @derivatives instance variable to including classes.
+	def self::extend_object( obj )
+		obj.instance_variable_set( :@plugin_prefixes, [] )
+		obj.instance_variable_set( :@derivatives, {} )
+		super
+	end
+
+
+	#############################################################
+	###	M I X I N   M E T H O D S
+	#############################################################
+
+	### Get/set the prefixes that will be used when searching for particular plugins
+	### for the calling Class.
+	def plugin_prefixes( *args )
+		@plugin_prefixes.replace( args ) if !args.empty?
+		return @plugin_prefixes
+	end
+
+
+	### Set the prefixes that will be used when searching for particular plugins
+	### for the calling Class.
+	def plugin_prefixes=( args )
+		@plugin_prefixes = Array( args )
+	end
+
+
+	### Return the Hash of derivative classes, keyed by various versions of
+	### the class name.
+	def derivatives
+		ancestors.each do |klass|
+			if klass.instance_variables.include?( :@derivatives ) ||
+			   klass.instance_variables.include?( "@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 do |klass|
+			if klass.instance_variables.include?( :@derivatives ) ||
+				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 =~ /^.*::(.*)/
+			return $1
+		else
+			return base.name
+		end
+	end
+
+
+	### Inheritance callback -- Register subclasses in the derivatives hash
+	### so that ::create knows about them.
+	def inherited( subclass )
+		keys = [ subclass ]
+
+		# If it's not an anonymous class, make some keys out of variants of its name
+		if subclass.name
+			simple_name = subclass.name.sub( /#<Class:0x[[:xdigit:]]+>::/i, '' )
+			keys << simple_name << simple_name.downcase
+
+			# Handle class names like 'FooBar' for 'Bar' factories.
+			Pluggability.logger.debug "Inherited %p for %p-type plugins" % [ subclass, self.factory_type ]
+			if subclass.name.match( /(?:.*::)?(\w+)(?:#{self.factory_type})/i )
+				keys << Regexp.last_match[1].downcase
+			else
+				keys << subclass.name.sub( /.*::/, '' ).downcase
+			end
+		else
+			Pluggability.logger.debug "  no name-based variants for anonymous subclass %p" % [ subclass ]
+		end
+
+		keys.compact.uniq.each do |key|
+			Pluggability.logger.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
+	def derivative_classes
+		self.derivatives.values.uniq
+	end
+
+
+	### 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>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( class_name, *args, &block )
+		subclass = get_subclass( class_name )
+
+		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>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( 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
+
+		class_name = class_name.to_s
+
+		# 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[ 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" %
+					[ class_name, self.name, subclass ]
+			end
+		end
+
+		return self.derivatives[ class_name.downcase ]
+	end
+
+
+	### Calculates an appropriate filename for the derived class using the
+	### name of the base class and tries to load it via <tt>require</tt>. If
+	### the including class responds to a method named
+	### <tt>derivativeDirs</tt>, its return value (either a String, or an
+	### 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( class_name )
+		Pluggability.logger.debug "Loading derivative #{class_name}"
+
+		# 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( 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[ class_name.downcase ]
+			errmsg = "Require of '%s' succeeded, but didn't load a %s named '%s' for some reason." % [
+				result,
+				self.factory_type,
+				class_name.downcase,
+			]
+			Pluggability.logger.error( errmsg )
+			raise FactoryError, errmsg, caller(3)
+		end
+	end
+
+
+	### 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( class_name )
+		if class_name =~ /\w+#{self.factory_type}/
+			mod_name = class_name.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" )
+		else
+			mod_name = class_name
+		end
+
+		return mod_name
+	end
+
+
+	### Search for the module with the specified <tt>mod_name</tt>, using any
+	### #plugin_prefixes that have been set.
+	def require_derivative( mod_name )
+
+		subdirs = self.plugin_prefixes
+		subdirs << '' if subdirs.empty?
+		Pluggability.logger.debug "Subdirs are: %p" % [subdirs]
+		fatals = []
+		tries  = []
+
+		# Iterate over the subdirs until we successfully require a
+		# module.
+		subdirs.map( &:strip ).each do |subdir|
+			self.make_require_path( mod_name, subdir ).each do |path|
+				Pluggability.logger.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
+					Pluggability.logger.debug "No module at '%s', trying the next alternative: '%s'" %
+						[ path, err.message ]
+				rescue Exception => err
+					fatals << err
+					Pluggability.logger.error "Found '#{path}', but encountered an error: %s\n\t%s" %
+						[ err.message, err.backtrace.join("\n\t") ]
+				else
+					Pluggability.logger.info "Loaded '#{path}' without error."
+					return path
+				end
+			end
+		end
+
+		Pluggability.logger.debug "fatals = %p" % [ fatals ]
+
+		# 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
+			  ]
+			Pluggability.logger.error( errmsg )
+			raise FactoryError, errmsg
+		else
+			Pluggability.logger.debug "Re-raising first fatal error"
+			Kernel.raise( fatals.first )
+		end
+	end
+
+
+	### Make a list of permutations of the given +modname+ for the given
+	### +subdir+. Called on a +DataDriver+ class with the arguments 'Socket' and
+	### 'drivers', returns:
+	###   ["drivers/socketdatadriver", "drivers/socketDataDriver",
+	###    "drivers/SocketDataDriver", "drivers/socket", "drivers/Socket"]
+	def make_require_path( modname, subdir )
+		path = []
+		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
+
+		# 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)}
+		end
+
+		Pluggability.logger.debug "Path is: #{path.uniq.reverse.inspect}..."
+		return path.uniq.reverse
+	end
+
+end # module Factory

File lib/pluginfactory.rb

-#!/usr/bin/env ruby -w
-
-require 'logger'
-
-### 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
-# different underlying functionality.
-#
-# An example of where this might be useful is in a program which talks to a
-# database. To avoid coupling it to a specific database, you use a Driver class
-# which encapsulates your program's interaction with the database behind a
-# useful interface. Now you can create a concrete implementation of the Driver
-# class for each kind of database you wish to talk to. If you make the base
-# Driver class a PluginFactory, too, you can add new drivers simply by dropping
-# 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
-#
-# == Synopsis
-#
-# in driver.rb:
-#
-#	require "PluginFactory"
-#
-#	class Driver
-#		include PluginFactory
-#		def self::derivative_dirs
-#		   ["drivers"]
-#		end
-#	end
-#
-# in drivers/mysql.rb:
-#
-#	require 'driver'
-#
-#	class MysqlDriver < Driver
-#		...implementation...
-#	end
-#
-# in /usr/lib/ruby/1.8/PostgresDriver.rb:
-#
-#	require 'driver'
-#
-#	class PostgresDriver < Driver
-#		...implementation...
-#	end
-#
-# elsewhere
-#
-#	require 'driver'
-#
-#	config[:driver_type] #=> "mysql"
-#	driver = Driver.create( config[:driver_type] )
-#	driver.class #=> MysqlDriver
-#	pgdriver = Driver.create( "PostGresDriver" )
-#
-# == Authors
-#
-# * Martin Chase <stillflame@FaerieMUD.org>
-# * Michael Granger <ged@FaerieMUD.org>
-#
-# == License
-#
-# Copyright (c) 2008-2012 Michael Granger and Martin Chase
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-#   * Redistributions of source code must retain the above copyright notice,
-#     this list of conditions and the following disclaimer.
-#
-#   * Redistributions in binary form must reproduce the above copyright notice,
-#     this list of conditions and the following disclaimer in the documentation
-#     and/or other materials provided with the distribution.
-#
-#   * Neither the name of the author/s, nor the names of the project's
-#     contributors may be used to endorse or promote products derived from this
-#     software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-module PluginFactory
-
-	# Library version
-	VERSION = '1.0.8'
-
-
-	### Logging
-	@default_logger = Logger.new( $stderr )
-	@default_logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
-
-	@logger = @default_logger
-
-
-	class << self
-		# The logger that will be used when the logging subsystem is reset
-		attr_accessor :default_logger
-
-		# The logger that's currently in effect
-		attr_accessor :logger
-		alias_method :log, :logger
-		alias_method :log=, :logger=
-	end
-
-
-	### Deprecated: use the Logger object at #log to manipulate logging instead of this
-	### method.
-	def self::logger_callback=( callback )
-		if callback.nil?
-			self.logger.formatter = nil
-		else
-			self.logger.formatter = lambda {|lvl, _, _, msg|
-				callback.call(lvl.downcase.to_sym, msg)
-				''
-			}
-		end
-	end
-
-
-	### Reset the global logger object to the default
-	def self::reset_logger
-		self.logger = self.default_logger
-		self.logger.level = Logger::WARN
-	end
-
-
-	### Returns +true+ if the global logger has not been set to something other than
-	### the default one.
-	def self::using_default_logger?
-		return self.logger == self.default_logger
-	end
-
-
-	### Inclusion callback -- extends the including class. This is here so you can
-	### either 'include' or 'extend'.
-	def self::included( klass )
-		klass.extend( self )
-	end
-
-
-	### Add the @derivatives instance variable to including classes.
-	def self::extend_object( obj )
-		obj.instance_variable_set( :@derivatives, {} )
-		super
-	end
-
-
-	#############################################################
-	###	M I X I N   M E T H O D S
-	#############################################################
-
-	### Return the Hash of derivative classes, keyed by various versions of
-	### the class name.
-	def derivatives
-		ancestors.each do |klass|
-			if klass.instance_variables.include?( :@derivatives ) ||
-			   klass.instance_variables.include?( "@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 do |klass|
-			if klass.instance_variables.include?( :@derivatives ) ||
-				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 =~ /^.*::(.*)/
-			return $1
-		else
-			return base.name
-		end
-	end
-	alias_method :factoryType, :factory_type
-
-
-	### Inheritance callback -- Register subclasses in the derivatives hash
-	### so that ::create knows about them.
-	def inherited( subclass )
-		keys = [ subclass ]
-
-		# If it's not an anonymous class, make some keys out of variants of its name
-		if subclass.name
-			simple_name = subclass.name.sub( /#<Class:0x[[:xdigit:]]+>::/i, '' )
-			keys << simple_name << simple_name.downcase
-
-			# Handle class names like 'FooBar' for 'Bar' factories.
-			PluginFactory.log.debug "Inherited %p for %p-type plugins" % [ subclass, self.factory_type ]
-			if subclass.name.match( /(?:.*::)?(\w+)(?:#{self.factory_type})/i )
-				keys << Regexp.last_match[1].downcase
-			else
-				keys << subclass.name.sub( /.*::/, '' ).downcase
-			end
-		else
-			PluginFactory.log.debug "  no name-based variants for anonymous subclass %p" % [ subclass ]
-		end
-
-		keys.compact.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
-	def derivative_classes
-		self.derivatives.values.uniq
-	end
-	alias_method :derivativeClasses, :derivative_classes
-
-
-	### 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>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( class_name, *args, &block )
-		subclass = get_subclass( class_name )
-
-		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>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( 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
-
-		class_name = class_name.to_s
-
-		# 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[ 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" %
-					[ class_name, self.name, subclass ]
-			end
-		end
-
-		return self.derivatives[ class_name.downcase ]
-	end
-	alias_method :getSubclass, :get_subclass
-
-
-	### Calculates an appropriate filename for the derived class using the
-	### name of the base class and tries to load it via <tt>require</tt>. If
-	### the including class responds to a method named
-	### <tt>derivativeDirs</tt>, its return value (either a String, or an
-	### 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( class_name )
-		PluginFactory.log.debug "Loading derivative #{class_name}"
-
-		# 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( 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[ class_name.downcase ]
-			errmsg = "Require of '%s' succeeded, but didn't load a %s named '%s' for some reason." % [
-				result,
-				self.factory_type,
-				class_name.downcase,
-			]
-			PluginFactory.log.error( errmsg )
-			raise FactoryError, errmsg, caller(3)
-		end
-	end
-	alias_method :loadDerivative, :load_derivative
-
-
-	### 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( class_name )
-		if class_name =~ /\w+#{self.factory_type}/
-			mod_name = class_name.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" )
-		else
-			mod_name = class_name
-		end
-
-		return mod_name
-	end
-	alias_method :getModuleName, :get_module_name
-
-
-	### 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
-		# live in
-		if ( self.respond_to?(:derivative_dirs) )
-			subdirs = self.derivative_dirs
-
-		elsif ( self.respond_to?(:derivativeDirs) )
-			subdirs = self.derivativeDirs
-
-		# If not, just try requiring it from $LOAD_PATH
-		else
-			subdirs = ['']
-		end
-
-		subdirs = [ subdirs ] unless subdirs.is_a?( Array )
-		PluginFactory.log.debug "Subdirs are: %p" % [subdirs]
-		fatals = []
-		tries  = []
-
-		# Iterate over the subdirs until we successfully require a
-		# module.
-		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 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 ]
-
-		# 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
-	### +subdir+. Called on a +DataDriver+ class with the arguments 'Socket' and
-	### 'drivers', returns:
-	###   ["drivers/socketdatadriver", "drivers/socketDataDriver",
-	###    "drivers/SocketDataDriver", "drivers/socket", "drivers/Socket"]
-	def make_require_path( modname, subdir )
-		path = []
-		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
-
-		# 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)}
-		end
-
-		PluginFactory.log.debug "Path is: #{path.uniq.reverse.inspect}..."
-		return path.uniq.reverse
-	end
-	alias_method :makeRequirePath, :make_require_path
-
-end # module Factory

File spec/lib/helpers.rb

 }
 
 require 'rspec'
-require 'pluginfactory'
+require 'loggability/spechelpers'
+require 'pluggability'
 
 
-### RSpec helper functions.
-module PluginFactory
-
-	# An alternate formatter for Logger instances that outputs +div+ HTML
-	# fragments.
-	# @private
-	class HtmlLogFormatter < Logger::Formatter
-		include ERB::Util  # for html_escape()
-
-		# The default HTML fragment that'll be used as the template for each log message.
-		HTML_LOG_FORMAT = %q{
-		<div class="log-message %5$s">
-			<span class="log-time">%1$s.%2$06d</span>
-			[
-				<span class="log-pid">%3$d</span>
-				/
-				<span class="log-tid">%4$s</span>
-			]
-			<span class="log-level">%5$s</span>
-			:
-			<span class="log-name">%6$s</span>
-			<span class="log-message-text">%7$s</span>
-		</div>
-		}
-
-		### Override the logging formats with ones that generate HTML fragments
-		def initialize( logger, format=HTML_LOG_FORMAT ) # :notnew:
-			@logger = logger
-			@format = format
-			super()
-		end
-
-
-		######
-		public
-		######
-
-		# The HTML fragment that will be used as a format() string for the log
-		attr_accessor :format
-
-
-		### Return a log message composed out of the arguments formatted using the
-		### formatter's format string
-		def call( severity, time, progname, msg )
-			args = [
-				time.strftime( '%Y-%m-%d %H:%M:%S' ),                         # %1$s
-				time.usec,                                                    # %2$d
-				Process.pid,                                                  # %3$d
-				Thread.current == Thread.main ? 'main' : Thread.object_id,    # %4$s
-				severity.downcase,                                                     # %5$s
-				progname,                                                     # %6$s
-				html_escape( msg ).gsub(/\n/, '<br />')                       # %7$s
-			]
-
-			return self.format % args
-		end
-
-	end # class HtmlLogFormatter
-
-
-	### Spec helper functions
-	module SpecHelpers
-
-		class ArrayLogger
-			### Create a new ArrayLogger that will append content to +array+.
-			def initialize( array )
-				@array = array
-			end
-
-			### Write the specified +message+ to the array.
-			def write( message )
-				@array << message
-			end
-
-			### No-op -- this is here just so Logger doesn't complain
-			def close; end
-
-		end # class ArrayLogger
-
-
-		unless defined?( LEVEL )
-			LEVEL = {
-				:debug => Logger::DEBUG,
-				:info  => Logger::INFO,
-				:warn  => Logger::WARN,
-				:error => Logger::ERROR,
-				:fatal => Logger::FATAL,
-			  }
-		end
-
-		###############
-		module_function
-		###############
-
-		### Make an easily-comparable version vector out of +ver+ and return it.
-		def vvec( ver )
-			return ver.split('.').collect {|char| char.to_i }.pack('N*')
-		end
-
-
-		### Reset the logging subsystem to its default state.
-		def reset_logging
-			PluginFactory.reset_logger
-		end
-
-
-		### Alter the output of the default log formatter to be pretty in SpecMate output
-		def setup_logging( level=Logger::FATAL )
-
-			# Turn symbol-style level config into Logger's expected Fixnum level
-			if LEVEL.key?( level )
-				level = LEVEL[ level ]
-			end
-
-			logger = Logger.new( $stderr )
-			PluginFactory.logger = logger
-			PluginFactory.logger.level = level
-
-			# Only do this when executing from a spec in TextMate
-			if ENV['HTML_LOGGING'] || (ENV['TM_FILENAME'] && ENV['TM_FILENAME'] =~ /_spec\.rb/)
-				Thread.current['logger-output'] = []
-				logdevice = ArrayLogger.new( Thread.current['logger-output'] )
-				PluginFactory.logger = Logger.new( logdevice )
-				# PluginFactory.logger.level = level
-				PluginFactory.logger.formatter = PluginFactory::HtmlLogFormatter.new( logger )
-			end
-		end
-
-	end # module SpecHelpers
-
-end # module PluginFactory
-
 
 ### Mock with Rspec
-RSpec.configure do |c|
-	c.mock_with :rspec
-	c.include( PluginFactory::SpecHelpers )
+RSpec.configure do |config|
+	ruby_version_vec = RUBY_VERSION.split('.').map {|c| c.to_i }.pack( "N*" )
+
+	config.include( Loggability::SpecHelpers )
+	config.treat_symbols_as_metadata_keys_with_true_values = true
+
+	config.mock_with :rspec
 end
 
+
 # vim: set nosta noet ts=4 sw=4:
 

File spec/pluggability_spec.rb

+#!/usr/bin/env ruby
+
+BEGIN {
+	require 'pathname'
+	basedir = Pathname.new( __FILE__ ).dirname.parent
+
+	libdir = basedir + "lib"
+
+	$LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
+	$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
+}
+
+require 'rspec'
+require 'logger'
+require 'pluggability'
+
+require 'spec/lib/helpers'
+
+
+describe Pluggability do
+
+	before( :each ) do
+		setup_logging( :fitharn )
+		Loggability.level = :debug
+	end
+
+	after( :each ) do
+		reset_logging()
+	end
+
+
+	it "allows extended objects to declare one or more prefixes to use when requiring derviatives" do
+		pluginclass = Class.new
+		pluginclass.extend( Pluggability )
+		pluginclass.plugin_prefixes "plugins", "plugins/private"
+		pluginclass.plugin_prefixes.should == ["plugins", "plugins/private"]
+	end
+
+
+	context "-extended class" do
+
+		before( :all ) do
+			@plugin = Class.new do
+				def name; "Plugin"; end
+				extend Pluggability
+				plugin_prefixes "plugins", "plugins/private"
+			end
+			@subplugin = Class.new( @plugin ) { def name; "SubPlugin"; end }
+		end
+
+
+		it "knows about all of its derivatives" do
+			@plugin.derivatives.keys.should include( 'sub' )
+			@plugin.derivatives.keys.should include( 'subplugin' )
+			@plugin.derivatives.keys.should include( 'SubPlugin' )
+			@plugin.derivatives.keys.should include( @subplugin )
+		end
+
+		it "returns derivatives directly if they're already loaded" do
+			class AlreadyLoadedPlugin < @plugin; end
+			Kernel.should_not_receive( :require )
+			@plugin.create( 'alreadyloaded' ).should be_an_instance_of( AlreadyLoadedPlugin )
+			@plugin.create( 'AlreadyLoaded' ).should be_an_instance_of( AlreadyLoadedPlugin )
+			@plugin.create( 'AlreadyLoadedPlugin' ).should be_an_instance_of( AlreadyLoadedPlugin )
+			@plugin.create( AlreadyLoadedPlugin ).should be_an_instance_of( AlreadyLoadedPlugin )
+		end
+
+		it "filters errors that happen when creating an instance of derivatives so they " +
+			"point to the right place" do
+			class PugilistPlugin < @plugin
+				def initialize
+					raise "Oh noes -- an error!"
+				end
+			end
+
+			expect {
+				@plugin.create('pugilist')
+			}.to raise_error( RuntimeError, /#{__FILE__}/ )
+		end
+
+		it "will refuse to create an object other than one of its derivatives" do
+			class Doppelgaenger; end
+			expect {
+				@plugin.create(Doppelgaenger)
+			}.to raise_error( ArgumentError, /is not a descendent of/ )
+		end
+
+
+		it "will load new plugins from the require path if they're not loaded yet" do
+			loaded_class = nil
+
+			@plugin.should_receive( :require ).with( 'plugins/dazzle_plugin' ).and_return do |*args|
+				loaded_class = Class.new( @plugin )
+				# Simulate a named class, since we're not really requiring
+				@plugin.derivatives['dazzle'] = loaded_class 
+				true
+			end
+
+			@plugin.create( 'dazzle' ).should be_an_instance_of( loaded_class )
+		end
+
+
+		it "will output a sensible description of what it tried to load if requiring a " +
+			"derivative fails" do
+
+			# at least 6 -> 3 variants * 2 paths
+			@plugin.should_receive( :require ).
+				at_least(6).times.
+				and_return {|path| raise LoadError, "path" }
+
+			expect {
+				@plugin.create('scintillating')
+			}.to raise_error( FactoryError, /couldn't find a \S+ named \S+.*tried \[/i )
+		end
+
+
+		it "will output a sensible description when a require succeeds, but it loads something unintended" do
+			# at least 6 -> 3 variants * 2 paths
+			@plugin.should_receive( :require ).and_return( true )
+
+			expect {
+				@plugin.create('corruscating')
+			}.to raise_error( FactoryError, /Require of '\S+' succeeded, but didn't load a @plugin/i )
+		end
+
+
+		it "will re-raise the first exception raised when attempting to load a " +
+			"derivative if none of the paths work" do
+
+			# at least 6 -> 3 variants * 2 paths
+			@plugin.should_receive( :require ).at_least(6).times.and_return {|path|
+				raise ScriptError, "error while parsing #{path}"
+			}
+
+			expect {
+				@plugin.create('portable')
+			}.to raise_error( ScriptError, /error while parsing/ )
+		end
+	end
+
+
+	context "derivative of an extended class" do
+
+		it "knows what type of factory loads it" do
+			TestingPlugin.factory_type.should == '@plugin'
+		end
+
+		it "raises a FactoryError if it can't figure out what type of factory loads it" do
+			TestingPlugin.stub!( :ancestors ).and_return( [] )
+			expect {
+				TestingPlugin.factory_type
+			}.to raise_error( FactoryError, /couldn't find factory base/i )
+		end
+	end
+
+
+	context "derivative of an extended class that isn't named <Something>@plugin" do
+
+		it "is still creatable via its full name" do
+			@plugin.create( 'blacksheep' ).should be_an_instance_of( BlackSheep )
+		end
+
+	end
+
+
+	context "derivative of an extended class in another namespace" do
+
+		it "is still creatable via its derivative name" do
+			@plugin.create( 'loadable' ).should be_an_instance_of( Test::LoadablePlugin )
+		end
+
+	end
+
+end
+

File spec/pluginfactory_spec.rb

-#!/usr/bin/env ruby
-
-BEGIN {
-	require 'pathname'
-	basedir = Pathname.new( __FILE__ ).dirname.parent
-
-	libdir = basedir + "lib"
-
-	$LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
-	$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
-}
-
-require 'rspec'
-require 'logger'
-require 'pluginfactory'
-
-require 'spec/lib/helpers'
-
-class Plugin
-	extend PluginFactory
-	def self::derivative_dirs 
-		[ 'plugins', 'plugins/private' ]
-	end
-end
-
-class SubPlugin < Plugin; end
-class TestingPlugin < Plugin; end
-class BlackSheep < Plugin; end
-module Test
-	class LoadablePlugin < Plugin; end
-end
-
-
-describe PluginFactory do
-
-	before( :each ) do
-		setup_logging( :fatal )
-	end
-
-	after( :each ) do
-		reset_logging()
-	end
-
-
-	it "calls its logging callback with the level and joined message if set" do
-		level = nil; msg = nil
-		PluginFactory.logger_callback = lambda {|l, m| level = l; msg = m }
-		PluginFactory.logger.level = Logger::DEBUG
-
-		PluginFactory.log.debug( 'message' )
-		level.should == :debug
-		msg.should == 'message'
-	end
-
-	it "doesn't error when its log method is called if no logging callback is set" do
-		PluginFactory.logger_callback = nil
-		expect { PluginFactory.log.debug("msg") }.to_not raise_error()
-	end
-
-
-	context "-extended class" do
-
-		it "knows about all of its derivatives" do
-			Plugin.derivatives.keys.should include( 'sub' )
-			Plugin.derivatives.keys.should include( 'subplugin' )
-			Plugin.derivatives.keys.should include( 'SubPlugin' )
-			Plugin.derivatives.keys.should include( SubPlugin )
-		end
-
-		it "returns derivatives directly if they're already loaded" do
-			class AlreadyLoadedPlugin < Plugin; end
-			Kernel.should_not_receive( :require )
-			Plugin.create( 'alreadyloaded' ).should be_an_instance_of( AlreadyLoadedPlugin )
-			Plugin.create( 'AlreadyLoaded' ).should be_an_instance_of( AlreadyLoadedPlugin )
-			Plugin.create( 'AlreadyLoadedPlugin' ).should be_an_instance_of( AlreadyLoadedPlugin )
-			Plugin.create( AlreadyLoadedPlugin ).should be_an_instance_of( AlreadyLoadedPlugin )
-		end
-
-		it "filters errors that happen when creating an instance of derivatives so they " +
-			"point to the right place" do
-			class PugilistPlugin < Plugin
-				def initialize
-					raise "Oh noes -- an error!"
-				end
-			end
-
-			exception = nil
-			begin
-				Plugin.create('pugilist')
-			rescue ::RuntimeError => err
-				exception = err
-			else
-				fail "Expected an exception to be raised."
-			end
-
-			exception.backtrace.first.should =~ /#{__FILE__}/
-		end
-
-		it "will refuse to create an object other than one of its derivatives" do
-			class Doppelgaenger; end
-			expect {
-				Plugin.create(Doppelgaenger)
-			}.to raise_error( ArgumentError, /is not a descendent of/ )
-		end
-
-
-		it "will load new plugins from the require path if they're not loaded yet" do
-			loaded_class = nil
-
-			Plugin.should_receive( :require ).with( 'plugins/dazzle_plugin' ).and_return do |*args|
-				loaded_class = Class.new( Plugin )
-				# Simulate a named class, since we're not really requiring
-				Plugin.derivatives['dazzle'] = loaded_class 
-				true
-			end
-
-			Plugin.create( 'dazzle' ).should be_an_instance_of( loaded_class )
-		end
-
-
-		it "will output a sensible description of what it tried to load if requiring a " +
-			"derivative fails" do
-
-			# at least 6 -> 3 variants * 2 paths
-			Plugin.should_receive( :require ).
-				at_least(6).times.
-				and_return {|path| raise LoadError, "path" }
-
-			expect {
-				Plugin.create('scintillating')
-			}.to raise_error( FactoryError, /couldn't find a \S+ named \S+.*tried \[/i )
-		end
-
-
-		it "will output a sensible description when a require succeeds, but it loads something unintended" do
-			# at least 6 -> 3 variants * 2 paths
-			Plugin.should_receive( :require ).and_return( true )
-
-			expect {
-				Plugin.create('corruscating')
-			}.to raise_error( FactoryError, /Require of '\S+' succeeded, but didn't load a Plugin/i )
-		end
-
-
-		it "will re-raise the first exception raised when attempting to load a " +
-			"derivative if none of the paths work" do
-
-			# at least 6 -> 3 variants * 2 paths
-			Plugin.should_receive( :require ).at_least(6).times.and_return {|path|
-				raise ScriptError, "error while parsing #{path}"
-			}
-
-			expect {
-				Plugin.create('portable')
-			}.to raise_error( ScriptError, /error while parsing/ )
-		end
-	end
-
-
-	context "derivative of an extended class" do
-
-		it "knows what type of factory loads it" do
-			TestingPlugin.factory_type.should == 'Plugin'
-		end
-
-		it "raises a FactoryError if it can't figure out what type of factory loads it" do
-			TestingPlugin.stub!( :ancestors ).and_return( [] )
-			expect {
-				TestingPlugin.factory_type
-			}.to raise_error( FactoryError, /couldn't find factory base/i )
-		end
-	end
-
-
-	context "derivative of an extended class that isn't named <Something>Plugin" do
-
-		it "is still creatable via its full name" do
-			Plugin.create( 'blacksheep' ).should be_an_instance_of( BlackSheep )
-		end
-
-	end
-
-
-	context "derivative of an extended class in another namespace" do
-
-		it "is still creatable via its derivative name" do
-			Plugin.create( 'loadable' ).should be_an_instance_of( Test::LoadablePlugin )
-		end
-
-	end
-
-end
-