camping 2.1.532 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +72 -53
- data/Rakefile +25 -20
- data/bin/camping +1 -0
- data/book/01_introduction.md +6 -6
- data/book/02_getting_started.md +348 -267
- data/book/03_more_about_controllers.md +124 -0
- data/book/04_more_about_views.md +118 -0
- data/book/05_more_about_markaby.md +173 -0
- data/book/06_more_about_models.md +58 -0
- data/book/06_rules_of_thumb.md +143 -0
- data/book/07_philosophy.md +23 -0
- data/book/08_publishing_an_app.md +118 -0
- data/book/09_upgrade_notes.md +96 -0
- data/book/10_middleware.md +69 -0
- data/book/11_gear.md +50 -0
- data/examples/blog.rb +38 -38
- data/lib/camping/ar.rb +20 -5
- data/lib/camping/commands.rb +388 -0
- data/lib/camping/gear/filters.rb +48 -0
- data/lib/camping/gear/inspection.rb +32 -0
- data/lib/camping/gear/kuddly.rb +178 -0
- data/lib/camping/gear/nancy.rb +170 -0
- data/lib/camping/loads.rb +15 -0
- data/lib/camping/mab.rb +1 -1
- data/lib/camping/reloader.rb +22 -17
- data/lib/camping/server.rb +145 -70
- data/lib/camping/session.rb +8 -5
- data/lib/camping/template.rb +1 -2
- data/lib/camping/tools.rb +43 -0
- data/lib/camping/version.rb +6 -0
- data/lib/camping-unabridged.rb +360 -133
- data/lib/camping.rb +78 -47
- data/lib/campingtrip.md +341 -0
- data/test/app_camping_gear.rb +121 -0
- data/test/app_camping_tools.rb +1 -0
- data/test/app_config.rb +30 -0
- data/test/app_cookies.rb +1 -1
- data/test/app_file.rb +3 -3
- data/test/app_goes_meta.rb +23 -0
- data/test/app_inception.rb +39 -0
- data/test/app_markup.rb +5 -20
- data/test/app_migrations.rb +16 -0
- data/test/app_partials.rb +1 -1
- data/test/app_prefixed.rb +88 -0
- data/test/app_reloader.rb +1 -2
- data/test/app_route_generating.rb +69 -2
- data/test/app_sessions.rb +24 -2
- data/test/app_simple.rb +18 -18
- data/test/apps/migrations.rb +82 -82
- data/test/apps/misc.rb +1 -1
- data/test/gear/gear_nancy.rb +129 -0
- data/test/test_helper.rb +69 -12
- metadata +152 -92
- data/CHANGELOG +0 -145
- data/book/51_upgrading.md +0 -110
data/lib/camping-unabridged.rb
CHANGED
@@ -9,10 +9,11 @@
|
|
9
9
|
# nicely with piles of documentation everywhere. This documentation is entirely
|
10
10
|
# generated from lib/camping-unabridged.rb using RDoc and our "flipbook" template
|
11
11
|
# found in the extras directory of any camping distribution.
|
12
|
-
require "
|
13
|
-
require "rack"
|
12
|
+
require "cam\ping/loads"
|
14
13
|
|
15
14
|
$LOADED_FEATURES << "camping.rb"
|
15
|
+
E ||= "content-type"
|
16
|
+
Z ||= "text/html"
|
16
17
|
|
17
18
|
class Object #:nodoc:
|
18
19
|
def meta_def(m,&b) #:nodoc:
|
@@ -22,7 +23,7 @@ end
|
|
22
23
|
|
23
24
|
# If you're new to Camping, you should probably start by reading the first
|
24
25
|
# chapters of {The Camping Book}[file:book/01_introduction.html#toc].
|
25
|
-
#
|
26
|
+
#
|
26
27
|
# Okay. So, the important thing to remember is that <tt>Camping.goes :Nuts</tt>
|
27
28
|
# copies the Camping module into Nuts. This means that you should never use
|
28
29
|
# any of these methods/classes on the Camping module, but rather on your own
|
@@ -37,10 +38,10 @@ end
|
|
37
38
|
#
|
38
39
|
# Camping also ships with:
|
39
40
|
#
|
40
|
-
# * Camping::Session adds states to your app.
|
41
|
+
# * Camping::Session adds states to your app.
|
41
42
|
# * Camping::Server starts up your app in development.
|
42
43
|
# * Camping::Reloader automatically reloads your apps when a file has changed.
|
43
|
-
#
|
44
|
+
#
|
44
45
|
# More importantly, Camping also installs The Camping Server,
|
45
46
|
# please see Camping::Server.
|
46
47
|
module Camping
|
@@ -48,12 +49,13 @@ module Camping
|
|
48
49
|
S = IO.read(__FILE__) rescue nil
|
49
50
|
P = "<h1>Cam\ping Problem!</h1><h2>%s</h2>"
|
50
51
|
U = Rack::Utils
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
Apps = [] # Our array of Apps
|
53
|
+
SK = "camping" #Key for r.session
|
54
|
+
G = [] # Our array of Gear
|
55
|
+
|
54
56
|
# An object-like Hash.
|
55
57
|
# All Camping query string and cookie variables are loaded as this.
|
56
|
-
#
|
58
|
+
#
|
57
59
|
# To access the query string, for instance, use the <tt>@input</tt> variable.
|
58
60
|
#
|
59
61
|
# module Blog::Controllers
|
@@ -84,6 +86,8 @@ module Camping
|
|
84
86
|
undef id, type if ?? == 63
|
85
87
|
end
|
86
88
|
|
89
|
+
O=H.new;O[:url_prefix]="" # Our Hash of Options
|
90
|
+
|
87
91
|
class Cookies < H
|
88
92
|
attr_accessor :_p
|
89
93
|
#
|
@@ -101,7 +105,7 @@ module Camping
|
|
101
105
|
set k, v, v.is_a?(Hash) ? v : {}
|
102
106
|
end
|
103
107
|
end
|
104
|
-
|
108
|
+
|
105
109
|
# Helpers contains methods available in your controllers and views. You may
|
106
110
|
# add methods of your own to this module, including many helper methods from
|
107
111
|
# Rails. This is analogous to Rails' <tt>ApplicationHelper</tt> module.
|
@@ -115,9 +119,9 @@ module Camping
|
|
115
119
|
# class, you'll find that it's loaded from the <tt>action_view/helpers/form_tag_helper.rb</tt>
|
116
120
|
# file. You'll need to have the ActionPack gem installed for this to work.
|
117
121
|
#
|
118
|
-
#
|
122
|
+
# A helper often depends on other helpers, so you would have to look up
|
119
123
|
# the dependencies too. <tt>FormTagHelper</tt> for instance required the
|
120
|
-
# <tt>content_tag</tt> provided by <tt>TagHelper</tt>.
|
124
|
+
# <tt>content_tag</tt> provided by <tt>TagHelper</tt>.
|
121
125
|
#
|
122
126
|
# require 'action_view/helpers/form_tag_helper'
|
123
127
|
#
|
@@ -127,7 +131,7 @@ module Camping
|
|
127
131
|
# end
|
128
132
|
#
|
129
133
|
# == Return a response immediately
|
130
|
-
# If you need to return a response inside a helper, you can use <tt>throw :halt</tt>.
|
134
|
+
# If you need to return a response inside a helper, you can use <tt>throw :halt</tt>.
|
131
135
|
#
|
132
136
|
# module Nuts::Helpers
|
133
137
|
# def requires_login!
|
@@ -137,7 +141,7 @@ module Camping
|
|
137
141
|
# end
|
138
142
|
# end
|
139
143
|
# end
|
140
|
-
#
|
144
|
+
#
|
141
145
|
# module Nuts::Controllers
|
142
146
|
# class Admin
|
143
147
|
# def get
|
@@ -203,9 +207,9 @@ module Camping
|
|
203
207
|
p,h=/\(.+?\)/,g.grep(Hash)
|
204
208
|
g-=h
|
205
209
|
raise "bad route" if !u = c.urls.find{|x|
|
206
|
-
break x if x.scan(p).size == g.size &&
|
210
|
+
break x if x.scan(p).size == g.size &&
|
207
211
|
/^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
|
208
|
-
x.sub p,U.escape((a.to_param rescue a))}.gsub(
|
212
|
+
x.sub p,U.escape((a.to_param rescue a))}.gsub(CampTools.descape){$1})
|
209
213
|
}
|
210
214
|
h.any?? u+"?"+U.build_query(h[0]) : u
|
211
215
|
end
|
@@ -217,8 +221,10 @@ module Camping
|
|
217
221
|
# self / "styles.css" #=> "styles.css"
|
218
222
|
# self / R(Edit, 1) #=> "/blog/edit/1"
|
219
223
|
#
|
220
|
-
def /(p)
|
221
|
-
|
224
|
+
def /(p)
|
225
|
+
p[0] == ?/ ? (@root + @url_prefix.dup.prepend("/").chop + p) : p
|
226
|
+
end
|
227
|
+
|
222
228
|
# Builds a URL route to a controller or a path, returning a URI object.
|
223
229
|
# This way you'll get the hostname and the port number, a complete URL.
|
224
230
|
#
|
@@ -243,9 +249,14 @@ module Camping
|
|
243
249
|
def URL c='/',*a
|
244
250
|
c = R(c, *a) if c.respond_to? :urls
|
245
251
|
c = self/c
|
246
|
-
c = @request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/
|
252
|
+
c = @request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/ #/
|
247
253
|
URI(c)
|
248
254
|
end
|
255
|
+
|
256
|
+
# Just a helper to tell you the App Name
|
257
|
+
# During the instantiation of the app, "Camping" is replaced with the Apps namespace.
|
258
|
+
def app_name;"Camping"end
|
259
|
+
|
249
260
|
end
|
250
261
|
|
251
262
|
# Camping::Base is built into each controller by way of the generic routing
|
@@ -256,28 +267,44 @@ module Camping
|
|
256
267
|
# Everything in this module is accessible inside your controllers.
|
257
268
|
module Base
|
258
269
|
attr_accessor :env, :request, :root, :input, :cookies, :state,
|
259
|
-
:status, :headers, :body
|
260
|
-
|
270
|
+
:status, :headers, :body, :url_prefix
|
271
|
+
|
261
272
|
T = {}
|
262
273
|
L = :layout
|
263
|
-
|
274
|
+
|
264
275
|
# Finds a template, returning either:
|
265
|
-
#
|
276
|
+
#
|
266
277
|
# false # => Could not find template
|
267
278
|
# true # => Found template in Views
|
268
279
|
# instance of Tilt # => Found template in a file
|
269
280
|
def lookup(n)
|
270
|
-
T.fetch(n.to_sym)
|
281
|
+
T.fetch(n.to_sym) { |k|
|
282
|
+
# Find a view defined in the Views module first
|
271
283
|
t = Views.method_defined?(k) ||
|
284
|
+
# Find inline templates (delimited by @@), and then put it in a new Template and return that.
|
285
|
+
# `:_t` is the options key for inline templates. Inline templates are added in `Camping#goes`.
|
272
286
|
(t = O[:_t].keys.grep(/^#{n}\./)[0]and Template[t].new{O[:_t][t]}) ||
|
287
|
+
|
288
|
+
# Find templates in a views directory, and return the first view that matches the symbol provided.
|
289
|
+
# Then pipe that template file into Template, which is just Tilt.
|
273
290
|
(f = Dir[[O[:views] || "views", "#{n}.*"]*'/'][0]) &&
|
291
|
+
|
292
|
+
# Grab any settings set for the template files, as set by their filename extension
|
293
|
+
# and add that to the options of Template (Tilt), or an empty Hash
|
294
|
+
# What does adding settings for a template look like? :
|
295
|
+
# module Nuts
|
296
|
+
# def r404(path)
|
297
|
+
# @path = path
|
298
|
+
# render :not_found
|
299
|
+
# end
|
300
|
+
# end
|
274
301
|
Template.new(f, O[f[/\.(\w+)$/, 1].to_sym] || {})
|
275
|
-
|
302
|
+
|
276
303
|
O[:dynamic_templates] ? t : T[k] = t
|
277
|
-
|
304
|
+
}
|
278
305
|
end
|
279
|
-
|
280
|
-
# Display a view, calling it by its method name +v+.
|
306
|
+
|
307
|
+
# Display a view, calling it by its method name +v+. If a <tt>layout</tt>
|
281
308
|
# method is found in Camping::Views, it will be used to wrap the HTML.
|
282
309
|
#
|
283
310
|
# module Nuts::Controllers
|
@@ -291,9 +318,12 @@ module Camping
|
|
291
318
|
#
|
292
319
|
def render(v, *a, &b)
|
293
320
|
if t = lookup(v)
|
294
|
-
|
321
|
+
# Has this controller rendered before?
|
322
|
+
r = @_r
|
323
|
+
# Set @_r to truthy value
|
324
|
+
@_r = (o = Hash === a[-1] ? a.pop : {})
|
295
325
|
s = (t == true) ? mab { send(v, *a, &b) } : t.render(self, o[:locals] || {}, &b)
|
296
|
-
s = render(L, o.merge(L => false)) { s } if o[L] or o[L].nil? && lookup(L) &&
|
326
|
+
s = render(L, o.merge(L => false)) { s } if o[L] or o[L].nil? && lookup(L) && !r && v.to_s[0] != ?_
|
297
327
|
s
|
298
328
|
else
|
299
329
|
raise "no template: #{v}"
|
@@ -302,7 +332,7 @@ module Camping
|
|
302
332
|
|
303
333
|
# You can directly return HTML from your controller for quick debugging
|
304
334
|
# by calling this method and passing some Markaby to it.
|
305
|
-
#
|
335
|
+
#
|
306
336
|
# module Nuts::Controllers
|
307
337
|
# class Info
|
308
338
|
# def get; mab{ code @headers.inspect } end
|
@@ -314,7 +344,7 @@ module Camping
|
|
314
344
|
extend Mab
|
315
345
|
mab(&b)
|
316
346
|
end
|
317
|
-
|
347
|
+
|
318
348
|
# A quick means of setting this controller's status, body and headers
|
319
349
|
# based on a Rack response:
|
320
350
|
#
|
@@ -364,13 +394,13 @@ module Camping
|
|
364
394
|
P % "#{p} not found"
|
365
395
|
end
|
366
396
|
|
367
|
-
# Called when an exception is raised. However,
|
397
|
+
# Called when an exception is raised. However, if there is a parse error
|
368
398
|
# in Camping or in your application's source code, it will not be caught.
|
369
399
|
#
|
370
400
|
# +k+ is the controller class, +m+ is the request method (GET, POST, etc.)
|
371
401
|
# and +e+ is the Exception which can be mined for useful info.
|
372
402
|
#
|
373
|
-
#
|
403
|
+
# By default this simply re-raises the error so a Rack middleware can
|
374
404
|
# handle it, but you are free to override it here:
|
375
405
|
#
|
376
406
|
# module Nuts
|
@@ -378,7 +408,7 @@ module Camping
|
|
378
408
|
# send_email_alert(klass, method, exception)
|
379
409
|
# render :server_error
|
380
410
|
# end
|
381
|
-
# end
|
411
|
+
# end
|
382
412
|
def r500(k,m,e)
|
383
413
|
raise e
|
384
414
|
end
|
@@ -390,12 +420,12 @@ module Camping
|
|
390
420
|
end
|
391
421
|
|
392
422
|
# Serves the string +c+ with the MIME type of the filename +p+.
|
393
|
-
#
|
423
|
+
# Defaults to text/html.
|
394
424
|
def serve(p, c)
|
395
|
-
t = Rack::Mime.mime_type(p[/\..*$/],
|
425
|
+
t = Rack::Mime.mime_type(p[/\..*$/], Z) and @headers[E] = t
|
396
426
|
c
|
397
427
|
end
|
398
|
-
|
428
|
+
|
399
429
|
# Turn a controller into a Rack response. This is designed to be used to
|
400
430
|
# pipe controllers into the <tt>r</tt> method. A great way to forward your
|
401
431
|
# requests!
|
@@ -415,23 +445,29 @@ module Camping
|
|
415
445
|
end
|
416
446
|
r.to_a
|
417
447
|
end
|
418
|
-
|
419
|
-
|
448
|
+
|
449
|
+
# initialize
|
450
|
+
# Turns a camping controller class into an object and sets up
|
451
|
+
# the environment with input, cookies, state, headers, etc...
|
452
|
+
def initialize(env, m, p) #:nodoc:
|
420
453
|
r = @request = Rack::Request.new(@env = env)
|
421
454
|
@root, @input, @cookies, @state,
|
422
|
-
@headers, @status, @method =
|
455
|
+
@headers, @status, @method, @url_prefix =
|
423
456
|
r.script_name.sub(/\/$/,''), n(r.params),
|
424
457
|
Cookies[r.cookies], H[r.session[SK]||{}],
|
425
|
-
{
|
458
|
+
{E=>Z}, m =~ /r(\d+)/ ? $1.to_i : 200, m, p
|
426
459
|
@cookies._p = self/"/"
|
427
460
|
end
|
428
|
-
|
461
|
+
|
462
|
+
# n method
|
463
|
+
# accepts parameters and converts them to a hash.
|
464
|
+
# helper method for initialize
|
429
465
|
def n(h) # :nodoc:
|
430
466
|
if Hash === h
|
431
|
-
h.inject(H[])
|
467
|
+
h.inject(H[]) { |m, (k, v)|
|
432
468
|
m[k] = n(v)
|
433
469
|
m
|
434
|
-
|
470
|
+
}
|
435
471
|
else
|
436
472
|
h
|
437
473
|
end
|
@@ -441,13 +477,12 @@ module Camping
|
|
441
477
|
# Some magic in Camping can be performed by overriding this method.
|
442
478
|
def service(*a)
|
443
479
|
r = catch(:halt){send(@method, *a)}
|
444
|
-
@body ||= r
|
480
|
+
@body ||= r
|
445
481
|
self
|
446
482
|
end
|
447
483
|
end
|
448
|
-
|
449
|
-
|
450
|
-
# Controllers receive the requests and sends a response back to the client.
|
484
|
+
|
485
|
+
# Controllers receive the requests and send a response back to the client.
|
451
486
|
# A controller is simply a class which must implement the HTTP methods it
|
452
487
|
# wants to accept:
|
453
488
|
#
|
@@ -457,27 +492,31 @@ module Camping
|
|
457
492
|
# "Hello World"
|
458
493
|
# end
|
459
494
|
# end
|
460
|
-
#
|
495
|
+
#
|
461
496
|
# class Posts
|
462
497
|
# def post
|
463
|
-
# Post.create(@input)
|
498
|
+
# Post.create(@input)
|
464
499
|
# redirect Index
|
465
500
|
# end
|
466
|
-
# end
|
501
|
+
# end
|
467
502
|
# end
|
468
|
-
#
|
503
|
+
#
|
469
504
|
# == Defining a controller
|
470
|
-
#
|
471
|
-
# There are two ways to define controllers:
|
472
|
-
#
|
473
|
-
#
|
505
|
+
#
|
506
|
+
# There are two ways to define controllers:
|
507
|
+
#
|
508
|
+
# 1. Define a class and let Camping figure out the route.
|
509
|
+
# 2. Add the route explicitly using R.
|
510
|
+
#
|
474
511
|
# If you don't use R, Camping will first split the controller name up by
|
475
|
-
# words (HelloWorld => Hello and World).
|
476
|
-
#
|
512
|
+
# words (HelloWorld => Hello and World).
|
513
|
+
#
|
514
|
+
# After that, it will do the following:
|
515
|
+
#
|
477
516
|
# * Replace Index with /
|
478
517
|
# * Replace X with ([^/]+)
|
479
518
|
# * Replace N with (\\\d+)
|
480
|
-
# *
|
519
|
+
# * Turn everything else into lowercase
|
481
520
|
# * Join the words with slashes
|
482
521
|
#
|
483
522
|
#--
|
@@ -485,28 +524,29 @@ module Camping
|
|
485
524
|
# here in order to work correctly with RDoc.
|
486
525
|
#++
|
487
526
|
#
|
488
|
-
# Here
|
489
|
-
#
|
527
|
+
# Here are a few examples:
|
528
|
+
#
|
490
529
|
# Index # => /
|
491
530
|
# PostN # => /post/(\d+)
|
492
531
|
# PageX # => /page/([^/]+)
|
493
532
|
# Pages # => /pages
|
494
|
-
#
|
533
|
+
#
|
495
534
|
# == The request
|
496
|
-
#
|
497
|
-
#
|
498
|
-
#
|
499
|
-
# * @env contains the environment as defined in
|
535
|
+
#
|
536
|
+
# The following variables aid in describing a request:
|
537
|
+
#
|
538
|
+
# * @env contains the environment as defined in https://github.com/rack/rack/blob/main/SPEC.rdoc
|
500
539
|
# * @request is Rack::Request.new(@env)
|
501
540
|
# * @root is the path where the app is mounted
|
502
541
|
# * @cookies is a hash with the cookies sent by the client
|
503
542
|
# * @state is a hash with the sessions (see Camping::Session)
|
504
543
|
# * @method is the HTTP method in lowercase
|
544
|
+
# * @url_prefix is the set prefix of the route matched by your controller
|
505
545
|
#
|
506
546
|
# == The response
|
507
547
|
#
|
508
548
|
# You can change these variables to your needs:
|
509
|
-
#
|
549
|
+
#
|
510
550
|
# * @status is the HTTP status (defaults to 200)
|
511
551
|
# * @headers is a hash with the headers
|
512
552
|
# * @body is the body (a string or something which responds to #each)
|
@@ -515,13 +555,13 @@ module Camping
|
|
515
555
|
# If you haven't set @body, it will use the return value of the method:
|
516
556
|
#
|
517
557
|
# module Nuts::Controllers
|
518
|
-
# class Index
|
558
|
+
# class Index < Camper
|
519
559
|
# def get
|
520
560
|
# "This is the body"
|
521
561
|
# end
|
522
562
|
# end
|
523
563
|
#
|
524
|
-
# class Posts
|
564
|
+
# class Posts < Camper
|
525
565
|
# def get
|
526
566
|
# @body = "Hello World!"
|
527
567
|
# "This is ignored"
|
@@ -530,9 +570,15 @@ module Camping
|
|
530
570
|
# end
|
531
571
|
module Controllers
|
532
572
|
@r = []
|
573
|
+
|
574
|
+
# An empty controller class that our other Classes inherit from.
|
575
|
+
# Camper is used by the R method internally.
|
576
|
+
class Camper end
|
577
|
+
|
533
578
|
class << self
|
579
|
+
|
534
580
|
# Add routes to a controller class by piling them into the R method.
|
535
|
-
#
|
581
|
+
#
|
536
582
|
# The route is a regexp which will match the request path. Anything
|
537
583
|
# enclosed in parenthesis will be sent to the method as arguments.
|
538
584
|
#
|
@@ -545,18 +591,37 @@ module Camping
|
|
545
591
|
# end
|
546
592
|
# end
|
547
593
|
# end
|
594
|
+
#
|
595
|
+
# Routes may be inherited using the R command as well. In this case you'll
|
596
|
+
# pass the ancestor Controller as the first argument to R.
|
597
|
+
#
|
598
|
+
# module Camping::Controllers
|
599
|
+
# class Post < R Edit, '/edit/(\d+)', '/new'
|
600
|
+
# def get(id)
|
601
|
+
# if id # edit
|
602
|
+
# else # new
|
603
|
+
# end
|
604
|
+
# end
|
605
|
+
# end
|
606
|
+
# end
|
607
|
+
#
|
548
608
|
def R *u
|
549
|
-
r=@r
|
550
|
-
Class.new {
|
609
|
+
r,uf=@r,u.first
|
610
|
+
Class.new((uf.is_a?(Class) && (uf.ancestors.include?(Camper))) ? u.shift : Camper) {
|
551
611
|
meta_def(:urls){u}
|
552
|
-
meta_def(:inherited){|x|r<<x}
|
612
|
+
meta_def(:inherited){|x|r<< x}
|
553
613
|
}
|
554
614
|
end
|
555
615
|
|
616
|
+
# A Helper method to map and return the actual routes of our controllers
|
617
|
+
def v
|
618
|
+
@r.map(&:urls)
|
619
|
+
end
|
620
|
+
|
556
621
|
# Dispatch routes to controller classes.
|
557
622
|
# For each class, routes are checked for a match based on their order in the routing list
|
558
|
-
# given to Controllers::R.
|
559
|
-
# by the name of the controller
|
623
|
+
# given to Controllers::R. If no routes were given, the dispatcher uses a slash followed
|
624
|
+
# by the lowercased name of the controller.
|
560
625
|
#
|
561
626
|
# Controllers are searched in this order:
|
562
627
|
#
|
@@ -576,9 +641,20 @@ module Camping
|
|
576
641
|
[I, 'r404', p]
|
577
642
|
end
|
578
643
|
|
644
|
+
# A lambda to avoid internal controller route
|
645
|
+
A = -> (c, u, p) {
|
646
|
+
d = p.dup
|
647
|
+
d.chop! if u == ''
|
648
|
+
u.prepend("/"+d) if !["I"].include? c.to_s
|
649
|
+
if c.to_s == "Index"
|
650
|
+
while d[-1] == "/"; d.chop! end
|
651
|
+
u.prepend("/"+d)
|
652
|
+
end
|
653
|
+
u
|
654
|
+
}
|
655
|
+
|
579
656
|
N = H.new { |_,x| x.downcase }.merge! "N" => '(\d+)', "X" => '([^/]+)', "Index" => ''
|
580
|
-
# The route maker,
|
581
|
-
# need to call it.
|
657
|
+
# The route maker, called by Camping internally.
|
582
658
|
#
|
583
659
|
# Still, it's worth know what this method does. Since Ruby doesn't keep
|
584
660
|
# track of class creation order, we're keeping an internal list of the
|
@@ -588,16 +664,24 @@ module Camping
|
|
588
664
|
#
|
589
665
|
# Anyway, if you are calling the URI dispatcher from outside of a
|
590
666
|
# Camping server, you'll definitely need to call this to set things up.
|
591
|
-
# Don't call it too early though
|
592
|
-
# method
|
593
|
-
def M
|
594
|
-
def M #:nodoc:
|
667
|
+
# Don't call it too early though - any controllers added after this
|
668
|
+
# method was called won't work properly.
|
669
|
+
def M(p)
|
670
|
+
def M(p) #:nodoc:
|
595
671
|
end
|
596
|
-
|
672
|
+
# TODO: Refactor this to make it less convoluted around making urls.
|
673
|
+
constants.filter {|c| c.to_s != 'Camper'}.map { |c|
|
597
674
|
k = const_get(c)
|
598
675
|
k.send :include,C,X,Base,Helpers,Models
|
599
676
|
@r=[k]+@r if @r-[k]==@r
|
600
|
-
|
677
|
+
mu = false # Should we make urls?
|
678
|
+
ka = k.ancestors
|
679
|
+
# This complicated code checks the ancestor chain of a controller to see it has it's own urls,
|
680
|
+
# or if it's urls are from one of it's ancestors. ancestor URLs need to be discarded.
|
681
|
+
if (k.respond_to?(:urls) && ka[1].respond_to?(:urls)) && (k.urls == ka[1].urls)
|
682
|
+
mu = true unless ka[1].name == nil
|
683
|
+
end
|
684
|
+
k.meta_def(:urls){[A.(k,"#{c.to_s.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}", p)]} if (!k.respond_to?(:urls) || mu == true)
|
601
685
|
}
|
602
686
|
end
|
603
687
|
end
|
@@ -608,50 +692,41 @@ module Camping
|
|
608
692
|
X = Controllers
|
609
693
|
|
610
694
|
class << self
|
611
|
-
|
612
|
-
#
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
#
|
619
|
-
#
|
620
|
-
#
|
695
|
+
|
696
|
+
# Create method to setup routes for Camping upon reload.
|
697
|
+
def make_camp
|
698
|
+
X.M prx
|
699
|
+
Apps.map(&:make_camp)
|
700
|
+
end
|
701
|
+
|
702
|
+
# Helper method for getting routes from the controllers.
|
703
|
+
# helps Camping::Server map routes to multiple apps.
|
704
|
+
# Usage:
|
621
705
|
#
|
622
|
-
#
|
623
|
-
#
|
624
|
-
#
|
625
|
-
# code for a worker process together:
|
626
|
-
#
|
627
|
-
# module YourApplication
|
628
|
-
# Camping.goes :Web, binding()
|
629
|
-
# module Web
|
630
|
-
# ...
|
631
|
-
# end
|
632
|
-
# module Worker
|
633
|
-
# ...
|
634
|
-
# end
|
635
|
-
# end
|
706
|
+
# Nuts.routes
|
707
|
+
# Camping.routes
|
708
|
+
# Nuts.routes
|
636
709
|
#
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
710
|
+
def routes
|
711
|
+
(Apps.map(&:routes)<<X.v).flatten
|
712
|
+
end
|
713
|
+
|
714
|
+
# An internal method used to return the current app's url_prefix.
|
715
|
+
# the prefix is processed to make sure that it's not all wonky. excessive
|
716
|
+
# trailing and leading slashes are removed. A trailing slash is added.
|
717
|
+
def prx
|
718
|
+
@_prx ||= CampTools.normalize_slashes(O[:url_prefix])
|
644
719
|
end
|
645
|
-
|
720
|
+
|
646
721
|
# Ruby web servers use this method to enter the Camping realm. The +e+
|
647
722
|
# argument is the environment variables hash as per the Rack specification.
|
648
|
-
#
|
723
|
+
# Array with [status, headers, body] is expected at the output.
|
649
724
|
#
|
650
|
-
# See:
|
725
|
+
# See: https://github.com/rack/rack/blob/main/SPEC.rdoc
|
651
726
|
def call(e)
|
652
|
-
|
653
|
-
k,m,*a=X.D e[
|
654
|
-
k.new(e,m).service(*a).to_a
|
727
|
+
make_camp # TODO: Find a better, more consistent place for setting everything up.
|
728
|
+
k,m,*a=X.D e["PATH_INFO"],e['REQUEST_METHOD'].downcase,e
|
729
|
+
k.new(e,m,prx).service(*a).to_a
|
655
730
|
rescue
|
656
731
|
r500(:I, k, m, $!, :env => e).to_a
|
657
732
|
end
|
@@ -675,40 +750,187 @@ module Camping
|
|
675
750
|
# #=> #<Blog::Controllers::Info @headers={'HTTP_HOST'=>'wagon'} ...>
|
676
751
|
#
|
677
752
|
def method_missing(m, c, *a)
|
678
|
-
X.M
|
679
753
|
h = Hash === a[-1] ? a.pop : {}
|
680
754
|
e = H[Rack::MockRequest.env_for('',h.delete(:env)||{})]
|
681
|
-
|
755
|
+
# puts "method missing failure for controller: #{c}, method: #{m} "
|
756
|
+
k = X.const_get(c).new(e,m.to_s,prx)
|
682
757
|
h.each { |i, v| k.send("#{i}=", v) }
|
683
758
|
k.service(*a)
|
684
759
|
end
|
685
|
-
|
760
|
+
|
686
761
|
# Injects a middleware:
|
687
762
|
#
|
688
763
|
# module Blog
|
689
764
|
# use Rack::MethodOverride
|
690
765
|
# use Rack::Session::Memcache, :key => "session"
|
691
766
|
# end
|
767
|
+
#
|
768
|
+
# This piece of code feels a bit confusing, but let's walk through it.
|
769
|
+
# Rack apps all implement a Call method. This is how Rub web servers
|
770
|
+
# pass call the app, or code that you're set up. In our case, our camping
|
771
|
+
# apps.
|
772
|
+
#
|
773
|
+
# The Use method is setting up a new middleware, it first shifts the first
|
774
|
+
# argument supplied to Use, which should be the Middleware name, then
|
775
|
+
# initializes it. That's your new middleware. Rack based middleware accept
|
776
|
+
# a single argument to their initialize methods, which is an app. Optionally
|
777
|
+
# settings and a block are supplied.
|
778
|
+
#
|
779
|
+
# So a new app is made, and its settings are supplied, then immediately
|
780
|
+
# sent to the new middleware we just added. But the cool part is where we
|
781
|
+
# call meta_def. meta_def takes a symbol and a block, and defines a class
|
782
|
+
# method into the current context. Our current context is our camping app.
|
783
|
+
# So when we call it below we're redefining the call method to call the new
|
784
|
+
# middleware that we just added. The `m` variable below represents our
|
785
|
+
# newly created middleware object, that we initialized with our old app. and
|
786
|
+
# because we're defining a new call method with a block, it's captured in
|
787
|
+
# that block.
|
788
|
+
#
|
789
|
+
# This creates a sequence of middleware that isn't recorded anywhere, but
|
790
|
+
# nonetheless is set up in the proper order and called in the proper order.
|
692
791
|
def use(*a, &b)
|
693
792
|
m = a.shift.new(method(:call), *a, &b)
|
694
793
|
meta_def(:call) { |e| m.call(e) }
|
695
794
|
end
|
696
|
-
|
795
|
+
|
796
|
+
# Add gear to your app:
|
797
|
+
#
|
798
|
+
# module Blog
|
799
|
+
# pack Camping::Gear::CSRF
|
800
|
+
# end
|
801
|
+
#
|
802
|
+
# Why have plugins in the first place if we can just include and extend our
|
803
|
+
# modules and classes directly? To perform setup actions!
|
804
|
+
#
|
805
|
+
# Sometimes you might have ClassMethods that you want to modify Camping with,
|
806
|
+
# This gives us a way to do that. In your gear:
|
807
|
+
#
|
808
|
+
# module MyGear
|
809
|
+
# module ClassMethods
|
810
|
+
# # Define Class Methods here
|
811
|
+
# end
|
812
|
+
# def self.included(mod)
|
813
|
+
# mod.extend(ClassMethods)
|
814
|
+
# end
|
815
|
+
# end
|
816
|
+
#
|
817
|
+
# Optionally a plugin may have a setup method and a ClassMethods module:
|
818
|
+
#
|
819
|
+
# module MyGear
|
820
|
+
# def self.setup(s)
|
821
|
+
# # Perform setup actions
|
822
|
+
# end
|
823
|
+
# module ClassMethods
|
824
|
+
# # Define Class Methods here
|
825
|
+
# end
|
826
|
+
# end
|
827
|
+
#
|
828
|
+
def pack(*a, &b)
|
829
|
+
G << g = a.shift
|
830
|
+
include g
|
831
|
+
g.setup(self, *a, &b) # if g.respond_to?(:setup) # Force all gear to have a setup function
|
832
|
+
end
|
833
|
+
|
834
|
+
# Helper method to list gear
|
835
|
+
def gear
|
836
|
+
G
|
837
|
+
end
|
838
|
+
|
697
839
|
# A hash where you can set different settings.
|
698
840
|
def options
|
699
841
|
O
|
700
842
|
end
|
701
|
-
|
843
|
+
|
702
844
|
# Shortcut for setting options:
|
703
|
-
#
|
845
|
+
#
|
704
846
|
# module Blog
|
705
847
|
# set :secret, "Hello!"
|
706
848
|
# end
|
707
849
|
def set(k, v)
|
708
850
|
O[k] = v
|
709
851
|
end
|
852
|
+
|
853
|
+
# When you are running multiple applications, you may want to create
|
854
|
+
# independent modules for each Camping application. Camping::goes
|
855
|
+
# defines a top level constant with the whole MVC rack inside:
|
856
|
+
#
|
857
|
+
# require 'camping'
|
858
|
+
# Camping.goes :Nuts
|
859
|
+
#
|
860
|
+
# module Nuts::Controllers; ... end
|
861
|
+
# module Nuts::Models; ... end
|
862
|
+
# module Nuts::Views; ... end
|
863
|
+
#
|
864
|
+
# Additionally, you can pass a Binding as the second parameter,
|
865
|
+
# which enables you to create a Camping-based application within
|
866
|
+
# another module.
|
867
|
+
#
|
868
|
+
# Here's an example of namespacing your web interface and
|
869
|
+
# code for a worker process together:
|
870
|
+
#
|
871
|
+
# module YourApplication
|
872
|
+
# Camping.goes :Web, binding()
|
873
|
+
# module Web
|
874
|
+
# ...
|
875
|
+
# end
|
876
|
+
# module Worker
|
877
|
+
# ...
|
878
|
+
# end
|
879
|
+
# end
|
880
|
+
#
|
881
|
+
# All the applications will be available in Camping::Apps.
|
882
|
+
#
|
883
|
+
# Camping offers a shortcut for adding thin files, and templates to your apps.
|
884
|
+
# Add them at the end of the same ruby file that you call `Camping.goes`:
|
885
|
+
#
|
886
|
+
# require 'camping'
|
887
|
+
# Camping.goes :Nuts
|
888
|
+
#
|
889
|
+
# module Nuts::Controllers; ... end
|
890
|
+
# module Nuts::Models; ... end
|
891
|
+
# module Nuts::Views; ... end
|
892
|
+
#
|
893
|
+
# __END__
|
894
|
+
#
|
895
|
+
# @@ /style.css
|
896
|
+
# * { margin: 0; padding: 0 }
|
897
|
+
#
|
898
|
+
# @@ /test.foo
|
899
|
+
# <H1>Hello friends! Nice to meet you.<H1>
|
900
|
+
#
|
901
|
+
# @@ index.erb
|
902
|
+
# Hello <%= @world %>
|
903
|
+
#
|
904
|
+
# Also sets the apps Meta Data. Can be found at O[:_meta]
|
905
|
+
#
|
906
|
+
# @app: {String} - The app in question
|
907
|
+
# @parent: {String} -
|
908
|
+
# @root: {String} -
|
909
|
+
# @line_number: {Int} - The line number that the app was declared
|
910
|
+
# @file: {String} - The file location for this
|
911
|
+
#
|
912
|
+
def goes(m, g=TOPLEVEL_BINDING)
|
913
|
+
|
914
|
+
# setup caller data
|
915
|
+
sp = caller[0].split('`')[0].split(":")
|
916
|
+
fl, ln, pr = sp[0], sp[1].to_i, nil
|
917
|
+
|
918
|
+
# Create the app
|
919
|
+
Apps << a = eval(S.gsub(/Camping/,m.to_s), g, fl, ln)
|
920
|
+
|
921
|
+
caller[0]=~/:/
|
922
|
+
IO.read(a.set:__FILE__,$`)=~/^__END__/ &&
|
923
|
+
(b=$'.split(/^@@\s*(.+?)\s*\r?\n/m)).shift rescue nil
|
924
|
+
a.set :_t,H[*b||[]]
|
925
|
+
|
926
|
+
# setup parental data
|
927
|
+
a.set :_meta, H[file: fl, line_number: ln, parent: self, root: (name != "Cam\ping" ? '/' + CampTools.to_snake(name) : '/')]
|
928
|
+
|
929
|
+
# configure the app?
|
930
|
+
C.configure(a)
|
931
|
+
end
|
710
932
|
end
|
711
|
-
|
933
|
+
|
712
934
|
# Views is an empty module for storing methods which create HTML. The HTML
|
713
935
|
# is described using the Markaby language.
|
714
936
|
#
|
@@ -720,7 +942,7 @@ module Camping
|
|
720
942
|
# def index
|
721
943
|
# p "Welcome to my blog"
|
722
944
|
# end
|
723
|
-
#
|
945
|
+
#
|
724
946
|
# def show
|
725
947
|
# h1 @post.title
|
726
948
|
# self << @post.content
|
@@ -745,7 +967,7 @@ module Camping
|
|
745
967
|
# end
|
746
968
|
# end
|
747
969
|
module Views; include X, Helpers end
|
748
|
-
|
970
|
+
|
749
971
|
# Models is an empty Ruby module for housing model classes derived
|
750
972
|
# from ActiveRecord::Base. As a shortcut, you may derive from Base
|
751
973
|
# which is an alias for ActiveRecord::Base.
|
@@ -770,14 +992,19 @@ module Camping
|
|
770
992
|
# end
|
771
993
|
# end
|
772
994
|
#
|
773
|
-
# Models cannot be referred
|
995
|
+
# Models cannot be referred from Views at this time.
|
774
996
|
module Models
|
775
|
-
autoload :Base,'camping/ar'
|
776
997
|
Helpers.send(:include, X, self)
|
777
998
|
end
|
778
999
|
|
779
1000
|
autoload :Mab, 'camping/mab'
|
780
1001
|
autoload :Template, 'camping/template'
|
1002
|
+
|
1003
|
+
# Load default Gear
|
1004
|
+
pack Gear::Inspection
|
1005
|
+
pack Gear::Filters
|
1006
|
+
pack Gear::Nancy
|
1007
|
+
pack Gear::Kuddly
|
1008
|
+
|
781
1009
|
C
|
782
1010
|
end
|
783
|
-
|