camping 1.5.180 → 2.0.rc0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +35 -0
- data/README +43 -68
- data/Rakefile +155 -86
- data/bin/camping +64 -246
- data/book/01_introduction +19 -0
- data/book/02_getting_started +443 -0
- data/book/51_upgrading +93 -0
- data/doc/api.html +1953 -0
- data/doc/book.html +73 -0
- data/doc/book/01_introduction.html +57 -0
- data/doc/book/02_getting_started.html +573 -0
- data/doc/book/51_upgrading.html +146 -0
- data/doc/created.rid +1 -0
- data/{extras → doc/images}/Camping.gif +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/{extras → doc/images}/permalink.gif +0 -0
- data/doc/index.html +148 -0
- data/doc/js/camping.js +79 -0
- data/doc/js/jquery.js +32 -0
- data/doc/rdoc.css +117 -0
- data/examples/blog.rb +280 -181
- data/extras/images/badge.gif +0 -0
- data/extras/images/boys-life.png +0 -0
- data/extras/images/deerputer.png +0 -0
- data/extras/images/diagram.png +0 -0
- data/extras/images/hill.png +0 -0
- data/extras/images/i-wish.png +0 -0
- data/extras/images/latl.png +0 -0
- data/extras/images/little-wheels.png +0 -0
- data/extras/images/square-badge.png +0 -0
- data/extras/images/uniform.png +0 -0
- data/extras/images/whale-bounce.png +0 -0
- data/extras/rdoc/generator/singledarkfish.rb +205 -0
- data/extras/rdoc/generator/template/flipbook/images/Camping.gif +0 -0
- data/extras/rdoc/generator/template/flipbook/images/loadingAnimation.gif +0 -0
- data/extras/rdoc/generator/template/flipbook/images/permalink.gif +0 -0
- data/extras/rdoc/generator/template/flipbook/js/camping.js +79 -0
- data/extras/rdoc/generator/template/flipbook/js/jquery.js +32 -0
- data/extras/rdoc/generator/template/flipbook/page.rhtml +30 -0
- data/extras/rdoc/generator/template/flipbook/rdoc.css +117 -0
- data/extras/rdoc/generator/template/flipbook/readme.rhtml +31 -0
- data/extras/rdoc/generator/template/flipbook/reference.rhtml +71 -0
- data/extras/rdoc/generator/template/flipbook/toc.rhtml +43 -0
- data/lib/camping-unabridged.rb +420 -481
- data/lib/camping.rb +40 -55
- data/lib/camping/{db.rb → ar.rb} +5 -8
- data/lib/camping/mab.rb +26 -0
- data/lib/camping/reloader.rb +175 -147
- data/lib/camping/server.rb +178 -0
- data/lib/camping/session.rb +34 -121
- data/test/apps/env_debug.rb +65 -0
- data/test/apps/forms.rb +95 -0
- data/test/apps/forward_to_other_controller.rb +60 -0
- data/test/apps/migrations.rb +97 -0
- data/test/apps/misc.rb +86 -0
- data/test/apps/sessions.rb +38 -0
- metadata +120 -80
- data/doc/camping.1.gz +0 -0
- data/examples/campsh.rb +0 -630
- data/examples/tepee.rb +0 -242
- data/extras/flipbook_rdoc.rb +0 -491
- data/lib/camping/fastcgi.rb +0 -244
- data/lib/camping/webrick.rb +0 -65
- data/test/test_xhtml_trans.rb +0 -55
@@ -0,0 +1,31 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= h @options.title %></title>
|
5
|
+
<link rel="stylesheet" href="<%= rel_prefix %>/rdoc.css" type="text/css" media="screen" />
|
6
|
+
<script src="<%= rel_prefix %>/js/jquery.js" type="text/javascript"></script>
|
7
|
+
<script src="<%= rel_prefix %>/js/camping.js" type="text/javascript"></script>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div id="menu">
|
11
|
+
<ul id="links">
|
12
|
+
<li><a href="<%= rel_prefix %>/book.html">book</a> | </li>
|
13
|
+
<li><a href="<%= rel_prefix %>/api.html">reference</a> | </li>
|
14
|
+
<li><a href="http://wiki.github.com/camping/camping">wiki</a> | </li>
|
15
|
+
<li><a href="http://github.com/camping/camping">code</a></li>
|
16
|
+
</ul>
|
17
|
+
<p id="version">Camping <%= VERS %></p>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div id="fullpage">
|
21
|
+
<div class="page_shade">
|
22
|
+
<div class="page">
|
23
|
+
<p class="header"><%= Time.now %></p>
|
24
|
+
<h1><%= h @options.title %></h1>
|
25
|
+
<img src="images/Camping.gif" alt="The Camping Badge" id="logo">
|
26
|
+
<%= @files.find { |f| f.full_name == @options.main_page }.description.sub(%r{^\s*<h1.*?/h1>}i, '') %>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</body>
|
31
|
+
</html>
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Camping, the Reference</title>
|
5
|
+
<link rel="stylesheet" href="<%= rel_prefix %>/rdoc.css" type="text/css" media="screen" />
|
6
|
+
<script src="<%= rel_prefix %>/js/jquery.js" type="text/javascript"></script>
|
7
|
+
<script src="<%= rel_prefix %>/js/camping.js" type="text/javascript"></script>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div id="menu">
|
11
|
+
<ul id="links">
|
12
|
+
<li><a href="<%= rel_prefix %>/index.html">front</a> | </li>
|
13
|
+
<li><a href="<%= rel_prefix %>/book.html">book</a> | </li>
|
14
|
+
<li><a href="http://wiki.github.com/camping/camping">wiki</a> | </li>
|
15
|
+
<li><a href="http://github.com/camping/camping">code</a></li>
|
16
|
+
</ul>
|
17
|
+
<p id="version">Camping <%= VERS %></p>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div id="fullpage">
|
21
|
+
<div class="page_shade">
|
22
|
+
<div class="page ref">
|
23
|
+
<p class="header"><%= Time.now %></p>
|
24
|
+
<h1>Camping, the Reference</h1>
|
25
|
+
|
26
|
+
<% @modsort.each_with_index do |klass, index| %>
|
27
|
+
<h2 id="<%= klass.path[/#(.*)$/, 1] %>">
|
28
|
+
<a href="<%= klass.path[/#(.*)$/] %>">
|
29
|
+
<%= klass.type.capitalize %>
|
30
|
+
<%= klass.full_name %>
|
31
|
+
<% if klass.type == 'class' %>
|
32
|
+
<
|
33
|
+
<% unless String === klass.superclass %>
|
34
|
+
<%= klass.superclass.full_name %>
|
35
|
+
<% else %>
|
36
|
+
<%= klass.superclass %>
|
37
|
+
<% end %>
|
38
|
+
<% end %>
|
39
|
+
</a>
|
40
|
+
</h2>
|
41
|
+
|
42
|
+
<div class="mod">
|
43
|
+
<%= klass.description.gsub(/<(\/?)h(\d)>/) do
|
44
|
+
# replace <h2> with <h3> and so.
|
45
|
+
"<#{$1}h#{$2.to_i + 1}>"
|
46
|
+
end %>
|
47
|
+
|
48
|
+
<h3>Methods</h3>
|
49
|
+
<% methods_for(klass) do |method, type, visibility| %>
|
50
|
+
<h4 class="ruled" id="<%= method.aref %>">
|
51
|
+
<a href="#<%= method.aref %>">
|
52
|
+
<%= visibility.to_s.capitalize %> <%= type.capitalize %> method:
|
53
|
+
<strong><%= method.pretty_name %><%= method.params %></strong>
|
54
|
+
<img src="<%= rel_prefix %>/images/permalink.gif">
|
55
|
+
</a>
|
56
|
+
</h4>
|
57
|
+
<div class="method">
|
58
|
+
<%= method.description %>
|
59
|
+
<p class="source-link">[ <a href="#">show source</a> ]</p>
|
60
|
+
<pre class="sourcecode">
|
61
|
+
<%= method.markup_code %>
|
62
|
+
</pre>
|
63
|
+
</div>
|
64
|
+
<% end %>
|
65
|
+
</div>
|
66
|
+
<% end %>
|
67
|
+
</div>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</body>
|
71
|
+
</html>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= @options.title %></title>
|
5
|
+
<link rel="stylesheet" href="<%= rel_prefix %>/rdoc.css" type="text/css" media="screen" />
|
6
|
+
<script src="<%= rel_prefix %>/js/jquery.js" type="text/javascript"></script>
|
7
|
+
<script src="js/camping.js" type="text/javascript"></script>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div id="menu">
|
11
|
+
<ul id="links">
|
12
|
+
<li><a href="<%= rel_prefix %>/index.html">front</a> | </li>
|
13
|
+
<li><a href="<%= rel_prefix %>/api.html">reference</a> | </li>
|
14
|
+
<li><a href="http://wiki.github.com/camping/camping">wiki</a> | </li>
|
15
|
+
<li><a href="http://github.com/camping/camping">code</a></li>
|
16
|
+
</ul>
|
17
|
+
<p id="version">Camping <%= VERS %></p>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div id="fullpage">
|
21
|
+
<div class="page_shade">
|
22
|
+
<div class="page">
|
23
|
+
<p class="header"><%= Time.now %></p>
|
24
|
+
<h1>Camping, the Book</h1>
|
25
|
+
<ol>
|
26
|
+
<% chapters.each do |chapter| %>
|
27
|
+
<li>
|
28
|
+
<a href="<%= chapter.path %>"><%= chapter.title %></a>
|
29
|
+
<% unless chapter.toc.empty? %>
|
30
|
+
<ul>
|
31
|
+
<% chapter.toc.each do |id, title| %>
|
32
|
+
<li><a href="<%= chapter.path %>#<%= id %>"><%= title %></a></li>
|
33
|
+
<% end %>
|
34
|
+
</ul>
|
35
|
+
<% end %>
|
36
|
+
</li>
|
37
|
+
<% end %>
|
38
|
+
</ol>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</body>
|
43
|
+
</html>
|
data/lib/camping-unabridged.rb
CHANGED
@@ -6,120 +6,68 @@
|
|
6
6
|
# no more than 80 characters per line and must not exceed four kilobytes.
|
7
7
|
#
|
8
8
|
# On the other hand, lib/camping-unabridged.rb contains the same code, laid out
|
9
|
-
# nicely with piles of documentation everywhere.
|
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
|
-
|
13
|
-
|
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
|
-
# * MetAid, a few metaprogramming methods which Camping uses.
|
25
|
-
# * Tempfile, for storing file uploads.
|
26
|
-
#
|
27
|
-
# Camping also works well with Mongrel, the swift Ruby web server.
|
28
|
-
# http://rubyforge.org/projects/mongrel Mongrel comes with examples
|
29
|
-
# in its <tt>examples/camping</tt> directory.
|
30
|
-
#
|
31
|
-
%w[active_support markaby tempfile uri].each { |lib| require lib }
|
12
|
+
require "uri"
|
13
|
+
require "rack"
|
32
14
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
# How do you run Camping apps? Oh, uh... The Camping Server!
|
48
|
-
#
|
49
|
-
# The Camping Server is, firstly and thusly, a set of rules. At the very least, The Camping Server must:
|
50
|
-
#
|
51
|
-
# * Load all Camping apps in a directory.
|
52
|
-
# * Load new apps that appear in that directory.
|
53
|
-
# * Mount those apps according to their filename. (e.g. blog.rb is mounted at /blog.)
|
54
|
-
# * Run each app's <tt>create</tt> method upon startup.
|
55
|
-
# * Reload the app if its modification time changes.
|
56
|
-
# * Reload the app if it requires any files under the same directory and one of their modification times changes.
|
57
|
-
# * Support the X-Sendfile header.
|
58
|
-
#
|
59
|
-
# In fact, Camping comes with its own little The Camping Server.
|
60
|
-
#
|
61
|
-
# At a command prompt, run: <tt>camping examples/</tt> and the entire <tt>examples/</tt> directory will be served.
|
62
|
-
#
|
63
|
-
# Configurations also exist for Apache and Lighttpd. See http://code.whytheluckystiff.net/camping/wiki/TheCampingServer.
|
64
|
-
#
|
65
|
-
# == The <tt>create</tt> method
|
15
|
+
class Object #:nodoc:
|
16
|
+
def meta_def(m,&b) #:nodoc:
|
17
|
+
(class<<self;self end).send(:define_method,m,&b)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# If you're new to Camping, you should probably start by reading the first
|
22
|
+
# chapters of {The Camping Book}[file:book/01_introduction.html#toc].
|
23
|
+
#
|
24
|
+
# Okay. So, the important thing to remember is that <tt>Camping.goes :Nuts</tt>
|
25
|
+
# copies the Camping module into Nuts. This means that you should never use
|
26
|
+
# any of these methods/classes on the Camping module, but rather on your own
|
27
|
+
# app. Here's a short explanation on how Camping is organized:
|
66
28
|
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
29
|
+
# * Camping::Controllers is where your controllers live.
|
30
|
+
# * Camping::Models is where your models live.
|
31
|
+
# * Camping::Views is where your views live.
|
32
|
+
# * Camping::Base is a module which is included in all your controllers.
|
33
|
+
# * Camping::Helpers is a module with useful helpers, both for the controllers
|
34
|
+
# and the views. You should fill this up with your own helpers.
|
70
35
|
#
|
71
|
-
#
|
72
|
-
# unless Blog::Models::Post.table_exists?
|
73
|
-
# ActiveRecord::Schema.define do
|
74
|
-
# create_table :blog_posts, :force => true do |t|
|
75
|
-
# t.column :id, :integer, :null => false
|
76
|
-
# t.column :user_id, :integer, :null => false
|
77
|
-
# t.column :title, :string, :limit => 255
|
78
|
-
# t.column :body, :text
|
79
|
-
# end
|
80
|
-
# end
|
81
|
-
# end
|
82
|
-
# end
|
36
|
+
# Camping also ships with:
|
83
37
|
#
|
84
|
-
#
|
38
|
+
# * Camping::Session adds states to your app.
|
39
|
+
# * Camping::Server starts up your app in development.
|
40
|
+
# * Camping::Reloader automatically reloads your apps when a file has changed.
|
41
|
+
#
|
42
|
+
# More importantly, Camping also installs The Camping Server,
|
43
|
+
# please see Camping::Server.
|
85
44
|
module Camping
|
86
|
-
# Stores an +Array+ of all Camping applications modules. Modules are added
|
87
|
-
# automatically by +Camping.goes+.
|
88
|
-
#
|
89
|
-
# Camping.goes :Blog
|
90
|
-
# Camping.goes :Tepee
|
91
|
-
# Camping::Apps # => [Blog, Tepee]
|
92
|
-
#
|
93
|
-
Apps = []
|
94
45
|
C = self
|
95
|
-
S = IO.read(__FILE__)
|
96
|
-
P="Cam\ping Problem
|
97
|
-
|
98
|
-
|
99
|
-
# An object-like Hash
|
46
|
+
S = IO.read(__FILE__) rescue nil
|
47
|
+
P = "<h1>Cam\ping Problem!</h1><h2>%s</h2>"
|
48
|
+
U = Rack::Utils
|
49
|
+
Apps = []
|
50
|
+
# An object-like Hash.
|
100
51
|
# All Camping query string and cookie variables are loaded as this.
|
101
52
|
#
|
102
53
|
# To access the query string, for instance, use the <tt>@input</tt> variable.
|
103
54
|
#
|
104
|
-
# module Blog::
|
55
|
+
# module Blog::Controllers
|
105
56
|
# class Index < R '/'
|
106
57
|
# def get
|
107
|
-
# if page = @input.page.to_i > 0
|
58
|
+
# if (page = @input.page.to_i) > 0
|
108
59
|
# page -= 1
|
109
60
|
# end
|
110
|
-
# @posts = Post.
|
61
|
+
# @posts = Post.all, :offset => page * 20, :limit => 20
|
111
62
|
# render :index
|
112
63
|
# end
|
113
64
|
# end
|
114
65
|
# end
|
115
66
|
#
|
116
67
|
# In the above example if you visit <tt>/?page=2</tt>, you'll get the second
|
117
|
-
# page of twenty posts. You can also use <tt>@input[
|
118
|
-
#
|
119
|
-
|
120
|
-
# Use the <tt>@cookies</tt> variable in the same fashion to access cookie variables.
|
121
|
-
# Also, the <tt>@env</tt> variable is an H containing the HTTP headers and server info.
|
122
|
-
class H
|
68
|
+
# page of twenty posts. You can also use <tt>@input['page']</tt> to get the
|
69
|
+
# value for the <tt>page</tt> query variable.
|
70
|
+
class H < Hash
|
123
71
|
# Gets or sets keys in the hash.
|
124
72
|
#
|
125
73
|
# @cookies.my_favorite = :macadamian
|
@@ -127,33 +75,60 @@ module Camping
|
|
127
75
|
# => :macadamian
|
128
76
|
#
|
129
77
|
def method_missing(m,*a)
|
130
|
-
m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m]:
|
78
|
+
m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super
|
131
79
|
end
|
132
|
-
|
80
|
+
undef id, type if ?? == 63
|
133
81
|
end
|
134
|
-
|
135
|
-
# Helpers contains methods available in your controllers and views.
|
136
|
-
# methods of your own to this module, including many helper methods from
|
137
|
-
# This is analogous to Rails' <tt>ApplicationHelper</tt> module.
|
82
|
+
|
83
|
+
# Helpers contains methods available in your controllers and views. You may
|
84
|
+
# add methods of your own to this module, including many helper methods from
|
85
|
+
# Rails. This is analogous to Rails' <tt>ApplicationHelper</tt> module.
|
138
86
|
#
|
139
87
|
# == Using ActionPack Helpers
|
140
88
|
#
|
141
|
-
# If you'd like to include helpers from Rails' modules, you'll need to look
|
142
|
-
# helper module in the Rails documentation at http://api.rubyonrails.org/.
|
89
|
+
# If you'd like to include helpers from Rails' modules, you'll need to look
|
90
|
+
# up the helper module in the Rails documentation at http://api.rubyonrails.org/.
|
91
|
+
#
|
92
|
+
# For example, if you look up the <tt>ActionView::Helpers::FormTagHelper</tt>
|
93
|
+
# class, you'll find that it's loaded from the <tt>action_view/helpers/form_tag_helper.rb</tt>
|
94
|
+
# file. You'll need to have the ActionPack gem installed for this to work.
|
143
95
|
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
96
|
+
# Often the helpers depends on other helpers, so you would have to look up
|
97
|
+
# the dependencies too. <tt>FormTagHelper</tt> for instance required the
|
98
|
+
# <tt>content_tag</tt> provided by <tt>TagHelper</tt>.
|
147
99
|
#
|
148
|
-
# require 'action_view/helpers/
|
100
|
+
# require 'action_view/helpers/form_tag_helper'
|
149
101
|
#
|
150
|
-
#
|
102
|
+
# module Nuts::Helpers
|
103
|
+
# include ActionView::Helpers::TagHelper
|
104
|
+
# include ActionView::Helpers::FormTagHelper
|
105
|
+
# end
|
151
106
|
#
|
107
|
+
# == Return a response immediately
|
108
|
+
# If you need to return a response inside a helper, you can use <tt>throw :halt</tt>.
|
109
|
+
#
|
110
|
+
# module Nuts::Helpers
|
111
|
+
# def requires_login!
|
112
|
+
# unless @state.user_id
|
113
|
+
# redirect Login
|
114
|
+
# throw :halt
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# module Nuts::Controllers
|
120
|
+
# class Admin
|
121
|
+
# def get
|
122
|
+
# requires_login!
|
123
|
+
# "Never gets here unless you're logged in"
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
# end
|
152
127
|
module Helpers
|
153
128
|
# From inside your controllers and views, you will often need to figure out
|
154
|
-
# the route used to get to a certain controller +c+.
|
155
|
-
# and any arguments into the R method, a string containing the route
|
156
|
-
# returned to you.
|
129
|
+
# the route used to get to a certain controller +c+. Pass the controller
|
130
|
+
# class and any arguments into the R method, a string containing the route
|
131
|
+
# will be returned to you.
|
157
132
|
#
|
158
133
|
# Assuming you have a specific route in an edit controller:
|
159
134
|
#
|
@@ -165,22 +140,21 @@ module Camping
|
|
165
140
|
#
|
166
141
|
# Which outputs: <tt>/edit/1</tt>.
|
167
142
|
#
|
168
|
-
# You may also pass in a model object and the ID of the object will be used.
|
169
|
-
#
|
170
143
|
# If a controller has many routes, the route will be selected if it is the
|
171
144
|
# first in the routing list to have the right number of arguments.
|
172
145
|
#
|
173
146
|
# == Using R in the View
|
174
147
|
#
|
175
|
-
# Keep in mind that this route doesn't include the root path.
|
176
|
-
#
|
177
|
-
# Or, go ahead and use the Helpers#URL method to build a complete URL for
|
148
|
+
# Keep in mind that this route doesn't include the root path. You will
|
149
|
+
# need to use <tt>/</tt> (the slash method above) in your controllers.
|
150
|
+
# Or, go ahead and use the Helpers#URL method to build a complete URL for
|
151
|
+
# a route.
|
178
152
|
#
|
179
|
-
# However, in your views, the :href, :src and :action attributes
|
180
|
-
# pass through the slash method, so you are encouraged to
|
181
|
-
# <tt>URL</tt> in your views.
|
153
|
+
# However, in your views, the :href, :src and :action attributes
|
154
|
+
# automatically pass through the slash method, so you are encouraged to
|
155
|
+
# use <tt>R</tt> or <tt>URL</tt> in your views.
|
182
156
|
#
|
183
|
-
# module
|
157
|
+
# module Nuts::Views
|
184
158
|
# def menu
|
185
159
|
# div.menu! do
|
186
160
|
# a 'Home', :href => URL()
|
@@ -192,11 +166,12 @@ module Camping
|
|
192
166
|
# end
|
193
167
|
#
|
194
168
|
# Let's say the above example takes place inside an application mounted at
|
195
|
-
# <tt>http://localhost:3301/frodo</tt> and that a controller named
|
196
|
-
# is assigned to route <tt>/logout</tt>.
|
169
|
+
# <tt>http://localhost:3301/frodo</tt> and that a controller named
|
170
|
+
# <tt>Logout</tt> is assigned to route <tt>/logout</tt>.
|
171
|
+
# The HTML will come out as:
|
197
172
|
#
|
198
173
|
# <div id="menu">
|
199
|
-
# <a href="
|
174
|
+
# <a href="http://localhost:3301/frodo/">Home</a>
|
200
175
|
# <a href="/frodo/profile">Profile</a>
|
201
176
|
# <a href="/frodo/logout">Logout</a>
|
202
177
|
# <a href="http://google.com">Google</a>
|
@@ -204,114 +179,67 @@ module Camping
|
|
204
179
|
#
|
205
180
|
def R(c,*g)
|
206
181
|
p,h=/\(.+?\)/,g.grep(Hash)
|
207
|
-
|
208
|
-
|
209
|
-
|
182
|
+
g-=h
|
183
|
+
raise "bad route" unless u = c.urls.find{|x|
|
184
|
+
break x if x.scan(p).size == g.size &&
|
185
|
+
/^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
|
186
|
+
x.sub p,U.escape((a[a.class.primary_key]rescue a))})
|
187
|
+
}
|
188
|
+
h.any?? u+"?"+U.build_query(h[0]) : u
|
210
189
|
end
|
211
190
|
|
212
|
-
#
|
213
|
-
#
|
214
|
-
#
|
215
|
-
# An example might look like:
|
216
|
-
#
|
217
|
-
# errors_for @post
|
218
|
-
#
|
219
|
-
# Might (depending on actual data) render something like this in Markaby:
|
220
|
-
#
|
221
|
-
# ul.errors do
|
222
|
-
# li "Body can't be empty"
|
223
|
-
# li "Title must be unique"
|
224
|
-
# end
|
225
|
-
#
|
226
|
-
# Add a simple ul.errors {color:red; font-weight:bold;} CSS rule and you
|
227
|
-
# have built-in, usable error checking in only one line of code. :-)
|
228
|
-
#
|
229
|
-
# See AR validation documentation for details on validations.
|
230
|
-
def errors_for(o); ul.errors { o.errors.each_full { |er| li er } } if o.errors.any?; end
|
231
|
-
# Simply builds a complete path from a path +p+ within the app. If your application is
|
232
|
-
# mounted at <tt>/blog</tt>:
|
191
|
+
# Simply builds a complete path from a path +p+ within the app. If your
|
192
|
+
# application is mounted at <tt>/blog</tt>:
|
233
193
|
#
|
234
194
|
# self / "/view/1" #=> "/blog/view/1"
|
235
195
|
# self / "styles.css" #=> "styles.css"
|
236
196
|
# self / R(Edit, 1) #=> "/blog/edit/1"
|
237
197
|
#
|
238
|
-
def /(p); p[
|
198
|
+
def /(p); p[0]==?/?@root+p:p end
|
199
|
+
|
239
200
|
# Builds a URL route to a controller or a path, returning a URI object.
|
240
201
|
# This way you'll get the hostname and the port number, a complete URL.
|
241
|
-
# No scheme is given (http or https).
|
242
202
|
#
|
243
203
|
# You can use this to grab URLs for controllers using the R-style syntax.
|
244
204
|
# So, if your application is mounted at <tt>http://test.ing/blog/</tt>
|
245
205
|
# and you have a View controller which routes as <tt>R '/view/(\d+)'</tt>:
|
246
206
|
#
|
247
|
-
# URL(View, @post.id) #=> #<URL://test.ing/blog/view/12>
|
207
|
+
# URL(View, @post.id) #=> #<URL:http://test.ing/blog/view/12>
|
248
208
|
#
|
249
209
|
# Or you can use the direct path:
|
250
210
|
#
|
251
|
-
# self.URL #=> #<URL://test.ing/blog/>
|
252
|
-
# self.URL + "view/12" #=> #<URL://test.ing/blog/view/12>
|
253
|
-
# URL("/view/12") #=> #<URL://test.ing/blog/view/12>
|
254
|
-
#
|
255
|
-
# Since no scheme is given, you will need to add the scheme yourself:
|
256
|
-
#
|
257
|
-
# "http" + URL("/view/12") #=> "http://test.ing/blog/view/12"
|
211
|
+
# self.URL #=> #<URL:http://test.ing/blog/>
|
212
|
+
# self.URL + "view/12" #=> #<URL:http://test.ing/blog/view/12>
|
213
|
+
# URL("/view/12") #=> #<URL:http://test.ing/blog/view/12>
|
258
214
|
#
|
259
215
|
# It's okay to pass URL strings through this method as well:
|
260
216
|
#
|
261
|
-
# URL("http://google.com") #=> #<
|
217
|
+
# URL("http://google.com") #=> #<URL:http://google.com>
|
262
218
|
#
|
263
219
|
# Any string which doesn't begin with a slash will pass through
|
264
220
|
# unscathed.
|
265
221
|
def URL c='/',*a
|
266
222
|
c = R(c, *a) if c.respond_to? :urls
|
267
223
|
c = self/c
|
268
|
-
c =
|
224
|
+
c = @request.url[/.{8,}?(?=\/)/]+c if c[0]==?/
|
269
225
|
URI(c)
|
270
226
|
end
|
271
227
|
end
|
272
228
|
|
273
229
|
# Camping::Base is built into each controller by way of the generic routing
|
274
|
-
# class Camping::R.
|
275
|
-
# it saves code for all the glue to stay in one place.
|
276
|
-
#
|
277
|
-
# Forgivable, considering that it's only really a handful of methods and accessors.
|
278
|
-
#
|
279
|
-
# == Treating controller methods like Response objects
|
280
|
-
#
|
281
|
-
# Camping originally came with a barebones Response object, but it's often much more readable
|
282
|
-
# to just use your controller as the response.
|
283
|
-
#
|
284
|
-
# Go ahead and alter the status, cookies, headers and body instance variables as you
|
285
|
-
# see fit in order to customize the response.
|
286
|
-
#
|
287
|
-
# module Camping::Controllers
|
288
|
-
# class SoftLink
|
289
|
-
# def get
|
290
|
-
# redirect "/"
|
291
|
-
# end
|
292
|
-
# end
|
293
|
-
# end
|
294
|
-
#
|
295
|
-
# Is equivalent to:
|
296
|
-
#
|
297
|
-
# module Camping::Controllers
|
298
|
-
# class SoftLink
|
299
|
-
# def get
|
300
|
-
# @status = 302
|
301
|
-
# @headers['Location'] = "/"
|
302
|
-
# end
|
303
|
-
# end
|
304
|
-
# end
|
230
|
+
# class Camping::R. In some ways, this class is trying to do too much, but
|
231
|
+
# it saves code for all the glue to stay in one place. Forgivable,
|
232
|
+
# considering that it's only really a handful of methods and accessors.
|
305
233
|
#
|
234
|
+
# Everything in this module is accessable inside your controllers.
|
306
235
|
module Base
|
307
|
-
|
308
|
-
|
309
|
-
Z = "\r\n"
|
236
|
+
attr_accessor :env, :request, :root, :input, :cookies, :state,
|
237
|
+
:status, :headers, :body
|
310
238
|
|
311
|
-
# Display a view, calling it by its method name +
|
239
|
+
# Display a view, calling it by its method name +v+. If a <tt>layout</tt>
|
312
240
|
# method is found in Camping::Views, it will be used to wrap the HTML.
|
313
241
|
#
|
314
|
-
# module
|
242
|
+
# module Nuts::Controllers
|
315
243
|
# class Show
|
316
244
|
# def get
|
317
245
|
# @posts = Post.find :all
|
@@ -320,160 +248,246 @@ module Camping
|
|
320
248
|
# end
|
321
249
|
# end
|
322
250
|
#
|
323
|
-
def render(
|
251
|
+
def render(v,*a,&b)
|
252
|
+
mab(/^_/!~v.to_s){send(v,*a,&b)}
|
253
|
+
end
|
324
254
|
|
325
|
-
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
# module
|
255
|
+
# You can directly return HTML form your controller for quick debugging
|
256
|
+
# by calling this method and pass some Markaby to it.
|
257
|
+
#
|
258
|
+
# module Nuts::Controllers
|
329
259
|
# class Info
|
330
|
-
# def get; code @
|
260
|
+
# def get; mab{ code @headers.inspect } end
|
331
261
|
# end
|
332
262
|
# end
|
333
263
|
#
|
334
|
-
#
|
335
|
-
|
336
|
-
def method_missing(*a,&b)
|
337
|
-
a.shift if a[0]==:render
|
264
|
+
# You can also pass true to use the :layout HTML wrapping method
|
265
|
+
def mab(l=nil,&b)
|
338
266
|
m=Mab.new({},self)
|
339
|
-
s=m.capture
|
340
|
-
s=m.capture{
|
267
|
+
s=m.capture(&b)
|
268
|
+
s=m.capture{layout{s}} if l && m.respond_to?(:layout)
|
341
269
|
s
|
342
270
|
end
|
271
|
+
|
272
|
+
# A quick means of setting this controller's status, body and headers
|
273
|
+
# based on a Rack response:
|
274
|
+
#
|
275
|
+
# r(302, 'Location' => self / "/view/12", '')
|
276
|
+
# r(*another_app.call(@env))
|
277
|
+
#
|
278
|
+
# You can also switch the body and the header if you want:
|
279
|
+
#
|
280
|
+
# r(404, "Could not find page")
|
281
|
+
#
|
282
|
+
# See also: #r404, #r500 and #r501
|
283
|
+
def r(s, b, h = {})
|
284
|
+
b, h = h, b if Hash === b
|
285
|
+
@status = s
|
286
|
+
@headers.merge!(h)
|
287
|
+
@body = b
|
288
|
+
end
|
343
289
|
|
344
290
|
# Formulate a redirect response: a 302 status with <tt>Location</tt> header
|
345
|
-
# and a blank body.
|
346
|
-
# route or path.
|
291
|
+
# and a blank body. Uses Helpers#URL to build the location from a
|
292
|
+
# controller route or path.
|
347
293
|
#
|
348
294
|
# So, given a root of <tt>http://localhost:3301/articles</tt>:
|
349
295
|
#
|
350
|
-
# redirect "view/12"
|
351
|
-
# redirect View, 12
|
296
|
+
# redirect "view/12" # redirects to "//localhost:3301/articles/view/12"
|
297
|
+
# redirect View, 12 # redirects to "//localhost:3301/articles/view/12"
|
352
298
|
#
|
353
299
|
# <b>NOTE:</b> This method doesn't magically exit your methods and redirect.
|
354
300
|
# You'll need to <tt>return redirect(...)</tt> if this isn't the last statement
|
355
|
-
# in your code.
|
301
|
+
# in your code, or <tt>throw :halt</tt> if it's in a helper.
|
302
|
+
#
|
303
|
+
# See: Controllers
|
356
304
|
def redirect(*a)
|
357
|
-
r(302,'','Location'=>URL(*a))
|
305
|
+
r(302,'','Location'=>URL(*a).to_s)
|
358
306
|
end
|
359
307
|
|
360
|
-
#
|
361
|
-
#
|
308
|
+
# Called when a controller was not found. You can override this if you
|
309
|
+
# want to customize the error page:
|
362
310
|
#
|
363
|
-
#
|
311
|
+
# module Nuts
|
312
|
+
# def r404(path)
|
313
|
+
# @path = path
|
314
|
+
# render :not_found
|
315
|
+
# end
|
316
|
+
# end
|
317
|
+
def r404(p)
|
318
|
+
P % "#{p} not found"
|
319
|
+
end
|
320
|
+
|
321
|
+
# Called when an exception is raised. However, if there is a parse error
|
322
|
+
# in Camping or in your application's source code, it will not be caught.
|
364
323
|
#
|
365
|
-
#
|
324
|
+
# +k+ is the controller class, +m+ is the request method (GET, POST, etc.)
|
325
|
+
# and +e+ is the Exception which can be mined for useful info.
|
366
326
|
#
|
367
|
-
#
|
327
|
+
# Be default this simply re-raises the error so a Rack middleware can
|
328
|
+
# handle it, but you are free to override it here:
|
368
329
|
#
|
369
|
-
|
330
|
+
# module Nuts
|
331
|
+
# def r500(klass, method, exception)
|
332
|
+
# send_email_alert(klass, method, exception)
|
333
|
+
# render :server_error
|
334
|
+
# end
|
335
|
+
# end
|
336
|
+
def r500(k,m,e)
|
337
|
+
raise e
|
338
|
+
end
|
370
339
|
|
371
|
-
#
|
372
|
-
#
|
340
|
+
# Called if an undefined method is called on a controller, along with the
|
341
|
+
# request method +m+ (GET, POST, etc.)
|
342
|
+
def r501(m)
|
343
|
+
P % "#{m.upcase} not implemented"
|
344
|
+
end
|
345
|
+
|
346
|
+
# Turn a controller into a Rack response. This is designed to be used to
|
347
|
+
# pipe controllers into the <tt>r</tt> method. A great way to forward your
|
373
348
|
# requests!
|
374
349
|
#
|
375
350
|
# class Read < '/(\d+)'
|
376
351
|
# def get(id)
|
377
352
|
# Post.find(id)
|
378
353
|
# rescue
|
379
|
-
# r *Blog.get(:NotFound, @
|
354
|
+
# r *Blog.get(:NotFound, @headers.REQUEST_URI)
|
380
355
|
# end
|
381
356
|
# end
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
o=if fh[:filename]
|
408
|
-
o=fh[:tempfile]=Tempfile.new(:C)
|
409
|
-
o.binmode
|
410
|
-
else
|
411
|
-
fh=""
|
412
|
-
end
|
413
|
-
while l=@in.read(16384)
|
414
|
-
if l=~b
|
415
|
-
o<<$`.chomp
|
416
|
-
@in.seek(-$'.size,IO::SEEK_CUR)
|
417
|
-
break
|
418
|
-
end
|
419
|
-
o<<l
|
420
|
-
end
|
421
|
-
C.qsp(fn,'&;',fh,qs) if fn
|
422
|
-
fh[:tempfile].rewind if fh.is_a?H
|
357
|
+
def to_a
|
358
|
+
@env['rack.session'] = @state
|
359
|
+
r = Rack::Response.new(@body, @status, @headers)
|
360
|
+
@cookies.each do |k, v|
|
361
|
+
next if @old_cookies[k] == v
|
362
|
+
v = { :value => v, :path => self / "/" } if String === v
|
363
|
+
r.set_cookie(k, v)
|
364
|
+
end
|
365
|
+
r.to_a
|
366
|
+
end
|
367
|
+
|
368
|
+
def initialize(env, m) #:nodoc:
|
369
|
+
r = @request = Rack::Request.new(@env = env)
|
370
|
+
@root, @input, @cookies, @state,
|
371
|
+
@headers, @status, @method =
|
372
|
+
r.script_name.sub(/\/$/,''), n(r.params),
|
373
|
+
H[@old_cookies = r.cookies], H[r.session],
|
374
|
+
{}, m =~ /r(\d+)/ ? $1.to_i : 200, m
|
375
|
+
end
|
376
|
+
|
377
|
+
def n(h) # :nodoc:
|
378
|
+
if Hash === h
|
379
|
+
h.inject(H[]) do |m, (k, v)|
|
380
|
+
m[k] = n(v)
|
381
|
+
m
|
423
382
|
end
|
424
|
-
|
425
|
-
|
383
|
+
else
|
384
|
+
h
|
426
385
|
end
|
427
|
-
@cookies, @input = @k.dup, qs.dup
|
428
386
|
end
|
429
387
|
|
430
|
-
# All requests pass through this method before going to the controller.
|
431
|
-
# in Camping can be performed by overriding this method.
|
432
|
-
#
|
433
|
-
# See http://code.whytheluckystiff.net/camping/wiki/BeforeAndAfterOverrides for more
|
434
|
-
# on before and after overrides with Camping.
|
388
|
+
# All requests pass through this method before going to the controller.
|
389
|
+
# Some magic in Camping can be performed by overriding this method.
|
435
390
|
def service(*a)
|
436
|
-
|
437
|
-
@
|
391
|
+
r = catch(:halt){send(@method, *a)}
|
392
|
+
@body ||= r
|
438
393
|
self
|
439
394
|
end
|
440
|
-
|
441
|
-
# Used by the web server to convert the current request to a string. If you need to
|
442
|
-
# alter the way Camping builds HTTP headers, consider overriding this method.
|
443
|
-
def to_s
|
444
|
-
a=[]
|
445
|
-
@headers.map{|k,v|[*v].map{|x|a<<"#{k}: #{x}"}}
|
446
|
-
"Status: #{@status}#{Z+a*Z+Z*2+@body}"
|
447
|
-
end
|
448
|
-
|
449
395
|
end
|
450
|
-
|
451
|
-
|
452
|
-
#
|
396
|
+
|
397
|
+
|
398
|
+
# Controllers receive the requests and sends a response back to the client.
|
399
|
+
# A controller is simply a class which must implement the HTTP methods it
|
400
|
+
# wants to accept:
|
453
401
|
#
|
454
|
-
# module
|
455
|
-
# class
|
456
|
-
# def get
|
457
|
-
#
|
402
|
+
# module Nuts::Controllers
|
403
|
+
# class Index
|
404
|
+
# def get
|
405
|
+
# "Hello World"
|
406
|
+
# end
|
458
407
|
# end
|
408
|
+
#
|
409
|
+
# class Posts
|
410
|
+
# def post
|
411
|
+
# Post.create(@input)
|
412
|
+
# redirect Index
|
413
|
+
# end
|
414
|
+
# end
|
459
415
|
# end
|
416
|
+
#
|
417
|
+
# == Defining a controller
|
418
|
+
#
|
419
|
+
# There are two ways to define controllers: Just defining a class and let
|
420
|
+
# Camping figure out the route, or add the route explicitly using R.
|
421
|
+
#
|
422
|
+
# If you don't use R, Camping will first split the controller name up by
|
423
|
+
# words (HelloWorld => Hello and World). Then it would do the following:
|
424
|
+
#
|
425
|
+
# * Replace Index with /
|
426
|
+
# * Replace X with ([^/]+)
|
427
|
+
# * Replace N with (\\\d+)
|
428
|
+
# * Everything else turns into lowercase
|
429
|
+
# * Join the words with slashes
|
430
|
+
#
|
431
|
+
#--
|
432
|
+
# NB! N will actually be replaced with (\d+), but it needs to be escaped
|
433
|
+
# here in order to work correctly with RDoc.
|
434
|
+
#++
|
435
|
+
#
|
436
|
+
# Here's a few examples:
|
437
|
+
#
|
438
|
+
# Index # => /
|
439
|
+
# PostN # => /post/(\d+)
|
440
|
+
# PageX # => /page/([^/]+)
|
441
|
+
# Pages # => /pages
|
442
|
+
#
|
443
|
+
# == The request
|
444
|
+
#
|
445
|
+
# You have these variables which describes the request:
|
446
|
+
#
|
447
|
+
# * @env contains the environment as defined in http://rack.rubyforge.org/doc/SPEC.html
|
448
|
+
# * @request is Rack::Request.new(@env)
|
449
|
+
# * @root is the path where the app is mounted
|
450
|
+
# * @cookies is a hash with the cookies sent by the client
|
451
|
+
# * @state is a hash with the sessions (see Camping::Session)
|
452
|
+
# * @method is the HTTP method in lowercase
|
453
|
+
#
|
454
|
+
# == The response
|
455
|
+
#
|
456
|
+
# You can change these variables to your needs:
|
457
|
+
#
|
458
|
+
# * @status is the HTTP status (defaults to 200)
|
459
|
+
# * @headers is a hash with the headers
|
460
|
+
# * @body is the body (a string or something which responds to #each)
|
461
|
+
# * Any changes in @cookies and @state will also be sent to the client
|
460
462
|
#
|
461
|
-
# If
|
462
|
-
# The rule is very simple: the route becomes a slash followed by the lowercased
|
463
|
-
# class name. See Controllers::D for the complete rules of dispatch.
|
463
|
+
# If you haven't set @body, it will use the return value of the method:
|
464
464
|
#
|
465
|
-
#
|
465
|
+
# module Nuts::Controllers
|
466
|
+
# class Index
|
467
|
+
# def get
|
468
|
+
# "This is the body"
|
469
|
+
# end
|
470
|
+
# end
|
466
471
|
#
|
467
|
-
#
|
468
|
-
#
|
469
|
-
#
|
472
|
+
# class Posts
|
473
|
+
# def get
|
474
|
+
# @body = "Hello World!"
|
475
|
+
# "This is ignored"
|
476
|
+
# end
|
477
|
+
# end
|
478
|
+
# end
|
470
479
|
module Controllers
|
471
480
|
@r = []
|
472
481
|
class << self
|
482
|
+
# An array containing the various controllers available for dispatch.
|
473
483
|
def r #:nodoc:
|
474
484
|
@r
|
475
485
|
end
|
486
|
+
|
476
487
|
# Add routes to a controller class by piling them into the R method.
|
488
|
+
#
|
489
|
+
# The route is a regexp which will match the request path. Anything
|
490
|
+
# enclosed in parenthesis will be sent to the method as arguments.
|
477
491
|
#
|
478
492
|
# module Camping::Controllers
|
479
493
|
# class Edit < R '/edit/(\d+)', '/new'
|
@@ -484,14 +498,6 @@ module Camping
|
|
484
498
|
# end
|
485
499
|
# end
|
486
500
|
# end
|
487
|
-
#
|
488
|
-
# You will need to use routes in either of these cases:
|
489
|
-
#
|
490
|
-
# * You want to assign multiple routes to a controller.
|
491
|
-
# * You want your controller to receive arguments.
|
492
|
-
#
|
493
|
-
# Most of the time the rules inferred by dispatch method Controllers::D will get you
|
494
|
-
# by just fine.
|
495
501
|
def R *u
|
496
502
|
r=@r
|
497
503
|
Class.new {
|
@@ -507,209 +513,161 @@ module Camping
|
|
507
513
|
#
|
508
514
|
# Controllers are searched in this order:
|
509
515
|
#
|
510
|
-
#
|
511
|
-
#
|
516
|
+
# * Classes without routes, since they refer to a very specific URL.
|
517
|
+
# * Classes with routes are searched in order of their creation.
|
512
518
|
#
|
513
519
|
# So, define your catch-all controllers last.
|
514
|
-
def D(
|
520
|
+
def D(p, m)
|
521
|
+
p = '/' if !p || !p[0]
|
515
522
|
r.map { |k|
|
516
523
|
k.urls.map { |x|
|
517
|
-
return k
|
524
|
+
return (k.instance_method(m) rescue nil) ?
|
525
|
+
[k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
|
518
526
|
}
|
519
527
|
}
|
520
|
-
[
|
528
|
+
[I, 'r404', p]
|
521
529
|
end
|
522
530
|
|
523
|
-
|
531
|
+
N = H.new { |_,x| x.downcase }.merge! "N" => '(\d+)', "X" => '([^/]+)', "Index" => ''
|
532
|
+
# The route maker, this is called by Camping internally, you shouldn't
|
533
|
+
# need to call it.
|
524
534
|
#
|
525
|
-
# Still, it's worth know what this method does.
|
526
|
-
# creation order, we're keeping an internal list of the
|
527
|
-
# This method goes through and adds
|
528
|
-
#
|
535
|
+
# Still, it's worth know what this method does. Since Ruby doesn't keep
|
536
|
+
# track of class creation order, we're keeping an internal list of the
|
537
|
+
# controllers which inherit from R(). This method goes through and adds
|
538
|
+
# all the remaining routes to the beginning of the list and ensures all
|
539
|
+
# the controllers have the right mixins.
|
529
540
|
#
|
530
|
-
# Anyway, if you are calling the URI dispatcher from outside of a
|
531
|
-
# definitely need to call this
|
541
|
+
# Anyway, if you are calling the URI dispatcher from outside of a
|
542
|
+
# Camping server, you'll definitely need to call this to set things up.
|
543
|
+
# Don't call it too early though. Any controllers added after this
|
544
|
+
# method is called won't work properly
|
532
545
|
def M
|
533
546
|
def M #:nodoc:
|
534
547
|
end
|
535
548
|
constants.map { |c|
|
536
|
-
k=const_get(c)
|
537
|
-
k.send :include,C,Base,Models
|
538
|
-
r[
|
539
|
-
k.meta_def(:urls){["/#{c.
|
549
|
+
k = const_get(c)
|
550
|
+
k.send :include,C,Base,Helpers,Models
|
551
|
+
@r=[k]+r if r-[k]==r
|
552
|
+
k.meta_def(:urls){["/#{c.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}"]}if !k.respond_to?:urls
|
540
553
|
}
|
541
554
|
end
|
542
555
|
end
|
543
556
|
|
544
|
-
#
|
545
|
-
|
546
|
-
#
|
547
|
-
# module Camping::Controllers
|
548
|
-
# class NotFound
|
549
|
-
# def get(p)
|
550
|
-
# @status = 404
|
551
|
-
# div do
|
552
|
-
# h1 'Camping Problem!'
|
553
|
-
# h2 "#{p} not found"
|
554
|
-
# end
|
555
|
-
# end
|
556
|
-
# end
|
557
|
-
# end
|
558
|
-
#
|
559
|
-
class NotFound < R()
|
560
|
-
def get(p)
|
561
|
-
r(404, Mab.new{h1(P);h2("#{p} not found")})
|
562
|
-
end
|
563
|
-
end
|
564
|
-
|
565
|
-
# The ServerError class is a special controller class for handling many (but not all) 500 errors.
|
566
|
-
# If there is a parse error in Camping or in your application's source code, it will not be caught
|
567
|
-
# by Camping. The controller class +k+ and request method +m+ (GET, POST, etc.) where the error
|
568
|
-
# took place are passed in, along with the Exception +e+ which can be mined for useful info.
|
569
|
-
#
|
570
|
-
# module Camping::Controllers
|
571
|
-
# class ServerError
|
572
|
-
# def get(k,m,e)
|
573
|
-
# @status = 500
|
574
|
-
# div do
|
575
|
-
# h1 'Camping Problem!'
|
576
|
-
# h2 "in #{k}.#{m}"
|
577
|
-
# h3 "#{e.class} #{e.message}:"
|
578
|
-
# ul do
|
579
|
-
# e.backtrace.each do |bt|
|
580
|
-
# li bt
|
581
|
-
# end
|
582
|
-
# end
|
583
|
-
# end
|
584
|
-
# end
|
585
|
-
# end
|
586
|
-
# end
|
587
|
-
#
|
588
|
-
class ServerError < R()
|
589
|
-
def get(k,m,e)
|
590
|
-
r(500, Mab.new {
|
591
|
-
h1(P)
|
592
|
-
h2 "#{k}.#{m}"
|
593
|
-
h3 "#{e.class} #{e.message}:"
|
594
|
-
ul { e.backtrace.each { |bt| li bt } }
|
595
|
-
}.to_s)
|
596
|
-
end
|
597
|
-
end
|
557
|
+
# Internal controller with no route. Used to show internal messages.
|
558
|
+
I = R()
|
598
559
|
end
|
599
560
|
X = Controllers
|
600
561
|
|
601
562
|
class << self
|
602
|
-
# When you are running many applications, you may want to create
|
603
|
-
# modules for each Camping application.
|
604
|
-
# defines a toplevel constant with the whole MVC rack inside
|
563
|
+
# When you are running many applications, you may want to create
|
564
|
+
# independent modules for each Camping application. Camping::goes
|
565
|
+
# defines a toplevel constant with the whole MVC rack inside:
|
605
566
|
#
|
606
567
|
# require 'camping'
|
607
|
-
# Camping.goes :
|
568
|
+
# Camping.goes :Nuts
|
608
569
|
#
|
609
|
-
# module
|
610
|
-
# module
|
611
|
-
# module
|
570
|
+
# module Nuts::Controllers; ... end
|
571
|
+
# module Nuts::Models; ... end
|
572
|
+
# module Nuts::Views; ... end
|
612
573
|
#
|
574
|
+
# All the applications will be available in Camping::Apps.
|
613
575
|
def goes(m)
|
614
|
-
eval
|
576
|
+
Apps << eval(S.gsub(/Camping/,m.to_s), TOPLEVEL_BINDING)
|
615
577
|
end
|
616
|
-
|
617
|
-
#
|
618
|
-
#
|
619
|
-
#
|
620
|
-
# #=> "I%27d+go+to+the+museum+straightway%21"
|
621
|
-
#
|
622
|
-
def escape(s); s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ', '+') end
|
623
|
-
|
624
|
-
# Unescapes a URL-encoded string.
|
625
|
-
#
|
626
|
-
# Camping.un("I%27d+go+to+the+museum+straightway%21")
|
627
|
-
# #=> "I'd go to the museum straightway!"
|
628
|
-
#
|
629
|
-
def un(s); s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')} end
|
630
|
-
|
631
|
-
# Parses a query string into an Camping::H object.
|
632
|
-
#
|
633
|
-
# input = Camping.qsp("name=Philarp+Tremain&hair=sandy+blonde")
|
634
|
-
# input.name
|
635
|
-
# #=> "Philarp Tremaine"
|
636
|
-
#
|
637
|
-
# Also parses out the Hash-like syntax used in PHP and Rails and builds
|
638
|
-
# nested hashes from it.
|
639
|
-
#
|
640
|
-
# input = Camping.qsp("post[id]=1&post[user]=_why")
|
641
|
-
# #=> {'post' => {'id' => '1', 'user' => '_why'}}
|
642
|
-
#
|
643
|
-
def qsp(qs, d='&;', y=nil, z=H[])
|
644
|
-
m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
|
645
|
-
(qs||'').
|
646
|
-
split(/[#{d}] */n).
|
647
|
-
inject((b,z=z,H[])[0]) { |h,p| k, v=un(p).split('=',2)
|
648
|
-
h.u(k.split(/[\]\[]+/).reverse.
|
649
|
-
inject(y||v) { |x,i| H[i,x] },&m)
|
650
|
-
}
|
651
|
-
end
|
652
|
-
|
653
|
-
# Parses a string of cookies from the <tt>Cookie</tt> header.
|
654
|
-
def kp(s); c = qsp(s, ';,'); end
|
655
|
-
|
656
|
-
# Fields a request through Camping. For traditional CGI applications, the method can be
|
657
|
-
# executed without arguments.
|
658
|
-
#
|
659
|
-
# if __FILE__ == $0
|
660
|
-
# Camping::Models::Base.establish_connection :adapter => 'sqlite3',
|
661
|
-
# :database => 'blog3.db'
|
662
|
-
# Camping::Models::Base.logger = Logger.new('camping.log')
|
663
|
-
# puts Camping.run
|
664
|
-
# end
|
665
|
-
#
|
666
|
-
# The Camping controller returned from <tt>run</tt> has a <tt>to_s</tt> method in case you
|
667
|
-
# are running from CGI or want to output the full HTTP output. In the above example, <tt>puts</tt>
|
668
|
-
# will call <tt>to_s</tt> for you.
|
578
|
+
|
579
|
+
# Ruby web servers use this method to enter the Camping realm. The +e+
|
580
|
+
# argument is the environment variables hash as per the Rack specification.
|
581
|
+
# And array with [status, headers, body] is expected at the output.
|
669
582
|
#
|
670
|
-
#
|
671
|
-
|
672
|
-
# pass in the <tt>ENV</tt> replacement as part of your wrapper.
|
673
|
-
#
|
674
|
-
# See Camping::FastCGI and Camping::WEBrick for examples.
|
675
|
-
#
|
676
|
-
def run(r=$stdin,e=ENV)
|
583
|
+
# See: http://rack.rubyforge.org/doc/SPEC.html
|
584
|
+
def call(e)
|
677
585
|
X.M
|
678
|
-
|
679
|
-
k
|
680
|
-
|
681
|
-
|
586
|
+
p = e['PATH_INFO'] = U.unescape(e['PATH_INFO'])
|
587
|
+
k,m,*a=X.D p,e['REQUEST_METHOD'].downcase
|
588
|
+
k.new(e,m).service(*a).to_a
|
589
|
+
rescue
|
590
|
+
r500(:I, k, m, $!, :env => e).to_a
|
682
591
|
end
|
683
592
|
|
684
|
-
# The Camping scriptable dispatcher.
|
685
|
-
# be sent to a controller class, specified as an argument.
|
593
|
+
# The Camping scriptable dispatcher. Any unhandled method call to the app
|
594
|
+
# module will be sent to a controller class, specified as an argument.
|
686
595
|
#
|
687
596
|
# Blog.get(:Index)
|
688
597
|
# #=> #<Blog::Controllers::Index ... >
|
689
598
|
#
|
690
|
-
# The controller object contains all the @cookies, @body, @headers, etc.
|
691
|
-
# the response.
|
599
|
+
# The controller object contains all the @cookies, @body, @headers, etc.
|
600
|
+
# formulated by the response.
|
692
601
|
#
|
693
|
-
# You can also feed environment variables and query variables as a hash,
|
694
|
-
# argument.
|
602
|
+
# You can also feed environment variables and query variables as a hash,
|
603
|
+
# the final argument.
|
695
604
|
#
|
696
605
|
# Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'})
|
697
606
|
# #=> #<Blog::Controllers::Login @user=... >
|
698
607
|
#
|
699
|
-
# Blog.get(:Info, :env => {
|
700
|
-
# #=> #<Blog::Controllers::Info @
|
608
|
+
# Blog.get(:Info, :env => {'HTTP_HOST' => 'wagon'})
|
609
|
+
# #=> #<Blog::Controllers::Info @headers={'HTTP_HOST'=>'wagon'} ...>
|
701
610
|
#
|
702
611
|
def method_missing(m, c, *a)
|
703
612
|
X.M
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
k.
|
613
|
+
h = Hash === a[-1] ? a.pop : {}
|
614
|
+
e = H[Rack::MockRequest.env_for('',h.delete(:env)||{})]
|
615
|
+
k = X.const_get(c).new(e,m.to_s)
|
616
|
+
h.each { |i, v| k.send("#{i}=", v) }
|
617
|
+
k.service(*a)
|
618
|
+
end
|
619
|
+
|
620
|
+
# Injects a middleware:
|
621
|
+
#
|
622
|
+
# module Blog
|
623
|
+
# use Rack::MethodOverride
|
624
|
+
# use Rack::Session::Memcache, :key => "session"
|
625
|
+
# end
|
626
|
+
def use(*a, &b)
|
627
|
+
m = a.shift.new(method(:call), *a, &b)
|
628
|
+
meta_def(:call) { |e| m.call(e) }
|
708
629
|
end
|
709
630
|
end
|
710
|
-
|
631
|
+
|
632
|
+
# Views is an empty module for storing methods which create HTML. The HTML
|
633
|
+
# is described using the Markaby language.
|
634
|
+
#
|
635
|
+
# == Defining and calling templates
|
636
|
+
#
|
637
|
+
# Templates are simply Ruby methods with Markaby inside:
|
638
|
+
#
|
639
|
+
# module Blog::Views
|
640
|
+
# def index
|
641
|
+
# p "Welcome to my blog"
|
642
|
+
# end
|
643
|
+
#
|
644
|
+
# def show
|
645
|
+
# h1 @post.title
|
646
|
+
# self << @post.content
|
647
|
+
# end
|
648
|
+
# end
|
649
|
+
#
|
650
|
+
# In your controllers you just call <tt>render :template_name</tt> which will
|
651
|
+
# invoke the template. The views and controllers will share instance
|
652
|
+
# variables (as you can see above).
|
653
|
+
#
|
654
|
+
# == Using the layout method
|
655
|
+
#
|
656
|
+
# If your Views module has a <tt>layout</tt> method defined, it will be
|
657
|
+
# called with a block which will insert content from your view:
|
658
|
+
#
|
659
|
+
# module Blog::Views
|
660
|
+
# def layout
|
661
|
+
# html do
|
662
|
+
# head { title "My Blog "}
|
663
|
+
# body { self << yield }
|
664
|
+
# end
|
665
|
+
# end
|
666
|
+
# end
|
667
|
+
module Views; include X, Helpers end
|
668
|
+
|
711
669
|
# Models is an empty Ruby module for housing model classes derived
|
712
|
-
# from ActiveRecord::Base.
|
670
|
+
# from ActiveRecord::Base. As a shortcut, you may derive from Base
|
713
671
|
# which is an alias for ActiveRecord::Base.
|
714
672
|
#
|
715
673
|
# module Camping::Models
|
@@ -719,7 +677,7 @@ module Camping
|
|
719
677
|
#
|
720
678
|
# == Where Models are Used
|
721
679
|
#
|
722
|
-
# Models are used in your controller classes.
|
680
|
+
# Models are used in your controller classes. However, if your model class
|
723
681
|
# name conflicts with a controller class name, you will need to refer to it
|
724
682
|
# using the Models module.
|
725
683
|
#
|
@@ -734,29 +692,10 @@ module Camping
|
|
734
692
|
#
|
735
693
|
# Models cannot be referred to in Views at this time.
|
736
694
|
module Models
|
737
|
-
|
738
|
-
def Y;self;end
|
739
|
-
end
|
740
|
-
|
741
|
-
# Views is an empty module for storing methods which create HTML. The HTML is described
|
742
|
-
# using the Markaby language.
|
743
|
-
#
|
744
|
-
# == Using the layout method
|
745
|
-
#
|
746
|
-
# If your Views module has a <tt>layout</tt> method defined, it will be called with a block
|
747
|
-
# which will insert content from your view.
|
748
|
-
module Views; include Controllers, Helpers end
|
749
|
-
|
750
|
-
# The Mab class wraps Markaby, allowing it to run methods from Camping::Views
|
751
|
-
# and also to replace :href, :action and :src attributes in tags by prefixing the root
|
752
|
-
# path.
|
753
|
-
class Mab < Markaby::Builder
|
754
|
-
include Views
|
755
|
-
def tag!(*g,&b)
|
756
|
-
h=g[-1]
|
757
|
-
[:href,:action,:src].each{|a|(h[a]=self/h[a])rescue 0}
|
758
|
-
super
|
759
|
-
end
|
695
|
+
autoload :Base,'camping/ar'
|
760
696
|
end
|
697
|
+
|
698
|
+
autoload :Mab, 'camping/mab'
|
699
|
+
C
|
761
700
|
end
|
762
701
|
|