camping 1.3 → 1.4

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.
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