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