zuk-picnic 0.7.999.20090212

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