Michael Granger avatar Michael Granger committed a5ca03d

Finish up conversion to Pluggability

Comments (0)

Files changed (5)

 # .rvm.gems generated gem export file. Note that any env variable settings will be missing. Append these after using a ';' field separator
-hoe-deveiate -v0.0.8
+loggability -v0.5.0
+hoe-deveiate -v0.1.1
-== v1.0.8 [2012-02-20] Michael Granger <ged@FaerieMUD.org>
+== v0.0.1 [2012-08-03] Michael Granger <ged@FaerieMUD.org>
 
-Gem update (no code changes).
+First release after renaming from PluginFactory.
 
-
-== v1.0.7 [2010-09-27] Michael Granger <ged@FaerieMUD.org>
-
-Gemspec update.
-
-== v1.0.6 [2010-03-23] Michael Granger <ged@FaerieMUD.org>
-
-Gemspec fixes.
-
-== v1.0.5 [2009-11-06] Michael Granger <ged@FaerieMUD.org>
-
-* Fixes for Ruby 1.9.1.
-* Replace home-grown logger stuff with Logger library
-
-== v1.0.3 [2008-03-26] Michael Granger <ged@FaerieMUD.org>
-
-* More packaging/publishing fixes.
-* Updated documentation.
-* Converted test::unit tests to RSpec
-
-== v1.0.2 [2007-03-13] Michael Granger <ged@FaerieMUD.org>
-
-* Logging enhancements
-* Converted camelCased methods to under_barred ones.
-* Fixed tests.
-
-== v1.0.1 [2005-03-05] Michael Granger <ged@FaerieMUD.org>
-
-* Made load failures raise FactoryErrors as intended instead of
-  RuntimeErrors.
-* Gemified
-
-== v1.0.0 [2004-09-08] Michael Granger <ged@FaerieMUD.org>
-
-* Stable release
-* Fixed String =~ String warning
-* Bugfixes
-
-== v0.01 [2004-03-10] Michael Granger <ged@FaerieMUD.org>
-
-First release.
-
 	self.readme_file = 'README.rdoc'
 	self.history_file = 'History.rdoc'
 	self.extra_rdoc_files = Rake::FileList[ '*.rdoc' ]
+	self.spec_extras[:rdoc_options] = ['-f', 'fivefish', '-t', 'Pluggability Toolkit']
 
 	self.developer 'Martin Chase', 'stillflame@FaerieMUD.org'
 	self.developer 'Michael Granger', 'ged@FaerieMUD.org'
 
-	self.dependency 'hoe-deveiate', '~> 0.0', :development
+	self.dependency 'loggability', '~> 0.5'
+
+	self.dependency 'hoe-deveiate', '~> 0.1', :development
 
 	self.spec_extras[:licenses] = ["BSD"]
 	self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"

lib/pluggability.rb

 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
+	# Loggability API -- Set up a logger.
 	log_as :pluggability
 
 
 	VERSION = '0.0.1'
 
 
+	### An exception class for Pluggability specific errors.
+	class FactoryError < RuntimeError; end
+
+
 	### Add the @derivatives instance variable to including classes.
 	def self::extend_object( obj )
 		obj.instance_variable_set( :@plugin_prefixes, [] )
 			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 ]
+			Pluggability.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
-			Pluggability.logger.debug "  no name-based variants for anonymous subclass %p" % [ subclass ]
+			Pluggability.log.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" %
+			Pluggability.log.info "Registering %s derivative of %s as %p" %
 				[ subclass.name, self.name, key ]
 			self.derivatives[ key ] = subclass
 		end
 	end
 
 
+	### Find and load all derivatives of this class, using plugin_prefixes if any
+	### are defined, or a pattern derived from the #factory_type if not. Returns
+	### an array of all derivative classes. Load failures are logged but otherwise
+	### ignored.
+	def load_all
+		patterns = []
+		prefixes = self.plugin_prefixes
+
+		if prefixes && !prefixes.empty?
+			Pluggability.log.debug "Using plugin prefixes (%p) to build load patterns." % [ prefixes ]
+			prefixes.each do |prefix|
+				patterns << "#{prefix}/*.rb"
+			end
+		else
+			# Use all but the last pattern, which will just be '*.rb'
+			Pluggability.log.debug "Using factory type (%p) to build load patterns." %
+				[ self.factory_type ]
+			patterns += self.make_require_path( '*', '' )[0..-2].
+				map {|f| f + '.rb' }
+		end
+
+		patterns.each do |glob|
+			Pluggability.log.debug "  finding derivatives matching pattern %p" % [ glob ]
+			candidates = Gem.find_files( glob )
+			Pluggability.log.debug "  found %d matching files" % [ candidates.length ]
+			next if candidates.empty?
+
+			candidates.each {|path| require(path) }
+		end
+
+		return self.derivative_classes
+	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
 	### 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}"
+		Pluggability.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
 				self.factory_type,
 				class_name.downcase,
 			]
-			Pluggability.logger.error( errmsg )
+			Pluggability.log.error( errmsg )
 			raise FactoryError, errmsg, caller(3)
 		end
 	end
 
 		subdirs = self.plugin_prefixes
 		subdirs << '' if subdirs.empty?
-		Pluggability.logger.debug "Subdirs are: %p" % [subdirs]
+		Pluggability.log.debug "Subdirs are: %p" % [subdirs]
 		fatals = []
 		tries  = []
 
 				begin
 					require( path.untaint )
 				rescue LoadError => err
-					Pluggability.logger.debug "No module at '%s', trying the next alternative: '%s'" %
+					Pluggability.log.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" %
+					Pluggability.log.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."
+					Pluggability.log.info "Loaded '#{path}' without error."
 					return path
 				end
 			end
 				mod_name,
 				tries
 			  ]
-			Pluggability.logger.error( errmsg )
+			Pluggability.log.error( errmsg )
 			raise FactoryError, errmsg
 		else
-			Pluggability.logger.debug "Re-raising first fatal error"
+			Pluggability.log.debug "Re-raising first fatal error"
 			Kernel.raise( fatals.first )
 		end
 	end
 			path.collect! {|m| File.join(subdir, m)}
 		end
 
-		Pluggability.logger.debug "Path is: #{path.uniq.reverse.inspect}..."
+		Pluggability.log.debug "Path is: #{path.uniq.reverse.inspect}..."
 		return path.uniq.reverse
 	end
 
-end # module Factory
+end # module Pluggability
+

spec/pluggability_spec.rb

 
 require 'spec/lib/helpers'
 
+class Plugin
+	extend Pluggability
+	plugin_prefixes 'plugins', 'plugins/private'
+end
+
+class SubPlugin < Plugin; end
+class TestingPlugin < Plugin; end
+class BlackSheep < Plugin; end
+module Test
+	class LoadablePlugin < Plugin; end
+end
+
 
 describe Pluggability do
 
 	before( :each ) do
-		setup_logging( :fitharn )
-		Loggability.level = :debug
+		setup_logging( :fatal )
 	end
 
 	after( :each ) do
 
 
 	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"]
+		Plugin.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 )
+			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
+			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 )
+			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
+			class PugilistPlugin < Plugin
 				def initialize
 					raise "Oh noes -- an error!"
 				end
 			end
 
-			expect {
-				@plugin.create('pugilist')
-			}.to raise_error( RuntimeError, /#{__FILE__}/ )
+			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)
+				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 )
+			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 
+				Plugin.derivatives['dazzle'] = loaded_class 
 				true
 			end
 
-			@plugin.create( 'dazzle' ).should be_an_instance_of( loaded_class )
+			Plugin.create( 'dazzle' ).should be_an_instance_of( loaded_class )
 		end
 
 
 			"derivative fails" do
 
 			# at least 6 -> 3 variants * 2 paths
-			@plugin.should_receive( :require ).
+			Plugin.should_receive( :require ).
 				at_least(6).times.
 				and_return {|path| raise LoadError, "path" }
 
 			expect {
-				@plugin.create('scintillating')
+				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 )
+			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 )
+				Plugin.create('corruscating')
+			}.to raise_error( FactoryError, /Require of '\S+' succeeded, but didn't load a Plugin/i )
 		end
 
 
 			"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|
+			Plugin.should_receive( :require ).at_least(6).times.and_return {|path|
 				raise ScriptError, "error while parsing #{path}"
 			}
 
 			expect {
-				@plugin.create('portable')
+				Plugin.create('portable')
 			}.to raise_error( ScriptError, /error while parsing/ )
 		end
+
+
+		it "can preload all of its derivatives" do
+			Gem.should_receive( :find_files ).with( 'plugins/*.rb' ).
+				and_return([ 'plugins/first.rb' ])
+			Gem.should_receive( :find_files ).with( 'plugins/private/*.rb' ).
+				and_return([ 'plugins/private/second.rb', 'plugins/private/third.rb' ])
+
+			Plugin.should_receive( :require ).with( 'plugins/first.rb' ).
+				and_return( true )
+			Plugin.should_receive( :require ).with( 'plugins/private/second.rb' ).
+				and_return( true )
+			Plugin.should_receive( :require ).with( 'plugins/private/third.rb' ).
+				and_return( true )
+			
+			Plugin.load_all
+		end
 	end
 
 
 	context "derivative of an extended class" do
 
 		it "knows what type of factory loads it" do
-			TestingPlugin.factory_type.should == '@plugin'
+			TestingPlugin.factory_type.should == 'Plugin'
 		end
 
 		it "raises a FactoryError if it can't figure out what type of factory loads it" do
 	end
 
 
-	context "derivative of an extended class that isn't named <Something>@plugin" do
+	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 )
+			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 )
+			Plugin.create( 'loadable' ).should be_an_instance_of( Test::LoadablePlugin )
 		end
 
 	end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.