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.
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