1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Simplify, remove GemPlugin usage

This commit is contained in:
Evan Phoenix 2011-09-27 09:23:03 -07:00
parent 3e17dee2f9
commit a3fc7ec951
8 changed files with 91 additions and 934 deletions

222
bin/puma
View file

@ -1,223 +1,15 @@
#!/usr/bin/env ruby
#
# Copyright (c) 2011 Evan Phoenix
# Copyright (c) 2005 Zed A. Shaw
#
require 'yaml'
require 'etc'
require 'puma/cli'
require 'puma'
cli = Puma::CLI.new ARGV
require 'puma/gem_plugin'
# require 'ruby-debug'
# Debugger.start
module Puma
class Start < GemPlugin::Plugin "/commands"
include Puma::Command::Base
def configure
options [
['-p', '--port PORT', "Which port to bind to", :@port, 3000],
['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"],
['-l', '--log FILE', "Where to write log messages", :@log_file, "log/puma.log"],
['-P', '--pid FILE', "Where to write the PID", :@pid_file, "log/puma.pid"],
['-n', '--concurrency INT', "Number of concurrent threads to use",
:@concurrency, 16],
['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, Dir.pwd],
['-C', '--config PATH', "Use a config file", :@config_file, nil],
['-S', '--script PATH', "Load the given file as an extra config script", :@config_script, nil],
['-G', '--generate PATH', "Generate a config file for use with -C", :@generate, nil],
['-r', '--rackup PATH', 'Load a specific rackup file', :@rackup_file, "config.ru"],
['', '--user USER', "User to run as", :@user, nil],
['', '--group GROUP', "Group to run as", :@group, nil]
]
end
def validate
if @config_file
valid_exists?(@config_file, "Config file not there: #@config_file")
return false unless @valid
@config_file = File.expand_path(@config_file)
load_config
return false unless @valid
end
@cwd = File.expand_path(@cwd)
valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd"
# Change there to start, then we'll have to come back after daemonize
Dir.chdir(@cwd)
valid_dir? File.dirname(@log_file), "Path to log file not valid: #@log_file"
valid_dir? File.dirname(@pid_file), "Path to pid file not valid: #@pid_file"
valid_exists? @mime_map, "MIME mapping file does not exist: #@mime_map" if @mime_map
valid_exists? @config_file, "Config file not there: #@config_file" if @config_file
valid_dir? File.dirname(@generate), "Problem accessing directory to #@generate" if @generate
valid_user? @user if @user
valid_group? @group if @group
@rackup = ARGV.shift || "config.ru"
valid? File.exists?(@rackup), "Unable to find rackup file"
return @valid
end
def run
if @generate
@generate = File.expand_path(@generate)
@stderr.puts "** Writing config to \"#@generate\"."
open(@generate, "w") {|f| f.write(settings.to_yaml) }
@stderr.puts "** Finished. Run \"puma_rails start -C #@generate\" to use the config file."
exit 0
end
rackup = @rackup
config = Puma::Configurator.new(settings) do |c|
c.log "Starting Puma listening at #{c.defaults[:host]}:#{c.defaults[:port]}"
c.listener do |l|
l.load_rackup rackup
l.load_plugins
if c.defaults[:config_script]
c.log "Loading #{c.defaults[:config_script]} external config script"
c.run_config(c.defaults[:config_script])
end
end
end
config.run
config.log "Puma #{Puma::Const::PUMA_VERSION} available at #{@address}:#{@port}"
config.log "Use CTRL-C to stop."
config.join
end
def load_config
settings = {}
begin
settings = YAML.load_file(@config_file)
ensure
@stderr.puts "** Loading settings from #{@config_file} (they override command line)."
end
settings[:includes] ||= ["puma"]
# Config file settings will override command line settings
settings.each do |key, value|
key = key.to_s
if config_keys.include?(key)
key = 'address' if key == 'host'
self.instance_variable_set("@#{key}", value)
else
failure "Unknown configuration setting: #{key}"
@valid = false
end
end
end
def config_keys
@config_keys ||=
%w(address host port cwd log_file pid_file config_script concurrency user group)
end
def settings
config_keys.inject({}) do |hash, key|
value = self.instance_variable_get("@#{key}")
key = 'host' if key == 'address'
hash[key.to_sym] ||= value
hash
end
end
end
def Puma.send_signal(signal, pid_file)
pid = File.read(pid_file).to_i
print "Sending #{signal} to Puma at PID #{pid}..."
begin
Process.kill(signal, pid)
rescue Errno::ESRCH
puts "Process does not exist. Not running."
end
puts "Done."
end
class Stop < GemPlugin::Plugin "/commands"
include Puma::Command::Base
def configure
options [
['-c', '--chdir PATH', "Change to dir before starting (will be expanded).", :@cwd, "."],
['-f', '--force', "Force the shutdown (kill -9).", :@force, false],
['-w', '--wait SECONDS', "Wait SECONDS before forcing shutdown", :@wait, "0"],
['-P', '--pid FILE', "Where the PID file is located.", :@pid_file, "log/puma.pid"]
]
end
def validate
@cwd = File.expand_path(@cwd)
valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd"
Dir.chdir @cwd
valid_exists? @pid_file, "PID file #@pid_file does not exist. Not running?"
return @valid
end
def run
if @force
@wait.to_i.times do |waiting|
exit(0) if not File.exist? @pid_file
sleep 1
end
Puma.send_signal("KILL", @pid_file) if File.exist? @pid_file
else
Puma.send_signal("TERM", @pid_file)
end
end
end
class Restart < GemPlugin::Plugin "/commands"
include Puma::Command::Base
def configure
options [
['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, '.'],
['-s', '--soft', "Do a soft restart rather than a process exit restart", :@soft, false],
['-P', '--pid FILE', "Where the PID file is located", :@pid_file, "log/puma.pid"]
]
end
def validate
@cwd = File.expand_path(@cwd)
valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd"
Dir.chdir @cwd
valid_exists? @pid_file, "PID file #@pid_file does not exist. Not running?"
return @valid
end
def run
if @soft
Puma.send_signal("HUP", @pid_file)
else
Puma.send_signal("USR2", @pid_file)
end
end
end
begin
cli.run
rescue => e
STDERR.puts e.message
exit 1
end
Puma::GemPlugin::Manager.instance.load "puma" => Puma::GemPlugin::INCLUDE
exit 1 unless Puma::Command::Registry.instance.run(ARGV)

View file

@ -8,25 +8,12 @@ require 'etc'
require 'uri'
require 'stringio'
# Compiled Puma extension
# support multiple ruby version (fat binaries under windows)
begin
RUBY_VERSION =~ /(\d+.\d+)/
require "#{$1}/http11"
rescue LoadError
require 'http11'
end
# Gem conditional loader
require 'puma/gems'
require 'thread'
# Ruby Puma
require 'puma/command'
require 'puma/configurator'
require 'puma/const'
require 'puma/server'
require 'puma/utils'
Puma::Gems.optional "puma_experimental",
">=#{Puma::Const::PUMA_VERSION}"

71
lib/puma/cli.rb Normal file
View file

@ -0,0 +1,71 @@
require 'optparse'
require 'puma/configurator'
require 'puma/const'
module Puma
class CLI
Options = [
['-p', '--port PORT', "Which port to bind to", :@port, 3000],
['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"],
['-n', '--concurrency INT', "Number of concurrent threads to use",
:@concurrency, 16],
]
Banner = "puma <options> <rackup file>"
def initialize(argv, stdout=STDOUT)
@argv = argv
@stdout = stdout
setup_options
end
def setup_options
@options = OptionParser.new do |o|
Options.each do |short, long, help, variable, default|
instance_variable_set(variable, default)
o.on(short, long, help) do |arg|
instance_variable_set(variable, arg)
end
end
end
@options.banner = Banner
@options.on_tail "-h", "--help", "Show help" do
@stdout.puts @options
exit 1
end
end
def run
@options.parse! @argv
@rackup = ARGV.shift || "config.ru"
unless File.exists?(@rackup)
raise "Missing rackup file '#{@rackup}'"
end
settings = {
:host => @address,
:port => @port,
:concurrency => @concurrency,
:stdout => @stdout
}
config = Puma::Configurator.new(settings) do |c|
c.listener do |l|
l.load_rackup @rackup
end
end
config.run
config.log "Puma #{Puma::Const::PUMA_VERSION} available at #{@address}:#{@port}"
config.log "Use CTRL-C to stop."
config.join
end
end
end

View file

@ -1,244 +0,0 @@
# Copyright (c) 2011 Evan Phoenix
# Copyright (c) 2005 Zed A. Shaw
#
require 'singleton'
require 'optparse'
require 'puma/gem_plugin'
module Puma
# Contains all of the various commands that are used with
# Puma servers.
module Command
BANNER = "Usage: puma <command> [options]"
# A Command pattern implementation used to create the set of command available to the user
# from Puma. The script uses objects which implement this interface to do the
# user's bidding.
module Base
attr_reader :valid, :done_validating, :original_args
# Called by the implemented command to set the options for that command.
# Every option has a short and long version, a description, a variable to
# set, and a default value. No exceptions.
def options(opts)
# process the given options array
opts.each do |short, long, help, variable, default|
instance_variable_set(variable, default)
@opt.on(short, long, help) do |arg|
instance_variable_set(variable, arg)
end
end
end
# Called by the subclass to setup the command and parse the argv arguments.
# The call is destructive on argv since it uses the OptionParser#parse! function.
def initialize(options={})
argv = options[:argv] || []
@stderr = options[:stderr] || $stderr
@stdout = options[:stdout] || $stdout
@opt = OptionParser.new
@opt.banner = Puma::Command::BANNER
@valid = true
# this is retarded, but it has to be done this way because -h and -v exit
@done_validating = false
@original_args = argv.dup
configure
# I need to add my own -h definition to prevent the -h by default from exiting.
@opt.on_tail("-h", "--help", "Show this message") do
@done_validating = true
@stdout.puts @opt
end
# I need to add my own -v definition to prevent the -v from exiting by default as well.
@opt.on_tail("--version", "Show version") do
@done_validating = true
@stdout.puts "Version #{Puma::Const::PUMA_VERSION}"
end
@opt.parse! argv
end
def configure
options []
end
# Returns true/false depending on whether the command is configured properly.
def validate
return @valid
end
# Returns a help message. Defaults to OptionParser#help which should be good.
def help
@opt.help
end
# Runs the command doing it's job. You should implement this otherwise it will
# throw a NotImplementedError as a reminder.
def run
raise NotImplementedError
end
# Validates the given expression is true and prints the message if not, exiting.
def valid?(exp, message)
if !@done_validating and !exp
failure message
@valid = false
@done_validating = true
end
end
# Validates that a file exists and if not displays the message
def valid_exists?(file, message)
valid?(file != nil && File.exist?(file), message)
end
# Validates that the file is a file and not a directory or something else.
def valid_file?(file, message)
valid?(file != nil && File.file?(file), message)
end
# Validates that the given directory exists
def valid_dir?(file, message)
valid?(file != nil && File.directory?(file), message)
end
def valid_user?(user)
valid?(@group, "You must also specify a group.")
begin
Etc.getpwnam(user)
rescue
failure "User does not exist: #{user}"
@valid = false
end
end
def valid_group?(group)
valid?(@user, "You must also specify a user.")
begin
Etc.getgrnam(group)
rescue
failure "Group does not exist: #{group}"
@valid = false
end
end
# Just a simple method to display failure until something better is developed.
def failure(message)
@stderr.puts "!!! #{message}"
end
end
# A Singleton class that manages all of the available commands
# and handles running them.
class Registry
def self.instance
@global ||= new
end
def initialize(stdout=STDOUT, stderr=STDERR)
@stdout = stdout
@stderr = stderr
end
# Builds a list of possible commands from the Command derivates list
def commands
pmgr = GemPlugin::Manager.instance
list = pmgr.plugins["/commands"].keys
return list.sort
end
# Prints a list of available commands.
def print_command_list
@stdout.puts "#{Puma::Command::BANNER}\nAvailable commands are:\n\n"
self.commands.each do |name|
if /puma::(.*)/ =~ name
name = $1
end
@stdout.puts " - #{name}\n"
end
@stdout.puts "\nEach command takes -h as an option to get help."
end
BUILTIN_COMMANDS = ["start", "stop", "restart"]
# Runs the args against the first argument as the command name.
# If it has any errors it returns a false, otherwise it return true.
def run(args)
# find the command
cmd_name = args.first
if !cmd_name or (!BUILTIN_COMMANDS.include?(cmd_name) and
File.exists?(cmd_name))
cmd_name = "start"
else
args.shift
end
if !cmd_name or cmd_name == "?" or cmd_name == "help"
print_command_list
return true
elsif cmd_name == "--version"
@stdout.puts "Puma Web Server #{Puma::Const::PUMA_VERSION}"
return true
end
begin
# quick hack so that existing commands will keep working but the
# Puma:: ones can be moved
if BUILTIN_COMMANDS.include? cmd_name
cmd_name = "puma::" + cmd_name
end
opts = {
:argv => args,
:stderr => @stderr,
:stdout => @stdout
}
command = GemPlugin::Manager.instance.create("/commands/#{cmd_name}", opts)
rescue OptionParser::InvalidOption => e
@stderr.puts "#{e} for command '#{cmd_name}'"
@stderr.puts "Try #{cmd_name} -h to get help."
return false
rescue => e
@stderr.puts "ERROR RUNNING '#{cmd_name}': #{e.message} (#{e.class})"
@stderr.puts "Use help command to get help"
return false
end
# Normally the command is NOT valid right after being created
# but sometimes (like with -h or -v) there's no further processing
# needed so the command is already valid so we can skip it.
unless command.done_validating
if command.validate
command.run
else
@stderr.puts "#{cmd_name} reported an error. Use puma #{cmd_name} -h to get help."
return false
end
end
return true
end
end
end
end

View file

@ -1,8 +1,11 @@
require 'yaml'
require 'etc'
require 'rubygems'
require 'rack/builder'
require 'puma/server'
module Puma
# Implements a simple DSL for configuring a Puma server for your
# purposes. More used by framework implementers to setup Puma
@ -46,7 +49,6 @@ module Puma
@listeners = {}
@defaults = defaults
@needs_restart = false
@pid_file = defaults[:pid_file]
if block
yield self
@ -77,39 +79,18 @@ module Puma
end
end
def remove_pid_file
File.unlink(@pid_file) if @pid_file and File.exists?(@pid_file)
end
# Writes the PID file if we're not on Windows.
def write_pid_file
unless RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
log "Writing PID file to #{@pid_file}"
open(@pid_file,"w") {|f| f.write(Process.pid) }
open(@pid_file,"w") do |f|
f.write(Process.pid)
File.chmod(0644, @pid_file)
end
end
end
# This will resolve the given options against the defaults.
# Normally just used internally.
def resolve_defaults(options)
options.merge(@defaults)
end
# Starts a listener block. This is the only one that actually takes
# a block and then you make Configurator.uri calls in order to setup
# your URIs and handlers. If you write your Handlers as GemPlugins
# then you can use load_plugins and plugin to load them.
# Starts a listener block.
#
# It expects the following options (or defaults):
#
# * :host => Host name to bind.
# * :port => Port to bind.
# * :user => User to change to, must have :group as well.
# * :group => Group to change to, must have :user as well.
#
def listener(options={})
raise "Cannot call listener inside another listener block." if (@listener or @listener_name)
@ -137,54 +118,11 @@ module Puma
# Do something with options?
end
# Uses the GemPlugin system to easily load plugins based on their
# gem dependencies. You pass in either an :includes => [] or
# :excludes => [] setting listing the names of plugins to include
# or exclude from the determining the dependencies.
def load_plugins(options={})
ops = resolve_defaults(options)
load_settings = {}
if ops[:includes]
ops[:includes].each do |plugin|
load_settings[plugin] = GemPlugin::INCLUDE
end
end
if ops[:excludes]
ops[:excludes].each do |plugin|
load_settings[plugin] = GemPlugin::EXCLUDE
end
end
GemPlugin::Manager.instance.load(load_settings)
end
# Easy way to load a YAML file and apply default settings.
def load_yaml(file, default={})
default.merge(YAML.load_file(file))
end
# Loads and creates a plugin for you based on the given
# name and configured with the selected options. The options
# are merged with the defaults prior to passing them in.
def plugin(name, options={})
ops = resolve_defaults(options)
GemPlugin::Manager.instance.create(name, ops)
end
# Lets you do redirects easily as described in Puma::RedirectHandler.
# You use it inside the configurator like this:
#
# redirect("/test", "/to/there") # simple
# redirect("/to", /t/, 'w') # regexp
# redirect("/hey", /(w+)/) {|match| ...} # block
#
def redirect(from, pattern, replacement = nil, &block)
uri from, :handler => Puma::RedirectHandler.new(pattern, replacement, &block)
end
# Works like a meta run method which goes through all the
# configured listeners. Use the Configurator.join method
# to prevent Ruby from exiting until each one is done.
@ -224,21 +162,15 @@ module Puma
# It only configures if the platform is not win32 and doesn't do
# a HUP signal since this is typically framework specific.
#
# Requires a :pid_file option given to Configurator.new to indicate a file to delete.
# It sets the PumaConfig.needs_restart attribute if
# the start command should reload. It's up to you to detect this
# and do whatever is needed for a "restart".
#
# This command is safely ignored if the platform is win32 (with a warning)
def setup_signals(options={})
ops = resolve_defaults(options)
# forced shutdown, even if previously restarted (actually just like TERM but for CTRL-C)
# forced shutdown, even if previously restarted (actually just like TERM
# but for CTRL-C)
#
trap("INT") { log "INT signal received."; stop(false) }
# clean up the pid file always
at_exit { remove_pid_file }
unless RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
# graceful shutdown
trap("TERM") { log "TERM signal received."; stop }

View file

@ -1,302 +0,0 @@
require 'rubygems'
# Implements a dynamic plugin loading, configuration, and discovery system
# based on RubyGems and a simple additional name space that looks like a URI.
#
# A plugin is created and put into a category with the following code:
#
# class MyThing < GemPlugin::Plugin "/things"
# ...
# end
#
# What this does is sets up your MyThing in the plugin registry via GemPlugin::Manager.
# You can then later get this plugin with GemPlugin::Manager.create("/things/mything")
# and can also pass in options as a second parameter.
#
# This isn't such a big deal, but the power is really from the GemPlugin::Manager.load
# method. This method will go through the installed gems and require_gem any
# that depend on the gem_plugin RubyGem. You can arbitrarily include or exclude
# gems based on what they also depend on, thus letting you load these gems when appropriate.
#
# Since this system was written originally for the Mongrel project that'll be the
# best example of using it.
#
# Imagine you have a neat plugin for Mongrel called snazzy_command that gives the
# mongrel_rails a new command snazzy (like: mongrel_rails snazzy). You'd like
# people to be able to grab this plugin if they want and use it, because it's snazzy.
#
# First thing you do is create a gem of your project and make sure that it depends
# on "mongrel" AND "gem_plugin". This signals to the GemPlugin system that this is
# a plugin for mongrel.
#
# Next you put this code into a file like lib/init.rb (can be anything really):
#
# class Snazzy < GemPlugin::Plugin "/commands"
# ...
# end
#
# Then when you create your gem you have the following bits in your Rakefile:
#
# spec.add_dependency('mongrel', '>= 0.3.9')
# spec.add_dependency('gem_plugin', '>= 0.1')
# spec.autorequire = 'init.rb'
#
# Finally, you just have to now publish this gem for people to install and Mongrel
# will "magically" be able to install it.
#
# The "magic" part though is pretty simple and done via the GemPlugin::Manager.load
# method. Read that to see how it is really done.
module Puma::GemPlugin
EXCLUDE = true
INCLUDE = false
class PluginNotLoaded < StandardError; end
# This class is used by people who use gem plugins (but don't necessarily make them)
# to add plugins to their own systems. It provides a way to load plugins, list them,
# and create them as needed.
#
# It is a singleton so you use like this: GemPlugins::Manager.instance.load
class Manager
attr_reader :plugins
attr_reader :gems
def self.instance
@instance ||= new
end
def initialize
# plugins that have been loaded
@plugins = {}
# keeps track of gems which have been loaded already by the manager *and*
# where they came from so that they can be referenced later
@gems = {}
end
def every_gem
if Gem::VERSION >= '1.8.0'
gems = Gem::Specification.to_a
gems.each { |g| yield g }
else
gems = Gem::SourceIndex.from_gems_in(sdirs)
gems.each do |path, gem|
yield gem
end
end
end
# Responsible for going through the list of available gems and loading
# any plugins requested. It keeps track of what it's loaded already
# and won't load them again.
#
# It accepts one parameter which is a hash of gem depends that should include
# or exclude a gem from being loaded. A gem must depend on gem_plugin to be
# considered, but then each system has to add it's own INCLUDE to make sure
# that only plugins related to it are loaded.
#
# An example again comes from Mongrel. In order to load all Mongrel plugins:
#
# GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE
#
# Which will load all plugins that depend on mongrel AND gem_plugin. Now, one
# extra thing we do is we delay loading Rails Mongrel plugins until after rails
# is configured. Do do this the mongrel_rails script has:
#
# GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE
# The only thing to remember is that this is saying "include a plugin if it
# depends on gem_plugin, mongrel, but NOT rails". If a plugin also depends on other
# stuff then it's loaded just fine. Only gem_plugin, mongrel, and rails are
# ever used to determine if it should be included.
#
# NOTE: Currently RubyGems will run autorequire on gems that get required AND
# on their dependencies. This really messes with people running edge rails
# since activerecord or other stuff gets loaded for just touching a gem plugin.
# To prevent this load requires the full path to the "init.rb" file, which
# avoids the RubyGems autorequire magic.
def load(needs = {})
needs = needs.merge({"gem_plugin" => INCLUDE})
every_gem do |gem|
# don't load gems more than once
next if @gems.has_key? gem.name
check = needs.dup
# rolls through the depends and inverts anything it finds
gem.dependencies.each do |dep|
# this will fail if a gem is depended more than once
if check.has_key? dep.name
check[dep.name] = !check[dep.name]
end
end
# now since excluded gems start as true, inverting them
# makes them false so we'll skip this gem if any excludes are found
if (check.select {|name,test| !test}).length == 0
# looks like no needs were set to false, so it's good
# Previously was set wrong, we already have the correct gem path!
gem_dir = ""
Gem.path.each do |gem_path|
gem_dir = File.join(gem_path, "gems", path)
break if File.exists?(gem_dir)
end
require File.join(gem_dir, "lib", gem.name, "init.rb")
@gems[gem.name] = gem_dir
end
end
return nil
end
# Not necessary for you to call directly, but this is
# how GemPlugin::Base.inherited actually adds a
# plugin to a category.
def register(category, name, klass)
@plugins[category] ||= {}
@plugins[category][name.downcase] = klass
end
# Resolves the given name (should include /category/name) to
# find the plugin class and create an instance. You can
# pass a second hash option that is then given to the Plugin
# to configure it.
def create(name, options = {})
last_slash = name.rindex("/")
category = name[0 ... last_slash]
plugin = name[last_slash .. -1]
map = @plugins[category]
if not map
raise "Plugin category #{category} does not exist"
elsif not map.has_key? plugin
raise "Plugin #{plugin} does not exist in category #{category}"
else
map[plugin].new(options)
end
end
# Simply says whether the given gem has been loaded yet or not.
def loaded?(gem_name)
@gems.has_key? gem_name
end
# GemPlugins can have a 'resources' directory which is packaged with them
# and can hold any data resources the plugin may need. The main problem
# is that it's difficult to figure out where these resources are
# actually located on the file system. The resource method tries to
# locate the real path for a given resource path.
#
# Let's say you have a 'resources/stylesheets/default.css' file in your
# gem distribution, then finding where this file really is involves:
#
# Manager.instance.resource("mygem", "/stylesheets/default.css")
#
# You either get back the full path to the resource or you get a nil
# if it doesn't exist.
#
# If you request a path for a GemPlugin that hasn't been loaded yet
# then it will throw an PluginNotLoaded exception. The gem may be
# present on your system in this case, but you just haven't loaded
# it with Manager.instance.load properly.
def resource(gem_name, path)
if not loaded? gem_name
raise PluginNotLoaded.new("Plugin #{gem_name} not loaded when getting resource #{path}")
end
file = File.join(@gems[gem_name], "resources", path)
if File.exist? file
return file
else
return nil
end
end
# While Manager.resource will find arbitrary resources, a special
# case is when you need to load a set of configuration defaults.
# GemPlugin normalizes this to be if you have a file "resources/defaults.yaml"
# then you'll be able to load them via Manager.config.
#
# How you use the method is you get the options the user wants set, pass
# them to Manager.instance.config, and what you get back is a new Hash
# with the user's settings overriding the defaults.
#
# opts = Manager.instance.config "mygem", :age => 12, :max_load => .9
#
# In the above case, if defaults had {:age => 14} then it would be
# changed to 12.
#
# This loads the .yaml file on the fly every time, so doing it a
# whole lot is very stupid. If you need to make frequent calls to
# this, call it once with no options (Manager.instance.config) then
# use the returned defaults directly from then on.
def config(gem_name, options={})
config_file = Manager.instance.resource(gem_name, "/defaults.yaml")
if config_file
begin
defaults = YAML.load_file(config_file)
return defaults.merge(options)
rescue
raise "Error loading config #{config_file} for gem #{gem_name}"
end
else
return options
end
end
end
# This base class for plugins really does nothing
# more than wire up the new class into the right category.
# It is not thread-safe yet but will be soon.
class Base
attr_reader :options
# See Mongrel::Plugin for an explanation.
def Base.inherited(klass)
name = "/" + klass.to_s.downcase
Manager.instance.register(@@category, name, klass)
@@category = nil
end
# See Mongrel::Plugin for an explanation.
def Base.category=(category)
@@category = category
end
def initialize(options = {})
@options = options
end
end
# This nifty function works with the GemPlugin::Base to give you
# the syntax:
#
# class MyThing < GemPlugin::Plugin "/things"
# ...
# end
#
# What it does is temporarily sets the GemPlugin::Base.category, and then
# returns GemPlugin::Base. Since the next immediate thing Ruby does is
# use this returned class to create the new class, GemPlugin::Base.inherited
# gets called. GemPlugin::Base.inherited then uses the set category, class name,
# and class to register the plugin in the right way.
def self.Plugin(c)
Base.category = c
Base
end
end

View file

@ -1,5 +1,11 @@
require 'rack'
require 'puma/thread_pool'
require 'puma/const'
require 'http11'
require 'socket'
module Puma
class Server

View file

@ -1,85 +0,0 @@
# Copyright (c) 2011 Evan Phoenix
# Copyright (c) 2005 Zed A. Shaw
require 'test/testhelp'
class TestCommand < Puma::GemPlugin::Plugin "/commands"
include Puma::Command::Base
def configure
options [
["-e", "--environment ENV", "Rails environment to run as", :@environment, ENV['RAILS_ENV'] || "development"],
['', '--user USER', "User to run as", :@user, nil],
["-d", "--daemonize", "Whether to run in the background or not", :@daemon, false],
["-x", "--test", "Used to let the test run failures", :@test, false],
]
end
def validate
valid_dir? ".", "Can't validate current directory."
valid_exists? "Rakefile", "Rakefile not there, test is invalid."
if @test
valid_exist? "BADFILE", "Yeah, badfile"
valid_file? "BADFILE", "Not even a file"
valid_dir? "BADDIR", "No dir here"
valid? false, "Total failure"
end
return @valid
end
def run
$test_command_ran = true
end
end
class CommandTest < Test::Unit::TestCase
def setup
$test_command_ran = false
@stdout = StringIO.new
@stderr = StringIO.new
end
def teardown
end
def run_cmd(args)
Puma::Command::Registry.new(@stdout, @stderr).run args
end
def test_run_command
redirect_test_io do
run_cmd ["testcommand"]
assert $test_command_ran, "command didn't run"
end
end
def test_command_error
redirect_test_io do
run_cmd ["crapcommand"]
end
end
def test_command_listing
redirect_test_io do
run_cmd ["help"]
end
end
def test_options
redirect_test_io do
run_cmd ["testcommand","-h"]
run_cmd ["testcommand","--help"]
run_cmd ["testcommand","-e","test","-d","--user"]
end
end
def test_version
redirect_test_io do
run_cmd ["testcommand", "--version"]
end
end
end