Changeset 237

Show
Ignore:
Timestamp:
10/02/2007 15:23:16 (14 months ago)
Author:
zimbatm
Message:

* Introduced error 501 handling and changed NotFound? and ServerError? controllers to #r404 and #r500 methods. camping.rb is exactly at 4000 octets !
* Some doc fixes in man page, README, CHANGELOG. Unfortunately the rdoc are still badly parsed.

Location:
trunk
Files:
9 modified

Legend:

Unmodified
Added
Removed
  • trunk/CHANGELOG

    r167 r237  
     1= 1.6 
     2=== ???, 2007 
     3 
     4* Camping::Apps removed, it wasn't reliable. 
     5* bin/camping server kinds splitted in various files. 
     6* NotFound and ServerError controllers changed to methods : 
     7 
     8  r404 : called when a controller was not found 
     9  r500 : called on uncaught exception 
     10  r501 : called on undefined method 
     11   
     12  All of those can be overridden at your taste. 
     13 
     14* Markaby no longer required. Like AR, is it autoloaded on (Mab) usage. 
     15* Camping::H is now inheriting from Hash instead of HashWithIndifferentAccess. 
     16* Which made possible to remove the last strict dependency : active_support 
     17* #errors_for removed, it wasn't really used 
     18* Bug fixes ! 
     19 
    120= 1.5 
    221=== 3rd Oct, 2006 
  • trunk/README

    r159 r237  
    9494 
    9595* Run: <tt>camping blog.rb</tt> 
    96 * Visit http://localhost:3301/blog/ to use the app. 
     96* Visit http://localhost:3301/ to use the app. 
    9797 
    9898== How the Camping Tool Works 
  • trunk/Rakefile

    r218 r237  
    147147    if File.size("lib/camping.rb") > SIZE_LIMIT 
    148148      STDERR.puts "lib/camping.rb: file is too big (> #{SIZE_LIMIT})" 
    149       exit 1 
    150149    end 
    151150  end 
     
    156155    File.open("lib/camping.rb").each_line do |line| 
    157156      if line.size > 81 # 1 added for \n 
    158         puts "lib/camping.rb:#{i}: line too long (#{line[-10..-1].inspect})" 
     157        STDERR.puts "lib/camping.rb:#{i}: line too long (#{line[-10..-1].inspect})" 
    159158      end 
    160159      i += 1 
  • trunk/bin/camping

    r232 r237  
    66require 'yaml' 
    77 
    8 begin require 'rubygems' rescue LoadError end 
    98require 'camping' 
    109require 'camping/server' 
  • trunk/lib/camping-unabridged.rb

    r236 r237  
    3131%w[tempfile uri].map { |l| require l } 
    3232 
    33 class Object 
    34   # Define a method m with the passed block on the metaclass. 
    35   def meta_def(m,&b) 
     33class Object #:nodoc: 
     34  def meta_def(m,&b) #:nodoc: 
    3635    (class<<self;self end).send(:define_method,m,&b) 
    3736  end 
     
    9291  C = self 
    9392  S = IO.read(__FILE__) rescue nil 
    94   P = "Cam\ping Problem!" 
     93  P = '<h1>Cam\ping Problem!</h1><h2>%s</h2>' 
    9594  # An object-like Hash. 
    9695  # All Camping query string and cookie variables are loaded as this. 
     
    323322    end 
    324323 
     324    # A quick means of setting this controller's status, body and headers. 
     325    # Used internally by Camping, but... by all means... 
     326    # 
     327    #   r(302, '', 'Location' => self / "/view/12") 
     328    # 
     329    # Is equivalent to: 
     330    # 
     331    #   redirect "/view/12" 
     332    # 
     333    # See also: #r404, #r500 and #r501 
     334    def r(s, b, h = {}); @status = s; headers.u(h); @body = b; end 
     335 
    325336    # Formulate a redirect response: a 302 status with <tt>Location</tt> header 
    326337    # and a blank body.  Uses Helpers#URL to build the location from a controller 
     
    339350    end 
    340351 
    341     # A quick means of setting this controller's status, body and headers. 
    342     # Used internally by Camping, but... by all means... 
    343     # 
    344     #   r(302, '', 'Location' => self / "/view/12") 
    345     # 
    346     # Is equivalent to: 
    347     # 
    348     #   redirect "/view/12" 
    349     # 
    350     def r(s, b, h = {}); @status = s; headers.u(h); @body = b; end 
     352    # Called when a controller was not found. It is mainly used internally, but it can 
     353    # also be useful for you, if you want to filter some parameters. 
     354    # 
     355    # module Camping 
     356    #   def r404(p=env.PATH) 
     357    #     @status = 404 
     358    #     div do 
     359    #       h1 'Camping Problem!' 
     360    #       h2 "#{p} not found" 
     361    #     end 
     362    #   end 
     363    # end 
     364    # 
     365    # See: I 
     366    def r404(p=env.PATH) 
     367      r(404, P % "#{p} not found") 
     368    end 
     369 
     370    # If there is a parse error in Camping or in your application's source code, it will not be caught 
     371    # by Camping.  The controller class +k+ and request method +m+ (GET, POST, etc.) where the error 
     372    # took place are passed in, along with the Exception +e+ which can be mined for useful info. 
     373    # 
     374    # You can overide it, but if you have an error in here, it will be uncaught ! 
     375    # 
     376    # See: I 
     377    def r500(k,m,x) 
     378      r(500, P % "#{k}.#{m}" + "<h3>#{x.class} #{x.message}: <ul>#{x.backtrace.map{|b|"<li>#{b}</li>"}}</ul></h3>") 
     379    end 
     380 
     381    # Called if an undefined method is called on a Controller, along with the request method +m+ (GET, POST, etc.) 
     382    # 
     383    # See: I 
     384    def r501(m=@method) 
     385      r(501, P % "#{m.upcase} not implemented") 
     386    end 
    351387 
    352388    # Turn a controller into an array.  This is designed to be used to pipe 
     
    365401 
    366402    def initialize(r, e, m) #:nodoc: 
    367       @status, @method, @env, @headers, @root = 200, m.downcase, e,  
     403      @status, @method, @env, @headers, @root = 200, m, e,  
    368404          H['Content-Type','text/html'], e.SCRIPT_NAME.sub(/\/$/,'') 
    369405      @k = C.kp(e.HTTP_COOKIE) 
     
    418454    # on before and after overrides with Camping. 
    419455    def service(*a) 
    420       @body = send(@method, *a) if respond_to? @method 
     456      @body = send(@method, *a) 
    421457      headers['Set-Cookie'] = cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil] 
    422458      self 
     
    494530      # 
    495531      # So, define your catch-all controllers last. 
    496       def D(p) 
     532      def D(p, m) 
    497533        r.map { |k| 
    498534          k.urls.map { |x| 
    499             return k, $~[1..-1] if p =~ /^#{x}\/?$/ 
     535            return (k.instance_method(m) rescue nil) ? 
     536              [k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/ 
    500537          } 
    501538        } 
    502         [NotFound, [p]] 
     539        [I, 'r404', p] 
    503540      end 
    504541 
     
    518555          k=const_get(c) 
    519556          k.send :include,C,Base,Helpers,Models 
    520           r[0,0]=k if !r.include?k 
     557          @r=[k]+r if r-[k]==r 
    521558          k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls 
    522559        } 
     
    524561    end 
    525562 
    526     # The NotFound class is a special controller class for handling 404 errors, in case you'd 
    527     # like to alter the appearance of the 404.  The path is passed in as +p+. 
    528     # 
    529     #   module Camping::Controllers 
    530     #     class NotFound 
    531     #       def get(p) 
    532     #         @status = 404 
    533     #         div do 
    534     #           h1 'Camping Problem!' 
    535     #           h2 "#{p} not found" 
    536     #         end 
    537     #       end 
    538     #     end 
    539     #   end 
    540     # 
    541     class NotFound < R() 
    542       def get(p) 
    543         r(404, "<h1>#{P}</h1><h2>#{p} not found</h2>") 
    544       end 
    545     end 
    546  
    547     # The ServerError class is a special controller class for handling many (but not all) 500 errors. 
    548     # If there is a parse error in Camping or in your application's source code, it will not be caught 
    549     # by Camping.  The controller class +k+ and request method +m+ (GET, POST, etc.) where the error 
    550     # took place are passed in, along with the Exception +e+ which can be mined for useful info. 
    551     # 
    552     #   module Camping::Controllers 
    553     #     class ServerError 
    554     #       def get(k,m,e) 
    555     #         @status = 500 
    556     #         div do 
    557     #           h1 'Camping Problem!' 
    558     #           h2 "in #{k}.#{m}" 
    559     #           h3 "#{e.class} #{e.message}:" 
    560     #           ul do 
    561     #             e.backtrace.each do |bt| 
    562     #               li bt 
    563     #             end 
    564     #           end 
    565     #         end 
    566     #       end 
    567     #     end 
    568     #   end 
    569     # 
    570     class ServerError < R() 
    571       def get(k,m,e) 
    572         r(500, "<h1>#{P}</h1><h2>#{k}.#{m}</h2><h3>#{e.class} #{e.message 
    573           }:<ul>#{e.backtrace.map{ |b| "<li>#{b}</li>" } }") 
    574       end 
     563     
     564    # Internal controller with no route. Used by #D and C.run to show internal messages. 
     565    class I < R() 
    575566    end 
    576567 
     
    661652      X.M 
    662653      e = H[e.to_hash] 
    663       k,a=X.D e.PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/')) 
    664       k.new(r,e,(m=e.REQUEST_METHOD||"GET")).Y.service(*a) 
    665     rescue=>x 
    666       X::ServerError.new(r,e,'get').service(k,m,x) 
     654      k,m,*a=X.D e.PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/')),(e.REQUEST_METHOD||'get').downcase 
     655      k.new(r,e,m).Y.service(*a) 
     656    rescue => x 
     657      X::I.new(r,e,'r500').service(k,m,x) 
    667658    end 
    668659 
  • trunk/lib/camping.rb

    r236 r237  
    1 %w[tempfile uri].map{|l|require l};class Object;def meta_def m,&b 
    2 (class<<self;self end).send(:define_method,m,&b)end end 
    3 module Camping;C=self;S=IO.read(__FILE__)rescue nil;P="Cam\ping Problem!" 
    4 class H<Hash;def method_missing m,*a 
    5 m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end;alias u merge! 
    6 undef id, type;end 
    7 module Helpers def R c,*g;p,h=/\(.+?\)/,g.grep(Hash);g-=h; 
    8 raise"bad route"unless u=c.urls.find{|x|break x if x.scan(p).size==g.size&& 
    9 /^#{x}\/?$/=~(x=g.inject(x){|x,a|x.sub p,C.escape((a[a.class.primary_key 
    10 ]rescue a))})} 
    11 h.any?? u+"?"+h[0].map{|x|x.map{|z|C.escape z}*"="}*"&":u end 
    12 def /(p);p[/^\//]?@root+p:p;end;def URL c='/',*a 
    13 c=R(c,*a)if c.respond_to?:urls;c=self/c;c="//"+@env.HTTP_HOST+c if c[/^\//] 
    14 URI(c) end end;module Base;attr_accessor:input,:cookies,:env,:headers,:body, 
    15 :status,:root;Z="\r\n";def method_missing*a,&b;a.shift if a[0]==:render 
    16 m=Mab.new({},self);s=m.capture{send(*a,&b)};s=m.capture{send(:layout){s}}if 
    17 /^_/!~a[0].to_s and m.respond_to?:layout;s end;def redirect*a 
    18 r 302,'','Location'=>URL(*a)end;def r s,b,h={};@status=s;headers.u h 
    19 @body=b end;def to_a;[status,body,headers]end;def initialize r,e,m 
    20 @status,@method,@env,@headers,@root=200,m.downcase,e,H[ 
    21 'Content-Type',"text/html"],e.SCRIPT_NAME.sub(/\/$/,'');@k=C.kp e.HTTP_COOKIE 
    22 q=C.qsp e.QUERY_STRING;@in=r;case e.CONTENT_TYPE 
    23 when %r|\Amultipart/form-.*boundary=\"?([^\";,]+)|n 
    24 b=/(?:\r?\n|\A)#{Regexp::quote"--#$1"}(?:--)?\r$/;until 
    25 @in.eof?;fh=H[];for l in@in;case l;when Z;break;when/^Content-D.+?: form-data;/ 
    26 fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten];when 
    27 /^Content-Type: (.+?)(\r$|\Z)/m;fh.type=$1;end;end;fn=fh.name;o=if fh. 
    28 filename;o=fh.tempfile=Tempfile.new(:C);o.binmode;else;fh=""end;s=8192;k='' 
    29 l=@in.read(s*2);while l;if(k<<l)=~b;o<<$`.chomp;@in.seek(-$'.size,IO::SEEK_CUR) 
    30 break;end;o<<k.slice!(0...s);l=@in.read(s);end;C.qsp(fn,'&;',fh,q)if fn;fh. 
    31 tempfile.rewind if fh.is_a?H;end;when "application/x-www-form-urlencoded" 
    32 q.u C.qsp(@in.read)end;@cookies,@input=@k.dup,q.dup end;def service*a 
    33 @body=send(@method,*a)if respond_to?@method 
    34 headers["Set-Cookie"]=cookies.map{|k,v|"#{k}=#{C.escape v}; path=#{self/'/'}"if 
    35 v!=@k[k]}-[nil];self end;def to_s;"Status: #@status#{Z+headers.map{|k,v|[*v]. 
    36 map{|x|[k,v]*": "}}*Z+Z}#@body"end;end;X=module Controllers;@r=[];class<< 
    37 self;def r;@r;end;def R*u;r=@r;Class.new{meta_def(:urls){u};meta_def(:inherited 
    38 ){|x|r<<x}}end;def D p;r.map{|k|k.urls.map{|x|return k,$~[1..-1]if p=~/^#{x 
    39 }\/?$/}};[NotFound,[p]]end;def M;def M;end;constants.map{|c|k=const_get(c);k. 
    40 send:include,C,Base,Helpers,Models;r[0,0]=k if !r.include?k;k.meta_def(:urls){[ 
    41 "/#{c.downcase}"]}if !k.respond_to?:urls}end;end;class NotFound<R();def get p 
    42 r(404,"<h1>#{P}</h1><h2>#{p} not found</h2>")end end;class ServerError<R() 
    43 def get k,m,e;r(500,"<h1>#{P}</h1><h2>#{k}.#{m}</h2><h3>#{e.class} #{e.message 
    44 }:<ul>#{e.backtrace.map{ |b| "<li>#{b}</li>" } }")end end;self;end;class<<self 
    45 def goes m;eval S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING;end;def escape s 
    46 s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ','+') 
    47 end;def un s;s.tr('+',' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')}end 
     1%w[tempfile uri].map{|l|require l};class Object;def meta_def m,&b;(class<<self 
     2self end).send:define_method,m,&b end end;module Camping;C=self 
     3S=IO.read(__FILE__)rescue nil;P="<h1>Cam\\ping Problem!</h1><h2>%s</h2>" 
     4class H<Hash 
     5def method_missing m,*a;m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end 
     6alias u merge!;undef id,type;end;module Helpers;def R c,*g 
     7p,h=/\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"unless u=c.urls.find{|x| 
     8break x if x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a| 
     9x.sub p,C.escape((a[a.class.primary_key]rescue a))})} 
     10h.any?? u+"?"+h[0].map{|x|x.map{|z|C.escape z}*"="}*"&":u end;def / p 
     11p[/^\//]?@root+p:p end;def URL c='/',*a;c=R(c, *a) if c.respond_to?:urls 
     12c=self/c;c="//"+@env.HTTP_HOST+c if c[/^\//];URI c end end;module Base 
     13attr_accessor:input,:cookies,:env,:headers,:body,:status,:root;Z="\r\n" 
     14def method_missing *a,&b;a.shift if a[0]==:render;m=Mab.new({},self) 
     15s=m.capture{send(*a,&b)};s=m.capture{send(:layout){s}}if/^_/!~a[0].to_s and 
     16m.respond_to?:layout;s end;def r s,b,h={};@status=s;headers.u(h);@body=b 
     17end;def redirect *a;r 302,'','Location'=>URL(*a)end;def r404 p=env.PATH 
     18r 404,P%"#{p} not found"end;def r500 k,m,x 
     19r 500,P%"#{k}.#{m}"+"<h3>#{x.class} #{x.message}: <ul>#{x. 
     20backtrace.map{|b|"<li>#{b}</li>"}}</ul></h3>"end;def r501 m=@method 
     21r 501,P%"#{m.upcase} not implemented"end;def to_a 
     22[status,body,headers]end;def initialize r,e,m;@status,@method,@env,@headers, 
     23@root=200,m,e,H['Content-Type','text/html'],e.SCRIPT_NAME.sub(/\/$/,'') 
     24@k=C.kp e.HTTP_COOKIE;q=C.qsp e.QUERY_STRING;@in=r;case e.CONTENT_TYPE 
     25when%r|\Amultipart/form-.*boundary=\"?([^\";,]+)|n 
     26b=/(?:\r?\n|\A)#{Regexp.quote"--#$1"}(?:--)?\r$/;until@in.eof?;fh=H[] 
     27for l in@in;case l;when Z;break;when/^Content-D.+?: form-data;/ 
     28fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten] 
     29when/^Content-Type: (.+?)(\r$|\Z)/m: fh.type = $1 end end;fn=fh.name 
     30o=if fh.filename;o=fh.tempfile=Tempfile.new(:C);o.binmode;else;fh="";end;s=8192 
     31k='';l=@in.read(s*2);while l;if(k<<l)=~b;o<<$`.chomp 
     32@in.seek(-$'.size,IO::SEEK_CUR);break end;o<<k.slice!(0...s);l=@in.read(s) end 
     33C.qsp(fn,'&;',fh,q)if fn;fh.tempfile.rewind if fh.is_a?H end;when 
     34"application/x-www-form-urlencoded": q.u(C.qsp(@in.read))end 
     35@cookies,@input=@k.dup,q.dup end;def service *a;@body=send @method,*a 
     36headers['Set-Cookie']=cookies.map{|k,v|"#{k}=#{C.escape(v)}; path=#{self/ 
     37"/"}"if v!=@k[k]}-[nil];self end;def to_s 
     38"Status: #@status#{Z+headers.map{|k,v|[*v].map{|x|[k,v]*": "}}*Z+Z}#@body"end 
     39end;X=module Controllers;@r=[];class<<self;def r;@r end;def R *u;r=@r 
     40Class.new{meta_def(:urls){u};meta_def(:inherited){|x|r<<x}}end 
     41def D p,m;r.map{|k|k.urls.map{|x|return(k.instance_method(m)rescue nil)? 
     42[k,m,*$~[1..-1]]:[I,'r501',m]if p=~/^#{x}\/?$/}};[I,'r404',p] 
     43end;def M;def M;end;constants.map{|c|k=const_get(c) 
     44k.send:include,C,Base,Helpers,Models;@r=[k]+r if r-[k]==r 
     45k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls}end end;class I<R() 
     46end;self end;class<<self;def goes m 
     47eval S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING end;def escape s 
     48s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr' ','+' 
     49end;def un s;s.tr('+',' ').gsub(/%([\da-f]{2})/in){[$1].pack'H*'}end 
    4850def qsp q,d='&;',y=nil,z=H[];m=proc{|_,o,n|o.u(n,&m)rescue([*o]<<n)} 
    49 (q.to_s.split(/[#{d}]+ */n)-['']).inject((b,z=z,H[])[0]){|h,p|k,v=un(p). 
    50 split('=',2);h.u k.split(/[\]\[]+/).reverse.inject(y||v){|x,i|H[i,x]},&m}end 
    51 def kp s;c=qsp(s,';,')end;def run r=$stdin,e=ENV;X.M;e=H[e.to_hash] 
    52 k,a=X.D e.PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/'));k.new( 
    53 r,e,(m=e.REQUEST_METHOD||"GET")).Y.service(*a);rescue=>x;X::ServerError.new( 
    54 r,e,'get').service(k,m,x)end;def method_missing m,c,*a;X.M;k=X.const_get(c). 
    55 new(StringIO.new,H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s) 
    56 H[a.pop].each{|e,f|k.send("#{e}=",f)}if Hash===a[-1];k.service(*a);end;end 
    57 module Views;include X,Helpers;end;module Models;autoload:Base,'camping/db';def 
    58 Y;self;end;end;autoload:Mab,'camping/mab'end 
     51(q.to_s.split(/[#{d}]+ */n)-[""]).inject((b,z=z,H[])[0]){|h,p|k,v=un(p). 
     52split'=',2;h.u k.split(/[\]\[]+/).reverse.inject(y||v){|x,i|H[i,x]},&m}end 
     53def kp s;c=qsp s,';,'end;def run r=$stdin,e=ENV;X.M;e=H[e.to_hash];k,m,*a=X.D e. 
     54PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/')), 
     55(e.REQUEST_METHOD||'get').downcase 
     56k.new(r,e,m).Y.service(*a);rescue=>x;X::I.new(r,e,'r500').service k,m,x 
     57end;def method_missing m,c,*a;X.M;k=X.const_get(c).new StringIO.new, 
     58H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s 
     59H[a.pop].each{|e,f|k.send"#{e}=",f}if Hash===a[-1];k.service(*a)end end 
     60module Views;include X,Helpers end;module Models;autoload:Base,'camping/db' 
     61def Y;self;end end;autoload:Mab,'camping/mab'end 
  • trunk/lib/camping/server.rb

    r235 r237  
    9595   
    9696  def insert_app(script) 
    97     self[script] = Application.new(script) 
     97    self[script] = Reloader.new(script) 
    9898  end 
    99 end 
    100  
    101 class Application < Camping::Reloader 
    10299end 
    103100end 
  • trunk/test/apps/misc.rb

    r236 r237  
    2929      def get; render :bad_links; end 
    3030    end 
     31    class BadMethod; end 
    3132  end 
    3233 
     
    4344            li{ a "BadLinks", :href=>R(BadLinks)} 
    4445            li{ a "Redirect", :href=>R(Redirect)} 
     46            li{ a "BadMethod", :href=>R(BadMethod)} 
    4547          end 
    4648          p { yield }