Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial import.

git-svn-id: https://camping-picnic.googlecode.com/svn/trunk@4 c3e707da-6440-0410-ae22-65d24d045266
  • Loading branch information...
commit 242772a5c58987a8ceea07f311331f36c3b2b433 1 parent 9c3ad38
Matt Zukowski authored
Showing with 4,286 additions and 0 deletions.
  1. +5 −0 .loadpath
  2. +17 −0 .project
  3. 0  CHANGELOG.txt
  4. 0  History.txt
  5. +165 −0 LICENSE.txt
  6. +9 −0 Manifest.txt
  7. +17 −0 README.txt
  8. +56 −0 Rakefile
  9. +80 −0 lib/picnic.rb
  10. +90 −0 lib/picnic/conf.rb
  11. +21 −0 lib/picnic/controllers.rb
  12. +162 −0 lib/picnic/postambles.rb
  13. +181 −0 lib/picnic/service_control.rb
  14. +33 −0 lib/picnic/utils.rb
  15. +9 −0 lib/picnic/version.rb
  16. +1,585 −0 setup.rb
  17. +11 −0 test/picnic_test.rb
  18. +2 −0  test/test_helper.rb
  19. +99 −0 vendor/camping-1.5.180/CHANGELOG
  20. +18 −0 vendor/camping-1.5.180/COPYING
  21. +119 −0 vendor/camping-1.5.180/README
  22. +117 −0 vendor/camping-1.5.180/Rakefile
  23. +762 −0 vendor/camping-1.5.180/lib/camping-unabridged.rb
  24. +55 −0 vendor/camping-1.5.180/lib/camping.rb
  25. +78 −0 vendor/camping-1.5.180/lib/camping/db.rb
  26. +244 −0 vendor/camping-1.5.180/lib/camping/fastcgi.rb
  27. +163 −0 vendor/camping-1.5.180/lib/camping/reloader.rb
  28. +123 −0 vendor/camping-1.5.180/lib/camping/session.rb
  29. +65 −0 vendor/camping-1.5.180/lib/camping/webrick.rb
5 .loadpath
View
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<loadpath>
+ <pathentry path="" type="src"/>
+ <pathentry path="org.rubypeople.rdt.launching.RUBY_CONTAINER" type="con"/>
+</loadpath>
17 .project
View
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>picnic</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.rubypeople.rdt.core.rubybuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.rubypeople.rdt.core.rubynature</nature>
+ </natures>
+</projectDescription>
0  CHANGELOG.txt
View
No changes.
0  History.txt
View
No changes.
165 LICENSE.txt
View
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
9 Manifest.txt
View
@@ -0,0 +1,9 @@
+Rakefile
+README.txt
+CHANGELOG.txt
+Manifest.txt
+setup.rb
+lib/picnic/version.rb
+lib/picnic.rb
+test/test_helper.rb
+test/picnic_test.rb
17 README.txt
View
@@ -0,0 +1,17 @@
+README for picnic
+=================
+
+----
+
+picnic is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published
+by the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+Reststop is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
56 Rakefile
View
@@ -0,0 +1,56 @@
+require 'rubygems'
+require 'rake'
+require 'rake/clean'
+require 'rake/testtask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'rake/rdoctask'
+require 'rake/contrib/rubyforgepublisher'
+require 'fileutils'
+require 'hoe'
+include FileUtils
+require File.join(File.dirname(__FILE__), 'lib', 'picnic', 'version')
+
+AUTHOR = "Matt Zukowski"
+EMAIL = "matt@roughest.net"
+DESCRIPTION = "Camping for sissies"
+GEM_NAME = "picnic"
+RUBYFORGE_PROJECT = "picnic"
+HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
+
+
+NAME = "picnic"
+REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
+VERS = ENV['VERSION'] || (Picnic::VERSION::STRING + (REV ? ".#{REV}" : ""))
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
+RDOC_OPTS = ['--quiet', '--title', "picnic #{VERS} documentation",
+ "--opname", "index.html",
+ "--line-numbers",
+ "--main", "README",
+ "--inline-source"]
+
+class Hoe
+ def extra_deps
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
+ end
+end
+
+# Generate all the Rake tasks
+# Run 'rake -T' to see list of generated tasks (from gem root directory)
+hoe = Hoe.new(GEM_NAME, VERS) do |p|
+ p.author = AUTHOR
+ p.description = DESCRIPTION
+ p.email = EMAIL
+ p.summary = DESCRIPTION
+ p.url = HOMEPATH
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
+ p.test_globs = ["test/**/*_test.rb"]
+ p.clean_globs = CLEAN #An array of file patterns to delete on clean.
+
+ # == Optional
+ #p.changes - A description of the release's latest changes.
+ #p.extra_deps - An array of rubygem dependencies.
+ #p.spec_extras - A hash of extra values to set in the gemspec.
+
+ p.extra_deps = ['camping']
+end
80 lib/picnic.rb
View
@@ -0,0 +1,80 @@
+$: << File.dirname(File.expand_path(__FILE__))
+$: << File.dirname(File.expand_path(__FILE__))+"/../vendor/camping-1.5.180/lib"
+
+
+require 'camping'
+
+
+require 'active_support' unless Object.const_defined?(:ActiveSupport)
+
+require 'picnic/utils'
+require 'picnic/conf'
+require 'picnic/postambles'
+require 'picnic/controllers'
+
+
+
+
+class Module
+
+ def picnic!
+ include Picnic
+
+ puts "Adding Picnic functionality to #{self}..."
+ self.module_eval do
+ def init_logger
+ puts "Initializing #{self} logger..."
+ $LOG = Picnic::Utils::Logger.new(Picnic::Conf.log[:file])
+ $LOG.level = "Picnic::Utils::Logger::#{Picnic::Conf.log[:level]}".constantize
+ end
+ module_function :init_logger
+
+ def init_db_logger
+ begin
+ if Picnic::Conf.db_log
+ log_file = Picnic::Conf.db_log[:file] || 'fluxr_db.log'
+ self::Models::Base.logger = Logger.new(log_file)
+ self::Models::Base.logger.level = "Picnic::Utils::Logger::#{Picnic::Conf.db_log[:level] || 'DEBUG'}".constantize
+ end
+ rescue Errno::EACCES => e
+ $LOG.warn "Can't write to database log file at '#{log_file}': #{e}"
+ end
+ end
+ module_function :init_db_logger
+
+ def start_picnic
+ #Fluxr::Models::Base.establish_connection(Fluxr::Conf.database)
+ #Fluxr.init_db_logger unless Fluxr::Conf.server.to_s == 'mongrel'
+
+ require 'picnic/postambles'
+ self.extend Picnic::Postambles
+
+ if $PID_FILE && (Picnic::Conf.server.to_s != 'mongrel' || Picnic::Conf.server.to_s != 'webrick')
+ $LOG.warn("Unable to create a pid file. You must use mongrel or webrick for this feature.")
+ end
+
+ # begin
+ raise NoMethodError if Picnic::Conf.server.nil?
+ send(Picnic::Conf.server)
+ # rescue NoMethodError => e
+ # # FIXME: this rescue can sometime report the incorrect error messages due to other underlying problems
+ # # raising a NoMethodError
+ # if Fluxr::Conf.server
+ # raise e, "The server setting '#{Fluxr::Conf.server}' in your config.yml file is invalid."
+ # else
+ # raise e, "You must have a 'server' setting in your config.yml file. Please see the Fluxr documentation."
+ # end
+ # end
+ end
+ module_function :start_picnic
+
+ c = File.dirname(File.expand_path(__FILE__))+'/picnic/controllers.rb'
+ p = IO.read(c).gsub("Picnic", self.to_s)
+ eval p, TOPLEVEL_BINDING
+
+ end
+
+ Picnic::Conf.load(self)
+ init_logger
+ end
+end
90 lib/picnic/conf.rb
View
@@ -0,0 +1,90 @@
+module Picnic
+ module Conf
+ DEFAULTS = {
+ :log => {:file => STDOUT, :level => 'DEBUG'},
+ :uri_path => "/",
+ :base_dir => "."
+ }
+
+ def [](key)
+ $CONF[key] || DEFAULTS[key]
+ end
+ module_function "[]".intern
+
+ def self.method_missing(method, *args)
+ self[method]
+ end
+
+ def self.example_config_file_path
+ caller.last =~ /^(.*?):\d+$/
+ File.dirname(File.expand_path($1))+'/../config.example.yml'
+ end
+
+ def self.copy_example_config_file(app, dest_conf_file)
+ require 'fileutils'
+
+ example_conf_file = example_config_file_path
+
+ puts "\n#{app.to_s.upcase} SERVER HAS NOT YET BEEN CONFIGURED!!!\n"
+ puts "\nAttempting to copy sample configuration from '#{example_conf_file}' to '#{dest_conf_file}'...\n"
+
+ unless File.exists? example_conf_file
+ puts "\nThe example conf file does not exist! The author of #{app} may have forgotten to include it. You'll have to create the config file manually.\n"
+ exit 2
+ end
+
+ begin
+ dest_conf_file_dir = File.dirname(dest_conf_file)
+ FileUtils.mkpath(dest_conf_file_dir) unless File.exists? dest_conf_file_dir
+ FileUtils.cp(example_conf_file, dest_conf_file)
+ rescue Errno::EACCES
+ puts "\nIt appears that you do not have permissions to create the '#{dest_conf_file}' file. Try running this command using sudo (as root).\n"
+ exit 2
+ rescue => e
+ puts "\nFor some reason the '#{dest_conf_file}' file could not be created (#{e})."
+ puts "You'll have to copy the file manually. Use '#{example_conf_file}' as a template.\n"
+ exit 2
+ end
+
+ puts "\nA sample configuration has been created for you in '#{dest_conf_file}'. Please edit this file to" +
+ " suit your needs and then run #{app} again.\n"
+ exit 1
+ end
+
+ def self.load(app)
+
+ conf_file = "/etc/#{app.to_s.downcase}/config.yml"
+
+ puts "Loading configuration for #{app} from '#{conf_file}'..."
+
+ begin
+ conf_file = etc_conf = conf_file
+ unless File.exists? conf_file
+ # can use local config.yml file in case we're running non-gem installation
+ conf_file = File.dirname(File.expand_path(__FILE__))+"/../../config.yml"
+ end
+
+ unless File.exists? conf_file
+ copy_example_config_file(app, etc_conf)
+ end
+
+ loaded_conf = HashWithIndifferentAccess.new(YAML.load_file(conf_file))
+
+ if $CONF
+ $CONF = loaded_conf.merge $CONF
+ else
+ $CONF = loaded_conf
+ end
+
+ puts "\nLoaded configuration: #{$CONF.to_yaml}"
+ puts
+
+ rescue
+ raise "Your #{app} configuration may be invalid."+
+ " Please double-check check your config.yml file."+
+ " Make sure that you are using spaces instead of tabs for your indentation!!" +
+ "\n\nTHE UNDERLYING ERROR WAS:\n#{$!}"
+ end
+ end
+ end
+end
21 lib/picnic/controllers.rb
View
@@ -0,0 +1,21 @@
+module Picnic
+ module Controllers
+ class Public < Camping::Controllers::R '/public/(.+)'
+ BASE_PATH = File.expand_path(File.dirname(__FILE__))+'/lib/public'
+
+ MIME_TYPES = {'.css' => 'text/css', '.js' => 'text/javascript',
+ '.jpg' => 'image/jpeg', '.png' => 'image/png',
+ '.gif' => 'image/gif'}
+
+ def get(path)
+ @headers['Content-Type'] = MIME_TYPES[path[/\.\w+$/, 0]] || "text/plain"
+ unless path.include? ".." # prevent directory traversal attacks
+ @headers['X-Sendfile'] = "#{BASE_PATH}/#{path}"
+ else
+ @status = "403"
+ "403 - Invalid path"
+ end
+ end
+ end
+ end
+end
162 lib/picnic/postambles.rb
View
@@ -0,0 +1,162 @@
+module Picnic
+ module Postambles
+
+ def webrick
+ require 'webrick/httpserver'
+ require 'webrick/https'
+ require 'camping/webrick'
+
+ # TODO: verify the certificate's validity
+ # example of how to do this is here: http://pablotron.org/download/ruri-20050331.rb
+
+ cert_path = Picnic::Conf.ssl_cert
+ key_path = Picnic::Conf.ssl_key || Picnic::Conf.ssl_cert
+ # look for the key in the ssl_cert if no ssl_key is specified
+
+ begin
+ s = WEBrick::HTTPServer.new(
+ :BindAddress => "0.0.0.0",
+ :Port => Picnic::Conf.port
+ )
+ rescue Errno::EACCES
+ puts "\nThe server could not launch. Are you running on a privileged port? (e.g. port 443) If so, you must run the server as root."
+ exit 2
+ end
+
+ self.create
+ s.mount "#{Picnic::Conf.uri_path}", WEBrick::CampingHandler, self
+
+ puts "\n** #{self} is running at http://localhost:#{Picnic::Conf.port}#{Picnic::Conf.uri_path} and logging to '#{Picnic::Conf.log[:file]}'\n\n"
+
+ # This lets Ctrl+C shut down your server
+ trap(:INT) do
+ s.shutdown
+ end
+ trap(:TERM) do
+ s.shutdown
+ end
+
+ if $DAEMONIZE
+ WEBrick::Daemon.start do
+ write_pid_file if $PID_FILE
+ s.start
+ clear_pid_file
+ end
+ else
+ s.start
+ end
+ end
+
+
+
+ def mongrel
+ require 'rubygems'
+ require 'mongrel/camping'
+
+ # camping has fixes for mongrel currently only availabe in SVN
+ # ... you can install camping from svn (1.5.180) by running:
+ # gem install camping --source code.whytheluckystiff.net
+ gem 'camping', '~> 1.5.180'
+
+ if $DAEMONIZE
+ # check if log and pid are writable before daemonizing, otherwise we won't be able to notify
+ # the user if we run into trouble later (since once daemonized, we can't write to stdout/stderr)
+ check_pid_writable if $PID_FILE
+ check_log_writable
+ end
+
+ self.create
+
+ puts "\n** #{self} is starting. Look in '#{Picnic::Conf.log[:file]}' for further notices."
+
+ settings = {:host => "0.0.0.0", :log_file => Picnic::Conf.log[:file], :cwd => $CASSERVER_HOME}
+
+ # need to close all IOs before daemonizing
+ $LOG.close if $DAEMONIZE
+
+ begin
+ config = Mongrel::Configurator.new settings do
+ daemonize :log_file => Picnic::Conf.log[:file], :cwd => $CASSERVER_HOME if $DAEMONIZE
+
+ listener :port => Picnic::Conf.port do
+ uri Picnic::Conf.uri_path, :handler => Mongrel::Camping::CampingHandler.new(self)
+ setup_signals
+ end
+ end
+ rescue Errno::EADDRINUSE
+ exit 1
+ end
+
+ config.run
+
+ self.init_logger
+ #self.init_db_logger
+
+ if $DAEMONIZE && $PID_FILE
+ write_pid_file
+ unless File.exists? $PID_FILE
+ $LOG.error "#{self} could not start because pid file '#{$PID_FILE}' could not be created."
+ exit 1
+ end
+ end
+
+ puts "\n** #{self} is running at http://localhost:#{Picnic::Conf.port}#{Picnic::Conf.uri_path} and logging to '#{Picnic::Conf.log[:file]}'"
+ config.join
+
+ clear_pid_file
+
+ puts "\n** #{self} is stopped (#{Time.now})"
+ end
+
+
+ def fastcgi
+ require 'camping/fastcgi'
+ Dir.chdir('/srv/www/camping/fluxr/')
+
+ self.create
+ Camping::FastCGI.start(self)
+ end
+
+
+ def cgi
+ self.create
+ puts self.run
+ end
+
+ private
+ def check_log_writable
+ log_file = Picnic::Conf.log['file']
+ begin
+ f = open(log_file, 'w')
+ rescue
+ $stderr.puts "Couldn't write to log file at '#{log_file}' (#{$!})."
+ exit 1
+ end
+ f.close
+ end
+
+ def check_pid_writable
+ $LOG.debug "Checking if pid file '#{$PID_FILE}' is writable"
+ begin
+ f = open($PID_FILE, 'w')
+ rescue
+ $stderr.puts "Couldn't write to log at '#{$PID_FILE}' (#{$!})."
+ exit 1
+ end
+ f.close
+ end
+
+ def write_pid_file
+ $LOG.debug "Writing pid '#{Process.pid}' to pid file '#{$PID_FILE}'"
+ open($PID_FILE, "w") { |file| file.write(Process.pid) }
+ end
+
+ def clear_pid_file
+ if $PID_FILE && File.exists?($PID_FILE)
+ $LOG.debug "Clearing pid file '#{$PID_FILE}'"
+ File.unlink $PID_FILE
+ end
+ end
+
+ end
+end
181 lib/picnic/service_control.rb
View
@@ -0,0 +1,181 @@
+require 'optparse'
+
+module Picnic
+
+ # Provides functionality for controlling a Picnic-based server as
+ # an init.d service.
+ #
+ # Based on code from rubycas-server by jzylks and matt.zukowski.
+ class ServiceControl
+
+ attr_accessor :app, :options
+
+ def initialize(app, options = {})
+ @app = app
+
+ @options = {}
+ @options[:pid_file] ||= "/etc/#{app}/#{app}.pid"
+ @options[:conf_file] ||= nil
+ @options[:verbose] ||= false
+
+ @options = options
+ end
+
+ def parse_cli_opts
+ OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} (start|stop|restart) [options]"
+ opts.banner += "\n#{app} is only usable when using webrick or mongrel"
+
+ opts.on("-c", "--config FILE", "Path to #{app} configuration file") { |value| @options[:conf_file] = value }
+ opts.on("-P", "--pid_file FILE", "Path to #{app} pid file") { |value| @options[:pid_file] = value }
+ opts.on('-v', '--verbose', "Print debugging information to the console") { |value| @options[:verbose] = value }
+
+ if ARGV.empty?
+ puts opts
+ exit
+ else
+ @cmd = opts.parse!(ARGV)
+ if @cmd.nil?
+ puts opts
+ exit
+ end
+ end
+ end
+
+ if !@options[:conf_file].nil? && !File.exists?(@options[:conf_file])
+ puts "Invalid path to #{app} configuration file: #{@options[:conf_file]}"
+ exit
+ end
+
+ case @cmd[0]
+ when "start":
+ puts "Starting #{app}..."
+ start
+ when "stop":
+ puts "Stopping #{app}..."
+ stop
+ when "restart":
+ puts "Restarting #{app}..."
+ stop
+ start
+ when "status":
+ puts "Checking status of #{app}..."
+ status
+ else
+ puts "Invalid command. Usage: #{app}-ctl [-cPv] start|stop|restart|status"
+ end
+
+ exit
+ end
+
+ def start
+ # use local app bin if it exists and is executable -- makes debugging easier
+ bin = File.dirname(File.expand_path(__FILE__)) + "/#{app}"
+
+ if File.exists?(bin)
+ exec = "ruby #{bin}"
+ else
+ exec = app
+ end
+
+ case get_state
+ when :ok
+ $stderr.puts "#{app} is already running"
+ exit 1
+ when :not_running, :empty_pid
+ $stderr.puts "The pid file '#{@options[:pid_file]}' exists but #{app} is not running." +
+ " The pid file will be automatically deleted for you, but this shouldn't have happened!"
+ File.delete(@options[:pid_file])
+ when :dead
+ $stderr.puts "The pid file '#{@options[:pid_file]}' exists but #{app} is not running." +
+ " Please delete the pid file first."
+ exit 1
+ when :missing_pid
+ # we should be good to go (unless the server is already running without a pid file)
+ else
+ $stderr.puts "#{app} could not be started. Try looking in the log file for more info."
+ exit 1
+ end
+
+ cmd = "#{exec} -d -P #{@options[:pid_file]}"
+ cmd += " -c #{@options[:conf_file]}" if !@options[:conf_file].nil?
+
+ puts ">>> #{cmd}" if @options[:verbose]
+
+ output = `#{cmd}`
+
+ puts "<<< #{output}" if @options[:verbose]
+
+ if s = get_state == :ok
+ exit 0
+ else
+ $stderr.puts "#{app} could not start properly! (#{s})\nTry running with the --verbose option for details."
+ case s
+ when :missing_pid
+ exit 4
+ when :not_running
+ exit 3
+ when :dead
+ exit 1
+ else
+ exit 4
+ end
+ end
+ end
+
+ def stop
+ if File.exists? @options[:pid_file]
+ pid = open(@options[:pid_file]).read.to_i
+ begin
+ Process.kill("TERM", pid)
+ exit 0
+ rescue Errno::ESRCH
+ $stderr.puts "#{app} process '#{pid}' does not exist."
+ exit 1
+ end
+ else
+ $stderr.puts "#{@options[:pid_file]} not found. Is #{app} running?"
+ exit 4
+ end
+ end
+
+ def status
+ case get_state
+ when :ok
+ puts "#{app} appears to be up and running."
+ exit 0
+ when :missing_pid
+ $stderr.puts "#{app} does not appear to be running (pid file not found)."
+ exit 3
+ when :empty_pid
+ $stderr.puts "#{app} does not appear to be running (pid file exists but is empty)."
+ when :not_running
+ $stderr.puts "#{app} is not running."
+ exit 1
+ when :dead
+ $stderr.puts "#{app} is dead or unresponsive."
+ exit 102
+ end
+ end
+
+ def get_state
+ if File.exists? @options[:pid_file]
+ pid = File.read(@options[:pid_file]).strip
+
+ return :empty_pid unless pid and !pid.empty? # pid file exists but is empty
+
+ state = `ps -p #{pid} -o state=`.strip
+ if state == ''
+ return :not_running
+ elsif state == 'R' || state == 'S'
+ return :ok
+ else
+ return :dead
+ end
+ else
+ # TODO: scan through the process table to see if server is running without pid file
+ return :missing_pid
+ end
+ end
+ end
+end
33 lib/picnic/utils.rb
View
@@ -0,0 +1,33 @@
+# Misc utility function used throughout by Picnic
+module Picnic
+ module Utils
+ def random_string
+ "#{Time.now.to_i}r%X" % rand(10**32)
+ end
+ module_function :random_string
+
+ class Logger < ::Logger
+ def initialize(logdev, shift_age = 0, shift_size = 1048576)
+ begin
+ super
+ rescue Exception
+ puts "WARNING: Couldn't create Logger with output '#{logdev}'. Logger output will be redirected to STDOUT."
+ super(STDOUT, shift_age, shift_size)
+ end
+ end
+
+ def format_message(severity, datetime, progrname, msg)
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg)
+ end
+ end
+ end
+
+ class LogFormatter < ::Logger::Formatter
+ Format = "[%s#%d] %5s -- %s: %s\n"
+
+ def call(severity, time, progname, msg)
+ Format % [format_datetime(time), $$, severity, progname,
+ msg2str(msg)]
+ end
+ end
+end
9 lib/picnic/version.rb
View
@@ -0,0 +1,9 @@
+module Picnic #:nodoc:
+ module VERSION #:nodoc:
+ MAJOR = 0
+ MINOR = 0
+ TINY = 1
+
+ STRING = [MAJOR, MINOR, TINY].join('.')
+ end
+end
1,585 setup.rb
View
@@ -0,0 +1,1585 @@
+#
+# setup.rb
+#
+# Copyright (c) 2000-2005 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map) # Ruby 1.4.6
+ module Enumerable
+ alias map collect
+ end
+end
+
+unless File.respond_to?(:read) # Ruby 1.6
+ def File.read(fname)
+ open(fname) {|f|
+ return f.read
+ }
+ end
+end
+
+unless Errno.const_defined?(:ENOTEMPTY) # Windows?
+ module Errno
+ class ENOTEMPTY
+ # We do not raise this exception, implementation is not needed.
+ end
+ end
+end
+
+def File.binread(fname)
+ open(fname, 'rb') {|f|
+ return f.read
+ }
+end
+
+# for corrupted Windows' stat(2)
+def File.dir?(path)
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class ConfigTable
+
+ include Enumerable
+
+ def initialize(rbconfig)
+ @rbconfig = rbconfig
+ @items = []
+ @table = {}
+ # options
+ @install_prefix = nil
+ @config_opt = nil
+ @verbose = true
+ @no_harm = false
+ end
+
+ attr_accessor :install_prefix
+ attr_accessor :config_opt
+
+ attr_writer :verbose
+
+ def verbose?
+ @verbose
+ end
+
+ attr_writer :no_harm
+
+ def no_harm?
+ @no_harm
+ end
+
+ def [](key)
+ lookup(key).resolve(self)
+ end
+
+ def []=(key, val)
+ lookup(key).set val
+ end
+
+ def names
+ @items.map {|i| i.name }
+ end
+
+ def each(&block)
+ @items.each(&block)
+ end
+
+ def key?(name)
+ @table.key?(name)
+ end
+
+ def lookup(name)
+ @table[name] or setup_rb_error "no such config item: #{name}"
+ end
+
+ def add(item)
+ @items.push item
+ @table[item.name] = item
+ end
+
+ def remove(name)
+ item = lookup(name)
+ @items.delete_if {|i| i.name == name }
+ @table.delete_if {|name, i| i.name == name }
+ item
+ end
+
+ def load_script(path, inst = nil)
+ if File.file?(path)
+ MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
+ end
+ end
+
+ def savefile
+ '.config'
+ end
+
+ def load_savefile
+ begin
+ File.foreach(savefile()) do |line|
+ k, v = *line.split(/=/, 2)
+ self[k] = v.strip
+ end
+ rescue Errno::ENOENT
+ setup_rb_error $!.message + "\n#{File.basename($0)} config first"
+ end
+ end
+
+ def save
+ @items.each {|i| i.value }
+ File.open(savefile(), 'w') {|f|
+ @items.each do |i|
+ f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
+ end
+ }
+ end
+
+ def load_standard_entries
+ standard_entries(@rbconfig).each do |ent|
+ add ent
+ end
+ end
+
+ def standard_entries(rbconfig)
+ c = rbconfig
+
+ rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
+
+ major = c['MAJOR'].to_i
+ minor = c['MINOR'].to_i
+ teeny = c['TEENY'].to_i
+ version = "#{major}.#{minor}"
+
+ # ruby ver. >= 1.4.4?
+ newpath_p = ((major >= 2) or
+ ((major == 1) and
+ ((minor >= 5) or
+ ((minor == 4) and (teeny >= 4)))))
+
+ if c['rubylibdir']
+ # V > 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = c['rubylibdir']
+ librubyverarch = c['archdir']
+ siteruby = c['sitedir']
+ siterubyver = c['sitelibdir']
+ siterubyverarch = c['sitearchdir']
+ elsif newpath_p
+ # 1.4.4 <= V <= 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = c['sitedir']
+ siterubyver = "$siteruby/#{version}"
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ else
+ # V < 1.4.4
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
+ siterubyver = siteruby
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ end
+ parameterize = lambda {|path|
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
+ }
+
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+ else
+ makeprog = 'make'
+ end
+
+ [
+ ExecItem.new('installdirs', 'std/site/home',
+ 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
+ {|val, table|
+ case val
+ when 'std'
+ table['rbdir'] = '$librubyver'
+ table['sodir'] = '$librubyverarch'
+ when 'site'
+ table['rbdir'] = '$siterubyver'
+ table['sodir'] = '$siterubyverarch'
+ when 'home'
+ setup_rb_error '$HOME was not set' unless ENV['HOME']
+ table['prefix'] = ENV['HOME']
+ table['rbdir'] = '$libdir/ruby'
+ table['sodir'] = '$libdir/ruby'
+ end
+ },
+ PathItem.new('prefix', 'path', c['prefix'],
+ 'path prefix of target environment'),
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+ 'the directory for commands'),
+ PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
+ 'the directory for libraries'),
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+ 'the directory for shared data'),
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+ 'the directory for man pages'),
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+ 'the directory for system configuration files'),
+ PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
+ 'the directory for local state data'),
+ PathItem.new('libruby', 'path', libruby,
+ 'the directory for ruby libraries'),
+ PathItem.new('librubyver', 'path', librubyver,
+ 'the directory for standard ruby libraries'),
+ PathItem.new('librubyverarch', 'path', librubyverarch,
+ 'the directory for standard ruby extensions'),
+ PathItem.new('siteruby', 'path', siteruby,
+ 'the directory for version-independent aux ruby libraries'),
+ PathItem.new('siterubyver', 'path', siterubyver,
+ 'the directory for aux ruby libraries'),
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
+ 'the directory for aux ruby binaries'),
+ PathItem.new('rbdir', 'path', '$siterubyver',
+ 'the directory for ruby scripts'),
+ PathItem.new('sodir', 'path', '$siterubyverarch',
+ 'the directory for ruby extentions'),
+ PathItem.new('rubypath', 'path', rubypath,
+ 'the path to set to #! line'),
+ ProgramItem.new('rubyprog', 'name', rubypath,
+ 'the ruby program using for installation'),
+ ProgramItem.new('makeprog', 'name', makeprog,
+ 'the make program to compile ruby extentions'),
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+ 'shebang line (#!) editing mode'),
+ BoolItem.new('without-ext', 'yes/no', 'no',
+ 'does not compile/install ruby extentions')
+ ]
+ end
+ private :standard_entries
+
+ def load_multipackage_entries
+ multipackage_entries().each do |ent|
+ add ent
+ end
+ end
+
+ def multipackage_entries
+ [
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+ 'package names that you want to install'),
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+ 'package names that you do not want to install')
+ ]
+ end
+ private :multipackage_entries
+
+ ALIASES = {
+ 'std-ruby' => 'librubyver',
+ 'stdruby' => 'librubyver',
+ 'rubylibdir' => 'librubyver',
+ 'archdir' => 'librubyverarch',
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
+ 'site-ruby' => 'siterubyver', # For backward compatibility
+ 'bin-dir' => 'bindir',
+ 'bin-dir' => 'bindir',
+ 'rb-dir' => 'rbdir',
+ 'so-dir' => 'sodir',
+ 'data-dir' => 'datadir',
+ 'ruby-path' => 'rubypath',
+ 'ruby-prog' => 'rubyprog',
+ 'ruby' => 'rubyprog',
+ 'make-prog' => 'makeprog',
+ 'make' => 'makeprog'
+ }
+
+ def fixup
+ ALIASES.each do |ali, name|
+ @table[ali] = @table[name]
+ end
+ @items.freeze
+ @table.freeze
+ @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
+ end
+
+ def parse_opt(opt)
+ m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
+ m.to_a[1,2]
+ end
+
+ def dllext
+ @rbconfig['DLEXT']
+ end
+
+ def value_config?(name)
+ lookup(name).value?
+ end
+
+ class Item
+ def initialize(name, template, default, desc)
+ @name = name.freeze
+ @template = template
+ @value = default
+ @default = default
+ @description = desc
+ end
+
+ attr_reader :name
+ attr_reader :description
+
+ attr_accessor :default
+ alias help_default default
+
+ def help_opt
+ "--#{@name}=#{@template}"
+ end
+
+ def value?
+ true
+ end
+
+ def value
+ @value
+ end
+
+ def resolve(table)
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
+ end
+
+ def set(val)
+ @value = check(val)
+ end
+
+ private
+
+ def check(val)
+ setup_rb_error "config: --#{name} requires argument" unless val
+ val
+ end
+ end
+
+ class BoolItem < Item
+ def config_type
+ 'bool'
+ end
+
+ def help_opt
+ "--#{@name}"
+ end
+
+ private
+
+ def check(val)
+ return 'yes' unless val
+ case val
+ when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
+ when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
+ else
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+ end
+ end
+ end
+
+ class PathItem < Item
+ def config_type
+ 'path'
+ end
+
+ private
+
+ def check(path)
+ setup_rb_error "config: --#{@name} requires argument" unless path
+ path[0,1] == '$' ? path : File.expand_path(path)
+ end
+ end
+
+ class ProgramItem < Item
+ def config_type
+ 'program'
+ end
+ end
+
+ class SelectItem < Item
+ def initialize(name, selection, default, desc)
+ super
+ @ok = selection.split('/')
+ end
+
+ def config_type
+ 'select'
+ end
+
+ private
+
+ def check(val)
+ unless @ok.include?(val.strip)
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+ end
+ val.strip
+ end
+ end
+
+ class ExecItem < Item
+ def initialize(name, selection, desc, &block)
+ super name, selection, nil, desc
+ @ok = selection.split('/')
+ @action = block
+ end
+
+ def config_type
+ 'exec'
+ end
+
+ def value?
+ false
+ end
+
+ def resolve(table)
+ setup_rb_error "$#{name()} wrongly used as option value"
+ end
+
+ undef set
+
+ def evaluate(val, table)
+ v = val.strip.downcase
+ unless @ok.include?(v)
+ setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
+ end
+ @action.call v, table
+ end
+ end
+
+ class PackageSelectionItem < Item
+ def initialize(name, template, default, help_default, desc)
+ super name, template, default, desc
+ @help_default = help_default
+ end
+
+ attr_reader :help_default
+
+ def config_type
+ 'package'
+ end
+
+ private
+
+ def check(val)
+ unless File.dir?("packages/#{val}")
+ setup_rb_error "config: no such package: #{val}"
+ end
+ val
+ end
+ end
+
+ class MetaConfigEnvironment
+ def initialize(config, installer)
+ @config = config
+ @installer = installer
+ end
+
+ def config_names
+ @config.names
+ end
+
+ def config?(name)
+ @config.key?(name)
+ end
+
+ def bool_config?(name)
+ @config.lookup(name).config_type == 'bool'
+ end
+
+ def path_config?(name)
+ @config.lookup(name).config_type == 'path'
+ end
+
+ def value_config?(name)
+ @config.lookup(name).config_type != 'exec'
+ end
+
+ def add_config(item)
+ @config.add item
+ end
+
+ def add_bool_config(name, default, desc)
+ @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+ end
+
+ def add_path_config(name, default, desc)
+ @config.add PathItem.new(name, 'path', default, desc)
+ end
+
+ def set_config_default(name, default)
+ @config.lookup(name).default = default
+ end
+
+ def remove_config(name)
+ @config.remove(name)
+ end
+
+ # For only multipackage
+ def packages
+ raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages
+ end
+
+ # For only multipackage
+ def declare_packages(list)
+ raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages = list
+ end
+ end
+
+end # class ConfigTable
+
+
+# This module requires: #verbose?, #no_harm?
+module FileOperations
+
+ def mkdir_p(dirname, prefix = nil)
+ dirname = prefix + File.expand_path(dirname) if prefix
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
+ return if no_harm?
+
+ # Does not check '/', it's too abnormal.
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
+ if /\A[a-z]:\z/i =~ dirs[0]
+ disk = dirs.shift
+ dirs[0] = disk + dirs[0]
+ end
+ dirs.each_index do |idx|
+ path = dirs[0..idx].join('')
+ Dir.mkdir path unless File.dir?(path)
+ end
+ end
+
+ def rm_f(path)
+ $stderr.puts "rm -f #{path}" if verbose?
+ return if no_harm?
+ force_remove_file path
+ end
+
+ def rm_rf(path)
+ $stderr.puts "rm -rf #{path}" if verbose?
+ return if no_harm?
+ remove_tree path
+ end
+
+ def remove_tree(path)
+ if File.symlink?(path)
+ remove_file path
+ elsif File.dir?(path)
+ remove_tree0 path
+ else
+ force_remove_file path
+ end
+ end
+
+ def remove_tree0(path)
+ Dir.foreach(path) do |ent|
+ next if ent == '.'
+ next if ent == '..'
+ entpath = "#{path}/#{ent}"
+ if File.symlink?(entpath)
+ remove_file entpath
+ elsif File.dir?(entpath)
+ remove_tree0 entpath
+ else
+ force_remove_file entpath
+ end
+ end
+ begin
+ Dir.rmdir path
+ rescue Errno::ENOTEMPTY
+ # directory may not be empty
+ end
+ end
+
+ def move_file(src, dest)
+ force_remove_file dest
+ begin
+ File.rename src, dest
+ rescue
+ File.open(dest, 'wb') {|f|
+ f.write File.binread(src)
+ }
+ File.chmod File.stat(src).mode, dest
+ File.unlink src
+ end
+ end
+
+ def force_remove_file(path)
+ begin
+ remove_file path
+ rescue
+ end
+ end
+
+ def remove_file(path)
+ File.chmod 0777, path
+ File.unlink path
+ end
+
+ def install(from, dest, mode, prefix = nil)
+ $stderr.puts "install #{from} #{dest}" if verbose?
+ return if no_harm?
+
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+ str = File.binread(from)
+ if diff?(str, realdest)
+ verbose_off {
+ rm_f realdest if File.exist?(realdest)
+ }
+ File.open(realdest, 'wb') {|f|
+ f.write str
+ }
+ File.chmod mode, realdest
+
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+ if prefix
+ f.puts realdest.sub(prefix, '')
+ else
+ f.puts realdest
+ end
+ }
+ end
+ end
+
+ def diff?(new_content, path)
+ return true unless File.exist?(path)
+ new_content != File.binread(path)
+ end
+
+ def command(*args)
+ $stderr.puts args.join(' ') if verbose?
+ system(*args) or raise RuntimeError,
+ "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
+ end
+
+ def ruby(*args)
+ command config('rubyprog'), *args
+ end
+
+ def make(task = nil)
+ command(*[config('makeprog'), task].compact)
+ end
+
+ def extdir?(dir)
+ File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
+ end
+
+ def files_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.file?("#{dir}/#{ent}") }
+ }
+ end
+
+ DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
+
+ def directories_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
+ }
+ end
+
+end
+
+
+# This module requires: #srcdir_root, #objdir_root, #relpath
+module HookScriptAPI
+
+ def get_config(key)
+ @config[key]
+ end
+
+ alias config get_config
+
+ # obsolete: use metaconfig to change configuration
+ def set_config(key, val)
+ @config[key] = val
+ end
+
+ #
+ # srcdir/objdir (works only in the package directory)
+ #
+
+ def curr_srcdir
+ "#{srcdir_root()}/#{relpath()}"
+ end
+
+ def curr_objdir
+ "#{objdir_root()}/#{relpath()}"
+ end
+
+ def srcfile(path)
+ "#{curr_srcdir()}/#{path}"
+ end
+
+ def srcexist?(path)
+ File.exist?(srcfile(path))
+ end
+
+ def srcdirectory?(path)
+ File.dir?(srcfile(path))
+ end
+
+ def srcfile?(path)
+ File.file?(srcfile(path))
+ end
+
+ def srcentries(path = '.')
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
+ return d.to_a - %w(. ..)
+ }
+ end
+
+ def srcfiles(path = '.')
+ srcentries(path).select {|fname|
+ File.file?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+ def srcdirectories(path = '.')
+ srcentries(path).select {|fname|
+ File.dir?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+end
+
+
+class ToplevelInstaller
+
+ Version = '3.4.1'
+ Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
+
+ TASKS = [
+ [ 'all', 'do config, setup, then install' ],
+ [ 'config', 'saves your configurations' ],
+ [ 'show', 'shows current configuration' ],
+ [ 'setup', 'compiles ruby extentions and others' ],
+ [ 'install', 'installs files' ],
+ [ 'test', 'run all tests in test/' ],
+ [ 'clean', "does `make clean' for each extention" ],
+ [ 'distclean',"does `make distclean' for each extention" ]
+ ]
+
+ def ToplevelInstaller.invoke
+ config = ConfigTable.new(load_rbconfig())
+ config.load_standard_entries
+ config.load_multipackage_entries if multipackage?
+ config.fixup
+ klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
+ klass.new(File.dirname($0), config).invoke
+ end
+
+ def ToplevelInstaller.multipackage?
+ File.dir?(File.dirname($0) + '/packages')
+ end
+
+ def ToplevelInstaller.load_rbconfig
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+ ARGV.delete(arg)
+ load File.expand_path(arg.split(/=/, 2)[1])
+ $".push 'rbconfig.rb'
+ else
+ require 'rbconfig'
+ end
+ ::Config::CONFIG
+ end
+
+ def initialize(ardir_root, config)
+ @ardir = File.expand_path(ardir_root)
+ @config = config
+ # cache
+ @valid_task_re = nil
+ end
+
+ def config(key)
+ @config[key]
+ end
+
+ def inspect
+ "#<#{self.class} #{__id__()}>"
+ end
+
+ def invoke
+ run_metaconfigs
+ case task = parsearg_global()
+ when nil, 'all'
+ parsearg_config
+ init_installers
+ exec_config
+ exec_setup
+ exec_install
+ else
+ case task
+ when 'config', 'test'
+ ;
+ when 'clean', 'distclean'
+ @config.load_savefile if File.exist?(@config.savefile)
+ else
+ @config.load_savefile
+ end
+ __send__ "parsearg_#{task}"
+ init_installers
+ __send__ "exec_#{task}"
+ end
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig"
+ end
+
+ def init_installers
+ @installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ #
+ # Hook Script API bases
+ #
+
+ def srcdir_root
+ @ardir
+ end
+
+ def objdir_root
+ '.'
+ end
+
+ def relpath
+ '.'
+ end
+
+ #
+ # Option Parsing
+ #
+
+ def parsearg_global
+ while arg = ARGV.shift
+ case arg
+ when /\A\w+\z/
+ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
+ return arg
+ when '-q', '--quiet'
+ @config.verbose = false
+ when '--verbose'
+ @config.verbose = true
+ when '--help'
+ print_usage $stdout
+ exit 0
+ when '--version'
+ puts "#{File.basename($0)} version #{Version}"
+ exit 0
+ when '--copyright'
+ puts Copyright
+ exit 0
+ else
+ setup_rb_error "unknown global option '#{arg}'"
+ end
+ end
+ nil
+ end
+
+ def valid_task?(t)
+ valid_task_re() =~ t
+ end
+
+ def valid_task_re
+ @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
+ end
+
+ def parsearg_no_options
+ unless ARGV.empty?
+ task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
+ setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
+ end
+ end
+
+ alias parsearg_show parsearg_no_options
+ alias parsearg_setup parsearg_no_options
+ alias parsearg_test parsearg_no_options
+ alias parsearg_clean parsearg_no_options
+ alias parsearg_distclean parsearg_no_options
+
+ def parsearg_config
+ evalopt = []
+ set = []
+ @config.config_opt = []
+ while i = ARGV.shift
+ if /\A--?\z/ =~ i
+ @config.config_opt = ARGV.dup
+ break
+ end
+ name, value = *@config.parse_opt(i)
+ if @config.value_config?(name)
+ @config[name] = value
+ else
+ evalopt.push [name, value]
+ end
+ set.push name
+ end
+ evalopt.each do |name, value|
+ @config.lookup(name).evaluate value, @config
+ end
+ # Check if configuration is valid
+ set.each do |n|
+ @config[n] if @config.value_config?(n)
+ end
+ end
+
+ def parsearg_install
+ @config.no_harm = false
+ @config.install_prefix = ''
+ while a = ARGV.shift
+ case a
+ when '--no-harm'
+ @config.no_harm = true
+ when /\A--prefix=/
+ path = a.split(/=/, 2)[1]
+ path = File.expand_path(path) unless path[0,1] == '/'
+ @config.install_prefix = path
+ else
+ setup_rb_error "install: unknown option #{a}"
+ end
+ end
+ end
+
+ def print_usage(out)
+ out.puts 'Typical Installation Procedure:'
+ out.puts " $ ruby #{File.basename $0} config"
+ out.puts " $ ruby #{File.basename $0} setup"
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
+ out.puts
+ out.puts 'Detailed Usage:'
+ out.puts " ruby #{File.basename $0} <global option>"
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+ fmt = " %-24s %s\n"
+ out.puts
+ out.puts 'Global options:'
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
+ out.printf fmt, ' --verbose', 'output messages verbosely'
+ out.printf fmt, ' --help', 'print this message'
+ out.printf fmt, ' --version', 'print version and quit'
+ out.printf fmt, ' --copyright', 'print copyright and quit'
+ out.puts
+ out.puts 'Tasks:'
+ TASKS.each do |name, desc|
+ out.printf fmt, name, desc
+ end
+
+ fmt = " %-24s %s [%s]\n"
+ out.puts
+ out.puts 'Options for CONFIG or ALL:'
+ @config.each do |item|
+ out.printf fmt, item.help_opt, item.description, item.help_default
+ end
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+ out.puts
+ out.puts 'Options for INSTALL:'
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+ out.printf fmt, '--prefix=path', 'install path prefix', ''
+ out.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ @installer.exec_config
+ @config.save # must be final
+ end
+
+ def exec_setup
+ @installer.exec_setup
+ end
+
+ def exec_install
+ @installer.exec_install
+ end
+
+ def exec_test
+ @installer.exec_test
+ end
+
+ def exec_show
+ @config.each do |i|
+ printf "%-20s %s\n", i.name, i.value if i.value?
+ end
+ end
+
+ def exec_clean
+ @installer.exec_clean
+ end
+
+ def exec_distclean
+ @installer.exec_distclean
+ end
+
+end # class ToplevelInstaller
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+ include FileOperations
+
+ def initialize(ardir_root, config)
+ super
+ @packages = directories_of("#{@ardir}/packages")
+ raise 'no package exists' if @packages.empty?
+ @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig", self
+ @packages.each do |name|
+ @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
+ end
+ end
+
+ attr_reader :packages
+
+ def packages=(list)
+ raise 'package list is empty' if list.empty?
+ list.each do |name|
+ raise "directory packages/#{name} does not exist"\
+ unless File.dir?("#{@ardir}/packages/#{name}")
+ end
+ @packages = list
+ end
+
+ def init_installers
+ @installers = {}
+ @packages.each do |pack|
+ @installers[pack] = Installer.new(@config,
+ "#{@ardir}/packages/#{pack}",
+ "packages/#{pack}")
+ end
+ with = extract_selection(config('with'))
+ without = extract_selection(config('without'))
+ @selected = @installers.keys.select {|name|
+ (with.empty? or with.include?(name)) \
+ and not without.include?(name)
+ }
+ end
+
+ def extract_selection(list)
+ a = list.split(/,/)
+ a.each do |name|
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
+ end
+ a
+ end
+
+ def print_usage(f)
+ super
+ f.puts 'Inluded packages:'
+ f.puts ' ' + @packages.sort.join(' ')
+ f.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ run_hook 'pre-config'
+ each_selected_installers {|inst| inst.exec_config }
+ run_hook 'post-config'
+ @config.save # must be final
+ end
+
+ def exec_setup
+ run_hook 'pre-setup'
+ each_selected_installers {|inst| inst.exec_setup }
+ run_hook 'post-setup'
+ end
+
+ def exec_install
+ run_hook 'pre-install'
+ each_selected_installers {|inst| inst.exec_install }
+ run_hook 'post-install'
+ end
+
+ def exec_test
+ run_hook 'pre-test'
+ each_selected_installers {|inst| inst.exec_test }
+ run_hook 'post-test'
+ end
+
+ def exec_clean
+ rm_f @config.savefile
+ run_hook 'pre-clean'
+ each_selected_installers {|inst| inst.exec_clean }
+ run_hook 'post-clean'
+ end
+
+ def exec_distclean
+ rm_f @config.savefile
+ run_hook 'pre-distclean'
+ each_selected_installers {|inst| inst.exec_distclean }
+ run_hook 'post-distclean'
+ end
+
+ #
+ # lib
+ #
+
+ def each_selected_installers
+ Dir.mkdir 'packages' unless File.dir?('packages')
+ @selected.each do |pack|
+ $stderr.puts "Processing the package `#{pack}' ..." if verbose?
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+ Dir.chdir "packages/#{pack}"
+ yield @installers[pack]
+ Dir.chdir '../..'
+ end
+ end
+
+ def run_hook(id)
+ @root_installer.run_hook id
+ end
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+end # class ToplevelInstallerMulti
+
+
+class Installer
+
+ FILETYPES = %w( bin lib ext data conf man )
+
+ include FileOperations
+ include HookScriptAPI
+
+ def initialize(config, srcroot, objroot)
+ @config = config
+ @srcdir = File.expand_path(srcroot)
+ @objdir = File.expand_path(objroot)
+ @currdir = '.'
+ end
+
+ def inspect
+ "#<#{self.class} #{File.basename(@srcdir)}>"
+ end
+
+ def noop(rel)
+ end
+
+ #
+ # Hook Script API base methods
+ #
+
+ def srcdir_root
+ @srcdir
+ end
+
+ def objdir_root
+ @objdir
+ end
+
+ def relpath
+ @currdir
+ end
+
+ #
+ # Config Access
+ #
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+ def verbose_off
+ begin
+ save, @config.verbose = @config.verbose?, false
+ yield
+ ensure
+ @config.verbose = save
+ end
+ end
+
+ #
+ # TASK config
+ #
+
+ def exec_config
+ exec_task_traverse 'config'
+ end
+
+ alias config_dir_bin noop
+ alias config_dir_lib noop
+
+ def config_dir_ext(rel)
+ extconf if extdir?(curr_srcdir())
+ end
+
+ alias config_dir_data noop
+ alias config_dir_conf noop
+ alias config_dir_man noop
+
+ def extconf
+ ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
+ end
+
+ #
+ # TASK setup
+ #
+
+ def exec_setup
+ exec_task_traverse 'setup'
+ end
+
+ def setup_dir_bin(rel)
+ files_of(curr_srcdir()).each do |fname|
+ update_shebang_line "#{curr_srcdir()}/#{fname}"
+ end
+ end
+
+ alias setup_dir_lib noop
+
+ def setup_dir_ext(rel)
+ make if extdir?(curr_srcdir())
+ end
+
+ alias setup_dir_data noop
+ alias setup_dir_conf noop
+ alias setup_dir_man noop
+
+ def update_shebang_line(path)
+ return if no_harm?
+ return if config('shebang') == 'never'
+ old = Shebang.load(path)
+ if old
+ $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
+ new = new_shebang(old)
+ return if new.to_s == old.to_s
+ else
+ return unless config('shebang') == 'all'
+ new = Shebang.new(config('rubypath'))
+ end
+ $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
+ open_atomic_writer(path) {|output|
+ File.open(path, 'rb') {|f|
+ f.gets if old # discard
+ output.puts new.to_s
+ output.print f.read
+ }
+ }
+ end
+
+ def new_shebang(old)
+ if /\Aruby/ =~ File.basename(old.cmd)
+ Shebang.new(config('rubypath'), old.args)
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
+ Shebang.new(config('rubypath'), old.args[1..-1])
+ else
+ return old unless config('shebang') == 'all'
+ Shebang.new(config('rubypath'))
+ end
+ end
+
+ def open_atomic_writer(path, &block)
+ tmpfile = File.basename(path) + '.tmp'
+ begin
+ File.open(tmpfile, 'wb', &block)
+ File.rename tmpfile, File.basename(path)
+ ensure
+ File.unlink tmpfile if File.exist?(tmpfile)
+ end
+ end
+
+ class Shebang
+ def Shebang.load(path)
+ line = nil
+ File.open(path) {|f|
+ line = f.gets
+ }
+ return nil unless /\A#!/ =~ line
+ parse(line)
+ end
+
+ def Shebang.parse(line)
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
+ new(cmd, args)
+ end
+
+ def initialize(cmd, args = [])
+ @cmd = cmd
+ @args = args
+ end
+
+ attr_reader :cmd
+ attr_reader :args
+
+ def to_s
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
+ end
+ end
+
+ #
+ # TASK install
+ #
+
+ def exec_install
+ rm_f 'InstalledFiles'
+ exec_task_traverse 'install'
+ end
+
+ def install_dir_bin(rel)
+ install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
+ end
+
+ def install_dir_lib(rel)
+ install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
+ end
+
+ def install_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ install_files rubyextentions('.'),
+ "#{config('sodir')}/#{File.dirname(rel)}",
+ 0555
+ end
+
+ def install_dir_data(rel)
+ install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
+ end
+
+ def install_dir_conf(rel)
+ # FIXME: should not remove current config files
+ # (rename previous file to .old/.org)
+ install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
+ end
+
+ def install_dir_man(rel)
+ install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
+ end
+
+ def install_files(list, dest, mode)
+ mkdir_p dest, @config.install_prefix
+ list.each do |fname|
+ install fname, dest, mode, @config.install_prefix
+ end
+ end
+
+ def libfiles
+ glob_reject(%w(*.y *.output), targetfiles())
+ end
+
+ def rubyextentions(dir)
+ ents = glob_select("*.#{@config.dllext}", targetfiles())
+ if ents.empty?
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+ end
+ ents
+ end
+
+ def targetfiles
+ mapdir(existfiles() - hookfiles())
+ end
+
+ def mapdir(ents)
+ ents.map {|ent|
+ if File.exist?(ent)
+ then ent # objdir
+ else "#{curr_srcdir()}/#{ent}" # srcdir
+ end
+ }
+ end
+
+ # picked up many entries from cvs-1.11.1/src/ignore.c
+ JUNK_FILES = %w(
+ core RCSLOG tags TAGS .make.state
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+ *.org *.in .*
+ )
+
+ def existfiles
+ glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
+ end
+
+ def hookfiles
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+ }.flatten
+ end
+
+ def glob_select(pat, ents)
+ re = globs2re([pat])
+ ents.select {|ent| re =~ ent }
+ end
+
+ def glob_reject(pats, ents)
+ re = globs2re(pats)
+ ents.reject {|ent| re =~ ent }
+ end
+
+ GLOB2REGEX = {
+ '.' => '\.',
+ '$' => '\$',
+ '#' => '\#',
+ '*' => '.*'
+ }
+
+ def globs2re(pats)
+ /\A(?:#{
+ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
+ })\z/
+ end
+
+ #
+ # TASK test
+ #
+
+ TESTDIR = 'test'
+
+ def exec_test
+ unless File.directory?('test')
+ $stderr.puts 'no test in this package' if verbose?
+ return
+ end
+ $stderr.puts 'Running tests...' if verbose?
+ begin
+ require 'test/unit'
+ rescue LoadError
+ setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
+ end
+ runner = Test::Unit::AutoRunner.new(true)
+ runner.to_run << TESTDIR
+ runner.run
+ end
+
+ #
+ # TASK clean
+ #
+
+ def exec_clean
+ exec_task_traverse 'clean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias clean_dir_bin noop
+ alias clean_dir_lib noop
+ alias clean_dir_data noop
+ alias clean_dir_conf noop
+ alias clean_dir_man noop
+
+ def clean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'clean' if File.file?('Makefile')
+ end
+
+ #
+ # TASK distclean
+ #
+
+ def exec_distclean
+ exec_task_traverse 'distclean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias distclean_dir_bin noop
+ alias distclean_dir_lib noop
+
+ def distclean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'distclean' if File.file?('Makefile')
+ end
+
+ alias distclean_dir_data noop
+ alias distclean_dir_conf noop
+ alias distclean_dir_man noop
+
+ #
+ # Traversing
+ #
+
+ def exec_task_traverse(task)
+ run_hook "pre-#{task}"
+ FILETYPES.each do |type|
+ if type == 'ext' and config('without-ext') == 'yes'
+ $stderr.puts 'skipping ext/* by user option' if verbose?
+ next
+ end
+ traverse task, type, "#{task}_dir_#{type}"
+ end
+ run_hook "post-#{task}"
+ end
+
+ def traverse(task, rel, mid)
+ dive_into(rel) {
+ run_hook "pre-#{task}"
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+ directories_of(curr_srcdir()).each do |d|
+ traverse task, "#{rel}/#{d}", mid
+ end
+ run_hook "post-#{task}"
+ }
+ end
+
+ def dive_into(rel)
+ return unless File.dir?("#{@srcdir}/#{rel}")
+
+ dir = File.basename(rel)
+ Dir.mkdir dir unless File.dir?(dir)
+ prevdir = Dir.pwd
+ Dir.chdir dir
+ $stderr.puts '---> ' + rel if verbose?
+ @currdir = rel
+ yield
+ Dir.chdir prevdir
+ $stderr.puts '<--- ' + rel if verbose?
+ @currdir = File.dirname(rel)
+ end
+
+ def run_hook(id)
+ path = [ "#{curr_srcdir()}/#{id}",
+ "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
+ return unless path
+ begin
+ instance_eval File.read(path), path, 1
+ rescue
+ raise if $DEBUG
+ setup_rb_error "hook #{path} failed:\n" + $!.message
+ end
+ end
+
+end # class Installer
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+ raise SetupError, msg
+end
+
+if $0 == __FILE__
+ begin
+ ToplevelInstaller.invoke
+ rescue SetupError
+ raise if $DEBUG
+ $stderr.puts $!.message
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+ exit 1
+ end
+end
11 test/picnic_test.rb
View
@@ -0,0 +1,11 @@
+require File.dirname(__FILE__) + '/test_helper.rb'
+
+class PicnicTest < Test::Unit::TestCase
+
+ def setup
+ end
+
+ def test_truth
+ assert true
+ end
+end
2  test/test_helper.rb
View
@@ -0,0 +1,2 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../lib/picnic'
99 vendor/camping-1.5.180/CHANGELOG
View
@@ -0,0 +1,99 @@
+= 1.5
+=== 3rd Oct, 2006
+
+* Camping::Apps stores an array of classes for all loaded apps.
+* bin/camping can be given a directory. Like: <tt>camping examples/</tt>
+* Console mode -- thank zimbatm. Use: camping -C yourapp.rb
+* Call controllers with Camping.method_missing.
+
+ Tepee.get(:Index) #=> (Response)
+ Blog.post(:Delete, id) #=> (Response)
+
+ Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'})
+ #=> #<Blog::Controllers::Login @user=... >
+
+ Blog.get(:Info, :env => {:HTTP_HOST => 'wagon'})
+ #=> #<Blog::Controllers::Info @env={'HTTP_HOST'=>'wagon'} ...>
+
+* Using \r\n instead of \n on output. FastCGI has these needs.
+* ActiveRecord no longer required or installed.
+* If you refer to Models::Base, however, ActiveRecord will be loaded with autoload. (see lib/camping/db.rb)
+* new Camping::FastCGI.serve which will serve a whole directory of apps
+ (see http://code.whytheluckystiff.net/camping/wiki/TheCampingServer)
+* ~/.campingrc can contain database connection info if you want your default to be something other than SQLite.
+
+ database:
+ adapter: mysql
+ username: camping