glamping 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +4 -0
- data/License.txt +18 -0
- data/Manifest.txt +9 -0
- data/README.txt +60 -0
- data/lib/glamping.rb +823 -0
- data/lib/glamping/boot.rb +90 -0
- data/lib/glamping/db.rb +333 -0
- data/lib/glamping/session.rb +132 -0
- data/lib/glamping/version.rb +9 -0
- data/test/test_glamping.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +122 -0
- metadata.gz.sig +0 -0
data.tar.gz.sig
ADDED
Binary file
|
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2007 Magnus Holm
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# == About Glamping
|
2
|
+
#
|
3
|
+
# Glamping is a fork of Camping[http://code.whytheluckystiff.net/camping],
|
4
|
+
# and much of the documentation is identical. Camping is licensed under
|
5
|
+
# the MIT-license (included below) and so is Glamping (see
|
6
|
+
# License.txt[link:files/License_txt.html]). If you think that I break any
|
7
|
+
# of the conditions required by the license, please contact me at
|
8
|
+
# mailto:judofyr@gmail.com.
|
9
|
+
#
|
10
|
+
# An importing thing to remember while using Glamping, is that it's in fact
|
11
|
+
# Camping. Most Camping-plugins (even though there isn't so many) will very
|
12
|
+
# often work in Glamping. You can use both Mongrel, Rack and Rv together with
|
13
|
+
# Glamping by using the Camping-adapters.
|
14
|
+
#
|
15
|
+
# If you need help, there is a forum located at our
|
16
|
+
# developer[http://code.simpleflux.com/projects/show/1] site. There you can
|
17
|
+
# also submit bugs and features and browse the source.
|
18
|
+
#
|
19
|
+
# == Requirements
|
20
|
+
#
|
21
|
+
# Glamping requires at least Ruby 1.8.2.
|
22
|
+
#
|
23
|
+
# Glamping depends on the following libraries. If you install through RubyGems,
|
24
|
+
# these will be automatically installed for you.
|
25
|
+
#
|
26
|
+
# * ActiveRecord, used in your models.
|
27
|
+
# ActiveRecord is an object-to-relational database mapper with adapters
|
28
|
+
# for SQLite3, MySQL, PostgreSQL, SQL Server and more.
|
29
|
+
# * Erubis or ERB, used in your views.
|
30
|
+
# * MetAid, a few metaprogramming methods which Glamping uses.
|
31
|
+
# * Tempfile, for storing file uploads.
|
32
|
+
# * MimeTypes, for serving static files with correct Content-Type
|
33
|
+
#
|
34
|
+
# Glamping also works well with Mongrel[http://rubyforge.org/projects/mongrel],
|
35
|
+
# the swift Ruby web server.
|
36
|
+
#
|
37
|
+
# == Getting Started
|
38
|
+
# * (Learn Camping)
|
39
|
+
# * Read <i>The Puffy Guide to Glamorous Camping</i>: http://judofyr.net/g
|
40
|
+
# * Read my blog: http://blog.judofyr.net
|
41
|
+
#
|
42
|
+
# == The Camping License
|
43
|
+
# Copyright (c) 2006 why the lucky stiff
|
44
|
+
#
|
45
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
46
|
+
# of this software and associated documentation files (the "Software"), to
|
47
|
+
# deal in the Software without restriction, including without limitation the
|
48
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
49
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
50
|
+
# furnished to do so, subject to the following conditions:
|
51
|
+
#
|
52
|
+
# The above copyright notice and this permission notice shall be included in
|
53
|
+
# all copies or substantial portions of the Software.
|
54
|
+
#
|
55
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
56
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
57
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
58
|
+
# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
59
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
60
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/glamping.rb
ADDED
@@ -0,0 +1,823 @@
|
|
1
|
+
%w[metaid tempfile uri fileutils mime/types].map { |l| require l }
|
2
|
+
begin
|
3
|
+
require 'erubis'
|
4
|
+
ERB = Erubis::Eruby unless Object.const_defined(:ERB)
|
5
|
+
rescue
|
6
|
+
require 'erb'
|
7
|
+
end
|
8
|
+
|
9
|
+
# == Glamping
|
10
|
+
#
|
11
|
+
# The glamping module contains three modules for separating your application:
|
12
|
+
#
|
13
|
+
# * Glamping::Models for your database interaction classes, all derived from ActiveRecord::Base.
|
14
|
+
# * Glamping::Controllers for storing controller classes, which map URLs to code.
|
15
|
+
# * Glamping::Views for storing methods which generate HTML.
|
16
|
+
#
|
17
|
+
# Of use to you is also one module for storing helpful additional methods:
|
18
|
+
#
|
19
|
+
# * Glamping::Helpers which can be used in controllers and views.
|
20
|
+
#
|
21
|
+
# == The <tt>create</tt> method
|
22
|
+
#
|
23
|
+
# Many postambles will check for your application's <tt>create</tt> method and will run it
|
24
|
+
# when the web server starts up. This is a good place to check for database tables and create
|
25
|
+
# those tables to save users of your application from needing to manually set them up.
|
26
|
+
#
|
27
|
+
# def Blog.create
|
28
|
+
# unless Blog::Models::Post.table_exists?
|
29
|
+
# ActiveRecord::Schema.define do
|
30
|
+
# create_table :blog_posts, :force => true do |t|
|
31
|
+
# t.column :user_id, :integer, :null => false
|
32
|
+
# t.column :title, :string, :limit => 255
|
33
|
+
# t.column :body, :text
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# For more tips, see http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
|
40
|
+
module Glamping
|
41
|
+
G = Object.const_get("Glam\ping")
|
42
|
+
C = self
|
43
|
+
S = IO.read(__FILE__) rescue nil
|
44
|
+
P = "<h1>Glam\ping Problem!</h1><h2>%s</h2>"
|
45
|
+
# An object-like Hash.
|
46
|
+
# All Glamping query string and cookie variables are loaded as this.
|
47
|
+
#
|
48
|
+
# To access the query string, for instance, use the <tt>@input</tt> variable.
|
49
|
+
#
|
50
|
+
# module Blog::Controllers
|
51
|
+
# class Index < R '/'
|
52
|
+
# def get
|
53
|
+
# if page = @input.page.to_i > 0
|
54
|
+
# page -= 1
|
55
|
+
# end
|
56
|
+
# @posts = Post.find :all, :offset => page * 20, :limit => 20
|
57
|
+
# render :index
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# In the above example if you visit <tt>/?page=2</tt>, you'll get the second
|
63
|
+
# page of twenty posts. You can also use <tt>@input[:page]</tt> or <tt>@input['page']</tt>
|
64
|
+
# to get the value for the <tt>page</tt> query variable.
|
65
|
+
#
|
66
|
+
# Use the <tt>@cookies</tt> variable in the same fashion to access cookie variables.
|
67
|
+
# Also, the <tt>@env</tt> variable is an H containing the HTTP headers and server info.
|
68
|
+
class H < Hash
|
69
|
+
# Gets or sets keys in the hash.
|
70
|
+
#
|
71
|
+
# @cookies.my_favorite = :macadamian
|
72
|
+
# @cookies.my_favorite
|
73
|
+
# => :macadamian
|
74
|
+
#
|
75
|
+
def method_missing(m,*a)
|
76
|
+
m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super
|
77
|
+
end
|
78
|
+
alias u merge!
|
79
|
+
end
|
80
|
+
|
81
|
+
# Helpers contains methods available in your controllers and views. You may add
|
82
|
+
# methods of your own to this module, including many helper methods from Rails.
|
83
|
+
# This is analogous to Rails' <tt>ApplicationHelper</tt> module.
|
84
|
+
#
|
85
|
+
# == Using ActionPack Helpers
|
86
|
+
#
|
87
|
+
# If you'd like to include helpers from Rails' modules, you'll need to look up the
|
88
|
+
# helper module in the Rails documentation at http://api.rubyonrails.org/.
|
89
|
+
#
|
90
|
+
# For example, if you look up the <tt>ActionView::Helpers::FormHelper</tt> class,
|
91
|
+
# you'll find that it's loaded from the <tt>action_view/helpers/form_helper.rb</tt>
|
92
|
+
# file. You'll need to have the ActionPack gem installed for this to work.
|
93
|
+
#
|
94
|
+
# require 'action_view/helpers/form_helper.rb'
|
95
|
+
#
|
96
|
+
# # This example is unfinished.. soon..
|
97
|
+
#
|
98
|
+
module Helpers
|
99
|
+
# Page caching is an efficient caching method, but is only useful
|
100
|
+
# under this conditions:
|
101
|
+
# * The page is same for all users
|
102
|
+
# * The page is available to the public
|
103
|
+
#
|
104
|
+
# This implementation is based on Manfred Stienstra's
|
105
|
+
# work[http://operation0.org/2006/10/simple-page-caching-with-hidden-carrots]
|
106
|
+
#
|
107
|
+
# == Setup
|
108
|
+
# (Please read glamping/boot.rb[link:files/lib/glamping/boot_rb.html]
|
109
|
+
# when using <tt>boot.rb</tt>)
|
110
|
+
#
|
111
|
+
# Before we can start using PageCaching we need to set where our
|
112
|
+
# caching-files should go.
|
113
|
+
#
|
114
|
+
# SampleApp.config.root = "/full/path/to/cache/folder"
|
115
|
+
#
|
116
|
+
# == Usage
|
117
|
+
# Then we can simply use the <tt>cache_page</tt>-method to cache
|
118
|
+
# our controller:
|
119
|
+
#
|
120
|
+
# class Post < R '/post/(\d+)'
|
121
|
+
# def get(id)
|
122
|
+
# cache_page do
|
123
|
+
# @post = Post.find(id)
|
124
|
+
# render :post
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# There is no expire time for this caches, in stead you should use
|
130
|
+
# <tt>sweep_page</tt> when needed (after a post is created etc.)
|
131
|
+
module PageCaching
|
132
|
+
# Checks if a page is cached by the <b>local</b> path
|
133
|
+
def page_cached?(path)
|
134
|
+
File.exists?(path) && !File.size(path).zero?
|
135
|
+
end
|
136
|
+
|
137
|
+
# Figures out the full local path to the cache, based on the URL.
|
138
|
+
# Returns an array where the first item is the folder and the second
|
139
|
+
# is the file.
|
140
|
+
# page_cache_path("/post/1") #=> ["/cache/folder/post",
|
141
|
+
# "/cache/folder/post/1.html"]
|
142
|
+
def page_cache_path(path = env['REQUEST_URI'])
|
143
|
+
p = path.split("/")[1..-1].to_a
|
144
|
+
file = p.pop || "index"
|
145
|
+
file = file[0...file.index("?")] if file.include?("?")
|
146
|
+
file << '.html' unless file.include?(".")
|
147
|
+
[path = File.join(C.config.cache, *p), File.join(path, file)]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Caches a page based on the URL. The cache-file is sent using the
|
151
|
+
# X-Sendfile header
|
152
|
+
def cache_page # :yields:
|
153
|
+
return yield unless env['REQUEST_URI']
|
154
|
+
path, file = page_cache_path
|
155
|
+
unless page_cached?(file)
|
156
|
+
FileUtils.mkdir_p(path)
|
157
|
+
File.open(file, 'w') { |f| f.write yield.to_s }
|
158
|
+
end
|
159
|
+
serve_static(file)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sweeps a page by a controller. If <tt>:all</tt> is given as first
|
163
|
+
# argument it will simply <b>delete the whole cache folder</b>
|
164
|
+
# sweep_page(Post, 1)
|
165
|
+
# sweep_page(Index)
|
166
|
+
# sweep_page(Controller, "argument", 123)
|
167
|
+
# sweep_page(:all)
|
168
|
+
def sweep_page(*args)
|
169
|
+
return FileUtils.rm_rf(PATH) if args.first == :all
|
170
|
+
path, file = page_cache_path(R(*args))
|
171
|
+
FileUtils.rm_rf Dir[file]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
include PageCaching
|
175
|
+
# From inside your controllers and views, you will often need to figure out
|
176
|
+
# the route used to get to a certain controller +c+. Pass the controller class
|
177
|
+
# and any arguments into the R method, a string containing the route will be
|
178
|
+
# returned to you.
|
179
|
+
#
|
180
|
+
# Assuming you have a specific route in an edit controller:
|
181
|
+
#
|
182
|
+
# class Edit < R '/edit/(\d+)'
|
183
|
+
#
|
184
|
+
# A specific route to the Edit controller can be built with:
|
185
|
+
#
|
186
|
+
# R(Edit, 1)
|
187
|
+
#
|
188
|
+
# Which outputs: <tt>/edit/1</tt>.
|
189
|
+
#
|
190
|
+
# You may also pass in a model object and the ID of the object will be used.
|
191
|
+
#
|
192
|
+
# If a controller has many routes, the route will be selected if it is the
|
193
|
+
# first in the routing list to have the right number of arguments.
|
194
|
+
#
|
195
|
+
# == Using R in the View
|
196
|
+
#
|
197
|
+
# Keep in mind that this route doesn't include the root path.
|
198
|
+
# You will need to use <tt>/</tt> (the slash method above) in your controllers.
|
199
|
+
# Or, go ahead and use the Helpers#URL method to build a complete URL for a route.
|
200
|
+
#
|
201
|
+
# However, in your views, the :href, :src and :action attributes automatically
|
202
|
+
# pass through the slash method, so you are encouraged to use <tt>R</tt> or
|
203
|
+
# <tt>URL</tt> in your views.
|
204
|
+
#
|
205
|
+
# module Blog::Views
|
206
|
+
# def menu
|
207
|
+
# div.menu! do
|
208
|
+
# a 'Home', :href => URL()
|
209
|
+
# a 'Profile', :href => "/profile"
|
210
|
+
# a 'Logout', :href => R(Logout)
|
211
|
+
# a 'Google', :href => 'http://google.com'
|
212
|
+
# end
|
213
|
+
# end
|
214
|
+
# end
|
215
|
+
#
|
216
|
+
# Let's say the above example takes place inside an application mounted at
|
217
|
+
# <tt>http://localhost:3301/frodo</tt> and that a controller named <tt>Logout</tt>
|
218
|
+
# is assigned to route <tt>/logout</tt>. The HTML will come out as:
|
219
|
+
#
|
220
|
+
# <div id="menu">
|
221
|
+
# <a href="//localhost:3301/frodo/">Home</a>
|
222
|
+
# <a href="/frodo/profile">Profile</a>
|
223
|
+
# <a href="/frodo/logout">Logout</a>
|
224
|
+
# <a href="http://google.com">Google</a>
|
225
|
+
# </div>
|
226
|
+
#
|
227
|
+
# If you pass wrong number of arguments (or passing nil into some position),
|
228
|
+
# it will return nil
|
229
|
+
def R(c,*g)
|
230
|
+
p,h=/\(.+?\)/,g.grep(Hash)
|
231
|
+
g-=h
|
232
|
+
raise "bad route" unless u = c.urls.find{|x|
|
233
|
+
break x if x.scan(p).size == g.size &&
|
234
|
+
/^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
|
235
|
+
x.sub p,C.escape((a[a.class.primary_key]rescue a))})
|
236
|
+
}
|
237
|
+
h.any?? u+"?"+h[0].map{|x|x.map{|z|C.escape z}*"="}*"&": u
|
238
|
+
end
|
239
|
+
|
240
|
+
# Simply builds a complete path from a path +p+ within the app. If your application is
|
241
|
+
# mounted at <tt>/blog</tt>:
|
242
|
+
#
|
243
|
+
# self / "/view/1" #=> "/blog/view/1"
|
244
|
+
# self / "styles.css" #=> "styles.css"
|
245
|
+
# self / R(Edit, 1) #=> "/blog/edit/1"
|
246
|
+
#
|
247
|
+
def /(p); p[/^\//]?@root+p:p end
|
248
|
+
# Builds a URL route to a controller or a path, returning a URI object.
|
249
|
+
# This way you'll get the hostname and the port number, a complete URL.
|
250
|
+
# No scheme is given (http or https).
|
251
|
+
#
|
252
|
+
# You can use this to grab URLs for controllers using the R-style syntax.
|
253
|
+
# So, if your application is mounted at <tt>http://test.ing/blog/</tt>
|
254
|
+
# and you have a View controller which routes as <tt>R '/view/(\d+)'</tt>:
|
255
|
+
#
|
256
|
+
# URL(View, @post.id) #=> #<URL://test.ing/blog/view/12>
|
257
|
+
#
|
258
|
+
# Or you can use the direct path:
|
259
|
+
#
|
260
|
+
# self.URL #=> #<URL://test.ing/blog/>
|
261
|
+
# self.URL + "view/12" #=> #<URL://test.ing/blog/view/12>
|
262
|
+
# URL("/view/12") #=> #<URL://test.ing/blog/view/12>
|
263
|
+
#
|
264
|
+
# Since no scheme is given, you will need to add the scheme yourself:
|
265
|
+
#
|
266
|
+
# "http" + URL("/view/12") #=> "http://test.ing/blog/view/12"
|
267
|
+
#
|
268
|
+
# It's okay to pass URL strings through this method as well:
|
269
|
+
#
|
270
|
+
# URL("http://google.com") #=> #<URI:http://google.com>
|
271
|
+
#
|
272
|
+
# Any string which doesn't begin with a slash will pass through
|
273
|
+
# unscathed.
|
274
|
+
def URL c='/',*a
|
275
|
+
c = R(c, *a) if c.respond_to? :urls
|
276
|
+
c = self/c
|
277
|
+
c = "//"+@env.HTTP_HOST+c if c[/^\//]
|
278
|
+
URI(c)
|
279
|
+
end
|
280
|
+
|
281
|
+
# Builds a link to the previous page, named with +text+. If you need
|
282
|
+
# to customize the link, you should just call this without any arguments.
|
283
|
+
# Then it will only give you the page.
|
284
|
+
#
|
285
|
+
# If it can't find the previous page it will use a JavaScript-solution.
|
286
|
+
#
|
287
|
+
# back("Go to previous page") #=> A link to the previous page
|
288
|
+
# back #=> Just the previous page
|
289
|
+
def back(text = nil)
|
290
|
+
return u = @env['HTTP_REFERER'] || "javascript:history.go(-1)" unless text
|
291
|
+
'<a href="%s">%s</a>'%[u,text]
|
292
|
+
end
|
293
|
+
|
294
|
+
# Serves a static file.
|
295
|
+
def serve_static(path)
|
296
|
+
if s = MIME::Types.type_for(path)[0]
|
297
|
+
@headers['Content-Type'] = s.content_type
|
298
|
+
end
|
299
|
+
@headers['X-Sendfile'] = path
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Glamping::Base is built into each controller by way of the generic routing
|
304
|
+
# class Glamping::R. In some ways, this class is trying to do too much, but
|
305
|
+
# it saves code for all the glue to stay in one place.
|
306
|
+
#
|
307
|
+
# Forgivable, considering that it's only really a handful of methods and accessors.
|
308
|
+
#
|
309
|
+
# == Treating controller methods like Response objects
|
310
|
+
#
|
311
|
+
# Glamping originally came with a barebones Response object, but it's often much more readable
|
312
|
+
# to just use your controller as the response.
|
313
|
+
#
|
314
|
+
# Go ahead and alter the status, cookies, headers and body instance variables as you
|
315
|
+
# see fit in order to customize the response.
|
316
|
+
#
|
317
|
+
# module Glamping::Controllers
|
318
|
+
# class SoftLink
|
319
|
+
# def get
|
320
|
+
# redirect "/"
|
321
|
+
# end
|
322
|
+
# end
|
323
|
+
# end
|
324
|
+
#
|
325
|
+
# Is equivalent to:
|
326
|
+
#
|
327
|
+
# module Glamping::Controllers
|
328
|
+
# class SoftLink
|
329
|
+
# def get
|
330
|
+
# @status = 302
|
331
|
+
# @headers['Location'] = "/"
|
332
|
+
# end
|
333
|
+
# end
|
334
|
+
# end
|
335
|
+
#
|
336
|
+
module Base
|
337
|
+
attr_accessor :input, :cookies, :env, :headers, :body, :status, :root
|
338
|
+
Z = "\r\n"
|
339
|
+
|
340
|
+
#
|
341
|
+
# Display a view, calling it by its method name +m+. If a <tt>layout</tt>
|
342
|
+
# method is found in Glamping::Views, it will be used to wrap the HTML.
|
343
|
+
#
|
344
|
+
# module Glamping::Controllers
|
345
|
+
# class Show
|
346
|
+
# def get
|
347
|
+
# @posts = Post.find :all
|
348
|
+
# render :index
|
349
|
+
# end
|
350
|
+
# end
|
351
|
+
# end
|
352
|
+
#
|
353
|
+
def render(m, layout_name = "layout")
|
354
|
+
@content = render_view(m)
|
355
|
+
begin;layout_name ? render_view(layout_name) : @content
|
356
|
+
rescue Errno::ENOENT; @content ;end
|
357
|
+
end
|
358
|
+
|
359
|
+
def render_view(m)
|
360
|
+
ERB.new(IO.read("#{C.config.views}/#{m}.erb")).result(binding)
|
361
|
+
end
|
362
|
+
=begin
|
363
|
+
# Any stray method calls will be passed to Markaby. This means you can reply
|
364
|
+
# with HTML directly from your controller for quick debugging.
|
365
|
+
#
|
366
|
+
# module Glamping::Controllers
|
367
|
+
# class Info
|
368
|
+
# def get; code @env.inspect end
|
369
|
+
# end
|
370
|
+
# end
|
371
|
+
#
|
372
|
+
# If you have a <tt>layout</tt> method in Glamping::Views, it will be used to
|
373
|
+
# wrap the HTML.
|
374
|
+
def method_missing(*a,&b)
|
375
|
+
a.shift if a[0]==:render
|
376
|
+
m=Mab.new({},self)
|
377
|
+
s=m.capture{send(*a,&b)}
|
378
|
+
s=m.capture{send(:layout){s}} if /^_/!~a[0].to_s and m.respond_to?:layout
|
379
|
+
s
|
380
|
+
end
|
381
|
+
=end
|
382
|
+
# A quick means of setting this controller's status, body and headers.
|
383
|
+
# Used internally by Camping, but... by all means...
|
384
|
+
#
|
385
|
+
# r(302, '', 'Location' => self / "/view/12")
|
386
|
+
#
|
387
|
+
# Is equivalent to:
|
388
|
+
#
|
389
|
+
# redirect "/view/12"
|
390
|
+
#
|
391
|
+
# See also: #r404, #r500 and #r501
|
392
|
+
def r(s, b, h = {}); @status = s; headers.u(h); @body = b; end
|
393
|
+
|
394
|
+
# Formulate a redirect response: a 302 status with <tt>Location</tt> header
|
395
|
+
# and a blank body. Uses Helpers#URL to build the location from a controller
|
396
|
+
# route or path.
|
397
|
+
#
|
398
|
+
# So, given a root of <tt>http://localhost:3301/articles</tt>:
|
399
|
+
#
|
400
|
+
# redirect "view/12" # redirects to "//localhost:3301/articles/view/12"
|
401
|
+
# redirect View, 12 # redirects to "//localhost:3301/articles/view/12"
|
402
|
+
#
|
403
|
+
# <b>NOTE:</b> This method doesn't magically exit your methods and redirect.
|
404
|
+
# You'll need to <tt>return redirect(...)</tt> if this isn't the last statement
|
405
|
+
# in your code.
|
406
|
+
def redirect(*a)
|
407
|
+
r(302,'','Location'=>URL(*a))
|
408
|
+
end
|
409
|
+
|
410
|
+
# Called when a controller was not found. It is mainly used internally, but it can
|
411
|
+
# also be useful for you, if you want to filter some parameters.
|
412
|
+
#
|
413
|
+
# module Glamping
|
414
|
+
# def r404(p=env.PATH)
|
415
|
+
# @status = 404
|
416
|
+
# div do
|
417
|
+
# h1 'Glamping Problem!'
|
418
|
+
# h2 "#{p} not found"
|
419
|
+
# end
|
420
|
+
# end
|
421
|
+
# end
|
422
|
+
#
|
423
|
+
# See: I
|
424
|
+
def r404(p=env.PATH)
|
425
|
+
if (a=C.config.public) && Dir[a+"/**/*"].include?(a+p)
|
426
|
+
return serve_static(a+p)
|
427
|
+
end
|
428
|
+
r(404, X.const_defined?(:NotFound) ? C.get(:NotFound, p).body : P % "#{p} not found")
|
429
|
+
end
|
430
|
+
|
431
|
+
# If there is a parse error in Glamping or in your application's source code, it will not be caught
|
432
|
+
# by Glamping. The controller class +k+ and request method +m+ (GET, POST, etc.) where the error
|
433
|
+
# took place are passed in, along with the Exception +e+ which can be mined for useful info.
|
434
|
+
#
|
435
|
+
# You can overide it, but if you have an error in here, it will be uncaught !
|
436
|
+
#
|
437
|
+
# See: I
|
438
|
+
def r500(k,m,x)
|
439
|
+
r(500, X.const_defined?(:ServerError) ? C.get(:ServerError, k, m, x).body : P % "#{k}.#{m}" + "<h3>#{x.class} #{x.message}: <ul>#{x.backtrace.map{|b|"<li>#{b}</li>"}}</ul></h3>")
|
440
|
+
end
|
441
|
+
|
442
|
+
# Called if an undefined method is called on a Controller, along with the request method +m+ (GET, POST, etc.)
|
443
|
+
#
|
444
|
+
# See: I
|
445
|
+
def r501(m=@method)
|
446
|
+
r(501, X.const_defined?(:NotImplemented) ? C.get(:NotImplemented, m).body : P % "#{m.upcase} not implemented")
|
447
|
+
end
|
448
|
+
|
449
|
+
# Turn a controller into an array. This is designed to be used to pipe
|
450
|
+
# controllers into the <tt>r</tt> method. A great way to forward your
|
451
|
+
# requests!
|
452
|
+
#
|
453
|
+
# class Read < '/(\d+)'
|
454
|
+
# def get(id)
|
455
|
+
# Post.find(id)
|
456
|
+
# rescue
|
457
|
+
# r *Blog.get(:NotFound, @env.REQUEST_URI)
|
458
|
+
# end
|
459
|
+
# end
|
460
|
+
#
|
461
|
+
def to_a;[status, body, headers] end
|
462
|
+
|
463
|
+
def initialize(r, e, m) #:nodoc:
|
464
|
+
@status, @method, @env, @headers, @root = 200, m, e,
|
465
|
+
H['Content-Type'=>'text/html'], e.SCRIPT_NAME.sub(/\/$/,'')
|
466
|
+
@k = C.kp(e.HTTP_COOKIE)
|
467
|
+
q = C.qsp(e.QUERY_STRING)
|
468
|
+
@in = r
|
469
|
+
case e.CONTENT_TYPE
|
470
|
+
when %r|\Amultipart/form-.*boundary=\"?([^\";,]+)|n
|
471
|
+
b = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
|
472
|
+
until @in.eof?
|
473
|
+
fh=H[]
|
474
|
+
for l in @in
|
475
|
+
case l
|
476
|
+
when Z: break
|
477
|
+
when /^Content-D.+?: form-data;/
|
478
|
+
fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten]
|
479
|
+
when /^Content-Type: (.+?)(\r$|\Z)/m
|
480
|
+
fh.type = $1
|
481
|
+
end
|
482
|
+
end
|
483
|
+
fn=fh.name
|
484
|
+
o=if fh.filename
|
485
|
+
o=fh.tempfile=Tempfile.new(:C)
|
486
|
+
o.binmode
|
487
|
+
else
|
488
|
+
fh=""
|
489
|
+
end
|
490
|
+
s=8192
|
491
|
+
k=''
|
492
|
+
l=@in.read(s*2)
|
493
|
+
while l
|
494
|
+
if (k<<l)=~b
|
495
|
+
o<<$`.chomp
|
496
|
+
@in.seek(-$'.size,IO::SEEK_CUR)
|
497
|
+
break
|
498
|
+
end
|
499
|
+
o<<k.slice!(0...s)
|
500
|
+
l=@in.read(s)
|
501
|
+
end
|
502
|
+
C.qsp(fn,'&;',fh,q) if fn
|
503
|
+
fh.tempfile.rewind if fh.is_a?H
|
504
|
+
end
|
505
|
+
when "application/x-www-form-urlencoded"
|
506
|
+
q.u(C.qsp(@in.read))
|
507
|
+
end
|
508
|
+
@cookies, @input = @k.dup, q.dup
|
509
|
+
end
|
510
|
+
|
511
|
+
# All requests pass through this method before going to the controller. Some magic
|
512
|
+
# in Glamping can be performed by overriding this method.
|
513
|
+
#
|
514
|
+
# See http://code.whytheluckystiff.net/camping/wiki/BeforeAndAfterOverrides for more
|
515
|
+
# on before and after overrides with Glamping.
|
516
|
+
def service(*a)
|
517
|
+
@body = send(@method, *a)
|
518
|
+
headers['Set-Cookie'] = cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil]
|
519
|
+
self
|
520
|
+
end
|
521
|
+
|
522
|
+
# Used by the web server to convert the current request to a string. If you need to
|
523
|
+
# alter the way Glamping builds HTTP headers, consider overriding this method.
|
524
|
+
def to_s
|
525
|
+
"Status: #@status#{Z+(headers.map{|k,v|[*v].map{|x|[k,v]*": "}}*Z).gsub(Z*2,Z)+Z+Z}#@body"
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
# Controllers is a module for placing classes which handle URLs. This is done
|
530
|
+
# by defining a route to each class using the Controllers::R method.
|
531
|
+
#
|
532
|
+
# module Glamping::Controllers
|
533
|
+
# class Edit < R '/edit/(\d+)'
|
534
|
+
# def get; end
|
535
|
+
# def post; end
|
536
|
+
# end
|
537
|
+
# end
|
538
|
+
#
|
539
|
+
# If no route is set, Glamping will guess the route from the class name.
|
540
|
+
# The rule is very simple: the route becomes a slash followed by the lowercased
|
541
|
+
# class name. See Controllers::D for the complete rules of dispatch.
|
542
|
+
#
|
543
|
+
# == Special classes
|
544
|
+
#
|
545
|
+
# There are two special classes used for handling 404 and 500 errors. The
|
546
|
+
# NotFound class handles URLs not found. The ServerError class handles exceptions
|
547
|
+
# uncaught by your application.
|
548
|
+
X = module Controllers
|
549
|
+
Base.send(:include,self)
|
550
|
+
@r = []
|
551
|
+
class << self
|
552
|
+
def r #:nodoc:
|
553
|
+
@r
|
554
|
+
end
|
555
|
+
# Add routes to a controller class by piling them into the R method.
|
556
|
+
#
|
557
|
+
# module Glamping::Controllers
|
558
|
+
# class Edit < R '/edit/(\d+)', '/new'
|
559
|
+
# def get(id)
|
560
|
+
# if id # edit
|
561
|
+
# else # new
|
562
|
+
# end
|
563
|
+
# end
|
564
|
+
# end
|
565
|
+
# end
|
566
|
+
#
|
567
|
+
# You will need to use routes in either of these cases:
|
568
|
+
#
|
569
|
+
# * You want to assign multiple routes to a controller.
|
570
|
+
# * You want your controller to receive arguments.
|
571
|
+
#
|
572
|
+
# Most of the time the rules inferred by dispatch method Controllers::D will get you
|
573
|
+
# by just fine.
|
574
|
+
#
|
575
|
+
# After the route have been created, a method named by the class-name will be created.
|
576
|
+
# It works the same way as R, but each route will be prefixed with the first URI you
|
577
|
+
# passed to R.
|
578
|
+
#
|
579
|
+
# class Admin < R '/admin'
|
580
|
+
# # lots of code
|
581
|
+
# end
|
582
|
+
#
|
583
|
+
# class AdminEdit < Admin '/edit'
|
584
|
+
# # lots of code
|
585
|
+
# end
|
586
|
+
#
|
587
|
+
# In this example we will now have two routes: /admin (Admin) and /admin/edit (AdminEdit).
|
588
|
+
def R *u
|
589
|
+
RR u
|
590
|
+
end
|
591
|
+
|
592
|
+
def RR u, p='' # :nodoc:
|
593
|
+
r=@r
|
594
|
+
Class.new {
|
595
|
+
meta_def(:urls){u.map{|y|p+y}}
|
596
|
+
meta_def(:inherited) do |x|
|
597
|
+
r<<x
|
598
|
+
Controllers.meta_def(x.to_s.split("::").last){|*u|RR(u,x.urls.first)}
|
599
|
+
end
|
600
|
+
}
|
601
|
+
end
|
602
|
+
|
603
|
+
# Dispatch routes to controller classes.
|
604
|
+
# For each class, routes are checked for a match based on their order in the routing list
|
605
|
+
# given to Controllers::R. If no routes were given, the dispatcher uses a slash followed
|
606
|
+
# by the name of the controller lowercased.
|
607
|
+
#
|
608
|
+
# Controllers are searched in this order:
|
609
|
+
#
|
610
|
+
# # Classes without routes, since they refer to a very specific URL.
|
611
|
+
# # Classes with routes are searched in order of their creation.
|
612
|
+
#
|
613
|
+
# So, define your catch-all controllers last.
|
614
|
+
def D(p, m)
|
615
|
+
r.map { |k|
|
616
|
+
k.urls.map { |x|
|
617
|
+
return (k.instance_method(m) rescue nil) ?
|
618
|
+
[k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
|
619
|
+
}
|
620
|
+
}
|
621
|
+
[I, 'r404', p]
|
622
|
+
end
|
623
|
+
|
624
|
+
# The route maker, this is called by Glamping internally, you shouldn't need to call it.
|
625
|
+
#
|
626
|
+
# Still, it's worth know what this method does. Since Ruby doesn't keep track of class
|
627
|
+
# creation order, we're keeping an internal list of the controllers which inherit from R().
|
628
|
+
# This method goes through and adds all the remaining routes to the beginning of the list
|
629
|
+
# and ensures all the controllers have the right mixins.
|
630
|
+
#
|
631
|
+
# Anyway, if you are calling the URI dispatcher from outside of a Glamping server, you'll
|
632
|
+
# definitely need to call this at least once to set things up.
|
633
|
+
def M
|
634
|
+
constants.map { |c|
|
635
|
+
k=const_get(c)
|
636
|
+
k.send :include,C,Base,Helpers,Models
|
637
|
+
@r=[k]+r if r-[k]==r
|
638
|
+
k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls
|
639
|
+
}
|
640
|
+
end
|
641
|
+
end
|
642
|
+
=begin
|
643
|
+
# The NotFound class is a special controller class for handling 404 errors, in case you'd
|
644
|
+
# like to alter the appearance of the 404. The path is passed in as +p+.
|
645
|
+
#
|
646
|
+
# module Glamping::Controllers
|
647
|
+
# class NotFound
|
648
|
+
# def get(p)
|
649
|
+
# @status = 404
|
650
|
+
# div do
|
651
|
+
# h1 'Glamping Problem!'
|
652
|
+
# h2 "#{p} not found"
|
653
|
+
# end
|
654
|
+
# end
|
655
|
+
# end
|
656
|
+
# end
|
657
|
+
#
|
658
|
+
class NotFound < R()
|
659
|
+
def get(p)
|
660
|
+
r(404, "<h1>#{P}</h1><h2>#{p} not found</h2>")
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
# The ServerError class is a special controller class for handling many (but not all) 500 errors.
|
665
|
+
# If there is a parse error in Glamping or in your application's source code, it will not be caught
|
666
|
+
# by Glamping. The controller class +k+ and request method +m+ (GET, POST, etc.) where the error
|
667
|
+
# took place are passed in, along with the Exception +e+ which can be mined for useful info.
|
668
|
+
#
|
669
|
+
# module Glamping::Controllers
|
670
|
+
# class ServerError
|
671
|
+
# def get(k,m,e)
|
672
|
+
# @status = 500
|
673
|
+
# div do
|
674
|
+
# h1 'Glamping Problem!'
|
675
|
+
# h2 "in #{k}.#{m}"
|
676
|
+
# h3 "#{e.class} #{e.message}:"
|
677
|
+
# ul do
|
678
|
+
# e.backtrace.each do |bt|
|
679
|
+
# li bt
|
680
|
+
# end
|
681
|
+
# end
|
682
|
+
# end
|
683
|
+
# end
|
684
|
+
# end
|
685
|
+
# end
|
686
|
+
#
|
687
|
+
class ServerError < R()
|
688
|
+
def get(k,m,e)
|
689
|
+
r(500, "<h1>#{P}</h1><h2>#{k}.#{m}</h2><h3>#{e.class} #{e.message}:</h3>
|
690
|
+
<ul><li>#{e.backtrace.join('</li><li>')}</li></ul>")
|
691
|
+
end
|
692
|
+
end
|
693
|
+
=end
|
694
|
+
# Internal controller with no route. Used by #D and C.run to show internal messages.
|
695
|
+
class I < R()
|
696
|
+
end
|
697
|
+
self
|
698
|
+
end
|
699
|
+
|
700
|
+
class << self
|
701
|
+
# When you are running many applications, you may want to create independent
|
702
|
+
# modules for each Glamping application. Namespaces for each. Glamping::goes
|
703
|
+
# defines a toplevel constant with the whole MVC rack inside.
|
704
|
+
#
|
705
|
+
# require 'camping'
|
706
|
+
# Glamping.goes :Blog
|
707
|
+
#
|
708
|
+
# module Blog::Controllers; ... end
|
709
|
+
# module Blog::Models; ... end
|
710
|
+
# module Blog::Views; ... end
|
711
|
+
#
|
712
|
+
def goes(m)
|
713
|
+
eval S.gsub(/Glamping/,m.to_s), TOPLEVEL_BINDING
|
714
|
+
end
|
715
|
+
|
716
|
+
# URL escapes a string.
|
717
|
+
#
|
718
|
+
# Glamping.escape("I'd go to the museum straightway!")
|
719
|
+
# #=> "I%27d+go+to+the+museum+straightway%21"
|
720
|
+
#
|
721
|
+
def escape(s); s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ', '+') end
|
722
|
+
|
723
|
+
# Unescapes a URL-encoded string.
|
724
|
+
#
|
725
|
+
# Glamping.un("I%27d+go+to+the+museum+straightway%21")
|
726
|
+
# #=> "I'd go to the museum straightway!"
|
727
|
+
#
|
728
|
+
def un(s); s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')} end
|
729
|
+
|
730
|
+
# Parses a query string into an Glamping::H object.
|
731
|
+
#
|
732
|
+
# input = Glamping.qsp("name=Philarp+Tremain&hair=sandy+blonde")
|
733
|
+
# input.name
|
734
|
+
# #=> "Philarp Tremaine"
|
735
|
+
#
|
736
|
+
# Also parses out the Hash-like syntax used in PHP and Rails and builds
|
737
|
+
# nested hashes from it.
|
738
|
+
#
|
739
|
+
# input = Glamping.qsp("post[id]=1&post[user]=_why")
|
740
|
+
# #=> {'post' => {'id' => '1', 'user' => '_why'}}
|
741
|
+
#
|
742
|
+
# And finally, Array syntax like:
|
743
|
+
#
|
744
|
+
# input = Glamping.qsp("user[]=_why&user[]=lucky&user[]=stiff")
|
745
|
+
# #=> {"user" => ["_why", "lucky", "stiff"]}
|
746
|
+
#
|
747
|
+
def qsp(q, d='&;', y=nil, z=H[])
|
748
|
+
m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
|
749
|
+
(q.to_s.split(/[#{d}]+ */n) - [""]).
|
750
|
+
inject((b,z=z,H[])[0]) { |h,p|
|
751
|
+
k, v=un(p).split('=',2)
|
752
|
+
h.u(k.split(/[\]\[]+/).reverse.
|
753
|
+
inject(y||v) { |x,i| H[i,x] },&m)
|
754
|
+
}
|
755
|
+
end
|
756
|
+
|
757
|
+
# Parses a string of cookies from the <tt>Cookie</tt> header.
|
758
|
+
def kp(s); c = qsp(s, ';,'); end
|
759
|
+
|
760
|
+
# Fields a request through Glamping. For traditional CGI applications, the method can be
|
761
|
+
# executed without arguments.
|
762
|
+
#
|
763
|
+
# if __FILE__ == $0
|
764
|
+
# Glamping::Models::Base.establish_connection :adapter => 'sqlite3',
|
765
|
+
# :database => 'blog3.db'
|
766
|
+
# Glamping::Models::Base.logger = Logger.new('camping.log')
|
767
|
+
# puts Glamping.run
|
768
|
+
# end
|
769
|
+
#
|
770
|
+
# The Glamping controller returned from <tt>run</tt> has a <tt>to_s</tt> method in case you
|
771
|
+
# are running from CGI or want to output the full HTTP output. In the above example, <tt>puts</tt>
|
772
|
+
# will call <tt>to_s</tt> for you.
|
773
|
+
#
|
774
|
+
# For FastCGI and Webrick-loaded applications, you will need to use a request loop, with <tt>run</tt>
|
775
|
+
# at the center, passing in the read +r+ and write +w+ streams. You will also need to mimick or
|
776
|
+
# pass in the <tt>ENV</tt> replacement as part of your wrapper.
|
777
|
+
#
|
778
|
+
# See Glamping::FastCGI and Glamping::WEBrick for examples.
|
779
|
+
#
|
780
|
+
def run(r=$stdin,e=ENV)
|
781
|
+
X.M
|
782
|
+
e = H[e.to_hash]
|
783
|
+
k,m,*a=X.D e.PATH_INFO=un("/#{e.PATH_INFO}".gsub(/\/+/,'/')),(e.REQUEST_METHOD||'get').downcase
|
784
|
+
k.new(r,e,m).Y.service(*a)
|
785
|
+
rescue => x
|
786
|
+
X::I.new(r,e,'r500').service(k,m,x)
|
787
|
+
end
|
788
|
+
|
789
|
+
# The Glamping scriptable dispatcher. Any unhandled method call to the app module will
|
790
|
+
# be sent to a controller class, specified as an argument.
|
791
|
+
#
|
792
|
+
# Blog.get(:Index)
|
793
|
+
# #=> #<Blog::Controllers::Index ... >
|
794
|
+
#
|
795
|
+
# The controller object contains all the @cookies, @body, @headers, etc. formulated by
|
796
|
+
# the response.
|
797
|
+
#
|
798
|
+
# You can also feed environment variables and query variables as a hash, the final
|
799
|
+
# argument.
|
800
|
+
#
|
801
|
+
# Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'})
|
802
|
+
# #=> #<Blog::Controllers::Login @user=... >
|
803
|
+
#
|
804
|
+
# Blog.get(:Info, :env => {'HTTP_HOST' => 'wagon'})
|
805
|
+
# #=> #<Blog::Controllers::Info @env={'HTTP_HOST'=>'wagon'} ...>
|
806
|
+
#
|
807
|
+
def method_missing(m, c, *a)
|
808
|
+
X.M
|
809
|
+
k = X.const_get(c).new(StringIO.new,
|
810
|
+
H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s)
|
811
|
+
H[a.pop].each { |e,f| k.send("#{e}=",f) } if Hash === a[-1]
|
812
|
+
k.service(*a)
|
813
|
+
end
|
814
|
+
|
815
|
+
def config
|
816
|
+
@config ||= H.new
|
817
|
+
end
|
818
|
+
end
|
819
|
+
|
820
|
+
module Models # :nodoc:
|
821
|
+
def Y;self;end
|
822
|
+
end
|
823
|
+
end
|