提交 b0e1430c 编写于 作者: M Marcel Molina

Split plugin location and loading out of the initializer and into a new Plugin...

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.]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6277 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 36cf67e8
*SVN*
* 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]
* Added source-annotations extractor tasks to rake [Jamis Buck]. This allows you to add FIXME, OPTIMIZE, and TODO comments to your source code that can then be extracted in concert with rake notes (shows all), rake notes:fixme, rake notes:optimize and rake notes:todo.
......
......@@ -2,6 +2,9 @@
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/loader')
RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)
......@@ -184,19 +187,10 @@ def add_support_load_paths
# will be loaded in that order. Otherwise, plugins are loaded in alphabetical
# order.
def load_plugins
if configuration.plugins.nil?
# a nil value implies we don't care about plugins; load 'em all in a reliable order
find_plugins(configuration.plugin_paths).sort.each { |path| load_plugin path }
elsif !configuration.plugins.empty?
# we've specified a config.plugins array, so respect that order
plugin_paths = find_plugins(configuration.plugin_paths)
configuration.plugins.each do |name|
path = plugin_paths.find { |p| File.basename(p) == name }
raise(LoadError, "Cannot find the plugin '#{name}'!") if path.nil?
load_plugin path
end
end
$LOAD_PATH.uniq!
Plugin::Locater.new(self).each do |plugin|
plugin.load
end
$LOAD_PATH.uniq!
end
# Loads the environment specified by Configuration#environment_path, which
......@@ -343,74 +337,6 @@ def load_application_initializers
load(initializer)
end
end
protected
# Return a list of plugin paths within base_path. A plugin path is
# a directory that contains either a lib directory or an init.rb file.
# This recurses into directories which are not plugin paths, so you
# may organize your plugins within the plugin path.
def find_plugins(*base_paths)
base_paths.flatten.inject([]) do |plugins, base_path|
Dir.glob(File.join(base_path, '*')).each do |path|
if plugin_path?(path)
plugins << path if plugin_enabled?(path)
elsif File.directory?(path)
plugins += find_plugins(path)
end
end
plugins
end
end
def plugin_path?(path)
File.directory?(path) and (File.directory?(File.join(path, 'lib')) or File.file?(File.join(path, 'init.rb')))
end
def plugin_enabled?(path)
configuration.plugins.nil? || configuration.plugins.include?(File.basename(path))
end
# Load the plugin at <tt>path</tt> unless already loaded.
#
# Each plugin is initialized:
# * add its +lib+ directory, if present, to the beginning of the load path
# * evaluate <tt>init.rb</tt> if present
#
# Returns <tt>true</tt> if the plugin is successfully loaded or
# <tt>false</tt> if it is already loaded (similar to Kernel#require).
# Raises <tt>LoadError</tt> if the plugin is not found.
def load_plugin(directory)
name = File.basename(directory)
return false if loaded_plugins.include?(name)
# Catch nonexistent and empty plugins.
raise LoadError, "No such plugin: #{directory}" unless plugin_path?(directory)
lib_path = File.join(directory, 'lib')
init_path = File.join(directory, 'init.rb')
has_lib = File.directory?(lib_path)
has_init = File.file?(init_path)
# Add lib to load path *after* the application lib, to allow
# application libraries to override plugin libraries.
if has_lib
application_lib_index = $LOAD_PATH.index(File.join(RAILS_ROOT, "lib")) || 0
$LOAD_PATH.insert(application_lib_index + 1, lib_path)
Dependencies.load_paths << lib_path
Dependencies.load_once_paths << lib_path
end
# Allow plugins to reference the current configuration object
config = configuration
# Add to set of loaded plugins before 'name' collapsed in eval.
loaded_plugins << name
# Evaluate init.rb.
silence_warnings { eval(IO.read(init_path), binding, init_path) } if has_init
true
end
end
# The Configuration class holds all the parameters for the Initializer and
......@@ -502,6 +428,11 @@ class Configuration
# The path to the root of the plugins directory. By default, it is in
# <tt>vendor/plugins</tt>.
attr_accessor :plugin_paths
# 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.
attr_accessor :plugin_loader
# Create a new Configuration instance, initialized with the default
# values.
......@@ -518,6 +449,7 @@ def initialize
self.whiny_nils = default_whiny_nils
self.plugins = default_plugins
self.plugin_paths = default_plugin_paths
self.plugin_loader = default_plugin_loader
self.database_configuration_file = default_database_configuration_file
for framework in default_frameworks
......@@ -672,6 +604,10 @@ def default_plugins
def default_plugin_paths
["#{root_path}/vendor/plugins"]
end
def default_plugin_loader
Plugin::Loader
end
end
end
......
module Rails
module Plugin
class Loader
include Comparable
attr_reader :initializer, :directory, :name
class << self
def load(*args)
new(*args).load
end
end
def initialize(initializer, directory)
@initializer = initializer
@directory = directory
@name = File.basename(directory)
end
def load
return false if loaded?
report_nonexistant_or_empty_plugin!
add_to_load_path!
register_plugin_as_loaded
evaluate
true
end
def loaded?
initializer.loaded_plugins.include?(name)
end
def plugin_path?
File.directory?(directory) && (has_lib_directory? || has_init_file?)
end
def enabled?
config.plugins.nil? || config.plugins.include?(name)
end
private
def report_nonexistant_or_empty_plugin!
raise LoadError, "No such plugin: #{directory}" unless plugin_path?
end
def lib_path
File.join(directory, 'lib')
end
def init_path
File.join(directory, 'init.rb')
end
def has_lib_directory?
File.directory?(lib_path)
end
def has_init_file?
File.file?(init_path)
end
def add_to_load_path!
# Add lib to load path *after* the application lib, to allow
# application libraries to override plugin libraries.
if has_lib_directory?
application_lib_index = $LOAD_PATH.index(application_library_path) || 0
$LOAD_PATH.insert(application_lib_index + 1, lib_path)
Dependencies.load_paths << lib_path
Dependencies.load_once_paths << lib_path
end
end
def application_library_path
File.join(RAILS_ROOT, 'lib')
end
# Allow plugins to reference the current configuration object
def config
initializer.configuration
end
def register_plugin_as_loaded
initializer.loaded_plugins << name
end
# Evaluate in init.rb
def evaluate
silence_warnings { eval(IO.read(init_path), binding, init_path)} if has_init_file?
end
def <=>(other_plugin_loader)
name <=> other_plugin_loader.name
end
end
end
end
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
\ No newline at end of file
require File.dirname(__FILE__) + '/plugin_test_helper'
class TestPluginLoader < Test::Unit::TestCase
def setup
@initializer = Rails::Initializer.new(Rails::Configuration.new)
@valid_plugin_path = plugin_fixture_path('default/stubby')
@empty_plugin_path = plugin_fixture_path('default/empty')
end
def test_determining_whether_a_given_plugin_is_loaded
plugin_loader = loader_for(@valid_plugin_path)
assert !plugin_loader.loaded?
assert_nothing_raised do
plugin_loader.send(:register_plugin_as_loaded)
end
assert plugin_loader.loaded?
end
def test_if_a_path_is_a_plugin_path
# This is a plugin path, with a lib dir
assert loader_for(@valid_plugin_path).plugin_path?
# This just has an init.rb and no lib dir
assert loader_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).plugin_path?
# This would be a plugin path, but the directory is empty
assert !loader_for(plugin_fixture_path('default/empty')).plugin_path?
# This is a non sense path
assert !loader_for(plugin_fixture_path('default/this_directory_does_not_exist')).plugin_path?
end
def test_if_you_try_to_load_a_non_plugin_path_you_get_a_load_error
# This path is fine so nothing is raised
assert_nothing_raised do
loader_for(@valid_plugin_path).send(:report_nonexistant_or_empty_plugin!)
end
# This is an empty path so it raises
assert_raises(LoadError) do
loader_for(@empty_plugin_path).send(:report_nonexistant_or_empty_plugin!)
end
assert_raises(LoadError) do
loader_for('this_is_not_a_plugin_directory').send(:report_nonexistant_or_empty_plugin!)
end
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."
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
assert_nothing_raised do
loader_for(@valid_plugin_path).load
end
assert added_to_load_path?(@valid_plugin_path)
assert defined?(StubbyMixin)
end
private
def loader_for(path, initializer = @initializer)
Rails::Plugin::Loader.new(initializer, path)
end
def plugin_fixture_path(path)
File.join(plugin_fixture_root_path, path)
end
def added_to_load_path?(path)
$LOAD_PATH.grep(/#{path}/).size == 1
end
end
\ No newline at end of file
require File.dirname(__FILE__) + '/plugin_test_helper'
class TestPluginLocater < 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
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?)
end
def test_no_plugins_are_loaded_if_the_configuration_has_an_empty_plugin_list
only_load_the_following_plugins! []
assert_equal [], @locater.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
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)
assert_raises(LoadError) do
@locater.plugin_names
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
end
private
def new_locater(initializer = @initializer)
Rails::Plugin::Locater.new(initializer)
end
def only_load_the_following_plugins!(plugins)
@initializer.configuration.plugins = plugins
end
end
\ No newline at end of file
$:.unshift File.dirname(__FILE__) + "/../lib"
$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib"
require 'test/unit'
require 'active_support'
require 'initializer'
unless defined?(RAILS_ROOT)
module Rails
class Initializer
RAILS_ROOT = '.'
end
end
end
class PluginTest < Test::Unit::TestCase
class TestConfig < Rails::Configuration
protected
def root_path
File.dirname(__FILE__)
end
end
def setup
@init = Rails::Initializer.new(TestConfig.new)
end
def test_plugin_path?
assert @init.send(:plugin_path?, "#{File.dirname(__FILE__)}/fixtures/plugins/default/stubby")
assert !@init.send(:plugin_path?, "#{File.dirname(__FILE__)}/fixtures/plugins/default/empty")
assert !@init.send(:plugin_path?, "#{File.dirname(__FILE__)}/fixtures/plugins/default/jalskdjflkas")
end
def test_find_plugins
base = "#{File.dirname(__FILE__)}/fixtures/plugins"
default = "#{base}/default"
alt = "#{base}/alternate"
acts = "#{default}/acts"
assert_equal ["#{acts}/acts_as_chunky_bacon"], @init.send(:find_plugins, acts)
assert_equal ["#{acts}/acts_as_chunky_bacon", "#{default}/stubby"], @init.send(:find_plugins, default).sort
assert_equal ["#{alt}/a", "#{acts}/acts_as_chunky_bacon", "#{default}/stubby"], @init.send(:find_plugins, base).sort
end
def test_load_plugin
stubby = "#{File.dirname(__FILE__)}/fixtures/plugins/default/stubby"
expected = ['stubby']
assert @init.send(:load_plugin, stubby)
assert_equal expected, @init.loaded_plugins
assert !@init.send(:load_plugin, stubby)
assert_equal expected, @init.loaded_plugins
assert_raise(LoadError) { @init.send(:load_plugin, 'lakjsdfkasljdf') }
assert_equal expected, @init.loaded_plugins
end
def test_load_default_plugins
assert_loaded_plugins %w(stubby acts_as_chunky_bacon), 'default'
end
def test_load_alternate_plugins
assert_loaded_plugins %w(a), 'alternate'
end
def test_load_plugins_from_two_sources
assert_loaded_plugins %w(a stubby acts_as_chunky_bacon), ['default', 'alternate']
end
def test_load_all_plugins_when_config_plugins_is_nil
@init.configuration.plugins = nil
assert_loaded_plugins %w(a stubby acts_as_chunky_bacon), ['default', 'alternate']
end
def test_load_no_plugins_when_config_plugins_is_empty_array
@init.configuration.plugins = []
assert_loaded_plugins [], ['default', 'alternate']
end
def test_load_only_selected_plugins
plugins = %w(stubby a)
@init.configuration.plugins = plugins
assert_loaded_plugins plugins, ['default', 'alternate']
end
def test_load_plugins_in_order
plugins = %w(stubby acts_as_chunky_bacon a)
@init.configuration.plugins = plugins
assert_plugin_load_order plugins, ['default', 'alternate']
end
def test_raise_error_when_plugin_not_found
@init.configuration.plugins = %w(this_plugin_does_not_exist)
assert_raise(LoadError) { load_plugins(['default', 'alternate']) }
end
protected
def assert_loaded_plugins(plugins, paths)
assert_equal plugins.sort, load_plugins(paths).sort
end
def assert_plugin_load_order(plugins, paths)
assert_equal plugins, load_plugins(paths)
end
def load_plugins(*paths)
@init.configuration.plugin_paths = paths.flatten.map { |p| "#{File.dirname(__FILE__)}/fixtures/plugins/#{p}" }
@init.load_plugins
@init.loaded_plugins
end
end
$:.unshift File.dirname(__FILE__) + "/../lib"
$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib"
require 'test/unit'
require 'active_support'
require 'initializer'
# We need to set RAILS_ROOT if it isn't already set
RAILS_ROOT = '.' unless defined?(RAILS_ROOT)
class Test::Unit::TestCase
def plugin_fixture_root_path
File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
end
end
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册