glamping 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2007-10-06
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -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.
@@ -0,0 +1,9 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ lib/glamping.rb
6
+ lib/glamping/version.rb
7
+ lib/glamping/db.rb
8
+ lib/glamping/boot.rb
9
+ lib/glamping/session.rb
@@ -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.
@@ -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