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:
parent
b0e1430c52
commit
15c466dd72
8 changed files with 152 additions and 121 deletions
|
@ -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]
|
||||
|
|
|
@ -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,13 +183,15 @@ 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|
|
||||
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
|
||||
|
|
|
@ -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,8 +108,16 @@ module Rails
|
|||
end
|
||||
|
||||
def <=>(other_plugin_loader)
|
||||
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
|
||||
end
|
||||
|
|
|
@ -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
|
75
railties/lib/plugin/locator.rb
Normal file
75
railties/lib/plugin/locator.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue