camping 1.3 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
Binary file
Binary file
Binary file
@@ -7,39 +7,38 @@ RAILS_CONNECTION_ADAPTERS = %w[sqlite]
7
7
  $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
8
8
  require 'stringio'
9
9
  require 'webrick/httpserver'
10
- require 'camping'
10
+ require 'camping/webrick'
11
11
 
12
12
  # All applications share a single database
13
13
  Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'serve.db'
14
14
  Camping::Models::Base.logger = Logger.new('camping.log')
15
15
 
16
16
  # Find the working applications
17
- apps =
18
- Dir['*'].select do |d|
19
- if File.directory? "#{d}"
20
- begin
21
- load "#{d}/#{d}.rb"
22
- true
23
- rescue Exception => e
24
- puts "Camping app `#{d}' will not load: #{e.class} #{e.message}"
17
+ def find_apps
18
+ apps =
19
+ Dir['*'].select do |d|
20
+ if File.directory? "#{d}"
21
+ begin
22
+ load "#{d}/#{d}.rb"
23
+ true
24
+ rescue Exception => e
25
+ puts "Camping app `#{d}' will not load: #{e.class} #{e.message}"
26
+ end
25
27
  end
26
28
  end
29
+ apps.map! do |app|
30
+ begin
31
+ klass = Object.const_get(Object.constants.grep(/^#{app}$/i)[0])
32
+ klass.create if klass.respond_to? :create
33
+ [app, klass]
34
+ rescue Exception => e
35
+ puts "Camping app `#{app}' will not load: #{e.class} #{e.message}"
36
+ end
27
37
  end
28
- apps.map! do |app|
29
- begin
30
- klass = Object.const_get(Object.constants.grep(/^#{app}$/i)[0])
31
- klass.create if klass.respond_to? :create
32
- [app, klass]
33
- rescue Exception => e
34
- puts "Camping app `#{app}' will not load: #{e.class} #{e.message}"
35
- end
38
+ apps.compact
36
39
  end
37
- apps.compact!
38
-
39
- s = WEBrick::HTTPServer.new(:BindAddress => '0.0.0.0', :Port => 3301)
40
40
 
41
- # Root mount displays applications mounted
42
- s.mount_proc("/") do |req, resp|
41
+ def index_page req, resp, apps
43
42
  welcome = "Welcome to the Camping Example Server"
44
43
  b = Markaby::Builder.new({}, {})
45
44
  b = b.instance_eval do
@@ -80,6 +79,14 @@ s.mount_proc("/") do |req, resp|
80
79
  resp.body = b.to_s
81
80
  end
82
81
 
82
+ apps = find_apps
83
+ s = WEBrick::HTTPServer.new(:BindAddress => '0.0.0.0', :Port => 3301)
84
+
85
+ # Root mount displays applications mounted
86
+ s.mount_proc("/") do |req, resp|
87
+ index_page req, resp, apps
88
+ end
89
+
83
90
  # Mount which handles each application
84
91
  apps.each do |app, klass|
85
92
  # Mount for view source
@@ -88,17 +95,7 @@ apps.each do |app, klass|
88
95
  resp.body = File.read("#{app}/#{app}.rb")
89
96
  end
90
97
 
91
- s.mount_proc("/#{app}") do |req, resp|
92
- controller = klass.run((req.body and StringIO.new(req.body)), req.meta_vars)
93
- resp.status = controller.status
94
- controller.headers.each do |k, v|
95
- [*v].each do |vi|
96
- resp[k] = vi
97
- end
98
- end
99
- resp.body = controller.body
100
- nil
101
- end
98
+ s.mount("/#{app}", WEBrick::CampingHandler, klass)
102
99
  end
103
100
 
104
101
  # Server up
Binary file
@@ -115,12 +115,13 @@ module Tepee::Views
115
115
  def _markup body
116
116
  return '' if body.blank?
117
117
  body.gsub!(Tepee::Models::Page::PAGE_LINK) do
118
- page = title = $1.underscore
118
+ page = title = $1
119
119
  title = $2 unless $2.empty?
120
+ page = page.gsub /\W/, '_'
120
121
  if Tepee::Models::Page.find(:all, :select => 'title').collect { |p| p.title }.include?(page)
121
- %Q{<a href="#{R Show, page}">#{title}</a>}
122
+ %Q{<a href="#{self/R(Show, page)}">#{title}</a>}
122
123
  else
123
- %Q{<span>#{title}<a href="#{R Edit, page, 1}">?</a></span>}
124
+ %Q{<span>#{title}<a href="#{self/R(Edit, page, 1)}">?</a></span>}
124
125
  end
125
126
  end
126
127
  RedCloth.new(body, [ :hard_breaks ]).to_html
@@ -135,8 +136,14 @@ def Tepee.create
135
136
  end
136
137
 
137
138
  if __FILE__ == $0
139
+ require 'mongrel/camping'
140
+
138
141
  Tepee::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'tepee.db'
139
142
  Tepee::Models::Base.logger = Logger.new('camping.log')
143
+ Tepee::Models::Base.threaded_connections=false
140
144
  Tepee.create
141
- puts Tepee.run
145
+
146
+ server = Mongrel::Camping::start("0.0.0.0",3001,"/tepee",Tepee)
147
+ puts "** Tepee example is running at http://localhost:3000/tepee"
148
+ server.join
142
149
  end
@@ -7,7 +7,7 @@ class HTMLGenerator
7
7
  'allfiles' => gen_into_index(@files),
8
8
  'allclasses' => gen_into_index(@classes),
9
9
  "initial_page" => main_url,
10
- 'title' => CGI.escapeHTML(@options.title),
10
+ 'realtitle' => CGI.escapeHTML(@options.title),
11
11
  'charset' => @options.charset
12
12
  }
13
13
 
@@ -53,8 +53,10 @@ class HTMLGenerator
53
53
  end
54
54
  template.write_html_on(f, values)
55
55
  end
56
- camping_gif = File.join(CAMPING_EXTRAS_DIR, 'Camping.gif')
57
- File.copy(camping_gif, 'Camping.gif')
56
+ ['Camping.gif', 'permalink.gif'].each do |img|
57
+ ipath = File.join(CAMPING_EXTRAS_DIR, img)
58
+ File.copy(ipath, img)
59
+ end
58
60
  end
59
61
  end
60
62
  end
@@ -138,22 +140,24 @@ STYLE = %{
138
140
  #menu { background-color: #dfa; padding: 4px 12px; margin: 0; }
139
141
  #menu h3 { padding: 0; margin: 0; }
140
142
  #menu #links { float: right; }
141
- .dyn-source { background-color: #f3f3e5; border: solid 1px #99C; padding: 4px 8px; margin: 0; display: none; }
142
- .dyn-source pre { font-size: 8pt; }
143
+ pre { font-weight: bold; color: #730; }
144
+ tt { color: #703; font-size: 12pt; }
145
+ .dyn-source { background-color: #775915; padding: 4px 8px; margin: 0; display: none; }
146
+ .dyn-source pre { color: #DDDDDD; font-size: 8pt; }
143
147
  .source-link { text-align: right; font-size: 8pt; }
144
148
  .ruby-comment { color: green; font-style: italic }
145
- .ruby-constant { color: #4433aa; font-weight: bold; }
146
- .ruby-identifier { color: #222222; }
147
- .ruby-ivar { color: #2233dd; }
148
- .ruby-keyword { color: #3333FF; font-weight: bold }
149
- .ruby-node { color: #777777; }
150
- .ruby-operator { color: #111111; }
151
- .ruby-regexp { color: #662222; }
152
- .ruby-value { color: #662222; font-style: italic }
153
- .kw { color: #3333FF; font-weight: bold }
154
- .cmt { color: green; font-style: italic }
155
- .str { color: #662222; font-style: italic }
156
- .re { color: #662222; }
149
+ .ruby-constant { color: #CCDDFF; font-weight: bold; }
150
+ .ruby-identifier { color: #CCCCCC; }
151
+ .ruby-ivar { color: #BBCCFF; }
152
+ .ruby-keyword { color: #EEEEFF; font-weight: bold }
153
+ .ruby-node { color: #FFFFFF; }
154
+ .ruby-operator { color: #CCCCCC; }
155
+ .ruby-regexp { color: #DDFFDD; }
156
+ .ruby-value { color: #FFAAAA; font-style: italic }
157
+ .kw { color: #DDDDFF; font-weight: bold }
158
+ .cmt { color: #CCFFCC; font-style: italic }
159
+ .str { color: #EECCCC; font-style: italic }
160
+ .re { color: #EECCCC; }
157
161
  }
158
162
 
159
163
  CONTENTS_XML = %{
@@ -206,10 +210,10 @@ IF:methods
206
210
  START:methods
207
211
  <h4 class="ruled">%type% %category% method:
208
212
  IF:callseq
209
- <strong><a name="%aref%">%callseq%</a></strong>
213
+ <strong><a name="%aref%">%callseq%</a></strong> <a href="#%aref%"><img src="%root%/permalink.gif" border="0" title="Permalink to %callseq%" /></a>
210
214
  ENDIF:callseq
211
215
  IFNOT:callseq
212
- <strong><a name="%aref%">%name%%params%</a></strong></h4>
216
+ <strong><a name="%aref%">%name%%params%</a></strong> <a href="#%aref%"><img src="%root%/permalink.gif" border="0" title="Permalink to %type% %category% method: %name%" /></a></h4>
213
217
  ENDIF:callseq
214
218
 
215
219
  IF:m_desc
@@ -240,7 +244,14 @@ BODY = %{
240
244
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
241
245
  <html>
242
246
  <head>
243
- <title>%title%</title>
247
+ <title>
248
+ IF:title
249
+ %realtitle% &raquo; %title%
250
+ ENDIF:title
251
+ IFNOT:title
252
+ %realtitle%
253
+ ENDIF:title
254
+ </title>
244
255
  <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
245
256
  <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
246
257
  <script language="JavaScript" type="text/javascript">
@@ -468,7 +479,7 @@ INDEX = %{
468
479
  <HTML>
469
480
  <HEAD>
470
481
  <META HTTP-EQUIV="refresh" content="0;URL=%initial_page%">
471
- <TITLE>%title%</TITLE>
482
+ <TITLE>%realtitle%</TITLE>
472
483
  </HEAD>
473
484
  <BODY>
474
485
  Click <a href="%initial_page%">here</a> to open the Camping docs.
Binary file
@@ -28,7 +28,7 @@
28
28
  # http://rubyforge.org/projects/mongrel Mongrel comes with examples
29
29
  # in its <tt>examples/camping</tt> directory.
30
30
  #
31
- %w[rubygems active_record markaby metaid tempfile].each { |lib| require lib }
31
+ %w[rubygems active_record markaby metaid tempfile uri].each { |lib| require lib }
32
32
 
33
33
  # == Camping
34
34
  #
@@ -90,6 +90,7 @@ module Camping
90
90
  F = __FILE__
91
91
  S = IO.read(F).gsub(/_+FILE_+/,F.dump)
92
92
 
93
+ H = HashWithIndifferentAccess
93
94
  # An object-like Hash, based on ActiveSupport's HashWithIndifferentAccess.
94
95
  # All Camping query string and cookie variables are loaded as this.
95
96
  #
@@ -112,7 +113,7 @@ module Camping
112
113
  # to get the value for the <tt>page</tt> query variable.
113
114
  #
114
115
  # Use the <tt>@cookies</tt> variable in the same fashion to access cookie variables.
115
- class H < HashWithIndifferentAccess
116
+ class H
116
117
  def method_missing(m,*a)
117
118
  if m.to_s =~ /=$/
118
119
  self[$`] = a[0]
@@ -162,8 +163,38 @@ module Camping
162
163
  # If a controller has many routes, the route will be selected if it is the
163
164
  # first in the routing list to have the right number of arguments.
164
165
  #
165
- # Keep in mind that this route doesn't include the root path. Occassionally
166
- # you will need to use <tt>/</tt> (the slash method above).
166
+ # == Using R in the View
167
+ #
168
+ # Keep in mind that this route doesn't include the root path.
169
+ # You will need to use <tt>/</tt> (the slash method above) in your controllers.
170
+ # Or, go ahead and use the Helpers#URL method to build a complete URL for a route.
171
+ #
172
+ # However, in your views, the :href, :src and :action attributes automatically
173
+ # pass through the slash method, so you are encouraged to use <tt>R</tt> or
174
+ # <tt>URL</tt> in your views.
175
+ #
176
+ # module Blog::Views
177
+ # def menu
178
+ # div.menu! do
179
+ # a 'Home', :href => URL()
180
+ # a 'Profile', :href => "/profile"
181
+ # a 'Logout', :href => R(Logout)
182
+ # a 'Google', :href => 'http://google.com'
183
+ # end
184
+ # end
185
+ # end
186
+ #
187
+ # Let's say the above example takes place inside an application mounted at
188
+ # <tt>http://localhost:3301/frodo</tt> and that a controller named <tt>Logout</tt>
189
+ # is assigned to route <tt>/logout</tt>. The HTML will come out as:
190
+ #
191
+ # <div id="menu">
192
+ # <a href="http://localhost:3301/frodo/">Home</a>
193
+ # <a href="/frodo/profile">Profile</a>
194
+ # <a href="/frodo/logout">Logout</a>
195
+ # <a href="http://google.com">Google</a>
196
+ # </div>
197
+ #
167
198
  def R(c,*args)
168
199
  p = /\(.+?\)/
169
200
  args.inject(c.urls.find{|x|x.scan(p).size==args.size}.dup){|str,a|
@@ -188,7 +219,7 @@ module Camping
188
219
  # have built-in, usable error checking in only one line of code. :-)
189
220
  #
190
221
  # See AR validation documentation for details on validations.
191
- def errors_for(o); ul.errors { o.errors.each_full { |er| li er } } unless o.errors.empty?; end
222
+ def errors_for(o); ul.errors { o.errors.each_full { |er| li er } } if o.errors.any?; end
192
223
  # Simply builds the complete URL from a relative or absolute path +p+. If your
193
224
  # application is running from <tt>/blog</tt>:
194
225
  #
@@ -197,170 +228,197 @@ module Camping
197
228
  # self / R(Edit, 1) #=> "/blog/edit/1"
198
229
  #
199
230
  def /(p); p[/^\//]?@root+p:p end
231
+ # Builds a URL route to a controller or a path, returning a URI object.
232
+ # This way you'll get the hostname and the port number, a complete URL.
233
+ #
234
+ # You can use this to grab URLs for controllers using the R-style syntax.
235
+ # So, if your application is mounted at <tt>http://test.ing/blog/</tt>
236
+ # and you have a View controller which routes as <tt>R '/view/(\d+)'</tt>:
237
+ #
238
+ # URL(View, @post.id) #=> #<URI:http://test.ing/blog/view/12>
239
+ #
240
+ # Or you can use the direct path:
241
+ #
242
+ # self.URL #=> #<URI:http://test.ing/blog/>
243
+ # self.URL + "view/12" #=> #<URI:http://test.ing/blog/view/12>
244
+ # URL("/view/12") #=> #<URI:http://test.ing/blog/view/12>
245
+ #
246
+ # It's okay to pass URL strings through this method as well:
247
+ #
248
+ # URL("http://google.com") #=> #<URI:http://google.com>
249
+ #
250
+ # Any string which doesn't begin with a slash will pass through
251
+ # unscathed.
252
+ def URL c='/',*a
253
+ c = R(c, *a) if c.respond_to? :urls
254
+ c = self/c
255
+ c = "http://"+@env.HTTP_HOST+c if c[/^\//]
256
+ URI(c)
257
+ end
200
258
  end
201
259
 
202
- # Controllers is a module for placing classes which handle URLs. This is done
203
- # by defining a route to each class using the Controllers::R method.
260
+ # Camping::Base is built into each controller by way of the generic routing
261
+ # class Camping::R. In some ways, this class is trying to do too much, but
262
+ # it saves code for all the glue to stay in one place.
263
+ #
264
+ # Forgivable, considering that it's only really a handful of methods and accessors.
265
+ #
266
+ # == Treating controller methods like Response objects
267
+ #
268
+ # Camping originally came with a barebones Response object, but it's often much more readable
269
+ # to just use your controller as the response.
270
+ #
271
+ # Go ahead and alter the status, cookies, headers and body instance variables as you
272
+ # see fit in order to customize the response.
204
273
  #
205
274
  # module Camping::Controllers
206
- # class Edit < R '/edit/(\d+)'
207
- # def get; end
208
- # def post; end
275
+ # class SoftLink
276
+ # def get
277
+ # redirect "/"
278
+ # end
209
279
  # end
210
280
  # end
211
281
  #
212
- # If no route is set, Camping will guess the route from the class name.
213
- # The rule is very simple: the route becomes a slash followed by the lowercased
214
- # class name. See Controllers::D for the complete rules of dispatch.
282
+ # Is equivalent to:
215
283
  #
216
- # == Special classes
284
+ # module Camping::Controllers
285
+ # class SoftLink
286
+ # def get
287
+ # @status = 302
288
+ # @headers['Location'] = "/"
289
+ # end
290
+ # end
291
+ # end
217
292
  #
218
- # There are two special classes used for handling 404 and 500 errors. The
219
- # NotFound class handles URLs not found. The ServerError class handles exceptions
220
- # uncaught by your application.
221
- module Controllers
222
- # Controllers::Base is built into each controller by way of the generic routing
223
- # class Controllers::R. In some ways, this class is trying to do too much, but
224
- # it saves code for all the glue to stay in one place.
225
- #
226
- # Forgivable, considering that it's only really a handful of methods and accessors.
227
- #
228
- # == Treating controller methods like Response objects
229
- #
230
- # Camping originally came with a barebones Response object, but it's often much more readable
231
- # to just use your controller as the response.
232
- #
233
- # Go ahead and alter the status, cookies, headers and body instance variables as you
234
- # see fit in order to customize the response.
293
+ module Base
294
+ include Helpers
295
+ attr_accessor :input, :cookies, :env, :headers, :body, :status, :root
296
+ # Display a view, calling it by its method name +m+. If a <tt>layout</tt>
297
+ # method is found in Camping::Views, it will be used to wrap the HTML.
235
298
  #
236
299
  # module Camping::Controllers
237
- # class SoftLink
300
+ # class Show
238
301
  # def get
239
- # redirect "/"
302
+ # @posts = Post.find :all
303
+ # render :index
240
304
  # end
241
305
  # end
242
306
  # end
243
307
  #
244
- # Is equivalent to:
308
+ def render(m); end; undef_method :render
309
+
310
+ # Any stray method calls will be passed to Markaby. This means you can reply
311
+ # with HTML directly from your controller for quick debugging.
245
312
  #
246
313
  # module Camping::Controllers
247
- # class SoftLink
248
- # def get
249
- # @status = 302
250
- # @headers['Location'] = "/"
251
- # end
314
+ # class Info
315
+ # def get; code @env.inspect end
252
316
  # end
253
317
  # end
254
318
  #
255
- module Base
256
- include Helpers
257
- attr_accessor :input, :cookies, :env, :headers, :body, :status, :root
258
- # Display a view, calling it by its method name +m+. If a <tt>layout</tt>
259
- # method is found in Camping::Views, it will be used to wrap the HTML.
260
- #
261
- # module Camping::Controllers
262
- # class Show
263
- # def get
264
- # @posts = Post.find :all
265
- # render :index
266
- # end
267
- # end
268
- # end
269
- #
270
- def render(m); end; undef_method :render
271
-
272
- # Any stray method calls will be passed to Markaby. This means you can reply
273
- # with HTML directly from your controller for quick debugging.
274
- #
275
- # module Camping::Controllers
276
- # class Info
277
- # def get; code @env.inspect end
278
- # end
279
- # end
280
- #
281
- # If you have a <tt>layout</tt> method in Camping::Views, it will be used to
282
- # wrap the HTML.
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
286
- r(200, str.to_s)
287
- end
319
+ # If you have a <tt>layout</tt> method in Camping::Views, it will be used to
320
+ # wrap the HTML.
321
+ def method_missing(m, *a, &b)
322
+ str = m==:render ? markaview(*a, &b):eval("markaby.#{m}(*a, &b)")
323
+ str = markaview(:layout) { str } if Views.method_defined? :layout
324
+ r(200, str.to_s)
325
+ end
288
326
 
289
- # Formulate a redirect response: a 302 status with <tt>Location</tt> header
290
- # and a blank body. If +c+ is a string, the root path will be added. If
291
- # +c+ is a controller class, Helpers::R will be used to route the redirect
292
- # and the root path will be added.
293
- #
294
- # So, given a root of <tt>/articles</tt>:
295
- #
296
- # redirect "view/12" # redirects to "/articles/view/12"
297
- # redirect View, 12 # redirects to "/articles/view/12"
298
- #
299
- def redirect(c, *args)
300
- c = R(c,*args) if c.respond_to? :urls
301
- r(302, '', 'Location' => self/c)
302
- end
327
+ # Formulate a redirect response: a 302 status with <tt>Location</tt> header
328
+ # and a blank body. Uses Helpers#URL to build the location from a controller
329
+ # route or path.
330
+ #
331
+ # So, given a root of <tt>http://localhost:3301/articles</tt>:
332
+ #
333
+ # redirect "view/12" # redirects to "http://localhost:3301/articles/view/12"
334
+ # redirect View, 12 # redirects to "http://localhost:3301/articles/view/12"
335
+ #
336
+ def redirect(*a)
337
+ r(302,'','Location'=>URL(*a))
338
+ end
303
339
 
304
- # A quick means of setting this controller's status, body and headers.
305
- # Used internally by Camping, but... by all means...
306
- #
307
- # r(302, '', 'Location' => self / "/view/12")
308
- #
309
- # Is equivalent to:
310
- #
311
- # redirect "/view/12"
312
- #
313
- def r(s, b, h = {}); @status = s; @headers.merge!(h); @body = b; end
340
+ # A quick means of setting this controller's status, body and headers.
341
+ # Used internally by Camping, but... by all means...
342
+ #
343
+ # r(302, '', 'Location' => self / "/view/12")
344
+ #
345
+ # Is equivalent to:
346
+ #
347
+ # redirect "/view/12"
348
+ #
349
+ def r(s, b, h = {}); @status = s; @headers.merge!(h); @body = b; end
314
350
 
315
- def service(r, e, m, a) #:nodoc:
316
- @status, @env, @headers, @root = 200, e, {'Content-Type'=>'text/html'}, e['SCRIPT_NAME']
317
- cook = C.kp(e['HTTP_COOKIE'])
318
- qs = C.qs_parse(e['QUERY_STRING'])
319
- if "post" == m
320
- inp = r.read(e['CONTENT_LENGTH'].to_i)
321
- if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.match(e['CONTENT_TYPE'])
322
- b = "--#$1"
323
- inp.split(/(?:\r?\n|\A)#{ Regexp::quote( b ) }(?:--)?\r\n/m).each { |pt|
324
- h,v=pt.split("\r\n\r\n",2);fh={}
325
- [:name, :filename].each { |x|
326
- fh[x] = $1 if h =~ /^Content-Disposition: form-data;.*(?:\s#{x}="([^"]+)")/m
327
- }
328
- fn = fh[:name]
329
- if fh[:filename]
330
- fh[:type]=$1 if h =~ /^Content-Type: (.+?)(\r\n|\Z)/m
331
- fh[:tempfile]=Tempfile.new("C").instance_eval {binmode;write v;rewind;self}
332
- else
333
- fh=v
334
- end
335
- qs[fn]=fh if fn
336
- }
351
+ def initialize(r, e, m) #:nodoc:
352
+ e = H[e.to_hash]
353
+ @status, @method, @env, @headers, @root = 200, m.downcase, e,
354
+ {'Content-Type'=>'text/html'}, e.SCRIPT_NAME.sub(/\/$/,'')
355
+ @k = C.kp(e.HTTP_COOKIE)
356
+ qs = C.qs_parse(e.QUERY_STRING)
357
+ @in = r
358
+ if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.match(e.CONTENT_TYPE)
359
+ b = "--#$1"
360
+ @in.read.split(/(?:\r?\n|\A)#{ Regexp::quote( b ) }(?:--)?\r\n/m).each { |pt|
361
+ h,v=pt.split("\r\n\r\n",2);fh={}
362
+ [:name, :filename].each { |x|
363
+ fh[x] = $1 if h =~ /^Content-Disposition: form-data;.*(?:\s#{x}="([^"]+)")/m
364
+ }
365
+ fn = fh[:name]
366
+ if fh[:filename]
367
+ fh[:type]=$1 if h =~ /^Content-Type: (.+?)(\r\n|\Z)/m
368
+ fh[:tempfile]=Tempfile.new(:C).instance_eval {binmode;write v;rewind;self}
337
369
  else
338
- qs.merge!(C.qs_parse(inp))
370
+ fh=v
339
371
  end
340
- end
341
- @cookies, @input = cook.dup, qs.dup
342
-
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
345
- self
346
- end
347
- def to_s #:nodoc:
348
- "Status: #{@status}\n#{@headers.map{|k,v|[*v].map{|x|"#{k}: #{x}"}*"\n"}*"\n"}\n\n#{@body}"
349
- end
350
- def markaby #:nodoc:
351
- Mab.new( instance_variables.map { |iv|
352
- [iv[1..-1], instance_variable_get(iv)] } )
353
- end
354
- def markaview(m, *a, &b) #:nodoc:
355
- h=markaby
356
- h.send(m, *a, &b)
357
- h.to_s
372
+ qs[fn]=fh if fn
373
+ }
374
+ elsif @method == "post"
375
+ qs.merge!(C.qs_parse(@in.read))
358
376
  end
377
+ @cookies, @input = @k.dup, qs.dup
378
+ end
379
+
380
+ def service(*a) #:nodoc:
381
+ @body = send(@method, *a) if respond_to? @method
382
+ @headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] }.compact
383
+ self
384
+ end
385
+ def to_s #:nodoc:
386
+ "Status: #{@status}\n#{@headers.map{|k,v|[*v].map{|x|"#{k}: #{x}"}*"\n"}*"\n"}\n\n#{@body}"
387
+ end
388
+ def markaby #:nodoc:
389
+ Mab.new( instance_variables.map { |iv|
390
+ [iv[1..-1], instance_variable_get(iv)] } )
391
+ end
392
+ def markaview(m, *a, &b) #:nodoc:
393
+ h=markaby
394
+ h.send(m, *a, &b)
395
+ h.to_s
359
396
  end
397
+ end
360
398
 
361
- # The R class is the parent class for all controllers and ensures they all get the Base mixin.
362
- class R; include Base end
399
+ # The R class is the parent class for all routed controllers.
400
+ class R; include Base end
363
401
 
402
+ # Controllers is a module for placing classes which handle URLs. This is done
403
+ # by defining a route to each class using the Controllers::R method.
404
+ #
405
+ # module Camping::Controllers
406
+ # class Edit < R '/edit/(\d+)'
407
+ # def get; end
408
+ # def post; end
409
+ # end
410
+ # end
411
+ #
412
+ # If no route is set, Camping will guess the route from the class name.
413
+ # The rule is very simple: the route becomes a slash followed by the lowercased
414
+ # class name. See Controllers::D for the complete rules of dispatch.
415
+ #
416
+ # == Special classes
417
+ #
418
+ # There are two special classes used for handling 404 and 500 errors. The
419
+ # NotFound class handles URLs not found. The ServerError class handles exceptions
420
+ # uncaught by your application.
421
+ module Controllers
364
422
  # The NotFound class is a special controller class for handling 404 errors, in case you'd
365
423
  # like to alter the appearance of the 404. The path is passed in as +p+.
366
424
  #
@@ -376,7 +434,7 @@ module Camping
376
434
  # end
377
435
  # end
378
436
  #
379
- class NotFound; def get(p); r(404, div{h1("Cam\ping Problem!")+h2("#{p} not found")}); end end
437
+ class NotFound; def get(p); r(404, div{h1("Cam\ping Problem!");h2("#{p} not found")}); end end
380
438
 
381
439
  # The ServerError class is a special controller class for handling many (but not all) 500 errors.
382
440
  # If there is a parse error in Camping or in your application's source code, it will not be caught
@@ -464,10 +522,10 @@ module Camping
464
522
  def escape(s); s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join('%').upcase}.tr(' ', '+') end
465
523
  # Unescapes a URL-encoded string.
466
524
  #
467
- # Camping.unescape("I%27d+go+to+the+museum+straightway%21")
525
+ # Camping.un("I%27d+go+to+the+museum+straightway%21")
468
526
  # #=> "I'd go to the museum straightway!"
469
527
  #
470
- def unescape(s); s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.delete('%')].pack('H*')} end
528
+ def un(s); s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.delete('%')].pack('H*')} end
471
529
 
472
530
  # Parses a query string into an Camping::H object.
473
531
  #
@@ -485,7 +543,7 @@ module Camping
485
543
  m = proc {|_,o,n|o.merge(n,&m)rescue([*o]<<n)}
486
544
  (qs||'').
487
545
  split(/[#{d}] */n).
488
- inject(H[]) { |h,p| k, v=unescape(p).split('=',2)
546
+ inject(H[]) { |h,p| k, v=un(p).split('=',2)
489
547
  h.merge(k.split(/[\]\[]+/).reverse.
490
548
  inject(v) { |x,i| H[i,x] },&m)
491
549
  }
@@ -524,16 +582,12 @@ module Camping
524
582
  # end
525
583
  # end
526
584
  #
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
585
+ def run(r=$stdin,e=ENV)
586
+ k, a = Controllers.D un("/#{e['PATH_INFO']}".gsub(%r!/+!,'/'))
587
+ k.send :include, C, Base, Models
588
+ k.new(r,e,(m=e['REQUEST_METHOD']||"GET")).service(*a)
589
+ rescue => x
590
+ Controllers::ServerError.new(r,e,'get').service(k,m,x)
537
591
  end
538
592
  end
539
593
 
@@ -590,13 +644,13 @@ module Camping
590
644
  module Views; include Controllers, Helpers end
591
645
 
592
646
  # The Mab class wraps Markaby, allowing it to run methods from Camping::Views
593
- # and also to replace :href and :action attributes in tags by prefixing the root
647
+ # and also to replace :href, :action and :src attributes in tags by prefixing the root
594
648
  # path.
595
649
  class Mab < Markaby::Builder
596
650
  include Views
597
651
  def tag!(*g,&b)
598
652
  h=g[-1]
599
- [:href,:action].each{|a|(h[a]=self/h[a])rescue 0}
653
+ [:href,:action,:src].each{|a|(h[a]=self/h[a])rescue 0}
600
654
  super
601
655
  end
602
656
  end