From c1f6e46a618984fa6be0a97c9cb2670d190996b6 Mon Sep 17 00:00:00 2001 From: _why Date: Thu, 6 Jul 2006 04:30:40 +0000 Subject: [PATCH] * lib/camping.rb: a few of zimbatm's wondrous patches (#32, got rid of R class, using respond_to? :urls instead.) Also, trimmed down the `un`, `escape` and `table_name_prefix` methods to clear some space. * lib/camping-unabridged.rb: ditto. * lib/camping/reloader.rb: extracted from bin/camping, such an essential part of TheCampingServer. * bin/camping: console mode (#65 from zimbatm as well!) --- bin/camping | 96 ++++++++-------------------------- lib/camping-unabridged.rb | 18 +++---- lib/camping.rb | 23 ++++---- lib/camping/reloader.rb | 107 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 94 deletions(-) create mode 100644 lib/camping/reloader.rb diff --git a/bin/camping b/bin/camping index 47cd8c1..1ea9c03 100755 --- a/bin/camping +++ b/bin/camping @@ -13,8 +13,9 @@ require 'stringio' require 'rubygems' require 'camping' require 'camping/session' +require 'camping/reloader' -host, port, db, log = '0.0.0.0', 3301 +server, host, port, db, log = :mongrel, '0.0.0.0', 3301 homes = [] homes << File.join( ENV['HOME'], '.camping.db' ) if ENV['HOME'] @@ -34,6 +35,7 @@ opts = OptionParser.new do |opts| opts.on("-p", "--port NUM", "Port for web server (defaults to #{port})") { |port| } opts.on("-d", "--database FILE", "Database file (defaults to #{db})") { |db| } opts.on("-l", "--log FILE", "Start a database log ('-' for STDOUT)") { |log| } + opts.on("-C", "--console", "Run in console mode with IRB") { server = :console } opts.separator "" opts.separator "Common options:" @@ -78,73 +80,6 @@ rescue LoadError abort "!! Please check out http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for tips." end -class CampingReloader - attr_accessor :klass, :mtime, :mount, :requires - - def initialize(script) - @script = File.expand_path(script) - @mount = File.basename(script, '.rb') - @requires = nil - load_app - end - - def load_app - title = File.basename(@script)[/^(\w+)/,1] - begin - all_requires = $LOADED_FEATURES.dup - load @script - @requires = ($LOADED_FEATURES - all_requires).select do |req| - req.index(File.basename(@script) + "/") == 0 || req.index(title + "/") == 0 - end - rescue Exception => e - puts "!! trouble loading #{title}: [#{e.class}] #{e.message}" - @klass = nil - return - end - - @mtime = mtime - @klass = Object.const_get(Object.constants.grep(/^#{title}$/i)[0]) rescue nil - unless @klass and @klass.const_defined? :C - puts "!! trouble loading #{title}: not a Camping app" - @klass = nil - return - end - @klass.create if @klass.respond_to? :create - @klass - end - - def mtime - ((@requires || []) + [@script]).map do |fname| - fname = fname.gsub(/^#{Regexp::quote File.dirname(@script)}\//, '') - File.mtime(File.join(File.dirname(@script), fname)) - end.max - end - - # Load the script, locate the module - def reload_app - return if @klass and @mtime and mtime <= @mtime - - if @requires - @requires.each { |req| $LOADED_FEATURES.delete(req) } - end - k = @klass - Object.instance_eval { remove_const k.name } if k - load_app - end - - def run(*a) - reload_app - if @klass - @klass.run(*a) - else - Camping.run(*a) - end - end - - def view_source - File.read(@script) - end -end apps = ARGV.inject([]) do |apps, script| if File.directory? script @@ -154,7 +89,7 @@ apps = ARGV.inject([]) do |apps, script| end end -apps.map! { |script| CampingReloader.new(script) } +apps.map! { |script| Camping::Reloader.new(script) } abort("** No apps successfully loaded") unless apps.detect { |app| app.klass } def apps.index_page @@ -194,9 +129,18 @@ def apps.index_page b.to_s end -begin - require 'mongrel' - require 'mongrel/camping' +# Check that mongrel exists +if server == :mongrel + begin + require 'mongrel' + require 'mongrel/camping' + rescue LoadError + server = :webrick + end +end + +case server +when :mongrel class IndexHandler < Mongrel::HttpHandler def initialize(apps) @apps = apps @@ -235,7 +179,7 @@ begin end end config.join -rescue LoadError +when :webrick require 'webrick/httpserver' require 'camping/webrick' @@ -259,4 +203,10 @@ rescue LoadError s.shutdown end s.start +when :console + ARGV.clear # Avoid passing args to IRB + + require 'irb' + require 'irb/completion' + IRB.start end diff --git a/lib/camping-unabridged.rb b/lib/camping-unabridged.rb index bee62bc..dbd4ee3 100644 --- a/lib/camping-unabridged.rb +++ b/lib/camping-unabridged.rb @@ -96,6 +96,7 @@ module Camping Apps = [] C = self S = IO.read(__FILE__).sub(/S=I.+$/,'') + P="Cam\ping Problem!" H = HashWithIndifferentAccess # An object-like Hash, based on ActiveSupport's HashWithIndifferentAccess. @@ -439,9 +440,6 @@ def markaby #:nodoc: end end - # The R class is the parent class for all routed controllers. - class R; include Base end - # Controllers is a module for placing classes which handle URLs. This is done # by defining a route to each class using the Controllers::R method. # @@ -477,7 +475,7 @@ module Controllers # end # end # - class NotFound; def get(p); r(404, div{h1("Cam\ping Problem!");h2("#{p} not found")}); end end + class NotFound; def get(p); r(404, div{h1(P);h2("#{p} not found")}); end end # The ServerError class is a special controller class for handling many (but not all) 500 errors. # If there is a parse error in Camping or in your application's source code, it will not be caught @@ -502,7 +500,7 @@ class NotFound; def get(p); r(404, div{h1("Cam\ping Problem!");h2("#{p} not foun # end # end # - class ServerError; include Base; def get(k,m,e); r(500, Mab.new { h1 "Cam\ping Problem!"; h2 "#{k}.#{m}"; h3 "#{e.class} #{e.message}:"; ul { e.backtrace.each { |bt| li bt } } }.to_s) end end + class ServerError; include Base; def get(k,m,e); r(500, Mab.new { h1(P); h2 "#{k}.#{m}"; h3 "#{e.class} #{e.message}:"; ul { e.backtrace.each { |bt| li bt } } }.to_s) end end class << self # Add routes to a controller class by piling them into the R method. @@ -524,7 +522,7 @@ class << self # # Most of the time the rules inferred by dispatch method Controllers::D will get you # by just fine. - def R(*urls); Class.new(R) { meta_def(:urls) { urls } }; end + def R(*urls); Class.new { meta_def(:urls) { urls } }; end # Dispatch routes to controller classes. Classes are searched in no particular order. # For each class, routes are checked for a match based on their order in the routing list @@ -533,7 +531,7 @@ def R(*urls); Class.new(R) { meta_def(:urls) { urls } }; end def D(path) constants.inject(nil) do |d,c| k = const_get(c) - k.meta_def(:urls){["/#{c.downcase}"]}if !(k "I%27d+go+to+the+museum+straightway%21" # - def escape(s); s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join('%').upcase}.tr(' ', '+') end + def escape(s); s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ', '+') end # Unescapes a URL-encoded string. # # Camping.un("I%27d+go+to+the+museum+straightway%21") # #=> "I'd go to the museum straightway!" # - def un(s); s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.delete('%')].pack('H*')} end + def un(s); s.tr('+', ' ').gsub(/%([\da-f]){2}/in){[$1].pack('H*')} end # Parses a query string into an Camping::H object. # @@ -674,7 +672,7 @@ module Models # #=> "tepee_pages" # def Base.table_name_prefix - "#{name[/^(\w+)/,1]}_".downcase.sub(/^(#{A}|camping)_/i,'') + "#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'') end end diff --git a/lib/camping.rb b/lib/camping.rb index 70bbeee..21480ec 100644 --- a/lib/camping.rb +++ b/lib/camping.rb @@ -1,5 +1,6 @@ %w[active_record markaby metaid tempfile uri].each{|l|require l} module Camping;Apps=[];C=self;S=IO.read(__FILE__).sub(/S=I.+$/,'') +P="Cam\ping Problem!" module Helpers;def R c,*args;p=/\(.+?\)/;args.inject(c.urls.find{|x|x.scan(p). size==args.size}.dup){|str,a|str.sub(p,C.escape((a.__send__(a.class.primary_key )rescue a)))} end;def URL c='/',*a;c=R(c,*a)if c.respond_to?:urls;c=self/c;c= @@ -25,25 +26,25 @@ module Base;include Helpers;attr_accessor :input,:cookies,:env,:headers,:body, " if v != @k[k]}.compact;self end;def to_s;"Status: #{@status}\r\n#{@headers.map{ |k,v|[*v].map{|x|"#{k}: #{x}"}}*"\r\n"}\r\n\r\n#{@body}" end;def markaby;Mab.new( instance_variables.map{|iv|[iv[1..-1],instance_variable_get(iv)]}) end;def -markaview m,*a,&b;h=markaby;h.send m,*a,&b;h.to_s end end;class R;include Base -end;module Controllers;class NotFound;def get p;r(404,div{h1 "Cam\ping Problem!" +markaview m,*a,&b;h=markaby;h.send m,*a,&b;h.to_s end end +module Controllers;class NotFound;def get p;r(404,div{h1 P h2 p+" not found"}) end end;class ServerError;include Base;def get k,m,e;r(500, -Mab.new{h1 "Cam\ping Problem!";h2 "#{k}.#{m}";h3 "#{e.class} #{e.message}:";ul{ -e.backtrace.each{|bt|li(bt)}}}.to_s)end end;class<x;Controllers::ServerError.new(r,e,'get').service( k,m,x)end end;module Views;include Controllers,Helpers end;module Models;A= -ActiveRecord;Base=A::Base;def Base.table_name_prefix;"#{name[/^(\w+)/,1]}_". +ActiveRecord;Base=A::Base;def Base.table_name_prefix;"#{name[/\w+/]}_". downcase.sub(/^(#{A}|camping)_/i,'')end end;class Mab e + puts "!! trouble loading #{title}: [#{e.class}] #{e.message}" + @klass = nil + return + end + + @mtime = mtime + @klass = Object.const_get(Object.constants.grep(/^#{title}$/i)[0]) rescue nil + unless @klass and @klass.const_defined? :C + puts "!! trouble loading #{title}: not a Camping app" + @klass = nil + return + end + @klass.create if @klass.respond_to? :create + @klass + end + + # The timestamp of the most recently modified app dependency. + def mtime + ((@requires || []) + [@script]).map do |fname| + fname = fname.gsub(/^#{Regexp::quote File.dirname(@script)}\//, '') + File.mtime(File.join(File.dirname(@script), fname)) + end.max + end + + # Conditional reloading of the app. This gets called on each request and + # only reloads if the modification times on any of the files is updated. + def reload_app + return if @klass and @mtime and mtime <= @mtime + + if @requires + @requires.each { |req| $LOADED_FEATURES.delete(req) } + end + k = @klass + Object.instance_eval { remove_const k.name } if k + load_app + end + + # Conditionally reloads (using reload_app.) Then passes the request through + # to the wrapped Camping app. + def run(*a) + reload_app + if @klass + @klass.run(*a) + else + Camping.run(*a) + end + end + + # Returns source code for the main script in the application. + def view_source + File.read(@script) + end +end +end