camping 1.5.180 → 2.0.rc0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|