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