camping 1.2 → 1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +33 -0
- data/README +64 -7
- data/bin/camping +63 -0
- data/examples/blog/blog.rb +44 -14
- data/examples/campsh/campsh.rb +631 -0
- data/examples/serve +77 -26
- data/examples/tepee/tepee.rb +11 -6
- data/extras/Camping.gif +0 -0
- data/extras/flipbook_rdoc.rb +480 -0
- data/lib/camping-unabridged.rb +190 -55
- data/lib/camping.rb +36 -32
- metadata +24 -13
- data/examples/blog/styles.css +0 -10
data/lib/camping-unabridged.rb
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
|
-
|
|
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
|
|
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',
|
|
53
|
+
# Camping::Models::Base.establish_connection :adapter => 'sqlite3',
|
|
54
|
+
# :database => 'blog3.db'
|
|
24
55
|
# Camping::Models::Base.logger = Logger.new('camping.log')
|
|
25
|
-
# Camping.
|
|
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
|
-
|
|
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.
|
|
66
|
-
str.sub(p,(a.
|
|
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
|
|
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, *
|
|
180
|
-
str = m==:render ? markaview(*
|
|
181
|
-
str = markaview(:layout) { str }
|
|
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.
|
|
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 "
|
|
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("
|
|
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 =
|
|
341
|
+
@cookies, @input = cook.dup, qs.dup
|
|
238
342
|
|
|
239
|
-
@body =
|
|
240
|
-
@headers['Set-Cookie'] = @cookies.
|
|
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#{
|
|
348
|
+
"Status: #{@status}\n#{@headers.map{|k,v|[*v].map{|x|"#{k}: #{x}"}*"\n"}*"\n"}\n\n#{@body}"
|
|
245
349
|
end
|
|
246
|
-
|
|
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, *
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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("
|
|
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,
|
|
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(:
|
|
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
|
|
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
|
-
|
|
376
|
-
|
|
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
|
|
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',
|
|
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
|
-
#
|
|
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',
|
|
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
|
-
#
|
|
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
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
|
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
|
|
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
|
data/lib/camping.rb
CHANGED
|
@@ -1,49 +1,53 @@
|
|
|
1
|
-
%w[rubygems active_record markaby metaid
|
|
2
|
-
module Camping;C=self;S=
|
|
3
|
-
module Helpers;def R c,*args;p=/\(.+?\)/;args.inject(c.urls.
|
|
4
|
-
).size==args.size}.dup){|str,a|str.sub(p,(a.
|
|
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,*
|
|
9
|
-
&
|
|
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 "
|
|
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("
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
|
26
|
-
|
|
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("
|
|
30
|
-
end;class ServerError;include Base;def get(k,m,e);r(500,
|
|
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(:
|
|
33
|
-
|
|
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
|
|
40
|
-
).split(/[#{d}] */n).inject(
|
|
41
|
-
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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;
|
|
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
|