camping 1.2 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,40 @@
1
- %w[rubygems active_record markaby metaid ostruct tempfile].each { |lib| require lib }
1
+ # == About camping.rb
2
+ #
3
+ # Camping comes with two versions of its source code. The code contained in
4
+ # lib/camping.rb is compressed, stripped of whitespace, using compact algorithms
5
+ # to keep it tight. The unspoken rule is that camping.rb should be flowed with
6
+ # no more than 80 characters per line and must not exceed four kilobytes.
7
+ #
8
+ # On the other hand, lib/camping-unabridged.rb contains the same code, laid out
9
+ # nicely with piles of documentation everywhere. This documentation is entirely
10
+ # generated from lib/camping-unabridged.rb using RDoc and our "flipbook" template
11
+ # found in the extras directory of any camping distribution.
12
+ #
13
+ # == Requirements
14
+ #
15
+ # Camping requires at least Ruby 1.8.2.
16
+ #
17
+ # Camping depends on the following libraries. If you install through RubyGems,
18
+ # these will be automatically installed for you.
19
+ #
20
+ # * ActiveRecord, used in your models.
21
+ # ActiveRecord is an object-to-relational database mapper with adapters
22
+ # for SQLite3, MySQL, PostgreSQL, SQL Server and more.
23
+ # * Markaby, used in your views to describe HTML in plain Ruby.
24
+ # * MetAid, a few metaprogramming methods which Camping uses.
25
+ # * Tempfile, for storing file uploads.
26
+ #
27
+ # Camping also works well with Mongrel, the swift Ruby web server.
28
+ # http://rubyforge.org/projects/mongrel Mongrel comes with examples
29
+ # in its <tt>examples/camping</tt> directory.
30
+ #
31
+ %w[rubygems active_record markaby metaid tempfile].each { |lib| require lib }
2
32
 
3
33
  # == Camping
4
34
  #
5
35
  # The camping module contains three modules for separating your application:
6
36
  #
7
- # * Camping::Models for storing classes derived from ActiveRecord::Base.
37
+ # * Camping::Models for your database interaction classes, all derived from ActiveRecord::Base.
8
38
  # * Camping::Controllers for storing controller classes, which map URLs to code.
9
39
  # * Camping::Views for storing methods which generate HTML.
10
40
  #
@@ -20,9 +50,11 @@
20
50
  # in motion.
21
51
  #
22
52
  # if __FILE__ == $0
23
- # Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'blog3.db'
53
+ # Camping::Models::Base.establish_connection :adapter => 'sqlite3',
54
+ # :database => 'blog3.db'
24
55
  # Camping::Models::Base.logger = Logger.new('camping.log')
25
- # Camping.run
56
+ # Camping.create if Camping.respond_to? :create
57
+ # puts Camping.run
26
58
  # end
27
59
  #
28
60
  # In the postamble, your job is to setup Camping::Models::Base (see: ActiveRecord::Base)
@@ -32,11 +64,83 @@
32
64
  #
33
65
  # For other configurations, see
34
66
  # http://code.whytheluckystiff.net/camping/wiki/PostAmbles
67
+ #
68
+ # == The <tt>create</tt> method
69
+ #
70
+ # Many postambles will check for your application's <tt>create</tt> method and will run it
71
+ # when the web server starts up. This is a good place to check for database tables and create
72
+ # those tables to save users of your application from needing to manually set them up.
73
+ #
74
+ # def Blog.create
75
+ # unless Blog::Models::Post.table_exists?
76
+ # ActiveRecord::Schema.define do
77
+ # create_table :blog_posts, :force => true do |t|
78
+ # t.column :id, :integer, :null => false
79
+ # t.column :user_id, :integer, :null => false
80
+ # t.column :title, :string, :limit => 255
81
+ # t.column :body, :text
82
+ # end
83
+ # end
84
+ # end
85
+ # end
86
+ #
87
+ # For more tips, see http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
35
88
  module Camping
36
89
  C = self
37
- S = File.read(__FILE__).gsub(/_{2}FILE_{2}/,__FILE__.dump)
90
+ F = __FILE__
91
+ S = IO.read(F).gsub(/_+FILE_+/,F.dump)
92
+
93
+ # An object-like Hash, based on ActiveSupport's HashWithIndifferentAccess.
94
+ # All Camping query string and cookie variables are loaded as this.
95
+ #
96
+ # To access the query string, for instance, use the <tt>@input</tt> variable.
97
+ #
98
+ # module Blog::Models
99
+ # class Index < R '/'
100
+ # def get
101
+ # if page = @input.page.to_i > 0
102
+ # page -= 1
103
+ # end
104
+ # @posts = Post.find :all, :offset => page * 20, :limit => 20
105
+ # render :index
106
+ # end
107
+ # end
108
+ # end
109
+ #
110
+ # In the above example if you visit <tt>/?page=2</tt>, you'll get the second
111
+ # page of twenty posts. You can also use <tt>@input[:page]</tt> or <tt>@input['page']</tt>
112
+ # to get the value for the <tt>page</tt> query variable.
113
+ #
114
+ # Use the <tt>@cookies</tt> variable in the same fashion to access cookie variables.
115
+ class H < HashWithIndifferentAccess
116
+ def method_missing(m,*a)
117
+ if m.to_s =~ /=$/
118
+ self[$`] = a[0]
119
+ elsif a.empty?
120
+ self[m]
121
+ else
122
+ raise NoMethodError, "#{m}"
123
+ end
124
+ end
125
+ end
38
126
 
39
- # Helpers contains methods available in your controllers and views.
127
+ # Helpers contains methods available in your controllers and views. You may add
128
+ # methods of your own to this module, including many helper methods from Rails.
129
+ # This is analogous to Rails' <tt>ApplicationHelper</tt> module.
130
+ #
131
+ # == Using ActionPack Helpers
132
+ #
133
+ # If you'd like to include helpers from Rails' modules, you'll need to look up the
134
+ # helper module in the Rails documentation at http://api.rubyonrails.org/.
135
+ #
136
+ # For example, if you look up the <tt>ActionView::Helpers::FormHelper</tt> class,
137
+ # you'll find that it's loaded from the <tt>action_view/helpers/form_helper.rb</tt>
138
+ # file. You'll need to have the ActionPack gem installed for this to work.
139
+ #
140
+ # require 'action_view/helpers/form_helper.rb'
141
+ #
142
+ # # This example is unfinished.. soon..
143
+ #
40
144
  module Helpers
41
145
  # From inside your controllers and views, you will often need to figure out
42
146
  # the route used to get to a certain controller +c+. Pass the controller class
@@ -62,8 +166,8 @@ module Camping
62
166
  # you will need to use <tt>/</tt> (the slash method above).
63
167
  def R(c,*args)
64
168
  p = /\(.+?\)/
65
- args.inject(c.urls.detect{|x|x.scan(p).size==args.size}.dup){|str,a|
66
- str.sub(p,(a.method(a.class.primary_key)[] rescue a).to_s)
169
+ args.inject(c.urls.find{|x|x.scan(p).size==args.size}.dup){|str,a|
170
+ str.sub(p,(a.__send__(a.class.primary_key) rescue a).to_s)
67
171
  }
68
172
  end
69
173
  # Shows AR validation errors for the object passed.
@@ -150,7 +254,7 @@ module Camping
150
254
  #
151
255
  module Base
152
256
  include Helpers
153
- attr_accessor :input, :cookies, :headers, :body, :status, :root
257
+ attr_accessor :input, :cookies, :env, :headers, :body, :status, :root
154
258
  # Display a view, calling it by its method name +m+. If a <tt>layout</tt>
155
259
  # method is found in Camping::Views, it will be used to wrap the HTML.
156
260
  #
@@ -170,15 +274,15 @@ module Camping
170
274
  #
171
275
  # module Camping::Controllers
172
276
  # class Info
173
- # def get; code ENV.inspect end
277
+ # def get; code @env.inspect end
174
278
  # end
175
279
  # end
176
280
  #
177
281
  # If you have a <tt>layout</tt> method in Camping::Views, it will be used to
178
282
  # wrap the HTML.
179
- def method_missing(m, *args, &blk)
180
- str = m==:render ? markaview(*args, &blk):eval("markaby.#{m}(*args, &blk)")
181
- str = markaview(:layout) { str } rescue nil
283
+ def method_missing(m, *a, &b)
284
+ str = m==:render ? markaview(*a, &b):eval("markaby.#{m}(*a, &b)")
285
+ str = markaview(:layout) { str } if Views.method_defined? :layout
182
286
  r(200, str.to_s)
183
287
  end
184
288
 
@@ -209,10 +313,10 @@ module Camping
209
313
  def r(s, b, h = {}); @status = s; @headers.merge!(h); @body = b; end
210
314
 
211
315
  def service(r, e, m, a) #:nodoc:
212
- @status, @headers, @root = 200, {}, e['SCRIPT_NAME']
213
- cook = C.cookie_parse(e['HTTP_COOKIE'] || e['COOKIE'])
316
+ @status, @env, @headers, @root = 200, e, {'Content-Type'=>'text/html'}, e['SCRIPT_NAME']
317
+ cook = C.kp(e['HTTP_COOKIE'])
214
318
  qs = C.qs_parse(e['QUERY_STRING'])
215
- if "POST" == m
319
+ if "post" == m
216
320
  inp = r.read(e['CONTENT_LENGTH'].to_i)
217
321
  if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.match(e['CONTENT_TYPE'])
218
322
  b = "--#$1"
@@ -224,7 +328,7 @@ module Camping
224
328
  fn = fh[:name]
225
329
  if fh[:filename]
226
330
  fh[:type]=$1 if h =~ /^Content-Type: (.+?)(\r\n|\Z)/m
227
- fh[:tempfile]=Tempfile.new("#{C}").instance_eval {binmode;write v;rewind;self}
331
+ fh[:tempfile]=Tempfile.new("C").instance_eval {binmode;write v;rewind;self}
228
332
  else
229
333
  fh=v
230
334
  end
@@ -234,24 +338,23 @@ module Camping
234
338
  qs.merge!(C.qs_parse(inp))
235
339
  end
236
340
  end
237
- @cookies, @input = [cook, qs].map{|_|OpenStruct.new(_)}
341
+ @cookies, @input = cook.dup, qs.dup
238
342
 
239
- @body = method( m.downcase ).call(*a)
240
- @headers['Set-Cookie'] = @cookies.marshal_dump.map { |k,v| "#{k}=#{C.escape(v)}; path=/" if v != cook[k] }.compact
343
+ @body = send(m, *a) if respond_to? m
344
+ @headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != cook[k] }.compact
241
345
  self
242
346
  end
243
347
  def to_s #:nodoc:
244
- "Status: #{@status}\n#{{'Content-Type'=>'text/html'}.merge(@headers).map{|k,v|v.to_a.map{|v2|"#{k}: #{v2}"}}.flatten.join("\n")}\n\n#{@body}"
348
+ "Status: #{@status}\n#{@headers.map{|k,v|[*v].map{|x|"#{k}: #{x}"}*"\n"}*"\n"}\n\n#{@body}"
245
349
  end
246
- private
247
- def markaby
350
+ def markaby #:nodoc:
248
351
  Mab.new( instance_variables.map { |iv|
249
- [iv[1..-1], instance_variable_get(iv)] }, {} )
352
+ [iv[1..-1], instance_variable_get(iv)] } )
250
353
  end
251
- def markaview(m, *args, &blk)
252
- b=markaby
253
- b.method(m).call(*args, &blk)
254
- b.to_s
354
+ def markaview(m, *a, &b) #:nodoc:
355
+ h=markaby
356
+ h.send(m, *a, &b)
357
+ h.to_s
255
358
  end
256
359
  end
257
360
 
@@ -273,7 +376,7 @@ module Camping
273
376
  # end
274
377
  # end
275
378
  #
276
- class NotFound; def get(p); r(404, div{h1("#{C} Problem!")+h2("#{p} not found")}); end end
379
+ class NotFound; def get(p); r(404, div{h1("Cam\ping Problem!")+h2("#{p} not found")}); end end
277
380
 
278
381
  # The ServerError class is a special controller class for handling many (but not all) 500 errors.
279
382
  # If there is a parse error in Camping or in your application's source code, it will not be caught
@@ -298,7 +401,7 @@ module Camping
298
401
  # end
299
402
  # end
300
403
  #
301
- class ServerError; include Base; def get(k,m,e); r(500, markaby.div{ h1 "#{C} Problem!"; h2 "#{k}.#{m}"; h3 "#{e.class} #{e.message}:"; ul { e.backtrace.each { |bt| li bt } } }) end end
404
+ 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
302
405
 
303
406
  class << self
304
407
  # Add routes to a controller class by piling them into the R method.
@@ -320,7 +423,7 @@ module Camping
320
423
  #
321
424
  # Most of the time the rules inferred by dispatch method Controllers::D will get you
322
425
  # by just fine.
323
- def R(*urls); Class.new(R) { meta_def(:inherited) { |c| c.meta_def(:urls) { urls } } }; end
426
+ def R(*urls); Class.new(R) { meta_def(:urls) { urls } }; end
324
427
 
325
428
  # Dispatch routes to controller classes. Classes are searched in no particular order.
326
429
  # For each class, routes are checked for a match based on their order in the routing list
@@ -366,54 +469,71 @@ module Camping
366
469
  #
367
470
  def unescape(s); s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.delete('%')].pack('H*')} end
368
471
 
369
- # Parses a query string into an OpenStruct object.
472
+ # Parses a query string into an Camping::H object.
370
473
  #
371
474
  # input = Camping.qs_parse("name=Philarp+Tremain&hair=sandy+blonde")
372
475
  # input.name
373
476
  # #=> "Philarp Tremaine"
374
477
  #
375
- def qs_parse(qs, d = '&;'); (qs||'').split(/[#{d}] */n).
376
- inject({}){|hsh, p|k, v = p.split('=',2).map {|v| unescape(v)}; hsh[k] = v unless v.blank?; hsh} end
478
+ # Also parses out the Hash-like syntax used in PHP and Rails and builds
479
+ # nested hashes from it.
480
+ #
481
+ # input = Camping.qs_parse("post[id]=1&post[user]=_why")
482
+ # #=> {'post' => {'id' => '1', 'user' => '_why'}}
483
+ #
484
+ def qs_parse(qs, d = '&;')
485
+ m = proc {|_,o,n|o.merge(n,&m)rescue([*o]<<n)}
486
+ (qs||'').
487
+ split(/[#{d}] */n).
488
+ inject(H[]) { |h,p| k, v=unescape(p).split('=',2)
489
+ h.merge(k.split(/[\]\[]+/).reverse.
490
+ inject(v) { |x,i| H[i,x] },&m)
491
+ }
492
+ end
377
493
 
378
494
  # Parses a string of cookies from the <tt>Cookie</tt> header.
379
- def cookie_parse(s); c = qs_parse(s, ';,'); end
495
+ def kp(s); c = qs_parse(s, ';,'); end
380
496
 
381
497
  # Fields a request through Camping. For traditional CGI applications, the method can be
382
498
  # executed without arguments.
383
499
  #
384
500
  # if __FILE__ == $0
385
- # Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'blog3.db'
501
+ # Camping::Models::Base.establish_connection :adapter => 'sqlite3',
502
+ # :database => 'blog3.db'
386
503
  # Camping::Models::Base.logger = Logger.new('camping.log')
387
- # Camping.run
504
+ # puts Camping.run
388
505
  # end
389
506
  #
507
+ # The Camping controller returned from <tt>run</tt> has a <tt>to_s</tt> method in case you
508
+ # are running from CGI or want to output the full HTTP output. In the above example, <tt>puts</tt>
509
+ # will call <tt>to_s</tt> for you.
510
+ #
390
511
  # For FastCGI and Webrick-loaded applications, you will need to use a request loop, with <tt>run</tt>
391
512
  # at the center, passing in the read +r+ and write +w+ streams. You will also need to mimick or
392
- # replace <tt>ENV</tt> as part of your wrapper.
513
+ # pass in the <tt>ENV</tt> replacement as part of your wrapper.
393
514
  #
394
515
  # if __FILE__ == $0
395
516
  # require 'fcgi'
396
- # Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'blog3.db'
517
+ # Camping::Models::Base.establish_connection :adapter => 'sqlite3',
518
+ # :database => 'blog3.db'
397
519
  # Camping::Models::Base.logger = Logger.new('camping.log')
398
520
  # FCGI.each do |req|
399
- # ENV.replace req.env
400
- # Camping.run req.in, req.out
521
+ # req.out << Camping.run req.in, req.env
401
522
  # req.finish
402
523
  # end
403
524
  # end
404
525
  # end
405
526
  #
406
- def run(r=$stdin,w=$stdout)
407
- w <<
408
- begin
409
- k, a = Controllers.D "/#{ENV['PATH_INFO']}".gsub(%r!/+!,'/')
410
- m = ENV['REQUEST_METHOD']||"GET"
411
- k.class_eval { include C; include Controllers::Base; include Models }
412
- o = k.new
413
- o.service(r, ENV, m, a)
414
- rescue => e
415
- Controllers::ServerError.new.service(r, ENV, "GET", [k,m,e])
416
- end
527
+ def run(r=$stdin)
528
+ begin
529
+ k, a = Controllers.D "/#{e['PATH_INFO']}".gsub(%r!/+!,'/')
530
+ m = e['REQUEST_METHOD']||"GET"
531
+ k.send :include, C, Controllers::Base, Models
532
+ o = k.new
533
+ o.service(r, e, m.downcase, a)
534
+ rescue => x
535
+ Controllers::ServerError.new.service(r, e, "get", [k,m,x])
536
+ end
417
537
  end
418
538
  end
419
539
 
@@ -442,7 +562,23 @@ module Camping
442
562
  # end
443
563
  #
444
564
  # Models cannot be referred to in Views at this time.
445
- module Models; end
565
+ module Models
566
+ A = ActiveRecord
567
+ # Base is an alias for ActiveRecord::Base. The big warning I'm going to give you
568
+ # about this: *Base overloads table_name_prefix.* This means that if you have a
569
+ # model class Blog::Models::Post, it's table name will be <tt>blog_posts</tt>.
570
+ Base = A::Base
571
+
572
+ # The default prefix for Camping model classes is the topmost module name lowercase
573
+ # and followed with an underscore.
574
+ #
575
+ # Tepee::Models::Page.table_name_prefix
576
+ # #=> "tepee_pages"
577
+ #
578
+ def Base.table_name_prefix
579
+ "#{name[/^(\w+)/,1]}_".downcase.sub(/^(#{A}|camping)_/i,'')
580
+ end
581
+ end
446
582
 
447
583
  # Views is an empty module for storing methods which create HTML. The HTML is described
448
584
  # using the Markaby language.
@@ -451,8 +587,7 @@ module Camping
451
587
  #
452
588
  # If your Views module has a <tt>layout</tt> method defined, it will be called with a block
453
589
  # which will insert content from your view.
454
- module Views; include Controllers; include Helpers end
455
- Models::Base = ActiveRecord::Base
590
+ module Views; include Controllers, Helpers end
456
591
 
457
592
  # The Mab class wraps Markaby, allowing it to run methods from Camping::Views
458
593
  # and also to replace :href and :action attributes in tags by prefixing the root
@@ -1,49 +1,53 @@
1
- %w[rubygems active_record markaby metaid ostruct tempfile].each{|l|require l}
2
- module Camping;C=self;S=File.read(__FILE__).gsub(/_{2}FILE_{2}/,__FILE__.dump)
3
- module Helpers;def R c,*args;p=/\(.+?\)/;args.inject(c.urls.detect{|x|x.scan(p
4
- ).size==args.size}.dup){|str,a|str.sub(p,(a.method(a.class.primary_key)[]rescue
1
+ %w[rubygems active_record markaby metaid tempfile].each{|l|require l}
2
+ module Camping;C=self;F=__FILE__;S=IO.read(F).gsub(/_+FILE_+/,F.dump)
3
+ module Helpers;def R c,*args;p=/\(.+?\)/;args.inject(c.urls.find{|x|x.scan(p
4
+ ).size==args.size}.dup){|str,a|str.sub(p,(a.__send__(a.class.primary_key)rescue
5
5
  a).to_s)};end;def / p;p[/^\//]?@root+p:p end;def errors_for(o);ul.errors{o.
6
6
  errors.each_full{|er|li er}}unless o.errors.empty?;end;end;module Controllers
7
- module Base; include Helpers;attr_accessor :input,:cookies,:headers,:body,
8
- :status,:root;def method_missing(m,*args,&blk);str=m==:render ? markaview(*args,
9
- &blk):eval("markaby.#{m}(*args,&blk)");str=markaview(:layout){str}rescue nil;r(
7
+ module Base; include Helpers;attr_accessor :input,:cookies,:env,:headers,:body,
8
+ :status,:root;def method_missing(m,*a,&b);str=m==:render ? markaview(*a,
9
+ &b):eval("markaby.#{m}(*a,&b)");str=markaview(:layout){str} if Views.method_defined? :layout;r(
10
10
  200,str.to_s);end;def r(s,b,h={});@status=s;@headers.merge!(h);@body=b;end;def
11
11
  redirect(c,*args);c=R(c,*args)if c.respond_to?:urls;r(302,'','Location'=>self/c)
12
- end;def service(r,e,m,a)@status,@headers,@root=200,{},e['SCRIPT_NAME'];cook=C.kp(
13
- e['HTTP_COOKIE']);qs=C.qs_parse(e['QUERY_STRING']);if "POST"==m;inp=r.read(e[
12
+ end;def service(r,e,m,a)@status,@env,@headers,@root=200,e,{'Content-Type'=>'text/html'},e['SCRIPT_NAME'];cook=C.kp(
13
+ e['HTTP_COOKIE']);qs=C.qs_parse(e['QUERY_STRING']);if "post"==m;inp=r.read(e[
14
14
  'CONTENT_LENGTH'].to_i);if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.
15
15
  match(e['CONTENT_TYPE']);b="--#$1";inp.split(/(?:\r?\n|\A)#{Regexp::quote(
16
16
  b)}(?:--)?\r\n/m).each{|pt|h,v=pt.split("\r\n\r\n",2);fh={};[:name,:filename].
17
17
  each{|x|fh[x]=$1 if h=~/^Content-Disposition: form-data;.*(?:\s#{x}="([^"]+)")\
18
18
  /m};fn=fh[:name];if fh[:filename];fh[:type]=$1 if h =~ /^Content-Type: (.+?)(\
19
- \r\n|\Z)/m;fh[:tempfile]=Tempfile.new("#{C}").instance_eval{binmode;write v
19
+ \r\n|\Z)/m;fh[:tempfile]=Tempfile.new("C").instance_eval{binmode;write v
20
20
  rewind;self};else;fh=v;end;qs[fn]=fh if fn};else;qs.merge!(C.qs_parse(inp));end
21
- end;@cookies,@input=[cook,qs].map{|_|OpenStruct.new(_)};@body=method(m.downcase
22
- ).call(*a);@headers["Set-Cookie"]=@cookies.marshal_dump.map{|k,v|"#{k}=#{C.
23
- escape(v)}; path=/" if v != cook[k]}.compact;self;end;def to_s;"Status: #{
24
- @status}\n#{{'Content-Type'=>'text/html'}.merge(@headers).map{|k,v|v.to_a.map{
25
- |v2|"#{k}: #{v2}"}}.flatten.join("\n")}\n\n#{@body}";end;def markaby;Mab.new(
26
- instance_variables.map{|iv|[iv[1..-1],instance_variable_get(iv)]},{});end;def
27
- markaview(m,*args,&blk);b=markaby;b.method(m).call(*args, &blk);b.to_s
21
+ end;@cookies, @input = cook.dup, qs.dup;@body=send(m,*a) if respond_to? m;@headers["Set-Cookie"]=@cookies.map{|k,v|"#{k}=#{C.
22
+ escape(v)}; path=#{self/"/"}" if v != cook[k]}.compact;self;end;def to_s;"Status: #{
23
+ @status}\n#{@headers.map{|k,v|[*v].map{
24
+ |x|"#{k}: #{x}"}*"\n"}*"\n"}\n\n#{@body}";end;def markaby;Mab.new(
25
+ instance_variables.map{|iv|[iv[1..-1],instance_variable_get(iv)]});end;def
26
+ markaview(m,*a,&b);h=markaby;h.send(m,*a,&b);h.to_s
28
27
  end;end;class R;include Base end;class
29
- NotFound;def get(p);r(404,div{h1("#{C} Problem!")+h2("#{p} not found")});end
30
- end;class ServerError;include Base;def get(k,m,e);r(500,markaby.div{h1 "#{C} Problem!"
31
- h2 "#{k}.#{m}";h3 "#{e.class} #{e.message}:";ul{e.backtrace.each{|bt|li(bt)}}}
32
- )end end;class<<self;def R(*urls);Class.new(R){meta_def(:inherited){|c|c.
33
- meta_def(:urls){urls}}};end;def D(path);constants.inject(nil){|d,c|k=
28
+ NotFound;def get(p);r(404,div{h1("Cam\ping Problem!")+h2("#{p} not found")});end
29
+ end;class ServerError;include Base;def get(k,m,e);r(500,Mab.new{h1 "Cam\ping Problem!"
30
+ h2 "#{k}.#{m}";h3 "#{e.class} #{e.message}:";ul{e.backtrace.each{|bt|li(bt)}}}.to_s
31
+ )end end;class<<self;def R(*urls);Class.new(R){meta_def(:urls){urls}};end;def
32
+ D(path);constants.inject(nil){|d,c|k=
34
33
  const_get(c);k.meta_def(:urls){["/#{c.downcase}"]}if !(k<R);d||([k, $~[1..-1]
35
34
  ] if k.urls.find { |x| path =~ /^#{x}\/?$/ })}||[NotFound, [path]];end end end
36
35
  class<<self;def goes m;eval(S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING)end;def
37
36
  escape s;s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join(
38
37
  '%').upcase}.tr(' ','+') end;def unescape(s);s.tr('+', ' ').gsub(/((?:%[0-9a-f\
39
- A-F]{2})+)/n){[$1.delete('%')].pack('H*')} end;def qs_parse(qs,d ='&;');(qs||''
40
- ).split(/[#{d}] */n).inject({}){|hsh, p|k,v=p.split('=',2).map{|v|unescape(v)}
41
- hsh[k]=v unless v.blank?;hsh} end; def kp(s);c=qs_parse(s,';,') end
42
- def run(r=$stdin,w=$stdout);w<<begin;k,a=Controllers.D "/#{ENV['PATH_INFO']}".
43
- gsub(%r!/+!,'/');m=ENV['REQUEST_METHOD']||"GET";k.class_eval{include C
44
- include Controllers::Base;include Models};o=k.new;o.service(r,ENV,m,a);rescue\
45
- =>e;Controllers::ServerError.new.service(r,ENV,"GET",[k,m,e]);end;end;end
46
- module Views; include Controllers; include Helpers end;module Models;end
47
- Models::Base=ActiveRecord::Base;class Mab<Markaby::Builder;include Views
38
+ A-F]{2})+)/n){[$1.delete('%')].pack('H*')} end;def qs_parse qs,d='&;';m=proc{
39
+ |_,o,n|o.merge(n,&m)rescue([*o]<<n)};qs.to_s.split(/[#{d}] */n).inject(H[]){
40
+ |h,p|k,v=unescape(p).split('=',2);h.merge(k.split(/[\]\[]+/).reverse.inject(v){
41
+ |x,i|H[i,x]},&m)}end;def kp(s);c=qs_parse(s,';,');end
42
+ def run(r=$stdin,e=ENV);begin;k,a=Controllers.D "/#{e['PATH_INFO']}".
43
+ gsub(%r!/+!,'/');m=e['REQUEST_METHOD']||"GET";k.send :include,C,Controllers::Base,
44
+ Models;o=k.new;o.service(r,e,m.downcase,a);rescue\
45
+ =>x;Controllers::ServerError.new.service(r,e,"get",[k,m,x]);end;end;end
46
+ module Views; include Controllers,Helpers end;module Models
47
+ A=ActiveRecord;Base=A::Base;def Base.table_name_prefix;"#{name[/^(\w+)/,1]}_".
48
+ downcase.sub(/^(#{A}|camping)_/i,'');end;end
49
+ class Mab<Markaby::Builder;include Views
48
50
  def tag!(*g,&b);h=g[-1];[:href,:action].each{|a|(h[a]=self/h[a])rescue 0}
49
- super;end;end;end
51
+ super;end;end;class H<HashWithIndifferentAccess;def method_missing(m,*a)
52
+ if m.to_s=~/=$/;self[$`]=a[0];elsif a.empty?;self[m];else;raise NoMethodError,
53
+ "#{m}";end;end;end;end