bmarzolf-picnic 0.8.0.20090420
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +1 -0
- data/History.txt +78 -0
- data/LICENSE.txt +165 -0
- data/Manifest.txt +45 -0
- data/README.txt +31 -0
- data/Rakefile +64 -0
- data/lib/picnic/authentication.rb +254 -0
- data/lib/picnic/cli.rb +165 -0
- data/lib/picnic/conf.rb +135 -0
- data/lib/picnic/controllers.rb +4 -0
- data/lib/picnic/logger.rb +41 -0
- data/lib/picnic/server.rb +99 -0
- data/lib/picnic/service_control.rb +274 -0
- data/lib/picnic/version.rb +9 -0
- data/lib/picnic.rb +11 -0
- data/setup.rb +1585 -0
- data/test/picnic_test.rb +11 -0
- data/test/test_helper.rb +2 -0
- data/vendor/camping-2.0.20090420/CHANGELOG +118 -0
- data/vendor/camping-2.0.20090420/COPYING +18 -0
- data/vendor/camping-2.0.20090420/README +82 -0
- data/vendor/camping-2.0.20090420/Rakefile +180 -0
- data/vendor/camping-2.0.20090420/bin/camping +97 -0
- data/vendor/camping-2.0.20090420/doc/camping.1.gz +0 -0
- data/vendor/camping-2.0.20090420/examples/README +5 -0
- data/vendor/camping-2.0.20090420/examples/blog.rb +375 -0
- data/vendor/camping-2.0.20090420/examples/campsh.rb +629 -0
- data/vendor/camping-2.0.20090420/examples/tepee.rb +242 -0
- data/vendor/camping-2.0.20090420/extras/Camping.gif +0 -0
- data/vendor/camping-2.0.20090420/extras/permalink.gif +0 -0
- data/vendor/camping-2.0.20090420/lib/camping/ar/session.rb +132 -0
- data/vendor/camping-2.0.20090420/lib/camping/ar.rb +78 -0
- data/vendor/camping-2.0.20090420/lib/camping/mab.rb +26 -0
- data/vendor/camping-2.0.20090420/lib/camping/reloader.rb +184 -0
- data/vendor/camping-2.0.20090420/lib/camping/server.rb +159 -0
- data/vendor/camping-2.0.20090420/lib/camping/session.rb +75 -0
- data/vendor/camping-2.0.20090420/lib/camping-unabridged.rb +630 -0
- data/vendor/camping-2.0.20090420/lib/camping.rb +52 -0
- data/vendor/camping-2.0.20090420/setup.rb +1551 -0
- data/vendor/camping-2.0.20090420/test/apps/env_debug.rb +65 -0
- data/vendor/camping-2.0.20090420/test/apps/forms.rb +95 -0
- data/vendor/camping-2.0.20090420/test/apps/misc.rb +86 -0
- data/vendor/camping-2.0.20090420/test/apps/sessions.rb +38 -0
- data/vendor/camping-2.0.20090420/test/test_camping.rb +54 -0
- metadata +140 -0
@@ -0,0 +1,630 @@
|
|
1
|
+
# == About camping.rb
|
2
|
+
#
|
3
|
+
# Camping comes with two versions of its source code. The code contained in
|
4
|
+
# lib/camping.rb is compressed, stripped of whitespace, using compact algorithms
|
5
|
+
# to keep it tight. The unspoken rule is that camping.rb should be flowed with
|
6
|
+
# no more than 80 characters per line and must not exceed four kilobytes.
|
7
|
+
#
|
8
|
+
# On the other hand, lib/camping-unabridged.rb contains the same code, laid out
|
9
|
+
# nicely with piles of documentation everywhere. This documentation is entirely
|
10
|
+
# generated from lib/camping-unabridged.rb using RDoc and our "flipbook" template
|
11
|
+
# found in the extras directory of any camping distribution.
|
12
|
+
%w[uri stringio rack].map { |l| require l }
|
13
|
+
|
14
|
+
class Object #:nodoc:
|
15
|
+
def meta_def(m,&b) #:nodoc:
|
16
|
+
(class<<self;self end).send(:define_method,m,&b)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# == Camping
|
21
|
+
# TODO: Tutorial: Camping.goes, MVC (link to Controllers, Models, Views where
|
22
|
+
# they're described in detail), Camping Server (for development), Rack
|
23
|
+
# (for production). the create-method. Service overload too, perhaps?
|
24
|
+
# Overriding r404, r500 and r501.
|
25
|
+
#
|
26
|
+
# The camping module contains three modules for separating your application:
|
27
|
+
#
|
28
|
+
# * Camping::Models for your database interaction classes, all derived from ActiveRecord::Base.
|
29
|
+
# * Camping::Controllers for storing controller classes, which map URLs to code.
|
30
|
+
# * Camping::Views for storing methods which generate HTML.
|
31
|
+
#
|
32
|
+
# Of use to you is also one module for storing helpful additional methods:
|
33
|
+
#
|
34
|
+
# * Camping::Helpers which can be used in controllers and views.
|
35
|
+
#
|
36
|
+
# == The Camping Server
|
37
|
+
# TODO: Only for development.
|
38
|
+
#
|
39
|
+
# How do you run Camping apps? Oh, uh... The Camping Server!
|
40
|
+
#
|
41
|
+
# The Camping Server is, firstly and thusly, a set of rules. At the very least, The Camping Server must:
|
42
|
+
#
|
43
|
+
# * Load all Camping apps in a directory.
|
44
|
+
# * Load new apps that appear in that directory.
|
45
|
+
# * Mount those apps according to their filename. (e.g. blog.rb is mounted at /blog.)
|
46
|
+
# * Run each app's <tt>create</tt> method upon startup.
|
47
|
+
# * Reload the app if its modification time changes.
|
48
|
+
# * Reload the app if it requires any files under the same directory and one of their modification times changes.
|
49
|
+
# * Support the X-Sendfile header.
|
50
|
+
#
|
51
|
+
# In fact, Camping comes with its own little The Camping Server.
|
52
|
+
#
|
53
|
+
# At a command prompt, run: <tt>camping examples/</tt> and the entire <tt>examples/</tt> directory will be served.
|
54
|
+
#
|
55
|
+
# Configurations also exist for Apache and Lighttpd. See http://code.whytheluckystiff.net/camping/wiki/TheCampingServer.
|
56
|
+
#
|
57
|
+
# == The <tt>create</tt> method
|
58
|
+
#
|
59
|
+
# Many postambles will check for your application's <tt>create</tt> method and will run it
|
60
|
+
# when the web server starts up. This is a good place to check for database tables and create
|
61
|
+
# those tables to save users of your application from needing to manually set them up.
|
62
|
+
#
|
63
|
+
# def Blog.create
|
64
|
+
# unless Blog::Models::Post.table_exists?
|
65
|
+
# ActiveRecord::Schema.define do
|
66
|
+
# create_table :blog_posts, :force => true do |t|
|
67
|
+
# t.column :user_id, :integer, :null => false
|
68
|
+
# t.column :title, :string, :limit => 255
|
69
|
+
# t.column :body, :text
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# TODO: Wiki is down.
|
76
|
+
# For more tips, see http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
|
77
|
+
module Camping
|
78
|
+
C = self
|
79
|
+
S = IO.read(__FILE__) rescue nil
|
80
|
+
P = "<h1>Cam\ping Problem!</h1><h2>%s</h2>"
|
81
|
+
U = Rack::Utils
|
82
|
+
Apps = []
|
83
|
+
# TODO: @input[:page] != @input['page']
|
84
|
+
# An object-like Hash.
|
85
|
+
# All Camping query string and cookie variables are loaded as this.
|
86
|
+
#
|
87
|
+
# To access the query string, for instance, use the <tt>@input</tt> variable.
|
88
|
+
#
|
89
|
+
# module Blog::Controllers
|
90
|
+
# class Index < R '/'
|
91
|
+
# def get
|
92
|
+
# if page = @input.page.to_i > 0
|
93
|
+
# page -= 1
|
94
|
+
# end
|
95
|
+
# @posts = Post.find :all, :offset => page * 20, :limit => 20
|
96
|
+
# render :index
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# In the above example if you visit <tt>/?page=2</tt>, you'll get the second
|
102
|
+
# page of twenty posts. You can also use <tt>@input[:page]</tt> or <tt>@input['page']</tt>
|
103
|
+
# to get the value for the <tt>page</tt> query variable.
|
104
|
+
#
|
105
|
+
# Use the <tt>@cookies</tt> variable in the same fashion to access cookie variables.
|
106
|
+
class H < Hash
|
107
|
+
# Gets or sets keys in the hash.
|
108
|
+
#
|
109
|
+
# @cookies.my_favorite = :macadamian
|
110
|
+
# @cookies.my_favorite
|
111
|
+
# => :macadamian
|
112
|
+
#
|
113
|
+
def method_missing(m,*a)
|
114
|
+
m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super
|
115
|
+
end
|
116
|
+
undef id, type
|
117
|
+
end
|
118
|
+
|
119
|
+
# TODO: Fair enough. Maybe complete the ActionPack example?
|
120
|
+
# Helpers contains methods available in your controllers and views. You may add
|
121
|
+
# methods of your own to this module, including many helper methods from Rails.
|
122
|
+
# This is analogous to Rails' <tt>ApplicationHelper</tt> module.
|
123
|
+
#
|
124
|
+
# == Using ActionPack Helpers
|
125
|
+
#
|
126
|
+
# If you'd like to include helpers from Rails' modules, you'll need to look up the
|
127
|
+
# helper module in the Rails documentation at http://api.rubyonrails.org/.
|
128
|
+
#
|
129
|
+
# For example, if you look up the <tt>ActionView::Helpers::FormHelper</tt> class,
|
130
|
+
# you'll find that it's loaded from the <tt>action_view/helpers/form_helper.rb</tt>
|
131
|
+
# file. You'll need to have the ActionPack gem installed for this to work.
|
132
|
+
#
|
133
|
+
# require 'action_view/helpers/form_helper.rb'
|
134
|
+
#
|
135
|
+
# # This example is unfinished.. soon..
|
136
|
+
#
|
137
|
+
module Helpers
|
138
|
+
# From inside your controllers and views, you will often need to figure out
|
139
|
+
# the route used to get to a certain controller +c+. Pass the controller class
|
140
|
+
# and any arguments into the R method, a string containing the route will be
|
141
|
+
# returned to you.
|
142
|
+
#
|
143
|
+
# Assuming you have a specific route in an edit controller:
|
144
|
+
#
|
145
|
+
# class Edit < R '/edit/(\d+)'
|
146
|
+
#
|
147
|
+
# A specific route to the Edit controller can be built with:
|
148
|
+
#
|
149
|
+
# R(Edit, 1)
|
150
|
+
#
|
151
|
+
# Which outputs: <tt>/edit/1</tt>.
|
152
|
+
#
|
153
|
+
# You may also pass in a model object and the ID of the object will be used.
|
154
|
+
#
|
155
|
+
# If a controller has many routes, the route will be selected if it is the
|
156
|
+
# first in the routing list to have the right number of arguments.
|
157
|
+
#
|
158
|
+
# == Using R in the View
|
159
|
+
#
|
160
|
+
# Keep in mind that this route doesn't include the root path.
|
161
|
+
# You will need to use <tt>/</tt> (the slash method above) in your controllers.
|
162
|
+
# Or, go ahead and use the Helpers#URL method to build a complete URL for a route.
|
163
|
+
#
|
164
|
+
# However, in your views, the :href, :src and :action attributes automatically
|
165
|
+
# pass through the slash method, so you are encouraged to use <tt>R</tt> or
|
166
|
+
# <tt>URL</tt> in your views.
|
167
|
+
#
|
168
|
+
# module Blog::Views
|
169
|
+
# def menu
|
170
|
+
# div.menu! do
|
171
|
+
# a 'Home', :href => URL()
|
172
|
+
# a 'Profile', :href => "/profile"
|
173
|
+
# a 'Logout', :href => R(Logout)
|
174
|
+
# a 'Google', :href => 'http://google.com'
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# Let's say the above example takes place inside an application mounted at
|
180
|
+
# <tt>http://localhost:3301/frodo</tt> and that a controller named <tt>Logout</tt>
|
181
|
+
# is assigned to route <tt>/logout</tt>. The HTML will come out as:
|
182
|
+
#
|
183
|
+
# <div id="menu">
|
184
|
+
# <a href="http://localhost:3301/frodo/">Home</a>
|
185
|
+
# <a href="/frodo/profile">Profile</a>
|
186
|
+
# <a href="/frodo/logout">Logout</a>
|
187
|
+
# <a href="http://google.com">Google</a>
|
188
|
+
# </div>
|
189
|
+
#
|
190
|
+
def R(c,*g)
|
191
|
+
p,h=/\(.+?\)/,g.grep(Hash)
|
192
|
+
g-=h
|
193
|
+
raise "bad route" unless u = c.urls.find{|x|
|
194
|
+
break x if x.scan(p).size == g.size &&
|
195
|
+
/^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
|
196
|
+
x.sub p,U.escape((a[a.class.primary_key]rescue a))})
|
197
|
+
}
|
198
|
+
h.any?? u+"?"+U.build_query(h[0]) : u
|
199
|
+
end
|
200
|
+
|
201
|
+
# Simply builds a complete path from a path +p+ within the app. If your application is
|
202
|
+
# mounted at <tt>/blog</tt>:
|
203
|
+
#
|
204
|
+
# self / "/view/1" #=> "/blog/view/1"
|
205
|
+
# self / "styles.css" #=> "styles.css"
|
206
|
+
# self / R(Edit, 1) #=> "/blog/edit/1"
|
207
|
+
#
|
208
|
+
def /(p); p[0]==?/?@root+p:p end
|
209
|
+
# Builds a URL route to a controller or a path, returning a URI object.
|
210
|
+
# This way you'll get the hostname and the port number, a complete URL.
|
211
|
+
#
|
212
|
+
# You can use this to grab URLs for controllers using the R-style syntax.
|
213
|
+
# So, if your application is mounted at <tt>http://test.ing/blog/</tt>
|
214
|
+
# and you have a View controller which routes as <tt>R '/view/(\d+)'</tt>:
|
215
|
+
#
|
216
|
+
# URL(View, @post.id) #=> #<URL:http://test.ing/blog/view/12>
|
217
|
+
#
|
218
|
+
# Or you can use the direct path:
|
219
|
+
#
|
220
|
+
# self.URL #=> #<URL:http://test.ing/blog/>
|
221
|
+
# self.URL + "view/12" #=> #<URL:http://test.ing/blog/view/12>
|
222
|
+
# URL("/view/12") #=> #<URL:http://test.ing/blog/view/12>
|
223
|
+
#
|
224
|
+
# It's okay to pass URL strings through this method as well:
|
225
|
+
#
|
226
|
+
# URL("http://google.com") #=> #<URL:http://google.com>
|
227
|
+
#
|
228
|
+
# Any string which doesn't begin with a slash will pass through
|
229
|
+
# unscathed.
|
230
|
+
def URL c='/',*a
|
231
|
+
c = R(c, *a) if c.respond_to? :urls
|
232
|
+
c = self/c
|
233
|
+
c = @request.url[/.{8,}?(?=\/)/]+c if c[0]==?/
|
234
|
+
URI(c)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Camping::Base is built into each controller by way of the generic routing
|
239
|
+
# class Camping::R. In some ways, this class is trying to do too much, but
|
240
|
+
# it saves code for all the glue to stay in one place.
|
241
|
+
#
|
242
|
+
# Forgivable, considering that it's only really a handful of methods and accessors.
|
243
|
+
#
|
244
|
+
# == Treating controller methods like Response objects
|
245
|
+
# TODO: I don't think this belongs here. Either Controllers or Camping.
|
246
|
+
#
|
247
|
+
# Camping originally came with a barebones Response object, but it's often much more readable
|
248
|
+
# to just use your controller as the response.
|
249
|
+
#
|
250
|
+
# Go ahead and alter the status, cookies, headers and body instance variables as you
|
251
|
+
# see fit in order to customize the response.
|
252
|
+
#
|
253
|
+
# module Camping::Controllers
|
254
|
+
# class SoftLink
|
255
|
+
# def get
|
256
|
+
# redirect "/"
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
# end
|
260
|
+
#
|
261
|
+
# Is equivalent to:
|
262
|
+
#
|
263
|
+
# module Camping::Controllers
|
264
|
+
# class SoftLink
|
265
|
+
# def get
|
266
|
+
# @status = 302
|
267
|
+
# @headers['Location'] = "/"
|
268
|
+
# end
|
269
|
+
# end
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
module Base
|
273
|
+
attr_accessor :input, :cookies, :headers, :body, :status, :root
|
274
|
+
M = proc { |_, o, n| o.merge(n, &M) }
|
275
|
+
|
276
|
+
# Display a view, calling it by its method name +m+. If a <tt>layout</tt>
|
277
|
+
# method is found in Camping::Views, it will be used to wrap the HTML.
|
278
|
+
#
|
279
|
+
# module Camping::Controllers
|
280
|
+
# class Show
|
281
|
+
# def get
|
282
|
+
# @posts = Post.find :all
|
283
|
+
# render :index
|
284
|
+
# end
|
285
|
+
# end
|
286
|
+
# end
|
287
|
+
#
|
288
|
+
# You can also return directly html by just passing a block
|
289
|
+
#
|
290
|
+
def render(v,*a,&b)
|
291
|
+
mab(/^_/!~v.to_s){send(v,*a,&b)}
|
292
|
+
end
|
293
|
+
|
294
|
+
# You can directly return HTML form your controller for quick debugging
|
295
|
+
# by calling this method and pass some Markaby to it.
|
296
|
+
#
|
297
|
+
# module Camping::Controllers
|
298
|
+
# class Info
|
299
|
+
# def get; mab{ code @headers.inspect } end
|
300
|
+
# end
|
301
|
+
# end
|
302
|
+
#
|
303
|
+
# You can also pass true to use the :layout HTML wrapping method
|
304
|
+
#
|
305
|
+
def mab(l=nil,&b)
|
306
|
+
m=Mab.new({},self)
|
307
|
+
s=m.capture(&b)
|
308
|
+
s=m.capture{layout{s}} if l && m.respond_to?(:layout)
|
309
|
+
s
|
310
|
+
end
|
311
|
+
|
312
|
+
# A quick means of setting this controller's status, body and headers.
|
313
|
+
# Used internally by Camping, but... by all means...
|
314
|
+
#
|
315
|
+
# r(302, '', 'Location' => self / "/view/12")
|
316
|
+
#
|
317
|
+
# Is equivalent to:
|
318
|
+
#
|
319
|
+
# redirect "/view/12"
|
320
|
+
#
|
321
|
+
# You can also switch the body and the header in order to support Rack:
|
322
|
+
#
|
323
|
+
# r(302, {'Location' => self / "/view/12"}, '')
|
324
|
+
# r(another_app.call(@env))
|
325
|
+
#
|
326
|
+
# See also: #r404, #r500 and #r501
|
327
|
+
def r(s, b, h = {})
|
328
|
+
b, h = h, b if Hash === b
|
329
|
+
@status = s
|
330
|
+
@headers.merge!(h)
|
331
|
+
@body = b
|
332
|
+
end
|
333
|
+
|
334
|
+
# Formulate a redirect response: a 302 status with <tt>Location</tt> header
|
335
|
+
# and a blank body. Uses Helpers#URL to build the location from a controller
|
336
|
+
# route or path.
|
337
|
+
#
|
338
|
+
# So, given a root of <tt>http://localhost:3301/articles</tt>:
|
339
|
+
#
|
340
|
+
# redirect "view/12" # redirects to "//localhost:3301/articles/view/12"
|
341
|
+
# redirect View, 12 # redirects to "//localhost:3301/articles/view/12"
|
342
|
+
#
|
343
|
+
# <b>NOTE:</b> This method doesn't magically exit your methods and redirect.
|
344
|
+
# You'll need to <tt>return redirect(...)</tt> if this isn't the last statement
|
345
|
+
# in your code.
|
346
|
+
def redirect(*a)
|
347
|
+
r(302,'','Location'=>URL(*a).to_s)
|
348
|
+
end
|
349
|
+
|
350
|
+
# Called when a controller was not found. It is mainly used internally, but it can
|
351
|
+
# also be useful for you, if you want to filter some parameters.
|
352
|
+
#
|
353
|
+
# module Camping
|
354
|
+
# def r404(p=env.PATH)
|
355
|
+
# @status = 404
|
356
|
+
# div do
|
357
|
+
# h1 'Camping Problem!'
|
358
|
+
# h2 "#{p} not found"
|
359
|
+
# end
|
360
|
+
# end
|
361
|
+
# end
|
362
|
+
#
|
363
|
+
# See: I
|
364
|
+
def r404(p)
|
365
|
+
P % "#{p} not found"
|
366
|
+
end
|
367
|
+
|
368
|
+
# If there is a parse error in Camping or in your application's source code, it will not be caught
|
369
|
+
# by Camping. The controller class +k+ and request method +m+ (GET, POST, etc.) where the error
|
370
|
+
# took place are passed in, along with the Exception +e+ which can be mined for useful info.
|
371
|
+
#
|
372
|
+
# You can overide it, but if you have an error in here, it will be uncaught !
|
373
|
+
#
|
374
|
+
# See: I
|
375
|
+
def r500(k,m,e)
|
376
|
+
raise e
|
377
|
+
end
|
378
|
+
|
379
|
+
# Called if an undefined method is called on a Controller, along with the request method +m+ (GET, POST, etc.)
|
380
|
+
#
|
381
|
+
# See: I
|
382
|
+
def r501(m)
|
383
|
+
P % "#{m.upcase} not implemented"
|
384
|
+
end
|
385
|
+
|
386
|
+
# Turn a controller into an array. This is designed to be used to pipe
|
387
|
+
# controllers into the <tt>r</tt> method. A great way to forward your
|
388
|
+
# requests!
|
389
|
+
#
|
390
|
+
# class Read < '/(\d+)'
|
391
|
+
# def get(id)
|
392
|
+
# Post.find(id)
|
393
|
+
# rescue
|
394
|
+
# r *Blog.get(:NotFound, @headers.REQUEST_URI)
|
395
|
+
# end
|
396
|
+
# end
|
397
|
+
def to_a
|
398
|
+
r = Rack::Response.new(@body, @status, @headers)
|
399
|
+
@cookies.each do |k, v|
|
400
|
+
v = {:value => v, :path => self / "/"} if String===v
|
401
|
+
r.set_cookie(k, v)
|
402
|
+
end
|
403
|
+
r.to_a
|
404
|
+
end
|
405
|
+
|
406
|
+
def initialize(env, m) #:nodoc:
|
407
|
+
r = @request = Rack::Request.new(@env = env)
|
408
|
+
@root, p, @cookies,
|
409
|
+
@headers, @status, @method =
|
410
|
+
(env.SCRIPT_NAME||'').sub(/\/$/,''),
|
411
|
+
H[r.params], H[r.cookies],
|
412
|
+
{}, m =~ /r(\d+)/ ? $1.to_i : 200, m
|
413
|
+
|
414
|
+
@input = p.inject(H[]) do |h, (k, v)|
|
415
|
+
h.merge(k.split(/[\]\[]+/).reverse.inject(v) { |x, i| H[i => x] }, &M)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# TODO: The wiki is down. Service overload should probably go in Camping.
|
420
|
+
# All requests pass through this method before going to the controller. Some magic
|
421
|
+
# in Camping can be performed by overriding this method.
|
422
|
+
#
|
423
|
+
# See http://code.whytheluckystiff.net/camping/wiki/BeforeAndAfterOverrides for more
|
424
|
+
# on before and after overrides with Camping.
|
425
|
+
def service(*a)
|
426
|
+
r = catch(:halt){send(@method, *a)}
|
427
|
+
@body ||= r
|
428
|
+
self
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# TODO: @input & @cookies at least.
|
433
|
+
# Controllers is a module for placing classes which handle URLs. This is done
|
434
|
+
# by defining a route to each class using the Controllers::R method.
|
435
|
+
#
|
436
|
+
# module Camping::Controllers
|
437
|
+
# class Edit < R '/edit/(\d+)'
|
438
|
+
# def get; end
|
439
|
+
# def post; end
|
440
|
+
# end
|
441
|
+
# end
|
442
|
+
#
|
443
|
+
# If no route is set, Camping will guess the route from the class name.
|
444
|
+
# The rule is very simple: the route becomes a slash followed by the lowercased
|
445
|
+
# class name. See Controllers::D for the complete rules of dispatch.
|
446
|
+
module Controllers
|
447
|
+
@r = []
|
448
|
+
class << self
|
449
|
+
# An array containing the various controllers available for dispatch.
|
450
|
+
def r #:nodoc:
|
451
|
+
@r
|
452
|
+
end
|
453
|
+
# Add routes to a controller class by piling them into the R method.
|
454
|
+
#
|
455
|
+
# module Camping::Controllers
|
456
|
+
# class Edit < R '/edit/(\d+)', '/new'
|
457
|
+
# def get(id)
|
458
|
+
# if id # edit
|
459
|
+
# else # new
|
460
|
+
# end
|
461
|
+
# end
|
462
|
+
# end
|
463
|
+
# end
|
464
|
+
#
|
465
|
+
# You will need to use routes in either of these cases:
|
466
|
+
#
|
467
|
+
# * You want to assign multiple routes to a controller.
|
468
|
+
# * You want your controller to receive arguments.
|
469
|
+
#
|
470
|
+
# Most of the time the rules inferred by dispatch method Controllers::D will get you
|
471
|
+
# by just fine.
|
472
|
+
def R *u
|
473
|
+
r=@r
|
474
|
+
Class.new {
|
475
|
+
meta_def(:urls){u}
|
476
|
+
meta_def(:inherited){|x|r<<x}
|
477
|
+
}
|
478
|
+
end
|
479
|
+
|
480
|
+
# Dispatch routes to controller classes.
|
481
|
+
# For each class, routes are checked for a match based on their order in the routing list
|
482
|
+
# given to Controllers::R. If no routes were given, the dispatcher uses a slash followed
|
483
|
+
# by the name of the controller lowercased.
|
484
|
+
#
|
485
|
+
# Controllers are searched in this order:
|
486
|
+
#
|
487
|
+
# # Classes without routes, since they refer to a very specific URL.
|
488
|
+
# # Classes with routes are searched in order of their creation.
|
489
|
+
#
|
490
|
+
# So, define your catch-all controllers last.
|
491
|
+
def D(p, m)
|
492
|
+
p = '/' if !p || !p[0]
|
493
|
+
r.map { |k|
|
494
|
+
k.urls.map { |x|
|
495
|
+
return (k.instance_method(m) rescue nil) ?
|
496
|
+
[k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
|
497
|
+
}
|
498
|
+
}
|
499
|
+
[I, 'r404', p]
|
500
|
+
end
|
501
|
+
|
502
|
+
N = H.new { |_,x| x.downcase }.merge! "N" => '(\d+)', "X" => '([^/]+)', "Index" => ''
|
503
|
+
# The route maker, this is called by Camping internally, you shouldn't need to call it.
|
504
|
+
#
|
505
|
+
# Still, it's worth know what this method does. Since Ruby doesn't keep track of class
|
506
|
+
# creation order, we're keeping an internal list of the controllers which inherit from R().
|
507
|
+
# This method goes through and adds all the remaining routes to the beginning of the list
|
508
|
+
# and ensures all the controllers have the right mixins.
|
509
|
+
#
|
510
|
+
# Anyway, if you are calling the URI dispatcher from outside of a Camping server, you'll
|
511
|
+
# definitely need to call this at least once to set things up.
|
512
|
+
def M
|
513
|
+
def M #:nodoc:
|
514
|
+
end
|
515
|
+
constants.map { |c|
|
516
|
+
k=const_get(c)
|
517
|
+
k.send :include,C,Base,Helpers,Models
|
518
|
+
@r=[k]+r if r-[k]==r
|
519
|
+
k.meta_def(:urls){["/#{c.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}"]}if !k.respond_to?:urls
|
520
|
+
}
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
# Internal controller with no route. Used by #D and C.call to show internal messages.
|
525
|
+
I = R()
|
526
|
+
end
|
527
|
+
X = Controllers
|
528
|
+
|
529
|
+
class << self
|
530
|
+
# When you are running many applications, you may want to create independent
|
531
|
+
# modules for each Camping application. Namespaces for each. Camping::goes
|
532
|
+
# defines a toplevel constant with the whole MVC rack inside.
|
533
|
+
#
|
534
|
+
# require 'camping'
|
535
|
+
# Camping.goes :Blog
|
536
|
+
#
|
537
|
+
# module Blog::Controllers; ... end
|
538
|
+
# module Blog::Models; ... end
|
539
|
+
# module Blog::Views; ... end
|
540
|
+
#
|
541
|
+
def goes(m)
|
542
|
+
Apps << eval(S.gsub(/Camping/,m.to_s), TOPLEVEL_BINDING)
|
543
|
+
end
|
544
|
+
|
545
|
+
# Ruby web servers use this method to enter the Camping realm. The e
|
546
|
+
# argument is the environment variables hash as per the Rack specification.
|
547
|
+
# And array with [statuc, headers, body] is expected at the output.
|
548
|
+
def call(e)
|
549
|
+
X.M
|
550
|
+
e = H[e]
|
551
|
+
p = e.PATH_INFO = U.unescape(e.PATH_INFO)
|
552
|
+
k,m,*a=X.D p,(e.REQUEST_METHOD||'get').downcase
|
553
|
+
k.new(e,m).service(*a).to_a
|
554
|
+
rescue
|
555
|
+
r500(:I, k, m, $!, :env => e).to_a
|
556
|
+
end
|
557
|
+
|
558
|
+
# The Camping scriptable dispatcher. Any unhandled method call to the app module will
|
559
|
+
# be sent to a controller class, specified as an argument.
|
560
|
+
#
|
561
|
+
# Blog.get(:Index)
|
562
|
+
# #=> #<Blog::Controllers::Index ... >
|
563
|
+
#
|
564
|
+
# The controller object contains all the @cookies, @body, @headers, etc. formulated by
|
565
|
+
# the response.
|
566
|
+
#
|
567
|
+
# You can also feed environment variables and query variables as a hash, the final
|
568
|
+
# argument.
|
569
|
+
#
|
570
|
+
# Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'})
|
571
|
+
# #=> #<Blog::Controllers::Login @user=... >
|
572
|
+
#
|
573
|
+
# Blog.get(:Info, :env => {'HTTP_HOST' => 'wagon'})
|
574
|
+
# #=> #<Blog::Controllers::Info @headers={'HTTP_HOST'=>'wagon'} ...>
|
575
|
+
#
|
576
|
+
def method_missing(m, c, *a)
|
577
|
+
X.M
|
578
|
+
h = Hash === a[-1] ? a.pop : {}
|
579
|
+
e = H[Rack::MockRequest.env_for('',h[:env]||{})]
|
580
|
+
k = X.const_get(c).new(e,m.to_s)
|
581
|
+
k.send("input=", h[:input]) if h[:input]
|
582
|
+
k.service(*a)
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
# TODO: More examples.
|
587
|
+
# Views is an empty module for storing methods which create HTML. The HTML is described
|
588
|
+
# using the Markaby language.
|
589
|
+
#
|
590
|
+
# == Using the layout method
|
591
|
+
#
|
592
|
+
# If your Views module has a <tt>layout</tt> method defined, it will be called with a block
|
593
|
+
# which will insert content from your view.
|
594
|
+
module Views; include X, Helpers end
|
595
|
+
|
596
|
+
# TODO: Migrations
|
597
|
+
# Models is an empty Ruby module for housing model classes derived
|
598
|
+
# from ActiveRecord::Base. As a shortcut, you may derive from Base
|
599
|
+
# which is an alias for ActiveRecord::Base.
|
600
|
+
#
|
601
|
+
# module Camping::Models
|
602
|
+
# class Post < Base; belongs_to :user end
|
603
|
+
# class User < Base; has_many :posts end
|
604
|
+
# end
|
605
|
+
#
|
606
|
+
# == Where Models are Used
|
607
|
+
#
|
608
|
+
# Models are used in your controller classes. However, if your model class
|
609
|
+
# name conflicts with a controller class name, you will need to refer to it
|
610
|
+
# using the Models module.
|
611
|
+
#
|
612
|
+
# module Camping::Controllers
|
613
|
+
# class Post < R '/post/(\d+)'
|
614
|
+
# def get(post_id)
|
615
|
+
# @post = Models::Post.find post_id
|
616
|
+
# render :index
|
617
|
+
# end
|
618
|
+
# end
|
619
|
+
# end
|
620
|
+
#
|
621
|
+
# Models cannot be referred to in Views at this time.
|
622
|
+
module Models
|
623
|
+
autoload :Base,'camping/ar'
|
624
|
+
def Y;self;end
|
625
|
+
end
|
626
|
+
|
627
|
+
autoload :Mab, 'camping/mab'
|
628
|
+
C
|
629
|
+
end
|
630
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
%w[uri stringio rack].map{|l|require l};class Object;def meta_def m,&b
|
2
|
+
(class<<self;self end).send:define_method,m,&b end end;module Camping;C=self
|
3
|
+
S=IO.read(__FILE__)rescue nil;P="<h1>Cam\ping Problem!</h1><h2>%s</h2>"
|
4
|
+
U=Rack::Utils;Apps=[];class H<Hash
|
5
|
+
def method_missing m,*a;m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end
|
6
|
+
undef id,type;end;module Helpers;def R c,*g
|
7
|
+
p,h=/\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"unless u=c.urls.find{|x|
|
8
|
+
break x if x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a|
|
9
|
+
x.sub p,U.escape((a[a.class.primary_key]rescue a))})}
|
10
|
+
h.any?? u+"?"+U.build_query(h[0]):u end;def / p
|
11
|
+
p[0]==?/?@root+p:p end;def URL c='/',*a;c=R(c, *a) if c.respond_to?:urls
|
12
|
+
c=self/c;c=@request.url[/.{8,}?(?=\/)/]+c if c[0]==?/;URI c end
|
13
|
+
end;module Base;attr_accessor:input,:cookies,:headers,:body,:status,:root
|
14
|
+
M=proc{|_,o,n|o.merge(n,&M)}
|
15
|
+
def render v,*a,&b;mab(/^_/!~v.to_s){send(v,*a,&b)} end
|
16
|
+
def mab l=nil,&b;m=Mab.new({},self);s=m.capture(&b)
|
17
|
+
s=m.capture{layout{s}} if l && m.respond_to?(:layout);s end
|
18
|
+
def r s,b,h={};b,h=h,b if Hash===b;@status=s;
|
19
|
+
@headers.merge!(h);@body=b;end;def redirect *a;r 302,'','Location'=>URL(*a).
|
20
|
+
to_s;end;def r404 p;P%"#{p} not found"end;def r500 k,m,e;raise e;end
|
21
|
+
def r501 m;P%"#{m.upcase} not implemented"end;def to_a
|
22
|
+
r=Rack::Response.new(@body,@status,@headers)
|
23
|
+
@cookies.each{|k,v|v={:value=>v,:path=>self/"/"} if String===v
|
24
|
+
r.set_cookie(k,v)}
|
25
|
+
r.to_a;end;def initialize(env,m)
|
26
|
+
r=@request=Rack::Request.new(@env=env)
|
27
|
+
@root,p,@cookies,@headers,@status,@method=
|
28
|
+
(env.SCRIPT_NAME||'').sub(/\/$/,''),H[r.params],
|
29
|
+
H[r.cookies],{},m=~/r(\d+)/?$1.to_i: 200,m
|
30
|
+
@input=p.inject(H[]){|h,(k,v)|h.merge k.split(/[\]\[]+/).reverse.inject(v){|x,i|
|
31
|
+
H[i=>x]},&M};end;def service *a
|
32
|
+
r=catch(:halt){send(@method,*a)};@body||=r
|
33
|
+
self;end;end;module Controllers;@r=[];class<<self;def r;@r end;def R *u;r=@r
|
34
|
+
Class.new{meta_def(:urls){u};meta_def(:inherited){|x|r<<x}}end
|
35
|
+
def D p,m;p='/'if !p||!p[0]
|
36
|
+
r.map{|k|k.urls.map{|x|return(k.instance_method(m)rescue nil)?
|
37
|
+
[k,m,*$~[1..-1]]:[I,'r501',m]if p=~/^#{x}\/?$/}};[I,'r404',p] end
|
38
|
+
N=H.new{|_,x|x.downcase}.merge! "N"=>'(\d+)',"X"=>'([^/]+)',"Index"=>''
|
39
|
+
def M;def M;end;constants.map{|c|k=const_get(c)
|
40
|
+
k.send:include,C,Base,Helpers,Models;@r=[k]+r if r-[k]==r
|
41
|
+
k.meta_def(:urls){["/#{c.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}"]
|
42
|
+
}if !k.respond_to?:urls}end end;I=R()
|
43
|
+
end;X=Controllers;class<<self;def goes m
|
44
|
+
Apps<<eval(S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING) end;def call e
|
45
|
+
X.M;e=H[e];p=e.PATH_INFO=U.unescape(e.PATH_INFO)
|
46
|
+
k,m,*a=X.D p,(e.REQUEST_METHOD||'get').downcase
|
47
|
+
k.new(e,m).service(*a).to_a;rescue;r500(:I,k,m,$!,:env=>e).to_a;end
|
48
|
+
def method_missing m,c,*a;X.M;h=Hash===a[-1]?a.pop: {}
|
49
|
+
e=H[Rack::MockRequest.env_for('',h[:env]||{})]
|
50
|
+
k=X.const_get(c).new(e,m.to_s);k.send("input=",h[:input])if h[:input]
|
51
|
+
k.service(*a);end;end;module Views;include X,Helpers end;module Models
|
52
|
+
autoload:Base,'camping/ar';def Y;self;end end;autoload:Mab,'camping/mab';C end
|