1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Split out the basic plugin locator functionality into an abstract super class. Add a FileSystemLocator to do the job of checking the plugin_paths for plugins. Add plugin_locators configuration option which will iterate over the set of plugin locators and load each of the plugin loaders they return. Rename locater everywhere to locator. [Marcel Molina Jr.]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6290 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Marcel Molina 2007-03-02 23:39:29 +00:00
parent b0e1430c52
commit 15c466dd72
8 changed files with 152 additions and 121 deletions

View file

@ -1,5 +1,7 @@
*SVN*
* Split out the basic plugin locator functionality into an abstract super class. Add a FileSystemLocator to do the job of checking the plugin_paths for plugins. Add plugin_locators configuration option which will iterate over the set of plugin locators and load each of the plugin loaders they return. Rename locater everywhere to locator. [Marcel Molina Jr.]
* Split plugin location and loading out of the initializer and into a new Plugin namespace, which includes Plugin::Locater and Plugin::Loader. The loader class that is used can be customized using the config.plugin_loader option. Those monkey patching the plugin loading subsystem take note, the internals changing here will likely break your modifications. The good news is that it should be substantially easier to hook into the plugin locating and loading process now. [Marcel Molina Jr.]
* Added assumption that all plugin creators desire to be sharing individuals and release their work under the MIT license [DHH]

View file

@ -2,7 +2,7 @@ require 'logger'
require 'set'
require File.join(File.dirname(__FILE__), 'railties_path')
require File.join(File.dirname(__FILE__), 'rails/version')
require File.join(File.dirname(__FILE__), 'plugin/locater')
require File.join(File.dirname(__FILE__), 'plugin/locator')
require File.join(File.dirname(__FILE__), 'plugin/loader')
@ -183,12 +183,14 @@ module Rails
# * evaluate <tt>init.rb</tt> if present
#
# After all plugins are loaded, duplicates are removed from the load path.
# If an array of plugin names is specified in config.plugins, the plugins
# will be loaded in that order. Otherwise, plugins are loaded in alphabetical
# If an array of plugin names is specified in config.plugins, only those plugins will be loaded
# and they plugins will be loaded in that order. Otherwise, plugins are loaded in alphabetical
# order.
def load_plugins
Plugin::Locater.new(self).each do |plugin|
plugin.load
configuration.plugin_locators.each do |locator|
locator.new(self).each do |plugin|
plugin.load
end
end
$LOAD_PATH.uniq!
end
@ -429,6 +431,12 @@ module Rails
# <tt>vendor/plugins</tt>.
attr_accessor :plugin_paths
# The classes that handle finding the desired plugins that you'd like to load for
# your application. By default it is the Rails::Plugin::FileSystemLocator which finds
# plugins to load in <tt>vendor/plugins</tt>. You can hook into gem location by subclassing
# Rails::Plugin::Locator and adding it onto the list of <tt>plugin_locators</tt>.
attr_accessor :plugin_locators
# The class that handles loading each plugin. Defaults to Rails::Plugin::Loader, but
# a sub class would have access to fine grained modification of the loading behavior. See
# the implementation of Rails::Plugin::Loader for more details.
@ -449,6 +457,7 @@ module Rails
self.whiny_nils = default_whiny_nils
self.plugins = default_plugins
self.plugin_paths = default_plugin_paths
self.plugin_locators = default_plugin_locators
self.plugin_loader = default_plugin_loader
self.database_configuration_file = default_database_configuration_file
@ -605,6 +614,10 @@ module Rails
["#{root_path}/vendor/plugins"]
end
def default_plugin_locators
[Plugin::FileSystemLocator]
end
def default_plugin_loader
Plugin::Loader
end

View file

@ -34,12 +34,32 @@ module Rails
end
def enabled?
config.plugins.nil? || config.plugins.include?(name)
!explicit_plugin_loading_order? || registered?
end
def registered?
explicit_plugin_loading_order? && registered_plugins.include?(name)
end
def plugin_does_not_exist!(plugin_name = name)
raise LoadError, "Can not find the plugin named: #{plugin_name}"
end
private
# The plugins that have been explicitly listed with config.plugins. If this list is nil
# then it means the client does not care which plugins or in what order they are loaded,
# so we load all in alphabetical order. If it is an empty array, we load no plugins, if it is
# non empty, we load the named plugins in the order specified.
def registered_plugins
config.plugins
end
def explicit_plugin_loading_order?
!registered_plugins.nil?
end
def report_nonexistant_or_empty_plugin!
raise LoadError, "No such plugin: #{directory}" unless plugin_path?
plugin_does_not_exist! unless plugin_path?
end
def lib_path
@ -88,7 +108,15 @@ module Rails
end
def <=>(other_plugin_loader)
name <=> other_plugin_loader.name
if explicit_plugin_loading_order?
if non_existent_plugin = [self, other_plugin_loader].detect {|plugin| !registered_plugins.include?(plugin.name)}
plugin_does_not_exist!(non_existent_plugin.name)
end
registered_plugins.index(name) <=> registered_plugins.index(other_plugin_loader.name)
else
name <=> other_plugin_loader.name
end
end
end
end

View file

@ -1,88 +0,0 @@
module Rails
module Plugin
class Locater
include Enumerable
attr_reader :initializer
def initialize(initializer)
@initializer = initializer
end
def plugins
if !explicit_plugin_loading_order?
# We don't care about which plugins get loaded or in what order they are loaded
# so we load 'em all in a reliable order
located_plugins.sort
elsif !registered_plugins.empty?
registered_plugins.inject([]) do |plugins, registered_plugin|
report_plugin_missing!(registered_plugin) unless plugin = locate_registered_plugin(registered_plugin)
plugins << plugin
end
else
[]
end
end
def each(&block)
plugins.each(&block)
end
def plugin_names
plugins.map {|plugin| plugin.name}
end
private
def locate_registered_plugin(registered_plugin)
located_plugins.detect {|plugin| plugin.name == registered_plugin }
end
def report_plugin_missing!(name)
raise LoadError, "Cannot find the plugin you registered called '#{name}'!"
end
def explicit_plugin_loading_order?
!registered_plugins.nil?
end
# The plugins that have been explicitly listed with config.plugins. If this list is nil
# then it means the client does not care which plugins or in what order they are loaded,
# so we load all in alphabetical order. If it is an empty array, we load no plugins, if it is
# non empty, we load the named plugins in the order specified.
def registered_plugins
initializer.configuration.plugins
end
def located_plugins
# We cache this as locate_plugins_under on the entire set of plugin directories could
# be potentially expensive
@located_plugins ||=
begin
initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path|
plugins.concat locate_plugins_under(path)
plugins
end.flatten
end
end
# This starts at the base path looking for directories that pass the plugin_path? test of the Plugin::Loader.
# Since plugins can be nested arbitrarily deep within an unspecified number of intermediary directories,
# this method runs recursively until it finds a plugin directory.
#
# e.g.
#
# locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon')
# => 'acts_as_chunky_bacon'
def locate_plugins_under(base_path)
Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path|
plugin_loader = initializer.configuration.plugin_loader.new(initializer, path)
if plugin_loader.plugin_path?
plugins << plugin_loader if plugin_loader.enabled?
elsif File.directory?(path)
plugins.concat locate_plugins_under(path)
end
plugins
end
end
end
end
end

View file

@ -0,0 +1,75 @@
module Rails
module Plugin
class Locator
include Enumerable
attr_reader :initializer
def initialize(initializer)
@initializer = initializer
end
def plugins
located_plugins.select(&:enabled?).sort
end
def each(&block)
plugins.each(&block)
end
def plugin_names
plugins.map(&:name)
end
private
def located_plugins
raise "The `located_plugins' method must be defined by concrete subclasses of #{self.class}"
end
end
class FileSystemLocator < Locator
private
def located_plugins
returning locate_plugins do |loaders|
ensure_all_registered_plugins_are_loaded!(loaders)
end
end
def locate_plugins
initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path|
plugins.concat locate_plugins_under(path)
plugins
end.flatten
end
def ensure_all_registered_plugins_are_loaded!(loaders)
registered_plugins = initializer.configuration.plugins
unless registered_plugins.nil? || registered_plugins.empty?
missing_plugins = registered_plugins - loaders.map(&:name)
unless missing_plugins.empty?
raise LoadError, "Could not locate the following plugins: #{missing_plugins.inspect}"
end
end
end
# This starts at the base path looking for directories that pass the plugin_path? test of the Plugin::Loader.
# Since plugins can be nested arbitrarily deep within an unspecified number of intermediary directories,
# this method runs recursively until it finds a plugin directory.
#
# e.g.
#
# locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon')
# => 'acts_as_chunky_bacon'
def locate_plugins_under(base_path)
Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path|
plugin_loader = initializer.configuration.plugin_loader.new(initializer, path)
if plugin_loader.plugin_path? && plugin_loader.enabled?
plugins << plugin_loader
elsif File.directory?(path)
plugins.concat locate_plugins_under(path)
end
plugins
end
end
end
end
end

View file

@ -7,6 +7,13 @@ class TestPluginLoader < Test::Unit::TestCase
@empty_plugin_path = plugin_fixture_path('default/empty')
end
def test_determining_if_the_plugin_order_has_been_explicitly_set
loader = loader_for(@valid_plugin_path)
assert !loader.send(:explicit_plugin_loading_order?)
only_load_the_following_plugins! %w(stubby acts_as_chunky_bacon)
assert loader.send(:explicit_plugin_loading_order?)
end
def test_determining_whether_a_given_plugin_is_loaded
plugin_loader = loader_for(@valid_plugin_path)
assert !plugin_loader.loaded?
@ -44,7 +51,7 @@ class TestPluginLoader < Test::Unit::TestCase
end
def test_loading_a_plugin_gives_the_init_file_access_to_all_it_needs
failure_tip = "Perhaps someone has written another test that loads this same plugin and therefore makes the SubbyMixin constant defined already."
failure_tip = "Perhaps someone has written another test that loads this same plugin and therefore makes the StubbyMixin constant defined already."
assert !defined?(StubbyMixin), failure_tip
assert !added_to_load_path?(@valid_plugin_path)
# The init.rb of this plugin raises if it doesn't have access to all the things it needs

View file

@ -1,51 +1,41 @@
require File.dirname(__FILE__) + '/plugin_test_helper'
class TestPluginLocater < Test::Unit::TestCase
class TestPluginFileSystemLocator < Test::Unit::TestCase
def setup
configuration = Rails::Configuration.new
# We need to add our testing plugin directory to the plugin paths so
# the locater knows where to look for our plugins
# the locator knows where to look for our plugins
configuration.plugin_paths << plugin_fixture_root_path
@initializer = Rails::Initializer.new(configuration)
@locater = new_locater
end
def test_determining_if_the_plugin_order_has_been_explicitly_set
assert !@locater.send(:explicit_plugin_loading_order?)
only_load_the_following_plugins! %w(stubby acts_as_chunky_bacon)
assert @locater.send(:explicit_plugin_loading_order?)
@locator = new_locator
end
def test_no_plugins_are_loaded_if_the_configuration_has_an_empty_plugin_list
only_load_the_following_plugins! []
assert_equal [], @locater.plugins
assert_equal [], @locator.plugins
end
def test_only_the_specified_plugins_are_located_in_the_order_listed
plugin_names = %w(stubby acts_as_chunky_bacon)
only_load_the_following_plugins! plugin_names
assert_equal plugin_names, @locater.plugin_names
assert_equal plugin_names, @locator.plugin_names
end
def test_registering_a_plugin_name_that_does_not_exist_raisesa_load_error
only_load_the_following_plugins! %w(stubby acts_as_non_existant_plugin)
def test_registering_a_plugin_name_that_does_not_exist_raises_a_load_error
only_load_the_following_plugins! %w(stubby acts_as_a_non_existant_plugin)
assert_raises(LoadError) do
@locater.plugin_names
@locator.plugins
end
end
def test_all_plugins_are_loaded_when_registered_plugin_list_is_untouched
failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
assert_equal %w(a acts_as_chunky_bacon plugin_with_no_lib_dir stubby), @locater.plugin_names, failure_tip
assert_equal %w(a acts_as_chunky_bacon plugin_with_no_lib_dir stubby), @locator.plugin_names, failure_tip
end
private
def new_locater(initializer = @initializer)
Rails::Plugin::Locater.new(initializer)
end
def only_load_the_following_plugins!(plugins)
@initializer.configuration.plugins = plugins
def new_locator(initializer = @initializer)
Rails::Plugin::FileSystemLocator.new(initializer)
end
end

View file

@ -11,4 +11,8 @@ class Test::Unit::TestCase
def plugin_fixture_root_path
File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
end
def only_load_the_following_plugins!(plugins)
@initializer.configuration.plugins = plugins
end
end