camping 1.3 → 1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +12 -0
- data/bin/camping +14 -14
- data/examples/blog/blog.db +0 -0
- data/examples/blog/blog.rb +28 -15
- data/examples/blog/camping.log +82 -0
- data/examples/blog/foo.log +0 -0
- data/examples/blog/test.yml +0 -0
- data/examples/camping.log +1111 -0
- data/examples/charts/1.gif +0 -0
- data/examples/charts/2.gif +0 -0
- data/examples/charts/3.gif +0 -0
- data/examples/serve +30 -33
- data/examples/serve.db +0 -0
- data/examples/tepee/tepee.rb +11 -4
- data/extras/flipbook_rdoc.rb +32 -21
- data/extras/permalink.gif +0 -0
- data/lib/camping-unabridged.rb +209 -155
- data/lib/camping.rb +48 -51
- data/lib/camping/session.rb +123 -0
- data/lib/camping/webrick.rb +20 -0
- metadata +21 -8
Binary file
|
Binary file
|
Binary file
|
data/examples/serve
CHANGED
@@ -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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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.
|
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
|
-
|
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.
|
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
|
data/examples/serve.db
ADDED
Binary file
|
data/examples/tepee/tepee.rb
CHANGED
@@ -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
|
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
|
122
|
+
%Q{<a href="#{self/R(Show, page)}">#{title}</a>}
|
122
123
|
else
|
123
|
-
%Q{<span>#{title}<a href="#{R
|
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
|
-
|
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
|
data/extras/flipbook_rdoc.rb
CHANGED
@@ -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
|
-
'
|
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
|
-
|
57
|
-
|
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
|
-
|
142
|
-
|
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: #
|
146
|
-
.ruby-identifier { color: #
|
147
|
-
.ruby-ivar { color: #
|
148
|
-
.ruby-keyword { color: #
|
149
|
-
.ruby-node { color: #
|
150
|
-
.ruby-operator { color: #
|
151
|
-
.ruby-regexp { color: #
|
152
|
-
.ruby-value { color: #
|
153
|
-
.kw { color: #
|
154
|
-
.cmt { color:
|
155
|
-
.str { color: #
|
156
|
-
.re { color: #
|
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
|
247
|
+
<title>
|
248
|
+
IF:title
|
249
|
+
%realtitle% » %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>%
|
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
|
data/lib/camping-unabridged.rb
CHANGED
@@ -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
|
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
|
-
#
|
166
|
-
#
|
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 } }
|
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
|
-
#
|
203
|
-
#
|
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
|
207
|
-
# def get
|
208
|
-
#
|
275
|
+
# class SoftLink
|
276
|
+
# def get
|
277
|
+
# redirect "/"
|
278
|
+
# end
|
209
279
|
# end
|
210
280
|
# end
|
211
281
|
#
|
212
|
-
#
|
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
|
-
#
|
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
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
#
|
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
|
300
|
+
# class Show
|
238
301
|
# def get
|
239
|
-
#
|
302
|
+
# @posts = Post.find :all
|
303
|
+
# render :index
|
240
304
|
# end
|
241
305
|
# end
|
242
306
|
# end
|
243
307
|
#
|
244
|
-
|
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
|
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
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
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
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
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
|
-
|
370
|
+
fh=v
|
339
371
|
end
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
@
|
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
|
-
|
362
|
-
|
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!")
|
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.
|
525
|
+
# Camping.un("I%27d+go+to+the+museum+straightway%21")
|
468
526
|
# #=> "I'd go to the museum straightway!"
|
469
527
|
#
|
470
|
-
def
|
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=
|
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
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
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 :
|
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
|