camping 1.5.180 → 2.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG +35 -0
  2. data/README +43 -68
  3. data/Rakefile +155 -86
  4. data/bin/camping +64 -246
  5. data/book/01_introduction +19 -0
  6. data/book/02_getting_started +443 -0
  7. data/book/51_upgrading +93 -0
  8. data/doc/api.html +1953 -0
  9. data/doc/book.html +73 -0
  10. data/doc/book/01_introduction.html +57 -0
  11. data/doc/book/02_getting_started.html +573 -0
  12. data/doc/book/51_upgrading.html +146 -0
  13. data/doc/created.rid +1 -0
  14. data/{extras → doc/images}/Camping.gif +0 -0
  15. data/doc/images/loadingAnimation.gif +0 -0
  16. data/{extras → doc/images}/permalink.gif +0 -0
  17. data/doc/index.html +148 -0
  18. data/doc/js/camping.js +79 -0
  19. data/doc/js/jquery.js +32 -0
  20. data/doc/rdoc.css +117 -0
  21. data/examples/blog.rb +280 -181
  22. data/extras/images/badge.gif +0 -0
  23. data/extras/images/boys-life.png +0 -0
  24. data/extras/images/deerputer.png +0 -0
  25. data/extras/images/diagram.png +0 -0
  26. data/extras/images/hill.png +0 -0
  27. data/extras/images/i-wish.png +0 -0
  28. data/extras/images/latl.png +0 -0
  29. data/extras/images/little-wheels.png +0 -0
  30. data/extras/images/square-badge.png +0 -0
  31. data/extras/images/uniform.png +0 -0
  32. data/extras/images/whale-bounce.png +0 -0
  33. data/extras/rdoc/generator/singledarkfish.rb +205 -0
  34. data/extras/rdoc/generator/template/flipbook/images/Camping.gif +0 -0
  35. data/extras/rdoc/generator/template/flipbook/images/loadingAnimation.gif +0 -0
  36. data/extras/rdoc/generator/template/flipbook/images/permalink.gif +0 -0
  37. data/extras/rdoc/generator/template/flipbook/js/camping.js +79 -0
  38. data/extras/rdoc/generator/template/flipbook/js/jquery.js +32 -0
  39. data/extras/rdoc/generator/template/flipbook/page.rhtml +30 -0
  40. data/extras/rdoc/generator/template/flipbook/rdoc.css +117 -0
  41. data/extras/rdoc/generator/template/flipbook/readme.rhtml +31 -0
  42. data/extras/rdoc/generator/template/flipbook/reference.rhtml +71 -0
  43. data/extras/rdoc/generator/template/flipbook/toc.rhtml +43 -0
  44. data/lib/camping-unabridged.rb +420 -481
  45. data/lib/camping.rb +40 -55
  46. data/lib/camping/{db.rb → ar.rb} +5 -8
  47. data/lib/camping/mab.rb +26 -0
  48. data/lib/camping/reloader.rb +175 -147
  49. data/lib/camping/server.rb +178 -0
  50. data/lib/camping/session.rb +34 -121
  51. data/test/apps/env_debug.rb +65 -0
  52. data/test/apps/forms.rb +95 -0
  53. data/test/apps/forward_to_other_controller.rb +60 -0
  54. data/test/apps/migrations.rb +97 -0
  55. data/test/apps/misc.rb +86 -0
  56. data/test/apps/sessions.rb +38 -0
  57. metadata +120 -80
  58. data/doc/camping.1.gz +0 -0
  59. data/examples/campsh.rb +0 -630
  60. data/examples/tepee.rb +0 -242
  61. data/extras/flipbook_rdoc.rb +0 -491
  62. data/lib/camping/fastcgi.rb +0 -244
  63. data/lib/camping/webrick.rb +0 -65
  64. 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
+ &lt;
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>
@@ -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. This documentation is entirely
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
- # == Requirements
14
- #
15
- # Camping requires at least Ruby 1.8.2.
16
- #
17
- # Camping depends on the following libraries. If you install through RubyGems,
18
- # these will be automatically installed for you.
19
- #
20
- # * ActiveRecord, used in your models.
21
- # ActiveRecord is an object-to-relational database mapper with adapters
22
- # for SQLite3, MySQL, PostgreSQL, SQL Server and more.
23
- # * Markaby, used in your views to describe HTML in plain Ruby.
24
- # * 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
- # == Camping
34
- #
35
- # The camping module contains three modules for separating your application:
36
- #
37
- # * Camping::Models for your database interaction classes, all derived from ActiveRecord::Base.
38
- # * Camping::Controllers for storing controller classes, which map URLs to code.
39
- # * Camping::Views for storing methods which generate HTML.
40
- #
41
- # Of use to you is also one module for storing helpful additional methods:
42
- #
43
- # * Camping::Helpers which can be used in controllers and views.
44
- #
45
- # == The Camping Server
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
- # Many postambles will check for your application's <tt>create</tt> method and will run it
68
- # when the web server starts up. This is a good place to check for database tables and create
69
- # those tables to save users of your application from needing to manually set them up.
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
- # def Blog.create
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
- # For more tips, see http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
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__).sub(/^ S = I.+$/,'')
96
- P="Cam\ping Problem!"
97
-
98
- H = HashWithIndifferentAccess
99
- # An object-like Hash, based on ActiveSupport's HashWithIndifferentAccess.
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::Models
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.find :all, :offset => page * 20, :limit => 20
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[:page]</tt> or <tt>@input['page']</tt>
118
- # to get the value for the <tt>page</tt> query variable.
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]:raise(NoMethodError,"#{m}")
78
+ m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super
131
79
  end
132
- alias_method :u, :regular_update
80
+ undef id, type if ?? == 63
133
81
  end
134
-
135
- # Helpers contains methods available in your controllers and views. You may add
136
- # methods of your own to this module, including many helper methods from Rails.
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 up the
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
- # For example, if you look up the <tt>ActionView::Helpers::FormHelper</tt> class,
145
- # you'll find that it's loaded from the <tt>action_view/helpers/form_helper.rb</tt>
146
- # file. You'll need to have the ActionPack gem installed for this to work.
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/form_helper.rb'
100
+ # require 'action_view/helpers/form_tag_helper'
149
101
  #
150
- # # This example is unfinished.. soon..
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+. Pass the controller class
155
- # and any arguments into the R method, a string containing the route will be
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
- # You will need to use <tt>/</tt> (the slash method above) in your controllers.
177
- # Or, go ahead and use the Helpers#URL method to build a complete URL for a route.
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 automatically
180
- # pass through the slash method, so you are encouraged to use <tt>R</tt> or
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 Blog::Views
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 <tt>Logout</tt>
196
- # is assigned to route <tt>/logout</tt>. The HTML will come out as:
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="//localhost:3301/frodo/">Home</a>
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
- (g-=h).inject(c.urls.find{|x|x.scan(p).size==g.size}.dup){|s,a|
208
- s.sub p,C.escape((a[a.class.primary_key]rescue a))
209
- }+(h.any?? "?"+h[0].map{|x|x.map{|z|C.escape z}*"="}*"&": "")
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
- # Shows AR validation errors for the object passed.
213
- # There is no output if there are no errors.
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[/^\//]?@root+p:p end
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") #=> #<URI: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 = "//"+@env.HTTP_HOST+c if 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. In some ways, this class is trying to do too much, but
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
- include Helpers
308
- attr_accessor :input, :cookies, :env, :headers, :body, :status, :root
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 +m+. If a <tt>layout</tt>
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 Camping::Controllers
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(m); end; undef_method :render
251
+ def render(v,*a,&b)
252
+ mab(/^_/!~v.to_s){send(v,*a,&b)}
253
+ end
324
254
 
325
- # Any stray method calls will be passed to Markaby. This means you can reply
326
- # with HTML directly from your controller for quick debugging.
327
- #
328
- # module Camping::Controllers
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 @env.inspect end
260
+ # def get; mab{ code @headers.inspect } end
331
261
  # end
332
262
  # end
333
263
  #
334
- # If you have a <tt>layout</tt> method in Camping::Views, it will be used to
335
- # wrap the HTML.
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{send(*a,&b)}
340
- s=m.capture{send(:layout){s}} if /^_/!~a[0].to_s and m.respond_to?:layout
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. Uses Helpers#URL to build the location from a controller
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" # redirects to "//localhost:3301/articles/view/12"
351
- # redirect View, 12 # redirects to "//localhost:3301/articles/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
- # A quick means of setting this controller's status, body and headers.
361
- # Used internally by Camping, but... by all means...
308
+ # Called when a controller was not found. You can override this if you
309
+ # want to customize the error page:
362
310
  #
363
- # r(302, '', 'Location' => self / "/view/12")
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
- # Is equivalent to:
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
- # redirect "/view/12"
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
- def r(s, b, h = {}); @status = s; @headers.merge!(h); @body = b; end
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
- # Turn a controller into an array. This is designed to be used to pipe
372
- # controllers into the <tt>r</tt> method. A great way to forward your
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, @env.REQUEST_URI)
354
+ # r *Blog.get(:NotFound, @headers.REQUEST_URI)
380
355
  # end
381
356
  # end
382
- #
383
- def to_a;[@status, @body, @headers] end
384
-
385
- def initialize(r, e, m) #:nodoc:
386
- e = H[e.to_hash]
387
- @status, @method, @env, @headers, @root = 200, m.downcase, e,
388
- {'Content-Type'=>'text/html'}, e.SCRIPT_NAME.sub(/\/$/,'')
389
- @k = C.kp(e.HTTP_COOKIE)
390
- qs = C.qsp(e.QUERY_STRING)
391
- @in = r
392
- if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.match(e.CONTENT_TYPE)
393
- b = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
394
- until @in.eof?
395
- fh=H[]
396
- for l in @in
397
- case l
398
- when Z: break
399
- when /^Content-Disposition: form-data;/
400
- fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten]
401
- when /^Content-Type: (.+?)(\r$|\Z)/m
402
- puts "=> fh[type] = #$1"
403
- fh[:type] = $1
404
- end
405
- end
406
- fn=fh[:name]
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
- elsif @method == "post"
425
- qs.merge!(C.qsp(@in.read))
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. Some magic
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
- @body = send(@method, *a) if respond_to? @method
437
- @headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil]
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
- # Controllers is a module for placing classes which handle URLs. This is done
452
- # by defining a route to each class using the Controllers::R method.
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 Camping::Controllers
455
- # class Edit < R '/edit/(\d+)'
456
- # def get; end
457
- # def post; end
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 no route is set, Camping will guess the route from the class name.
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
- # == Special classes
465
+ # module Nuts::Controllers
466
+ # class Index
467
+ # def get
468
+ # "This is the body"
469
+ # end
470
+ # end
466
471
  #
467
- # There are two special classes used for handling 404 and 500 errors. The
468
- # NotFound class handles URLs not found. The ServerError class handles exceptions
469
- # uncaught by your application.
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
- # # Classes without routes, since they refer to a very specific URL.
511
- # # Classes with routes are searched in order of their creation.
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(path)
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, $~[1..-1] if path =~ /^#{x}\/?$/
524
+ return (k.instance_method(m) rescue nil) ?
525
+ [k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
518
526
  }
519
527
  }
520
- [NotFound, [path]]
528
+ [I, 'r404', p]
521
529
  end
522
530
 
523
- # The route maker, this is called by Camping internally, you shouldn't need to call it.
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. Since Ruby doesn't keep track of class
526
- # creation order, we're keeping an internal list of the controllers which inherit from R().
527
- # This method goes through and adds all the remaining routes to the beginning of the list
528
- # and ensures all the controllers have the right mixins.
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 Camping server, you'll
531
- # definitely need to call this at least once to set things up.
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[0,0]=k if !r.include?k
539
- k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls
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
- # The NotFound class is a special controller class for handling 404 errors, in case you'd
545
- # like to alter the appearance of the 404. The path is passed in as +p+.
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 independent
603
- # modules for each Camping application. Namespaces for each. Camping::goes
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 :Blog
568
+ # Camping.goes :Nuts
608
569
  #
609
- # module Blog::Controllers; ... end
610
- # module Blog::Models; ... end
611
- # module Blog::Views; ... end
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 S.gsub(/Camping/,m.to_s).gsub("A\pps = []","Cam\ping::Apps<<self"), TOPLEVEL_BINDING
576
+ Apps << eval(S.gsub(/Camping/,m.to_s), TOPLEVEL_BINDING)
615
577
  end
616
-
617
- # URL escapes a string.
618
- #
619
- # Camping.escape("I'd go to the museum straightway!")
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
- # For FastCGI and Webrick-loaded applications, you will need to use a request loop, with <tt>run</tt>
671
- # at the center, passing in the read +r+ and write +w+ streams. You will also need to mimick or
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
- k,a=X.D un("/#{e['PATH_INFO']}".gsub(/\/+/,'/'))
679
- k.new(r,e,(m=e['REQUEST_METHOD']||"GET")).Y.service *a
680
- rescue Object=>x
681
- X::ServerError.new(r,e,'get').service(k,m,x)
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. Any unhandled method call to the app module will
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. formulated by
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, the final
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 => {:HTTP_HOST => 'wagon'})
700
- # #=> #<Blog::Controllers::Info @env={'HTTP_HOST'=>'wagon'} ...>
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
- k = X.const_get(c).new(StringIO.new,
705
- H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s)
706
- H.new(a.pop).each { |e,f| k.send("#{e}=",f) } if Hash === a[-1]
707
- k.service *a
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. As a shortcut, you may derive from 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. However, if your model class
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
- autoload :Base,'camping/db'
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