From b0e1430c523cf09155f72d5996be2cc2bf8e2eb7 Mon Sep 17 00:00:00 2001 From: Marcel Molina Date: Fri, 2 Mar 2007 00:20:32 +0000 Subject: 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 --- railties/lib/initializer.rb | 98 ++++++++---------------------------------- railties/lib/plugin/loader.rb | 95 ++++++++++++++++++++++++++++++++++++++++ railties/lib/plugin/locater.rb | 88 +++++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 81 deletions(-) create mode 100644 railties/lib/plugin/loader.rb create mode 100644 railties/lib/plugin/locater.rb (limited to 'railties/lib') diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index b535c8624b..b2b7b3b545 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -2,6 +2,9 @@ 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/loader') + RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV) @@ -184,19 +187,10 @@ module Rails # 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 @@ module Rails 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 path unless already loaded. - # - # Each plugin is initialized: - # * add its +lib+ directory, if present, to the beginning of the load path - # * evaluate init.rb if present - # - # Returns true if the plugin is successfully loaded or - # false if it is already loaded (similar to Kernel#require). - # Raises LoadError 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 @@ module Rails # The path to the root of the plugins directory. By default, it is in # vendor/plugins. 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 @@ module Rails 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 @@ module Rails def default_plugin_paths ["#{root_path}/vendor/plugins"] end + + def default_plugin_loader + Plugin::Loader + end end end diff --git a/railties/lib/plugin/loader.rb b/railties/lib/plugin/loader.rb new file mode 100644 index 0000000000..c3116d84c5 --- /dev/null +++ b/railties/lib/plugin/loader.rb @@ -0,0 +1,95 @@ +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 diff --git a/railties/lib/plugin/locater.rb b/railties/lib/plugin/locater.rb new file mode 100644 index 0000000000..4a7aa774ee --- /dev/null +++ b/railties/lib/plugin/locater.rb @@ -0,0 +1,88 @@ +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 -- cgit v1.2.3