1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00
puma--puma/lib/mongrel/plugins.rb
zedshaw 9d120bf9b5 New plugin loading system that works entirely with rubygems rather than custo-hacking files.
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@80 19e92222-5c0b-0410-8929-a290d50e31e9
2006-03-05 07:58:18 +00:00

191 lines
6 KiB
Ruby

require 'singleton'
require 'rubygems'
module Mongrel
# Implements the main method of managing plugins for Mongrel.
# "Plugins" in this sense are any classes which get registered
# with Mongrel for possible use when it's operating. These can
# be Handlers, Commands, or other classes. When you create a
# Plugin you register it into a URI-like namespace that makes
# it easy for you (and others) to reference it later during
# configuration.
#
# PluginManager is used as nothing more than a holder of all the
# plugins that have registered themselves. Let's say you have:
#
# class StopNow < Plugin "/commands"
# ...
# end
#
# Then you can get at this plugin with:
#
# cmd = PluginManager.create("/commands/stopnow")
#
# The funky syntax for StopNow is a weird trick borrowed from
# the Camping framework. See the Mongrel::Plugin *function* (yes,
# function). What this basically does is register it
# into the namespace for plugins at /commands. You could go
# as arbitrarily nested as you like.
#
# Why this strange almost second namespace? Why not just use
# the ObjectSpace and/or Modules? The main reason is speed and
# to avoid cluttering the Ruby namespace with what is really a
# configuration statement. This lets implementors put code
# into the Ruby structuring they need, and still have Plugins
# available to Mongrel via simple URI-like names.
#
# The alternative (as pluginfactory does it) is to troll through
# ObjectSpace looking for stuff that *might* be plugins every time
# one is needed. This alternative also means that you are stuck
# naming your commands in specific ways and putting them in specific
# modules in order to configure how Mongrel should use them.
#
# One downside to this is that you need to subclass plugin to
# make it work. In this case use mixins to add other functionality.
class PluginManager
include Singleton
def initialize
@plugins = URIClassifier.new
@loaded_gems = []
end
# Loads all the rubygems that depend on Mongrel so that they
# can be configured into the plugins system. This works by
# checking if the gem depends on Mongrel, and then doing require_gem.
# Since only plugins will configure themselves as plugins then
# everything is safe.
#
# You can also specify an alternative load path from the official
# system gems location. For example you can do the following:
#
# mkdir mygems
# gem install -i mygems fancy_plugin
# mongrel_rails -L mygems start
#
# Which installs the fancy_plugin to your mygems directory and then
# tells mongrel_rails to load from mygems instead.
#
# The excludes list is used to prevent mongrel from loading gem plugins
# that aren't ready yet. In the mongrel_rails script this is used to
# load gems that might need rails configured after rails is ready.
def load(load_path = nil, excludes=[])
sdir = File.join(load_path || Gem.dir, "specifications")
gems = Gem::SourceIndex.from_installed_gems(sdir)
gems.each do |path, gem|
found_one = false
gem.dependencies.each do |dep|
# don't load excluded or already loaded gems
if excludes.include? dep.name or @loaded_gems.include? gem.name
found_one = false
break
elsif dep.name == "mongrel"
found_one = true
end
end
if found_one
STDERR.puts "loading #{gem.name}"
require_gem gem.name
@loaded_gems << gem.name
end
end
end
# Not necessary for you to call directly, but this is
# how Mongrel::PluginBase.inherited actually adds a
# plugin to a category.
def register(category, name, klass)
cat, ignored, map = @plugins.resolve(category)
if not cat or ignored.length > 0
map = {name => klass}
@plugins.register(category, map)
elsif not map
raise "Unknown category #{category}"
else
map[name] = klass
end
end
# Resolves the given name (should include /category/name) to
# find the plugin class and create an instance. It uses
# the same URIClassifier that the rest of Mongrel does so it
# is fast.
def create(name, options = {})
category, plugin, map = @plugins.resolve(name)
if category and plugin and plugin.length > 0 and map[plugin]
map[plugin].new(options)
else
raise "Plugin #{name} does not exist"
end
end
# Returns a map of URIs->[handlers] that you can
# use to investigate available handlers.
def available
map = {}
@plugins.uris.each do |u|
cat, name, plugins = @plugins.resolve(u)
map[cat] ||= []
map[cat] += plugins.keys
end
return map
end
end
# This base class for plugins reallys does nothing
# more than wire up the new class into the right category.
# It is not thread-safe yet but will be soon.
class PluginBase
attr_reader :options
# See Mongrel::Plugin for an explanation.
def PluginBase.inherited(klass)
name = "/" + klass.to_s.downcase
PluginManager.instance.register(@@category, name, klass)
@@category = nil
end
# See Mongrel::Plugin for an explanation.
def PluginBase.category=(category)
@@category = category
end
def initialize(options = {})
@options = options
end
end
# This nifty function works with the PluginBase to give you
# the syntax:
#
# class MyThing < Plugin "/things"
# ...
# end
#
# What it does is temporarily sets the PluginBase.category, and then
# returns PluginBase. Since the next immediate thing Ruby does is
# use this returned class to create the new class, PluginBase.inherited
# gets called. PluginBase.inherited then uses the set category, class name,
# and class to register the plugin in the right way.
def Mongrel::Plugin(c)
PluginBase.category = c
PluginBase
end
end