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