Changeset 134

Show
Ignore:
Timestamp:
07/12/2006 12:44:46 (2 years ago)
Author:
why
Message:
  • lib/camping.rb: allow the Camping scriptable dispatcher (Camping.method_missing) to take a hash of environment vars or query vars. also, an empty StringIO is the default input stream. apply zimbatm's patch to allow urls to be overridden.
  • lib/camping-unabridged.rb: updated all the code and docs to match the above.
  • lib/camping/reloader.rb: update the not a Camping app error message to give the expected module name.
  • lib/camping/db.rb: docs.
  • Rakefile: at least Markaby 0.4.65 required.
Location:
trunk
Files:
6 modified

Legend:

Unmodified
Added
Removed
  • trunk/Rakefile

    r131 r134  
    6161 
    6262        s.add_dependency('activesupport', '>=1.3.1') 
    63         s.add_dependency('markaby', '>0.4') 
     63        s.add_dependency('markaby', '>=0.4.65') 
    6464        s.add_dependency('metaid') 
    6565        s.required_ruby_version = '>= 1.8.2' 
  • trunk/examples/blog.rb

    r129 r134  
    7171            div do 
    7272                code args.inspect; br; br 
    73                 code ENV.inspect; br 
     73                code @env.inspect; br 
    7474                code "Link: #{R(Info, 1, 2)}" 
    7575            end 
  • trunk/lib/camping-unabridged.rb

    r129 r134  
    2929# in its <tt>examples/camping</tt> directory.  
    3030# 
    31 %w[active_record markaby metaid tempfile uri].each { |lib| require lib } 
     31%w[active_support/core_ext/hash markaby metaid tempfile uri].each { |lib| require lib } 
    3232 
    3333# == Camping  
     
    207207    # 
    208208    def R(c,*g) 
    209       p = /\(.+?\)/ 
     209      p=/\(.+?\)/ 
    210210      g.inject(c.urls.find{|x|x.scan(p).size==g.size}.dup){|s,a| 
    211         s.sub(p,C.escape((a.__send__(a.class.primary_key) rescue a))) 
     211        s.sub p,C.escape((a[a.class.primary_key]rescue a)) 
    212212      } 
    213213    end 
     214 
    214215    # Shows AR validation errors for the object passed.  
    215216    # There is no output if there are no errors. 
     
    309310    include Helpers 
    310311    attr_accessor :input, :cookies, :env, :headers, :body, :status, :root 
     312    Z = "\r\n" 
     313 
    311314    # Display a view, calling it by its method name +m+.  If a <tt>layout</tt> 
    312315    # method is found in Camping::Views, it will be used to wrap the HTML. 
     
    334337    # If you have a <tt>layout</tt> method in Camping::Views, it will be used to 
    335338    # wrap the HTML. 
    336     def method_missing(m, *a, &b) 
    337       str = m==:render ? markaview(*a, &b):eval("markaby.#{m}(*a, &b)") 
    338       str = markaview(:layout) { str } if Views.method_defined? :layout 
    339       str 
     339    def method_missing(*a,&b) 
     340      a.shift if a[0]==:render 
     341      m=markaby 
     342      s=m.capture{send(*a,&b)} 
     343      s=m.layout{s} if m.respond_to?:layout 
     344      "#{s}" 
    340345    end 
    341346 
     
    380385          for l in @in 
    381386            case l 
    382             when "\r\n": break 
     387            when Z: break 
    383388            when /^Content-Disposition: form-data;/ 
    384389              fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten] 
     
    419424    def service(*a) 
    420425      @body = send(@method, *a) if respond_to? @method 
    421       @headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] }.compact 
     426      @headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil] 
    422427      self 
    423428    end 
     
    426431    # alter the way Camping builds HTTP headers, consider overriding this method. 
    427432    def to_s 
    428       "Status: #{@status}\r\n#{@headers.map{|k,v|[*v].map{|x|"#{k}: #{x}"}}*"\r\n"}\r\n\r\n#{@body}" 
     433      "Status: #{@status}#{Z+@headers.map{|k,v|[*v].map{|x|"#{k}: #{x}"}}*Z+Z*2+@body}" 
    429434    end 
    430435 
    431436    def markaby #:nodoc: 
    432         Mab.new( instance_variables.map { |iv|  
    433           [iv[1..-1], instance_variable_get(iv)] } ) 
    434     end 
    435      
    436     def markaview m,*a,&b #:nodoc: 
    437       h=markaby 
    438       h.send m,*a,&b 
    439       h.to_s 
     437      Mab.new({},self) 
    440438    end 
    441439  end 
    442440 
     441  X =  
    443442  # Controllers is a module for placing classes which handle URLs.  This is done 
    444443  # by defining a route to each class using the Controllers::R method. 
     
    461460  # uncaught by your application. 
    462461  module Controllers 
     462    @r = [] 
    463463    class << self 
    464       attr_accessor :routes 
     464      def r; @r end 
    465465      # Add routes to a controller class by piling them into the R method. 
    466466      # 
     
    482482      # Most of the time the rules inferred by dispatch method Controllers::D will get you 
    483483      # by just fine. 
    484       def R(*u); Class.new{meta_def(:urls){u};meta_def(:inherited){|x|X.routes<<x}}; end 
    485  
    486       # Dispatch routes to controller classes.  Classes are searched in no particular order. 
     484      def R *u 
     485        r=@r 
     486        Class.new { 
     487          meta_def(:urls){u} 
     488          meta_def(:inherited){|x|r<<x} 
     489        } 
     490      end 
     491 
     492      # Dispatch routes to controller classes. 
    487493      # For each class, routes are checked for a match based on their order in the routing list 
    488494      # given to Controllers::R.  If no routes were given, the dispatcher uses a slash followed 
    489495      # by the name of the controller lowercased. 
     496      # 
     497      # Controllers are searched in this order: 
     498      # 
     499      # # Classes without routes, since they refer to a very specific URL. 
     500      # # Classes with routes are searched in order of their creation. 
     501      # 
     502      # So, define your catch-all controllers last. 
    490503      def D(path) 
    491         constants.each do |c|  
    492             k = const_get(c) 
    493             k.meta_def(:urls){%r!^/#{c.downcase}/?$!}if !k.respond_to? :urls 
    494             case path when k.urls: return k, $~[1..-1] end 
    495         end 
     504        r.map { |k| 
     505          k.urls.map { |x| 
     506            return k, $~[1..-1] if path =~ /^#{x}\/?$/ 
     507          } 
     508        } 
    496509        [NotFound, [path]] 
     510      end 
     511 
     512      # The route maker, this is called by Camping internally, you shouldn't need to call it. 
     513      # 
     514      # Still, it's worth know what this method does.  Since Ruby doesn't keep track of class 
     515      # creation order, we're keeping an internal list of the controllers which inherit from R(). 
     516      # This method goes through and adds all the remaining routes to the beginning of the list 
     517      # and ensures all the controllers have the right mixins. 
     518      # 
     519      # Anyway, if you are calling the URI dispatcher from outside of a Camping server, you'll 
     520      # definitely need to call this at least once to set things up. 
     521      def M 
     522        def M; end 
     523        constants.map { |c| 
     524          k=const_get(c) 
     525          k.send:include,C,Base,Models 
     526          r[0,0]=k if !r.include?k 
     527          k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls 
     528        } 
    497529      end 
    498530    end 
     
    552584      end 
    553585    end 
     586    self 
    554587  end 
    555588 
     
    567600    # 
    568601    def goes(m) 
    569         eval(S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING) 
    570         Apps << const_get(m) 
     602      eval S.gsub(/Camping/,m.to_s).gsub("A\pps = []","Cam\ping::Apps<<self"), TOPLEVEL_BINDING 
    571603    end 
    572604 
     
    609641    # Parses a string of cookies from the <tt>Cookie</tt> header. 
    610642    def kp(s); c = qs_parse(s, ';,'); end 
    611  
    612     def method_missing(m, c, *a) 
    613         k = X.const_get(c) 
    614         k.send :include, C, Base, Models 
    615         k.allocate.method(m, *a) 
    616     end 
    617643 
    618644    # Fields a request through Camping.  For traditional CGI applications, the method can be 
     
    647673    # 
    648674    def run(r=$stdin,e=ENV) 
    649       k, a = Controllers.D un("/#{e['PATH_INFO']}".gsub(%r!/+!,'/')) 
    650       i(k).new(r,e,(m=e['REQUEST_METHOD']||"GET")).service(*a) 
    651     rescue Exception => x 
    652       i(Controllers::ServerError).new(r,e,'get').service(k,m,x) 
    653     end 
    654  
     675      X.M 
     676      k,a=X.D un("/#{e['PATH_INFO']}".gsub(/\/+/,'/')) 
     677      k.new(r,e,(m=e['REQUEST_METHOD']||"GET")).service *a 
     678    rescue Exception=>x 
     679      X::ServerError.new(r,e,'get').service(k,m,x) 
     680    end 
     681 
     682    # The Camping scriptable dispatcher.  Any unhandled method call to the app module will 
     683    # be sent to a controller class, specified as an argument. 
     684    # 
     685    #   Blog.get(:Index) 
     686    #   #=> #<Blog::Controllers::Index ... > 
     687    # 
     688    # The controller object contains all the @cookies, @body, @headers, etc. formulated by 
     689    # the response. 
     690    # 
     691    # You can also feed environment variables and query variables as a hash, the final 
     692    # argument. 
     693    # 
     694    #   Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'}) 
     695    #   #=> #<Blog::Controllers::Login @user=... > 
     696    # 
     697    #   Blog.get(:Info, :env => {:HTTP_HOST => 'wagon'}) 
     698    #   #=> #<Blog::Controllers::Info @env={'HTTP_HOST'=>'wagon'} ...> 
     699    # 
    655700    def method_missing(m, c, *a) 
    656       k = Controllers.const_get(c) 
    657       i(k).new(nil,H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s).service(*a) 
     701      X.M 
     702      k = X.const_get(c).new(StringIO.new, 
     703             H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s) 
     704      H.new(a.pop).each { |e,f| k.send("#{e}=",f) } if Hash === a[-1] 
     705      k.service *a 
    658706    end 
    659707  end 
     
    685733  # Models cannot be referred to in Views at this time. 
    686734  module Models 
    687       A = ActiveRecord 
    688       # Base is an alias for ActiveRecord::Base.  The big warning I'm going to give you 
    689       # about this: *Base overloads table_name_prefix.*  This means that if you have a 
    690       # model class Blog::Models::Post, it's table name will be <tt>blog_posts</tt>. 
    691       Base = A::Base 
    692  
    693       # The default prefix for Camping model classes is the topmost module name lowercase 
    694       # and followed with an underscore. 
    695       # 
    696       #   Tepee::Models::Page.table_name_prefix 
    697       #     #=> "tepee_pages" 
    698       # 
    699       def Base.table_name_prefix 
    700           "#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'') 
    701       end 
     735      autoload:Base,'camping/db' 
    702736  end 
    703737 
     
    723757  end 
    724758end 
     759 
     760autoload:ActiveRecord,'camping/db' 
  • trunk/lib/camping.rb

    r133 r134  
    2424@k.dup,q.dup end;def service *a;@body=send(@method,*a)if respond_to?@method 
    2525@headers["Set-Cookie"]=@cookies.map{|k,v|"#{k}=#{C.escape(v)}; path=#{self/"/"}\ 
    26 " if v!=@k[k]}-[nil];self end;def to_s;"Status: #{@status}#{Z+@headers.map{ 
    27 |k,v|[*v].map{|x|"#{k}: #{x}"}}*Z+Z*2+@body}" end;def markaby;Mab.new({},self) 
    28 end end;X=module Controllers 
    29 @r=[];class<<self;def r;@r;end;def R *u;r=@r;Class.new{meta_def(:urls){u} 
    30 meta_def(:inherited){|x|r<<x}}end;def M;constants.map{|c|k=const_get(c);k. 
    31 send:include,C,Base,Models;if !r.include?k;k.meta_def(:urls){["/#{c.downcase}"]} 
    32 r[0,0]=k;end}end;def D p;r.map{|k|k.urls.map{|x|return k,$~[1..-1]if p=~/^#{x}\/?$/} 
    33 };[NotFound, [p]]end end;class NotFound<R();def get p;r(404,Mab.new{h1 P 
    34 h2 p+" not found"}) end end;class ServerError<R();def get k,m,e;r(500, 
    35 Mab.new{h1 P;h2 "#{k}.#{m}";h3 "#{e.class} #{e.message}:";ul{ 
    36 e.backtrace.each{|bt|li(bt)}}}.to_s)end end;self;end;class<<self;def goes m 
    37 eval S.gsub(/Camping/,m.to_s).gsub("A\pps=[]","Cam\ping::Apps<<self"),TOPLEVEL_BINDING;end 
    38 def escape(s)s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ','+')end 
    39 def un(s)s.tr('+',' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')} end 
    40 def qs_parse q,d='&;';m=proc{|_,o,n|o.u(n,&m)rescue([*o 
    41 ]<<n)};q.to_s.split(/[#{d}] */n).inject(H[]){|h,p|k,v=un(p).split('=',2) 
    42 h.u k.split(/[\]\[]+/).reverse.inject(v){|x,i|H[i,x]},&m}end;def kp(s);c= 
    43 qs_parse(s,';,')end;def run r=$stdin,e=ENV;X.M;k,a=X.D un("/#{e['PATH_INFO'] 
    44 }".gsub(/\/+/,'/'));k.new(r,e,(m=e['REQUEST_METHOD' 
    45 ]||"GET")).service *a;rescue Exception=>x;X::ServerError.new(r,e,'get').service( 
    46 k,m,x)end;def method_missing m,c,*a;k=X.const_get c;k.new('', 
    47 H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s).service *a;end 
    48 end;module Views;include X,Helpers end;module Models;autoload(:Base,'camping/db') 
    49 end;class Mab<Markaby::Builder;include \ 
     26" if v!=@k[k]}-[nil];self end;def to_s;"Status: #{@status}#{Z+@headers.map{|k,v| 
     27[*v].map{|x|"#{k}: #{x}"}}*Z+Z*2+@body}" end;def markaby;Mab.new({},self)end;end 
     28X=module Controllers;@r=[];class<<self;def r;@r;end;def R *u;r=@r;Class.new{ 
     29meta_def(:urls){u};meta_def(:inherited){|x|r<<x}}end;def M;def M;end;constants.map{|c| 
     30k=const_get(c);k.send:include,C,Base,Models;r[0,0]=k if !r.include?k;k.meta_def( 
     31:urls){["/#{c.downcase}"]}if !k.respond_to?:urls}end;def D p;r.map{|k|k.urls. 
     32map{|x|return k,$~[1..-1]if p=~/^#{x}\/?$/}};[NotFound, [p]]end end;class  
     33NotFound<R();def get p;r(404,Mab.new{h1 P;h2 p+" not found"}) end end;class  
     34ServerError<R();def get k,m,e;r(500,Mab.new{h1 P;h2 "#{k}.#{m}";h3 "#{e.class 
     35} #{e.message}:";ul{e.backtrace.each{|bt|li(bt)}}}.to_s)end end;self;end;class<< 
     36self;def goes m;eval S.gsub(/Camping/,m.to_s).gsub("A\pps=[]","Cam\ping::Apps<<\ 
     37self"),TOPLEVEL_BINDING;end;def escape(s)s.to_s.gsub(/[^ \w.-]+/n){'%'+($&. 
     38unpack('H2'*$&.size)*'%').upcase}.tr(' ','+')end;def un(s)s.tr('+',' ').gsub( 
     39/%([\da-f]{2})/in){[$1].pack('H*')}end;def qs_parse q,d='&;';m=proc{|_,o,n|o.u( 
     40n,&m)rescue([*o]<<n)};q.to_s.split(/[#{d}] */n).inject(H[]){|h,p|k,v=un(p). 
     41split('=',2);h.u k.split(/[\]\[]+/).reverse.inject(v){|x,i|H[i,x]},&m}end;def 
     42kp(s);c=qs_parse(s,';,')end;def run r=$stdin,e=ENV;X.M;k,a=X.D un("/#{e[ 
     43'PATH_INFO']}".gsub(/\/+/,'/'));k.new(r,e,(m=e['REQUEST_METHOD']||"GET")). 
     44service *a;rescue Exception=>x;X::ServerError.new(r,e,'get').service(k,m,x)end 
     45def method_missing m,c,*a;X.M;k=X.const_get(c).new(StringIO.new,H['HTTP_HOST', 
     46'','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s);H.new(a.pop).each{|e,f|k.send( 
     47"#{e}=",f)}if Hash===a[-1];k.service *a;end;end;module Views;include X,Helpers 
     48end;module Models;autoload:Base,'camping/db';end;class Mab<Markaby::Builder;include \ 
    5049Views;def tag! *g,&b;h=g[-1];[:href,:action,:src].map{|a|(h[a]=self/h[a])rescue 
    51500};super end end;H=HashWithIndifferentAccess;class H;def method_missing m,*a 
    5251m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")end 
    53 alias_method:u,:regular_update;end end;autoload(:ActiveRecord,'camping/db') 
     52alias_method:u,:regular_update;end end;autoload:ActiveRecord,'camping/db' 
  • trunk/lib/camping/db.rb

    r133 r134  
    88module Camping::Models 
    99    A = ActiveRecord 
     10    # Base is an alias for ActiveRecord::Base.  The big warning I'm going to give you 
     11    # about this: *Base overloads table_name_prefix.*  This means that if you have a 
     12    # model class Blog::Models::Post, it's table name will be <tt>blog_posts</tt>. 
     13    # 
     14    # ActiveRecord is not loaded if you never reference this class.  The minute you 
     15    # use the ActiveRecord or Camping::Models::Base class, then the ActiveRecord library 
     16    # is loaded. 
    1017    Base = A::Base 
     18 
     19    # The default prefix for Camping model classes is the topmost module name lowercase 
     20    # and followed with an underscore. 
     21    # 
     22    #   Tepee::Models::Page.table_name_prefix 
     23    #     #=> "tepee_pages" 
     24    # 
    1125    def Base.table_name_prefix 
    1226        "#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'') 
    1327    end 
    1428end 
    15 Camping::S.sub! "autoload(:Base,'camping/db')", "Base=ActiveRecord::Base" 
     29Camping::S.sub! "autoload:Base,'camping/db'", "Base=ActiveRecord::Base" 
    1630Camping::Apps.each do |app| 
    1731    app::Models::Base = ActiveRecord::Base 
  • trunk/lib/camping/reloader.rb

    r131 r134  
    6060        @klass = Object.const_get(Object.constants.grep(/^#{title}$/i)[0]) rescue nil 
    6161        unless @klass and @klass.const_defined? :C 
    62             puts "!! trouble loading #{title}: not a Camping app" 
     62            puts "!! trouble loading #{title}: not a Camping app, no #{title.capitalize} module found" 
    6363            @klass = nil 
    6464            return