bmarzolf-picnic 0.8.0.20090420

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG.txt +1 -0
  2. data/History.txt +78 -0
  3. data/LICENSE.txt +165 -0
  4. data/Manifest.txt +45 -0
  5. data/README.txt +31 -0
  6. data/Rakefile +64 -0
  7. data/lib/picnic/authentication.rb +254 -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 +99 -0
  13. data/lib/picnic/service_control.rb +274 -0
  14. data/lib/picnic/version.rb +9 -0
  15. data/lib/picnic.rb +11 -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.20090420/CHANGELOG +118 -0
  20. data/vendor/camping-2.0.20090420/COPYING +18 -0
  21. data/vendor/camping-2.0.20090420/README +82 -0
  22. data/vendor/camping-2.0.20090420/Rakefile +180 -0
  23. data/vendor/camping-2.0.20090420/bin/camping +97 -0
  24. data/vendor/camping-2.0.20090420/doc/camping.1.gz +0 -0
  25. data/vendor/camping-2.0.20090420/examples/README +5 -0
  26. data/vendor/camping-2.0.20090420/examples/blog.rb +375 -0
  27. data/vendor/camping-2.0.20090420/examples/campsh.rb +629 -0
  28. data/vendor/camping-2.0.20090420/examples/tepee.rb +242 -0
  29. data/vendor/camping-2.0.20090420/extras/Camping.gif +0 -0
  30. data/vendor/camping-2.0.20090420/extras/permalink.gif +0 -0
  31. data/vendor/camping-2.0.20090420/lib/camping/ar/session.rb +132 -0
  32. data/vendor/camping-2.0.20090420/lib/camping/ar.rb +78 -0
  33. data/vendor/camping-2.0.20090420/lib/camping/mab.rb +26 -0
  34. data/vendor/camping-2.0.20090420/lib/camping/reloader.rb +184 -0
  35. data/vendor/camping-2.0.20090420/lib/camping/server.rb +159 -0
  36. data/vendor/camping-2.0.20090420/lib/camping/session.rb +75 -0
  37. data/vendor/camping-2.0.20090420/lib/camping-unabridged.rb +630 -0
  38. data/vendor/camping-2.0.20090420/lib/camping.rb +52 -0
  39. data/vendor/camping-2.0.20090420/setup.rb +1551 -0
  40. data/vendor/camping-2.0.20090420/test/apps/env_debug.rb +65 -0
  41. data/vendor/camping-2.0.20090420/test/apps/forms.rb +95 -0
  42. data/vendor/camping-2.0.20090420/test/apps/misc.rb +86 -0
  43. data/vendor/camping-2.0.20090420/test/apps/sessions.rb +38 -0
  44. data/vendor/camping-2.0.20090420/test/test_camping.rb +54 -0
  45. 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