From 6c434e8b8e420c76f86ba6605e98426fddca5031 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 7 Nov 2005 08:09:38 +0000 Subject: New configuration option config.plugin_paths which may be a single path like the default 'vendor/plugins' or an array of paths: ['vendor/plugins', 'lib/plugins']. Plugins are discovered in nested paths, so you can organize your plugins directory as you like. Refactor load_plugin from load_plugins. Simplify initializer unit test. Closes #2757. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2904 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- railties/CHANGELOG | 6 + railties/lib/initializer.rb | 135 ++++++++++++++------- .../test/fixtures/environment_with_constant.rb | 2 +- .../test/fixtures/plugins/default/stubby/init.rb | 2 + .../plugins/default/stubby/lib/stubby_mixin.rb | 2 + railties/test/initializer_test.rb | 21 ++-- railties/test/plugin_test.rb | 72 +++++++++++ 7 files changed, 186 insertions(+), 54 deletions(-) create mode 100644 railties/test/fixtures/plugins/default/stubby/init.rb create mode 100644 railties/test/fixtures/plugins/default/stubby/lib/stubby_mixin.rb create mode 100644 railties/test/plugin_test.rb diff --git a/railties/CHANGELOG b/railties/CHANGELOG index f3c9e0eb96..9c6e329c97 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,11 @@ *SVN* +* New configuration option config.plugin_paths which may be a single path like the default 'vendor/plugins' or an array of paths: ['vendor/plugins', 'lib/plugins']. [Jeremy Kemper] + +* Plugins are discovered in nested paths, so you can organize your plugins directory as you like. [Jeremy Kemper] + +* Refactor load_plugin from load_plugins. #2757 [alex.r.moon@gmail.com] + * Make use of silence_stderr in script/lighttpd, script/plugin, and Rails::Info [Sam Stephenson] * Enable HTTP installation of plugins when svn isn't avaialable. Closes #2661. [Chad Fowler] diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index 9035138779..f0eceb0a4e 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -1,4 +1,5 @@ require 'logger' +require 'set' RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV) @@ -22,7 +23,10 @@ module Rails class Initializer # The Configuration instance used by this Initializer instance. attr_reader :configuration - + + # The set of loaded plugins. + attr_reader :loaded_plugins + # Runs the initializer. By default, this will invoke the #process method, # which simply executes all of the initialization routines. Alternately, # you can specify explicitly which initialization routine you want: @@ -40,8 +44,9 @@ module Rails # instance. def initialize(configuration) @configuration = configuration + @loaded_plugins = Set.new end - + # Sequentially step through all of the available initialization routines, # in order: # @@ -120,32 +125,19 @@ module Rails def load_framework_info require 'rails_info' end - - # Loads all plugins in the vendor/plugins directory. Each - # subdirectory of vendor/plugins is inspected as follows: + + # Loads all plugins in config.plugin_paths. plugin_paths + # defaults to vendor/plugins but may also be set to a list of + # paths, such as + # config.plugin_paths = ['lib/plugins', 'vendor/plugins'] # - # * if the directory has a +lib+ subdirectory, add it to the load path - # * if the directory contains an init.rb file, read it in and - # eval it. + # Each plugin discovered in plugin_paths is initialized: + # * add its +lib+ directory, if present, to the beginning of the load path + # * evaluate init.rb if present # # After all plugins are loaded, duplicates are removed from the load path. def load_plugins - config = configuration - - Dir.glob("#{configuration.plugins_path}/*") do |directory| - next if File.basename(directory)[0] == ?. || !File.directory?(directory) - - if File.directory?("#{directory}/lib") - $LOAD_PATH.unshift "#{directory}/lib" - end - - if File.exist?("#{directory}/init.rb") - silence_warnings do - eval(IO.read("#{directory}/init.rb"), binding) - end - end - end - + find_plugins(configuration.plugin_paths).each { |path| load_plugin path } $LOAD_PATH.uniq! end @@ -260,8 +252,62 @@ module Rails end 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 which 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 + 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 + + # 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(path) + name = File.basename(path) + return false if loaded_plugins.include?(name) + + # Catch nonexistent and empty plugins. + raise LoadError, "No such plugin: #{path}" unless plugin_path?(path) + + lib_path = File.join(path, 'lib') + init_path = File.join(path, 'init.rb') + has_lib = File.directory?(lib_path) + has_init = File.file?(init_path) + + # Add lib to load path. + $LOAD_PATH.unshift(lib_path) if has_lib + + # Evaluate init.rb. + silence_warnings { eval(IO.read(init_path), binding) } if has_init + + # Add to set of loaded plugins. + loaded_plugins << name + true + end end - + # The Configuration class holds all the parameters for the Initializer and # ships with defaults that suites most Rails applications. But it's possible # to overwrite everything. Usually, you'll create an Configuration file @@ -339,6 +385,10 @@ module Rails # any method of +nil+. Set to +false+ for the standard Ruby behavior. attr_accessor :whiny_nils + # The path to the root of the plugins directory. By default, it is in + # vendor/plugins. + attr_accessor :plugin_paths + # Create a new Configuration instance, initialized with the default # values. def initialize @@ -351,8 +401,9 @@ module Rails self.cache_classes = default_cache_classes self.breakpoint_server = default_breakpoint_server self.whiny_nils = default_whiny_nils + self.plugin_paths = default_plugin_paths self.database_configuration_file = default_database_configuration_file - + for framework in default_frameworks self.send("#{framework}=", OrderedOptions.new) end @@ -368,15 +419,9 @@ module Rails # The path to the current environment's file (development.rb, etc.). By # default the file is at config/environments/#{environment}.rb. def environment_path - "#{RAILS_ROOT}/config/environments/#{environment}.rb" + "#{root_path}/config/environments/#{environment}.rb" end - # The path to the root of the plugins directory. By default, it is in - # vendor/plugins. - def plugins_path - "#{RAILS_ROOT}/vendor/plugins" - end - # Return the currently selected environment. By default, it returns the # value of the +RAILS_ENV+ constant. def environment @@ -384,17 +429,21 @@ module Rails end private + def root_path + ::RAILS_ROOT + end + def default_frameworks [ :active_record, :action_controller, :action_view, :action_mailer, :action_web_service ] end def default_load_paths - paths = ["#{RAILS_ROOT}/test/mocks/#{environment}"] + paths = ["#{root_path}/test/mocks/#{environment}"] # Then model subdirectories. # TODO: Don't include .rb models as load paths - paths.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) - paths.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) + paths.concat(Dir["#{root_path}/app/models/[_a-z]*"]) + paths.concat(Dir["#{root_path}/components/[_a-z]*"]) # Followed by the standard includes. # TODO: Don't include dirs for frameworks that are not used @@ -416,11 +465,11 @@ module Rails vendor/rails/activerecord/lib vendor/rails/actionmailer/lib vendor/rails/actionwebservice/lib - ).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } + ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) } end def default_log_path - File.join(RAILS_ROOT, 'log', "#{environment}.log") + File.join(root_path, 'log', "#{environment}.log") end def default_log_level @@ -428,15 +477,15 @@ module Rails end def default_database_configuration_file - File.join(RAILS_ROOT, 'config', 'database.yml') + File.join(root_path, 'config', 'database.yml') end def default_view_path - File.join(RAILS_ROOT, 'app', 'views') + File.join(root_path, 'app', 'views') end def default_controller_paths - [ File.join(RAILS_ROOT, 'app', 'controllers'), File.join(RAILS_ROOT, 'components') ] + [ File.join(root_path, 'app', 'controllers'), File.join(root_path, 'components') ] end def default_dependency_mechanism @@ -454,6 +503,10 @@ module Rails def default_whiny_nils false end + + def default_plugin_paths + ["#{root_path}/vendor/plugins"] + end end end diff --git a/railties/test/fixtures/environment_with_constant.rb b/railties/test/fixtures/environment_with_constant.rb index 96efe9c23a..1c7120655e 100644 --- a/railties/test/fixtures/environment_with_constant.rb +++ b/railties/test/fixtures/environment_with_constant.rb @@ -1 +1 @@ -SET_FROM_ENV = "success" \ No newline at end of file +$initialize_test_set_from_env = "success" diff --git a/railties/test/fixtures/plugins/default/stubby/init.rb b/railties/test/fixtures/plugins/default/stubby/init.rb new file mode 100644 index 0000000000..fc9470fd97 --- /dev/null +++ b/railties/test/fixtures/plugins/default/stubby/init.rb @@ -0,0 +1,2 @@ +require 'stubby_mixin' +raise unless defined? StubbyMixin diff --git a/railties/test/fixtures/plugins/default/stubby/lib/stubby_mixin.rb b/railties/test/fixtures/plugins/default/stubby/lib/stubby_mixin.rb new file mode 100644 index 0000000000..2d569e5002 --- /dev/null +++ b/railties/test/fixtures/plugins/default/stubby/lib/stubby_mixin.rb @@ -0,0 +1,2 @@ +module StubbyMixin +end diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb index 4c25ad8a0a..86f7569c50 100644 --- a/railties/test/initializer_test.rb +++ b/railties/test/initializer_test.rb @@ -15,22 +15,19 @@ class InitializerTest < Test::Unit::TestCase def environment_path @envpath end - end - def setup - Object.const_set(:RAILS_ROOT, "") rescue nil - end - - def teardown - Object.remove_const(:RAILS_ROOT) rescue nil + protected + def root_path + File.dirname(__FILE__) + end end - + def test_load_environment_with_constant config = ConfigurationMock.new("#{File.dirname(__FILE__)}/fixtures/environment_with_constant.rb") + assert_nil $initialize_test_set_from_env Rails::Initializer.run(:load_environment, config) - assert Object.const_defined?(:SET_FROM_ENV) - assert_equal "success", SET_FROM_ENV + assert_equal "success", $initialize_test_set_from_env ensure - Object.remove_const(:SET_FROM_ENV) rescue nil + $initialize_test_set_from_env = nil end -end \ No newline at end of file +end diff --git a/railties/test/plugin_test.rb b/railties/test/plugin_test.rb new file mode 100644 index 0000000000..54eea13538 --- /dev/null +++ b/railties/test/plugin_test.rb @@ -0,0 +1,72 @@ +$:.unshift File.dirname(__FILE__) + "/../lib" +$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib" + +require 'test/unit' +require 'active_support' +require 'initializer' + +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 = Set.new(['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 + + protected + def assert_loaded_plugins(plugins, path) + assert_equal Set.new(plugins), load_plugins(path) + 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 -- cgit v1.2.3