picnic 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +3 -0
- data/LICENSE.txt +165 -0
- data/Manifest.txt +28 -0
- data/README.txt +31 -0
- data/Rakefile +58 -0
- data/lib/picnic.rb +122 -0
- data/lib/picnic/authentication.rb +80 -0
- data/lib/picnic/cli.rb +112 -0
- data/lib/picnic/conf.rb +133 -0
- data/lib/picnic/controllers.rb +31 -0
- data/lib/picnic/postambles.rb +227 -0
- data/lib/picnic/service_control.rb +269 -0
- data/lib/picnic/utils.rb +36 -0
- data/lib/picnic/version.rb +9 -0
- data/setup.rb +1585 -0
- data/test/picnic_test.rb +11 -0
- data/test/test_helper.rb +2 -0
- data/vendor/camping-1.5.180/CHANGELOG +99 -0
- data/vendor/camping-1.5.180/COPYING +18 -0
- data/vendor/camping-1.5.180/README +119 -0
- data/vendor/camping-1.5.180/Rakefile +117 -0
- data/vendor/camping-1.5.180/lib/camping-unabridged.rb +762 -0
- data/vendor/camping-1.5.180/lib/camping.rb +55 -0
- data/vendor/camping-1.5.180/lib/camping/db.rb +78 -0
- data/vendor/camping-1.5.180/lib/camping/fastcgi.rb +244 -0
- data/vendor/camping-1.5.180/lib/camping/reloader.rb +163 -0
- data/vendor/camping-1.5.180/lib/camping/session.rb +123 -0
- data/vendor/camping-1.5.180/lib/camping/webrick.rb +65 -0
- metadata +77 -0
data/test/picnic_test.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
= 1.5
|
2
|
+
=== 3rd Oct, 2006
|
3
|
+
|
4
|
+
* Camping::Apps stores an array of classes for all loaded apps.
|
5
|
+
* bin/camping can be given a directory. Like: <tt>camping examples/</tt>
|
6
|
+
* Console mode -- thank zimbatm. Use: camping -C yourapp.rb
|
7
|
+
* Call controllers with Camping.method_missing.
|
8
|
+
|
9
|
+
Tepee.get(:Index) #=> (Response)
|
10
|
+
Blog.post(:Delete, id) #=> (Response)
|
11
|
+
|
12
|
+
Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'})
|
13
|
+
#=> #<Blog::Controllers::Login @user=... >
|
14
|
+
|
15
|
+
Blog.get(:Info, :env => {:HTTP_HOST => 'wagon'})
|
16
|
+
#=> #<Blog::Controllers::Info @env={'HTTP_HOST'=>'wagon'} ...>
|
17
|
+
|
18
|
+
* Using \r\n instead of \n on output. FastCGI has these needs.
|
19
|
+
* ActiveRecord no longer required or installed.
|
20
|
+
* If you refer to Models::Base, however, ActiveRecord will be loaded with autoload. (see lib/camping/db.rb)
|
21
|
+
* new Camping::FastCGI.serve which will serve a whole directory of apps
|
22
|
+
(see http://code.whytheluckystiff.net/camping/wiki/TheCampingServer)
|
23
|
+
* ~/.campingrc can contain database connection info if you want your default to be something other than SQLite.
|
24
|
+
|
25
|
+
database:
|
26
|
+
adapter: mysql
|
27
|
+
username: camping
|
28
|
+
socket: /tmp/mysql.sock
|
29
|
+
password: NOFORESTFIRES
|
30
|
+
database: camping
|
31
|
+
|
32
|
+
* controllers are now *ordered*. uses the inherited hook to keep track of all
|
33
|
+
classes created with R. those classes are scanned, in order, when a request is
|
34
|
+
made. any other controllers are handled first. so if you plan on overriding the
|
35
|
+
urls method, be sure to subclass from R().
|
36
|
+
* Console mode will load .irbrc in the working directory, if present.
|
37
|
+
(for example, in my ~/git/balloon directory, i have this in the .irbrc:
|
38
|
+
include Balloon::Models
|
39
|
+
when camping -C balloon.rb gets run, the models all get included in main.)
|
40
|
+
* And, of course, many other bugfixes from myself and the loyal+kind zimbatm...
|
41
|
+
* Markaby updated to 0.5. (See its CHANGELOG.)
|
42
|
+
|
43
|
+
= 1.4.2
|
44
|
+
=== 18th May, 2006
|
45
|
+
|
46
|
+
* Efficient file uploads for multipart/form-data POSTs.
|
47
|
+
* Camping tool now uses Mongrel, if available. If not, sticks with WEBrick.
|
48
|
+
* Multiple apps can be loaded with the camping tool, each mounted according to their file name.
|
49
|
+
|
50
|
+
= 1.4.1
|
51
|
+
=== 3rd May, 2006
|
52
|
+
|
53
|
+
* Streaming HTTP support. If body is IO, will simply pass to the controller. Mongrel, in particular, supports this nicely.
|
54
|
+
|
55
|
+
= 1.4
|
56
|
+
=== 11th April, 2006
|
57
|
+
|
58
|
+
* Moved Camping::Controllers::Base to Camping::Base.
|
59
|
+
* Moved Camping::Controllers::R to Camping::R.
|
60
|
+
* New session library (lib/camping/session.rb).
|
61
|
+
* WEBrick handler (lib/camping/webrick.rb) and Mongrel handler (lib/camping/mongrel.rb).
|
62
|
+
* Helpers#URL, builds a complete URL for a route. Returns a URI object. This way relative links could just return self.URL.path.
|
63
|
+
* Base#initialize takes over some of Base#service's duties.
|
64
|
+
* ENV now available as @env in controllers and views.
|
65
|
+
* Beautiful multi-page docs without frames!
|
66
|
+
|
67
|
+
= 1.3
|
68
|
+
=== 28th January, 2006
|
69
|
+
|
70
|
+
* bin/camping: an application launcher.
|
71
|
+
* <tt>Camping.run(request, response)</tt> now changed to <tt>controller = Camping.run(request, env)</tt>
|
72
|
+
* This means outputting the response is the wrapper/server's job. See bin/camping, you can do a controller.to_s at the least.
|
73
|
+
* <tt>Controllers::Base.env</tt> is the new thread-safe home for <tt>ENV</tt>.
|
74
|
+
* The input hash now works more like Rails params. You can call keys
|
75
|
+
like methods or with symbols or strings.
|
76
|
+
* Queries are now parsed more like PHP/Rails, in that you can denote
|
77
|
+
structure with brackets: post[user]=_why;post[id]=2
|
78
|
+
* Auto-prefix table names, to help prevent name clash.
|
79
|
+
* Helpers.errors_for simple validation.
|
80
|
+
* Lots of empty :href and :action attributes, a bug.
|
81
|
+
* New single-page flipbook RDoc template.
|
82
|
+
|
83
|
+
= 1.2
|
84
|
+
=== 23rd January, 2006
|
85
|
+
|
86
|
+
* Camping.goes allows fresh modules build from all Camping parts.
|
87
|
+
* File uploads now supported (multipart/form-data).
|
88
|
+
* Helpers.R can rebuild routes.
|
89
|
+
* Helpers./ for tracing paths from the root.
|
90
|
+
|
91
|
+
= 1.1
|
92
|
+
=== 19th January, 2006
|
93
|
+
|
94
|
+
* Allowed request and response streams to be passed in, to allow WEBrick and FastCGI support.
|
95
|
+
|
96
|
+
= 1.0
|
97
|
+
=== 17th January, 2006
|
98
|
+
|
99
|
+
* Initial checkin, see announcement at http://redhanded.hobix.com/bits/campingAMicroframework.html.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2006 why the lucky stiff
|
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,119 @@
|
|
1
|
+
== Camping, a Microframework
|
2
|
+
|
3
|
+
Camping is a web framework which consistently stays at less than 4kb of code.
|
4
|
+
You can probably view the complete source code on a single page. But, you know,
|
5
|
+
it's so small that, if you think about it, what can it really do?
|
6
|
+
|
7
|
+
The idea here is to store a complete fledgling web application in a single file
|
8
|
+
like many small CGIs. But to organize it as a Model-View-Controller application
|
9
|
+
like Rails does. You can then easily move it to Rails once you've got it going.
|
10
|
+
|
11
|
+
== A Camping Skeleton
|
12
|
+
|
13
|
+
A skeletal Camping blog could look like this:
|
14
|
+
|
15
|
+
require 'camping'
|
16
|
+
|
17
|
+
Camping.goes :Blog
|
18
|
+
|
19
|
+
module Blog::Models
|
20
|
+
class Post < Base; belongs_to :user; end
|
21
|
+
class Comment < Base; belongs_to :user; end
|
22
|
+
class User < Base; end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Blog::Controllers
|
26
|
+
class Index < R '/'
|
27
|
+
def get
|
28
|
+
@posts = Post.find :all
|
29
|
+
render :index
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Blog::Views
|
35
|
+
def layout
|
36
|
+
html do
|
37
|
+
body do
|
38
|
+
self << yield
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def index
|
44
|
+
for post in @posts
|
45
|
+
h1 post.title
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Some things you might have noticed:
|
51
|
+
|
52
|
+
* Camping::Models uses ActiveRecord to do its work. We love ActiveRecord!
|
53
|
+
* Camping::Controllers can be assigned URLs in the class definition. Neat?
|
54
|
+
* Camping::Views describes HTML using pure Ruby. Markup as Ruby, which we
|
55
|
+
call Markaby.
|
56
|
+
* You use Camping::goes to make a copy of the Camping framework under your
|
57
|
+
own module name (in this case: <tt>Blog</tt>.)
|
58
|
+
|
59
|
+
<b>NOTE:</b> Camping auto-prefixes table names. If your class is named
|
60
|
+
<tt>Blog::Models::Post</tt>, your table will be called <b>blog_posts</b>.
|
61
|
+
Since many Camping apps can be attached to a database at once, this helps
|
62
|
+
prevent name clash.
|
63
|
+
|
64
|
+
(If you want to see the full blog example, check out <tt>examples/blog/blog.rb</tt>
|
65
|
+
for the complete code.)
|
66
|
+
|
67
|
+
If you want to write larger applications with Camping, you are encouraged to
|
68
|
+
split the application into distinct parts which can be mounted at URLs on your
|
69
|
+
web server. You might have a blog at /blog and a wiki at /wiki. Each
|
70
|
+
self-contained. But you can certainly share layouts and models by storing them
|
71
|
+
in plain Ruby scripts.
|
72
|
+
|
73
|
+
Interested yet? Okay, okay, one step at a time.
|
74
|
+
|
75
|
+
== Installation
|
76
|
+
|
77
|
+
* <tt>gem install camping</tt>
|
78
|
+
|
79
|
+
Or for the bleeding edge:
|
80
|
+
|
81
|
+
* <tt>gem install camping --source code.whytheluckystiff.net</tt>
|
82
|
+
|
83
|
+
You are encourage to install Camping and SQLite3, since it is a small database
|
84
|
+
which fits perfectly with our compact bylaws, works well with the examples.
|
85
|
+
|
86
|
+
* See http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for instructions.
|
87
|
+
|
88
|
+
== Running Camping Apps
|
89
|
+
|
90
|
+
The blog example above and most Camping applications look a lot like CGI scripts.
|
91
|
+
If you run them from the commandline, you'll probably just see a pile of HTML.
|
92
|
+
|
93
|
+
Camping comes with an tool for launching apps from the commandline:
|
94
|
+
|
95
|
+
* Run: <tt>camping blog.rb</tt>
|
96
|
+
* Visit http://localhost:3301/blog/ to use the app.
|
97
|
+
|
98
|
+
== How the Camping Tool Works
|
99
|
+
|
100
|
+
If your application isn't working with the <tt>camping</tt> tool, keep in mind
|
101
|
+
that the tool expects the following conventions to be used:
|
102
|
+
|
103
|
+
1. You must have SQLite3 and SQLite3-ruby installed. (Once again, please see
|
104
|
+
http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for instructions.)
|
105
|
+
2. If your script is called <tt>test.rb</tt>, Camping expects your application to
|
106
|
+
be stored in a module called <tt>Test</tt>. Case is not imporant, though. The
|
107
|
+
module can be called <tt>TeSt</tt> or any other permutation.
|
108
|
+
3. Your script's postamble (anything enclosed in <tt>if __FILE__ == $0</tt>) will be
|
109
|
+
ignored by the tool, since the tool will create an SQLite3 database at
|
110
|
+
<tt>~/.camping.db</tt>. Or, on Windows, <tt>$USER/Application Data/Camping.db</tt>.
|
111
|
+
4. If your application's module has a <tt>create</tt> method, it will be executed before
|
112
|
+
the web server starts up.
|
113
|
+
|
114
|
+
== The Rules of Thumb
|
115
|
+
|
116
|
+
Once you've started writing your own Camping app, I'd highly recommend that you become familiar
|
117
|
+
with the Camping Rules of Thumb which are listed on the wiki:
|
118
|
+
http://code.whytheluckystiff.net/camping/wiki/CampingRulesOfThumb
|
119
|
+
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'fileutils'
|
7
|
+
include FileUtils
|
8
|
+
|
9
|
+
NAME = "camping"
|
10
|
+
REV = File.read(".svn/entries")[/committed-rev="(\d+)"/, 1] rescue nil
|
11
|
+
VERS = ENV['VERSION'] || ("1.5" + (REV ? ".#{REV}" : ""))
|
12
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config', 'test/test.log']
|
13
|
+
RDOC_OPTS = ['--quiet', '--title', "Camping, the Documentation",
|
14
|
+
"--opname", "index.html",
|
15
|
+
"--line-numbers",
|
16
|
+
"--main", "README",
|
17
|
+
"--inline-source"]
|
18
|
+
|
19
|
+
desc "Packages up Camping."
|
20
|
+
task :default => [:package]
|
21
|
+
task :package => [:clean]
|
22
|
+
|
23
|
+
task :doc => [:before_doc, :rdoc, :after_doc]
|
24
|
+
|
25
|
+
task :before_doc do
|
26
|
+
mv "lib/camping.rb", "lib/camping-mural.rb"
|
27
|
+
mv "lib/camping-unabridged.rb", "lib/camping.rb"
|
28
|
+
end
|
29
|
+
|
30
|
+
Rake::RDocTask.new do |rdoc|
|
31
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
32
|
+
rdoc.options += RDOC_OPTS
|
33
|
+
rdoc.template = "extras/flipbook_rdoc.rb"
|
34
|
+
rdoc.main = "README"
|
35
|
+
rdoc.title = "Camping, the Documentation"
|
36
|
+
rdoc.rdoc_files.add ['README', 'CHANGELOG', 'COPYING', 'lib/camping.rb', 'lib/camping/*.rb']
|
37
|
+
end
|
38
|
+
|
39
|
+
task :after_doc do
|
40
|
+
mv "lib/camping.rb", "lib/camping-unabridged.rb"
|
41
|
+
mv "lib/camping-mural.rb", "lib/camping.rb"
|
42
|
+
cp "extras/Camping.gif", "doc/rdoc/"
|
43
|
+
cp "extras/permalink.gif", "doc/rdoc/"
|
44
|
+
sh %{scp -r doc/rdoc/* #{ENV['USER']}@rubyforge.org:/var/www/gforge-projects/camping/}
|
45
|
+
end
|
46
|
+
|
47
|
+
spec =
|
48
|
+
Gem::Specification.new do |s|
|
49
|
+
s.name = NAME
|
50
|
+
s.version = VERS
|
51
|
+
s.platform = Gem::Platform::RUBY
|
52
|
+
s.has_rdoc = true
|
53
|
+
s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
|
54
|
+
s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)\/', '--exclude', 'lib/camping.rb']
|
55
|
+
s.summary = "minature rails for stay-at-home moms"
|
56
|
+
s.description = s.summary
|
57
|
+
s.author = "why the lucky stiff"
|
58
|
+
s.email = 'why@ruby-lang.org'
|
59
|
+
s.homepage = 'http://code.whytheluckystiff.net/camping/'
|
60
|
+
s.executables = ['camping']
|
61
|
+
|
62
|
+
s.add_dependency('activesupport', '>=1.3.1')
|
63
|
+
s.add_dependency('markaby', '>=0.5')
|
64
|
+
s.add_dependency('metaid')
|
65
|
+
s.required_ruby_version = '>= 1.8.2'
|
66
|
+
|
67
|
+
s.files = %w(COPYING README Rakefile) +
|
68
|
+
Dir.glob("{bin,doc,test,lib,extras}/**/*") +
|
69
|
+
Dir.glob("ext/**/*.{h,c,rb}") +
|
70
|
+
Dir.glob("examples/**/*.rb") +
|
71
|
+
Dir.glob("tools/*.rb")
|
72
|
+
|
73
|
+
s.require_path = "lib"
|
74
|
+
# s.extensions = FileList["ext/**/extconf.rb"].to_a
|
75
|
+
s.bindir = "bin"
|
76
|
+
end
|
77
|
+
|
78
|
+
omni =
|
79
|
+
Gem::Specification.new do |s|
|
80
|
+
s.name = "camping-omnibus"
|
81
|
+
s.version = VERS
|
82
|
+
s.platform = Gem::Platform::RUBY
|
83
|
+
s.summary = "the camping meta-package for updating ActiveRecord, Mongrel and SQLite3 bindings"
|
84
|
+
s.description = s.summary
|
85
|
+
%w[author email homepage].each { |x| s.__send__("#{x}=", spec.__send__(x)) }
|
86
|
+
|
87
|
+
s.add_dependency('camping', "=#{VERS}")
|
88
|
+
s.add_dependency('activerecord')
|
89
|
+
s.add_dependency('sqlite3-ruby', '>=1.1.0.1')
|
90
|
+
s.add_dependency('mongrel')
|
91
|
+
s.add_dependency('acts_as_versioned')
|
92
|
+
s.add_dependency('RedCloth')
|
93
|
+
end
|
94
|
+
|
95
|
+
Rake::GemPackageTask.new(spec) do |p|
|
96
|
+
p.need_tar = true
|
97
|
+
p.gem_spec = spec
|
98
|
+
end
|
99
|
+
|
100
|
+
Rake::GemPackageTask.new(omni) do |p|
|
101
|
+
p.gem_spec = omni
|
102
|
+
end
|
103
|
+
|
104
|
+
task :install do
|
105
|
+
sh %{rake package}
|
106
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
107
|
+
end
|
108
|
+
|
109
|
+
task :uninstall => [:clean] do
|
110
|
+
sh %{sudo gem uninstall #{NAME}}
|
111
|
+
end
|
112
|
+
|
113
|
+
Rake::TestTask.new(:test) do |t|
|
114
|
+
t.test_files = FileList['test/test_*.rb']
|
115
|
+
# t.warning = true
|
116
|
+
# t.verbose = true
|
117
|
+
end
|
@@ -0,0 +1,762 @@
|
|
1
|
+
# == About camping.rb
|
2
|
+
#
|
3
|
+
# Camping comes with two versions of its source code. The code contained in
|
4
|
+
# lib/camping.rb is compressed, stripped of whitespace, using compact algorithms
|
5
|
+
# to keep it tight. The unspoken rule is that camping.rb should be flowed with
|
6
|
+
# no more than 80 characters per line and must not exceed four kilobytes.
|
7
|
+
#
|
8
|
+
# On the other hand, lib/camping-unabridged.rb contains the same code, laid out
|
9
|
+
# nicely with piles of documentation everywhere. This documentation is entirely
|
10
|
+
# generated from lib/camping-unabridged.rb using RDoc and our "flipbook" template
|
11
|
+
# found in the extras directory of any camping distribution.
|
12
|
+
#
|
13
|
+
# == Requirements
|
14
|
+
#
|
15
|
+
# Camping requires at least Ruby 1.8.2.
|
16
|
+
#
|
17
|
+
# Camping depends on the following libraries. If you install through RubyGems,
|
18
|
+
# these will be automatically installed for you.
|
19
|
+
#
|
20
|
+
# * ActiveRecord, used in your models.
|
21
|
+
# ActiveRecord is an object-to-relational database mapper with adapters
|
22
|
+
# for SQLite3, MySQL, PostgreSQL, SQL Server and more.
|
23
|
+
# * Markaby, used in your views to describe HTML in plain Ruby.
|
24
|
+
# * MetAid, a few metaprogramming methods which Camping uses.
|
25
|
+
# * Tempfile, for storing file uploads.
|
26
|
+
#
|
27
|
+
# Camping also works well with Mongrel, the swift Ruby web server.
|
28
|
+
# http://rubyforge.org/projects/mongrel Mongrel comes with examples
|
29
|
+
# in its <tt>examples/camping</tt> directory.
|
30
|
+
#
|
31
|
+
%w[active_support markaby tempfile uri].each { |lib| require lib }
|
32
|
+
|
33
|
+
# == Camping
|
34
|
+
#
|
35
|
+
# The camping module contains three modules for separating your application:
|
36
|
+
#
|
37
|
+
# * Camping::Models for your database interaction classes, all derived from ActiveRecord::Base.
|
38
|
+
# * Camping::Controllers for storing controller classes, which map URLs to code.
|
39
|
+
# * Camping::Views for storing methods which generate HTML.
|
40
|
+
#
|
41
|
+
# Of use to you is also one module for storing helpful additional methods:
|
42
|
+
#
|
43
|
+
# * Camping::Helpers which can be used in controllers and views.
|
44
|
+
#
|
45
|
+
# == The Camping Server
|
46
|
+
#
|
47
|
+
# How do you run Camping apps? Oh, uh... The Camping Server!
|
48
|
+
#
|
49
|
+
# The Camping Server is, firstly and thusly, a set of rules. At the very least, The Camping Server must:
|
50
|
+
#
|
51
|
+
# * Load all Camping apps in a directory.
|
52
|
+
# * Load new apps that appear in that directory.
|
53
|
+
# * Mount those apps according to their filename. (e.g. blog.rb is mounted at /blog.)
|
54
|
+
# * Run each app's <tt>create</tt> method upon startup.
|
55
|
+
# * Reload the app if its modification time changes.
|
56
|
+
# * Reload the app if it requires any files under the same directory and one of their modification times changes.
|
57
|
+
# * Support the X-Sendfile header.
|
58
|
+
#
|
59
|
+
# In fact, Camping comes with its own little The Camping Server.
|
60
|
+
#
|
61
|
+
# At a command prompt, run: <tt>camping examples/</tt> and the entire <tt>examples/</tt> directory will be served.
|
62
|
+
#
|
63
|
+
# Configurations also exist for Apache and Lighttpd. See http://code.whytheluckystiff.net/camping/wiki/TheCampingServer.
|
64
|
+
#
|
65
|
+
# == The <tt>create</tt> method
|
66
|
+
#
|
67
|
+
# Many postambles will check for your application's <tt>create</tt> method and will run it
|
68
|
+
# when the web server starts up. This is a good place to check for database tables and create
|
69
|
+
# those tables to save users of your application from needing to manually set them up.
|
70
|
+
#
|
71
|
+
# def Blog.create
|
72
|
+
# unless Blog::Models::Post.table_exists?
|
73
|
+
# ActiveRecord::Schema.define do
|
74
|
+
# create_table :blog_posts, :force => true do |t|
|
75
|
+
# t.column :id, :integer, :null => false
|
76
|
+
# t.column :user_id, :integer, :null => false
|
77
|
+
# t.column :title, :string, :limit => 255
|
78
|
+
# t.column :body, :text
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# For more tips, see http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
|
85
|
+
module Camping
|
86
|
+
# Stores an +Array+ of all Camping applications modules. Modules are added
|
87
|
+
# automatically by +Camping.goes+.
|
88
|
+
#
|
89
|
+
# Camping.goes :Blog
|
90
|
+
# Camping.goes :Tepee
|
91
|
+
# Camping::Apps # => [Blog, Tepee]
|
92
|
+
#
|
93
|
+
Apps = []
|
94
|
+
C = self
|
95
|
+
S = IO.read(__FILE__).sub(/^ S = I.+$/,'')
|
96
|
+
P="Cam\ping Problem!"
|
97
|
+
|
98
|
+
H = HashWithIndifferentAccess
|
99
|
+
# An object-like Hash, based on ActiveSupport's HashWithIndifferentAccess.
|
100
|
+
# All Camping query string and cookie variables are loaded as this.
|
101
|
+
#
|
102
|
+
# To access the query string, for instance, use the <tt>@input</tt> variable.
|
103
|
+
#
|
104
|
+
# module Blog::Models
|
105
|
+
# class Index < R '/'
|
106
|
+
# def get
|
107
|
+
# if page = @input.page.to_i > 0
|
108
|
+
# page -= 1
|
109
|
+
# end
|
110
|
+
# @posts = Post.find :all, :offset => page * 20, :limit => 20
|
111
|
+
# render :index
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# In the above example if you visit <tt>/?page=2</tt>, you'll get the second
|
117
|
+
# page of twenty posts. You can also use <tt>@input[:page]</tt> or <tt>@input['page']</tt>
|
118
|
+
# to get the value for the <tt>page</tt> query variable.
|
119
|
+
#
|
120
|
+
# Use the <tt>@cookies</tt> variable in the same fashion to access cookie variables.
|
121
|
+
# Also, the <tt>@env</tt> variable is an H containing the HTTP headers and server info.
|
122
|
+
class H
|
123
|
+
# Gets or sets keys in the hash.
|
124
|
+
#
|
125
|
+
# @cookies.my_favorite = :macadamian
|
126
|
+
# @cookies.my_favorite
|
127
|
+
# => :macadamian
|
128
|
+
#
|
129
|
+
def method_missing(m,*a)
|
130
|
+
m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")
|
131
|
+
end
|
132
|
+
alias_method :u, :regular_update
|
133
|
+
end
|
134
|
+
|
135
|
+
# Helpers contains methods available in your controllers and views. You may add
|
136
|
+
# methods of your own to this module, including many helper methods from Rails.
|
137
|
+
# This is analogous to Rails' <tt>ApplicationHelper</tt> module.
|
138
|
+
#
|
139
|
+
# == Using ActionPack Helpers
|
140
|
+
#
|
141
|
+
# If you'd like to include helpers from Rails' modules, you'll need to look up the
|
142
|
+
# helper module in the Rails documentation at http://api.rubyonrails.org/.
|
143
|
+
#
|
144
|
+
# For example, if you look up the <tt>ActionView::Helpers::FormHelper</tt> class,
|
145
|
+
# you'll find that it's loaded from the <tt>action_view/helpers/form_helper.rb</tt>
|
146
|
+
# file. You'll need to have the ActionPack gem installed for this to work.
|
147
|
+
#
|
148
|
+
# require 'action_view/helpers/form_helper.rb'
|
149
|
+
#
|
150
|
+
# # This example is unfinished.. soon..
|
151
|
+
#
|
152
|
+
module Helpers
|
153
|
+
# From inside your controllers and views, you will often need to figure out
|
154
|
+
# the route used to get to a certain controller +c+. Pass the controller class
|
155
|
+
# and any arguments into the R method, a string containing the route will be
|
156
|
+
# returned to you.
|
157
|
+
#
|
158
|
+
# Assuming you have a specific route in an edit controller:
|
159
|
+
#
|
160
|
+
# class Edit < R '/edit/(\d+)'
|
161
|
+
#
|
162
|
+
# A specific route to the Edit controller can be built with:
|
163
|
+
#
|
164
|
+
# R(Edit, 1)
|
165
|
+
#
|
166
|
+
# Which outputs: <tt>/edit/1</tt>.
|
167
|
+
#
|
168
|
+
# You may also pass in a model object and the ID of the object will be used.
|
169
|
+
#
|
170
|
+
# If a controller has many routes, the route will be selected if it is the
|
171
|
+
# first in the routing list to have the right number of arguments.
|
172
|
+
#
|
173
|
+
# == Using R in the View
|
174
|
+
#
|
175
|
+
# Keep in mind that this route doesn't include the root path.
|
176
|
+
# You will need to use <tt>/</tt> (the slash method above) in your controllers.
|
177
|
+
# Or, go ahead and use the Helpers#URL method to build a complete URL for a route.
|
178
|
+
#
|
179
|
+
# However, in your views, the :href, :src and :action attributes automatically
|
180
|
+
# pass through the slash method, so you are encouraged to use <tt>R</tt> or
|
181
|
+
# <tt>URL</tt> in your views.
|
182
|
+
#
|
183
|
+
# module Blog::Views
|
184
|
+
# def menu
|
185
|
+
# div.menu! do
|
186
|
+
# a 'Home', :href => URL()
|
187
|
+
# a 'Profile', :href => "/profile"
|
188
|
+
# a 'Logout', :href => R(Logout)
|
189
|
+
# a 'Google', :href => 'http://google.com'
|
190
|
+
# end
|
191
|
+
# end
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
# Let's say the above example takes place inside an application mounted at
|
195
|
+
# <tt>http://localhost:3301/frodo</tt> and that a controller named <tt>Logout</tt>
|
196
|
+
# is assigned to route <tt>/logout</tt>. The HTML will come out as:
|
197
|
+
#
|
198
|
+
# <div id="menu">
|
199
|
+
# <a href="//localhost:3301/frodo/">Home</a>
|
200
|
+
# <a href="/frodo/profile">Profile</a>
|
201
|
+
# <a href="/frodo/logout">Logout</a>
|
202
|
+
# <a href="http://google.com">Google</a>
|
203
|
+
# </div>
|
204
|
+
#
|
205
|
+
def R(c,*g)
|
206
|
+
p,h=/\(.+?\)/,g.grep(Hash)
|
207
|
+
(g-=h).inject(c.urls.find{|x|x.scan(p).size==g.size}.dup){|s,a|
|
208
|
+
s.sub p,C.escape((a[a.class.primary_key]rescue a))
|
209
|
+
}+(h.any?? "?"+h[0].map{|x|x.map{|z|C.escape z}*"="}*"&": "")
|
210
|
+
end
|
211
|
+
|
212
|
+
# Shows AR validation errors for the object passed.
|
213
|
+
# There is no output if there are no errors.
|
214
|
+
#
|
215
|
+
# An example might look like:
|
216
|
+
#
|
217
|
+
# errors_for @post
|
218
|
+
#
|
219
|
+
# Might (depending on actual data) render something like this in Markaby:
|
220
|
+
#
|
221
|
+
# ul.errors do
|
222
|
+
# li "Body can't be empty"
|
223
|
+
# li "Title must be unique"
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# Add a simple ul.errors {color:red; font-weight:bold;} CSS rule and you
|
227
|
+
# have built-in, usable error checking in only one line of code. :-)
|
228
|
+
#
|
229
|
+
# See AR validation documentation for details on validations.
|
230
|
+
def errors_for(o); ul.errors { o.errors.each_full { |er| li er } } if o.errors.any?; end
|
231
|
+
# Simply builds a complete path from a path +p+ within the app. If your application is
|
232
|
+
# mounted at <tt>/blog</tt>:
|
233
|
+
#
|
234
|
+
# self / "/view/1" #=> "/blog/view/1"
|
235
|
+
# self / "styles.css" #=> "styles.css"
|
236
|
+
# self / R(Edit, 1) #=> "/blog/edit/1"
|
237
|
+
#
|
238
|
+
def /(p); p[/^\//]?@root+p:p end
|
239
|
+
# Builds a URL route to a controller or a path, returning a URI object.
|
240
|
+
# This way you'll get the hostname and the port number, a complete URL.
|
241
|
+
# No scheme is given (http or https).
|
242
|
+
#
|
243
|
+
# You can use this to grab URLs for controllers using the R-style syntax.
|
244
|
+
# So, if your application is mounted at <tt>http://test.ing/blog/</tt>
|
245
|
+
# and you have a View controller which routes as <tt>R '/view/(\d+)'</tt>:
|
246
|
+
#
|
247
|
+
# URL(View, @post.id) #=> #<URL://test.ing/blog/view/12>
|
248
|
+
#
|
249
|
+
# Or you can use the direct path:
|
250
|
+
#
|
251
|
+
# self.URL #=> #<URL://test.ing/blog/>
|
252
|
+
# self.URL + "view/12" #=> #<URL://test.ing/blog/view/12>
|
253
|
+
# URL("/view/12") #=> #<URL://test.ing/blog/view/12>
|
254
|
+
#
|
255
|
+
# Since no scheme is given, you will need to add the scheme yourself:
|
256
|
+
#
|
257
|
+
# "http" + URL("/view/12") #=> "http://test.ing/blog/view/12"
|
258
|
+
#
|
259
|
+
# It's okay to pass URL strings through this method as well:
|
260
|
+
#
|
261
|
+
# URL("http://google.com") #=> #<URI:http://google.com>
|
262
|
+
#
|
263
|
+
# Any string which doesn't begin with a slash will pass through
|
264
|
+
# unscathed.
|
265
|
+
def URL c='/',*a
|
266
|
+
c = R(c, *a) if c.respond_to? :urls
|
267
|
+
c = self/c
|
268
|
+
c = "//"+@env.HTTP_HOST+c if c[/^\//]
|
269
|
+
URI(c)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Camping::Base is built into each controller by way of the generic routing
|
274
|
+
# class Camping::R. In some ways, this class is trying to do too much, but
|
275
|
+
# it saves code for all the glue to stay in one place.
|
276
|
+
#
|
277
|
+
# Forgivable, considering that it's only really a handful of methods and accessors.
|
278
|
+
#
|
279
|
+
# == Treating controller methods like Response objects
|
280
|
+
#
|
281
|
+
# Camping originally came with a barebones Response object, but it's often much more readable
|
282
|
+
# to just use your controller as the response.
|
283
|
+
#
|
284
|
+
# Go ahead and alter the status, cookies, headers and body instance variables as you
|
285
|
+
# see fit in order to customize the response.
|
286
|
+
#
|
287
|
+
# module Camping::Controllers
|
288
|
+
# class SoftLink
|
289
|
+
# def get
|
290
|
+
# redirect "/"
|
291
|
+
# end
|
292
|
+
# end
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# Is equivalent to:
|
296
|
+
#
|
297
|
+
# module Camping::Controllers
|
298
|
+
# class SoftLink
|
299
|
+
# def get
|
300
|
+
# @status = 302
|
301
|
+
# @headers['Location'] = "/"
|
302
|
+
# end
|
303
|
+
# end
|
304
|
+
# end
|
305
|
+
#
|
306
|
+
module Base
|
307
|
+
include Helpers
|
308
|
+
attr_accessor :input, :cookies, :env, :headers, :body, :status, :root
|
309
|
+
Z = "\r\n"
|
310
|
+
|
311
|
+
# Display a view, calling it by its method name +m+. If a <tt>layout</tt>
|
312
|
+
# method is found in Camping::Views, it will be used to wrap the HTML.
|
313
|
+
#
|
314
|
+
# module Camping::Controllers
|
315
|
+
# class Show
|
316
|
+
# def get
|
317
|
+
# @posts = Post.find :all
|
318
|
+
# render :index
|
319
|
+
# end
|
320
|
+
# end
|
321
|
+
# end
|
322
|
+
#
|
323
|
+
def render(m); end; undef_method :render
|
324
|
+
|
325
|
+
# Any stray method calls will be passed to Markaby. This means you can reply
|
326
|
+
# with HTML directly from your controller for quick debugging.
|
327
|
+
#
|
328
|
+
# module Camping::Controllers
|
329
|
+
# class Info
|
330
|
+
# def get; code @env.inspect end
|
331
|
+
# end
|
332
|
+
# end
|
333
|
+
#
|
334
|
+
# If you have a <tt>layout</tt> method in Camping::Views, it will be used to
|
335
|
+
# wrap the HTML.
|
336
|
+
def method_missing(*a,&b)
|
337
|
+
a.shift if a[0]==:render
|
338
|
+
m=Mab.new({},self)
|
339
|
+
s=m.capture{send(*a,&b)}
|
340
|
+
s=m.capture{send(:layout){s}} if /^_/!~a[0].to_s and m.respond_to?:layout
|
341
|
+
s
|
342
|
+
end
|
343
|
+
|
344
|
+
# Formulate a redirect response: a 302 status with <tt>Location</tt> header
|
345
|
+
# and a blank body. Uses Helpers#URL to build the location from a controller
|
346
|
+
# route or path.
|
347
|
+
#
|
348
|
+
# So, given a root of <tt>http://localhost:3301/articles</tt>:
|
349
|
+
#
|
350
|
+
# redirect "view/12" # redirects to "//localhost:3301/articles/view/12"
|
351
|
+
# redirect View, 12 # redirects to "//localhost:3301/articles/view/12"
|
352
|
+
#
|
353
|
+
# <b>NOTE:</b> This method doesn't magically exit your methods and redirect.
|
354
|
+
# You'll need to <tt>return redirect(...)</tt> if this isn't the last statement
|
355
|
+
# in your code.
|
356
|
+
def redirect(*a)
|
357
|
+
r(302,'','Location'=>URL(*a))
|
358
|
+
end
|
359
|
+
|
360
|
+
# A quick means of setting this controller's status, body and headers.
|
361
|
+
# Used internally by Camping, but... by all means...
|
362
|
+
#
|
363
|
+
# r(302, '', 'Location' => self / "/view/12")
|
364
|
+
#
|
365
|
+
# Is equivalent to:
|
366
|
+
#
|
367
|
+
# redirect "/view/12"
|
368
|
+
#
|
369
|
+
def r(s, b, h = {}); @status = s; @headers.merge!(h); @body = b; end
|
370
|
+
|
371
|
+
# Turn a controller into an array. This is designed to be used to pipe
|
372
|
+
# controllers into the <tt>r</tt> method. A great way to forward your
|
373
|
+
# requests!
|
374
|
+
#
|
375
|
+
# class Read < '/(\d+)'
|
376
|
+
# def get(id)
|
377
|
+
# Post.find(id)
|
378
|
+
# rescue
|
379
|
+
# r *Blog.get(:NotFound, @env.REQUEST_URI)
|
380
|
+
# end
|
381
|
+
# end
|
382
|
+
#
|
383
|
+
def to_a;[@status, @body, @headers] end
|
384
|
+
|
385
|
+
def initialize(r, e, m) #:nodoc:
|
386
|
+
e = H[e.to_hash]
|
387
|
+
@status, @method, @env, @headers, @root = 200, m.downcase, e,
|
388
|
+
{'Content-Type'=>'text/html'}, e.SCRIPT_NAME.sub(/\/$/,'')
|
389
|
+
@k = C.kp(e.HTTP_COOKIE)
|
390
|
+
qs = C.qsp(e.QUERY_STRING)
|
391
|
+
@in = r
|
392
|
+
if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.match(e.CONTENT_TYPE)
|
393
|
+
b = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
|
394
|
+
until @in.eof?
|
395
|
+
fh=H[]
|
396
|
+
for l in @in
|
397
|
+
case l
|
398
|
+
when Z: break
|
399
|
+
when /^Content-Disposition: form-data;/
|
400
|
+
fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten]
|
401
|
+
when /^Content-Type: (.+?)(\r$|\Z)/m
|
402
|
+
puts "=> fh[type] = #$1"
|
403
|
+
fh[:type] = $1
|
404
|
+
end
|
405
|
+
end
|
406
|
+
fn=fh[:name]
|
407
|
+
o=if fh[:filename]
|
408
|
+
o=fh[:tempfile]=Tempfile.new(:C)
|
409
|
+
o.binmode
|
410
|
+
else
|
411
|
+
fh=""
|
412
|
+
end
|
413
|
+
while l=@in.read(16384)
|
414
|
+
if l=~b
|
415
|
+
o<<$`.chomp
|
416
|
+
@in.seek(-$'.size,IO::SEEK_CUR)
|
417
|
+
break
|
418
|
+
end
|
419
|
+
o<<l
|
420
|
+
end
|
421
|
+
C.qsp(fn,'&;',fh,qs) if fn
|
422
|
+
fh[:tempfile].rewind if fh.is_a?H
|
423
|
+
end
|
424
|
+
elsif @method == "post"
|
425
|
+
qs.merge!(C.qsp(@in.read))
|
426
|
+
end
|
427
|
+
@cookies, @input = @k.dup, qs.dup
|
428
|
+
end
|
429
|
+
|
430
|
+
# All requests pass through this method before going to the controller. Some magic
|
431
|
+
# in Camping can be performed by overriding this method.
|
432
|
+
#
|
433
|
+
# See http://code.whytheluckystiff.net/camping/wiki/BeforeAndAfterOverrides for more
|
434
|
+
# on before and after overrides with Camping.
|
435
|
+
def service(*a)
|
436
|
+
@body = send(@method, *a) if respond_to? @method
|
437
|
+
@headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil]
|
438
|
+
self
|
439
|
+
end
|
440
|
+
|
441
|
+
# Used by the web server to convert the current request to a string. If you need to
|
442
|
+
# alter the way Camping builds HTTP headers, consider overriding this method.
|
443
|
+
def to_s
|
444
|
+
a=[]
|
445
|
+
@headers.map{|k,v|[*v].map{|x|a<<"#{k}: #{x}"}}
|
446
|
+
"Status: #{@status}#{Z+a*Z+Z*2+@body}"
|
447
|
+
end
|
448
|
+
|
449
|
+
end
|
450
|
+
|
451
|
+
# Controllers is a module for placing classes which handle URLs. This is done
|
452
|
+
# by defining a route to each class using the Controllers::R method.
|
453
|
+
#
|
454
|
+
# module Camping::Controllers
|
455
|
+
# class Edit < R '/edit/(\d+)'
|
456
|
+
# def get; end
|
457
|
+
# def post; end
|
458
|
+
# end
|
459
|
+
# end
|
460
|
+
#
|
461
|
+
# If no route is set, Camping will guess the route from the class name.
|
462
|
+
# The rule is very simple: the route becomes a slash followed by the lowercased
|
463
|
+
# class name. See Controllers::D for the complete rules of dispatch.
|
464
|
+
#
|
465
|
+
# == Special classes
|
466
|
+
#
|
467
|
+
# There are two special classes used for handling 404 and 500 errors. The
|
468
|
+
# NotFound class handles URLs not found. The ServerError class handles exceptions
|
469
|
+
# uncaught by your application.
|
470
|
+
module Controllers
|
471
|
+
@r = []
|
472
|
+
class << self
|
473
|
+
def r #:nodoc:
|
474
|
+
@r
|
475
|
+
end
|
476
|
+
# Add routes to a controller class by piling them into the R method.
|
477
|
+
#
|
478
|
+
# module Camping::Controllers
|
479
|
+
# class Edit < R '/edit/(\d+)', '/new'
|
480
|
+
# def get(id)
|
481
|
+
# if id # edit
|
482
|
+
# else # new
|
483
|
+
# end
|
484
|
+
# end
|
485
|
+
# end
|
486
|
+
# end
|
487
|
+
#
|
488
|
+
# You will need to use routes in either of these cases:
|
489
|
+
#
|
490
|
+
# * You want to assign multiple routes to a controller.
|
491
|
+
# * You want your controller to receive arguments.
|
492
|
+
#
|
493
|
+
# Most of the time the rules inferred by dispatch method Controllers::D will get you
|
494
|
+
# by just fine.
|
495
|
+
def R *u
|
496
|
+
r=@r
|
497
|
+
Class.new {
|
498
|
+
meta_def(:urls){u}
|
499
|
+
meta_def(:inherited){|x|r<<x}
|
500
|
+
}
|
501
|
+
end
|
502
|
+
|
503
|
+
# Dispatch routes to controller classes.
|
504
|
+
# For each class, routes are checked for a match based on their order in the routing list
|
505
|
+
# given to Controllers::R. If no routes were given, the dispatcher uses a slash followed
|
506
|
+
# by the name of the controller lowercased.
|
507
|
+
#
|
508
|
+
# Controllers are searched in this order:
|
509
|
+
#
|
510
|
+
# # Classes without routes, since they refer to a very specific URL.
|
511
|
+
# # Classes with routes are searched in order of their creation.
|
512
|
+
#
|
513
|
+
# So, define your catch-all controllers last.
|
514
|
+
def D(path)
|
515
|
+
r.map { |k|
|
516
|
+
k.urls.map { |x|
|
517
|
+
return k, $~[1..-1] if path =~ /^#{x}\/?$/
|
518
|
+
}
|
519
|
+
}
|
520
|
+
[NotFound, [path]]
|
521
|
+
end
|
522
|
+
|
523
|
+
# The route maker, this is called by Camping internally, you shouldn't need to call it.
|
524
|
+
#
|
525
|
+
# Still, it's worth know what this method does. Since Ruby doesn't keep track of class
|
526
|
+
# creation order, we're keeping an internal list of the controllers which inherit from R().
|
527
|
+
# This method goes through and adds all the remaining routes to the beginning of the list
|
528
|
+
# and ensures all the controllers have the right mixins.
|
529
|
+
#
|
530
|
+
# Anyway, if you are calling the URI dispatcher from outside of a Camping server, you'll
|
531
|
+
# definitely need to call this at least once to set things up.
|
532
|
+
def M
|
533
|
+
def M #:nodoc:
|
534
|
+
end
|
535
|
+
constants.map { |c|
|
536
|
+
k=const_get(c)
|
537
|
+
k.send :include,C,Base,Models
|
538
|
+
r[0,0]=k if !r.include?k
|
539
|
+
k.meta_def(:urls){["/#{c.downcase}"]}if !k.respond_to?:urls
|
540
|
+
}
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
# The NotFound class is a special controller class for handling 404 errors, in case you'd
|
545
|
+
# like to alter the appearance of the 404. The path is passed in as +p+.
|
546
|
+
#
|
547
|
+
# module Camping::Controllers
|
548
|
+
# class NotFound
|
549
|
+
# def get(p)
|
550
|
+
# @status = 404
|
551
|
+
# div do
|
552
|
+
# h1 'Camping Problem!'
|
553
|
+
# h2 "#{p} not found"
|
554
|
+
# end
|
555
|
+
# end
|
556
|
+
# end
|
557
|
+
# end
|
558
|
+
#
|
559
|
+
class NotFound < R()
|
560
|
+
def get(p)
|
561
|
+
r(404, Mab.new{h1(P);h2("#{p} not found")})
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
# The ServerError class is a special controller class for handling many (but not all) 500 errors.
|
566
|
+
# If there is a parse error in Camping or in your application's source code, it will not be caught
|
567
|
+
# by Camping. The controller class +k+ and request method +m+ (GET, POST, etc.) where the error
|
568
|
+
# took place are passed in, along with the Exception +e+ which can be mined for useful info.
|
569
|
+
#
|
570
|
+
# module Camping::Controllers
|
571
|
+
# class ServerError
|
572
|
+
# def get(k,m,e)
|
573
|
+
# @status = 500
|
574
|
+
# div do
|
575
|
+
# h1 'Camping Problem!'
|
576
|
+
# h2 "in #{k}.#{m}"
|
577
|
+
# h3 "#{e.class} #{e.message}:"
|
578
|
+
# ul do
|
579
|
+
# e.backtrace.each do |bt|
|
580
|
+
# li bt
|
581
|
+
# end
|
582
|
+
# end
|
583
|
+
# end
|
584
|
+
# end
|
585
|
+
# end
|
586
|
+
# end
|
587
|
+
#
|
588
|
+
class ServerError < R()
|
589
|
+
def get(k,m,e)
|
590
|
+
r(500, Mab.new {
|
591
|
+
h1(P)
|
592
|
+
h2 "#{k}.#{m}"
|
593
|
+
h3 "#{e.class} #{e.message}:"
|
594
|
+
ul { e.backtrace.each { |bt| li bt } }
|
595
|
+
}.to_s)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
X = Controllers
|
600
|
+
|
601
|
+
class << self
|
602
|
+
# When you are running many applications, you may want to create independent
|
603
|
+
# modules for each Camping application. Namespaces for each. Camping::goes
|
604
|
+
# defines a toplevel constant with the whole MVC rack inside.
|
605
|
+
#
|
606
|
+
# require 'camping'
|
607
|
+
# Camping.goes :Blog
|
608
|
+
#
|
609
|
+
# module Blog::Controllers; ... end
|
610
|
+
# module Blog::Models; ... end
|
611
|
+
# module Blog::Views; ... end
|
612
|
+
#
|
613
|
+
def goes(m)
|
614
|
+
eval S.gsub(/Camping/,m.to_s).gsub("A\pps = []","Cam\ping::Apps<<self"), TOPLEVEL_BINDING
|
615
|
+
end
|
616
|
+
|
617
|
+
# URL escapes a string.
|
618
|
+
#
|
619
|
+
# Camping.escape("I'd go to the museum straightway!")
|
620
|
+
# #=> "I%27d+go+to+the+museum+straightway%21"
|
621
|
+
#
|
622
|
+
def escape(s); s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ', '+') end
|
623
|
+
|
624
|
+
# Unescapes a URL-encoded string.
|
625
|
+
#
|
626
|
+
# Camping.un("I%27d+go+to+the+museum+straightway%21")
|
627
|
+
# #=> "I'd go to the museum straightway!"
|
628
|
+
#
|
629
|
+
def un(s); s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')} end
|
630
|
+
|
631
|
+
# Parses a query string into an Camping::H object.
|
632
|
+
#
|
633
|
+
# input = Camping.qsp("name=Philarp+Tremain&hair=sandy+blonde")
|
634
|
+
# input.name
|
635
|
+
# #=> "Philarp Tremaine"
|
636
|
+
#
|
637
|
+
# Also parses out the Hash-like syntax used in PHP and Rails and builds
|
638
|
+
# nested hashes from it.
|
639
|
+
#
|
640
|
+
# input = Camping.qsp("post[id]=1&post[user]=_why")
|
641
|
+
# #=> {'post' => {'id' => '1', 'user' => '_why'}}
|
642
|
+
#
|
643
|
+
def qsp(qs, d='&;', y=nil, z=H[])
|
644
|
+
m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
|
645
|
+
(qs||'').
|
646
|
+
split(/[#{d}] */n).
|
647
|
+
inject((b,z=z,H[])[0]) { |h,p| k, v=un(p).split('=',2)
|
648
|
+
h.u(k.split(/[\]\[]+/).reverse.
|
649
|
+
inject(y||v) { |x,i| H[i,x] },&m)
|
650
|
+
}
|
651
|
+
end
|
652
|
+
|
653
|
+
# Parses a string of cookies from the <tt>Cookie</tt> header.
|
654
|
+
def kp(s); c = qsp(s, ';,'); end
|
655
|
+
|
656
|
+
# Fields a request through Camping. For traditional CGI applications, the method can be
|
657
|
+
# executed without arguments.
|
658
|
+
#
|
659
|
+
# if __FILE__ == $0
|
660
|
+
# Camping::Models::Base.establish_connection :adapter => 'sqlite3',
|
661
|
+
# :database => 'blog3.db'
|
662
|
+
# Camping::Models::Base.logger = Logger.new('camping.log')
|
663
|
+
# puts Camping.run
|
664
|
+
# end
|
665
|
+
#
|
666
|
+
# The Camping controller returned from <tt>run</tt> has a <tt>to_s</tt> method in case you
|
667
|
+
# are running from CGI or want to output the full HTTP output. In the above example, <tt>puts</tt>
|
668
|
+
# will call <tt>to_s</tt> for you.
|
669
|
+
#
|
670
|
+
# For FastCGI and Webrick-loaded applications, you will need to use a request loop, with <tt>run</tt>
|
671
|
+
# at the center, passing in the read +r+ and write +w+ streams. You will also need to mimick or
|
672
|
+
# pass in the <tt>ENV</tt> replacement as part of your wrapper.
|
673
|
+
#
|
674
|
+
# See Camping::FastCGI and Camping::WEBrick for examples.
|
675
|
+
#
|
676
|
+
def run(r=$stdin,e=ENV)
|
677
|
+
X.M
|
678
|
+
k,a=X.D un("/#{e['PATH_INFO']}".gsub(/\/+/,'/'))
|
679
|
+
k.new(r,e,(m=e['REQUEST_METHOD']||"GET")).Y.service *a
|
680
|
+
rescue Object=>x
|
681
|
+
X::ServerError.new(r,e,'get').service(k,m,x)
|
682
|
+
end
|
683
|
+
|
684
|
+
# The Camping scriptable dispatcher. Any unhandled method call to the app module will
|
685
|
+
# be sent to a controller class, specified as an argument.
|
686
|
+
#
|
687
|
+
# Blog.get(:Index)
|
688
|
+
# #=> #<Blog::Controllers::Index ... >
|
689
|
+
#
|
690
|
+
# The controller object contains all the @cookies, @body, @headers, etc. formulated by
|
691
|
+
# the response.
|
692
|
+
#
|
693
|
+
# You can also feed environment variables and query variables as a hash, the final
|
694
|
+
# argument.
|
695
|
+
#
|
696
|
+
# Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'})
|
697
|
+
# #=> #<Blog::Controllers::Login @user=... >
|
698
|
+
#
|
699
|
+
# Blog.get(:Info, :env => {:HTTP_HOST => 'wagon'})
|
700
|
+
# #=> #<Blog::Controllers::Info @env={'HTTP_HOST'=>'wagon'} ...>
|
701
|
+
#
|
702
|
+
def method_missing(m, c, *a)
|
703
|
+
X.M
|
704
|
+
k = X.const_get(c).new(StringIO.new,
|
705
|
+
H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s)
|
706
|
+
H.new(a.pop).each { |e,f| k.send("#{e}=",f) } if Hash === a[-1]
|
707
|
+
k.service *a
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
# Models is an empty Ruby module for housing model classes derived
|
712
|
+
# from ActiveRecord::Base. As a shortcut, you may derive from Base
|
713
|
+
# which is an alias for ActiveRecord::Base.
|
714
|
+
#
|
715
|
+
# module Camping::Models
|
716
|
+
# class Post < Base; belongs_to :user end
|
717
|
+
# class User < Base; has_many :posts end
|
718
|
+
# end
|
719
|
+
#
|
720
|
+
# == Where Models are Used
|
721
|
+
#
|
722
|
+
# Models are used in your controller classes. However, if your model class
|
723
|
+
# name conflicts with a controller class name, you will need to refer to it
|
724
|
+
# using the Models module.
|
725
|
+
#
|
726
|
+
# module Camping::Controllers
|
727
|
+
# class Post < R '/post/(\d+)'
|
728
|
+
# def get(post_id)
|
729
|
+
# @post = Models::Post.find post_id
|
730
|
+
# render :index
|
731
|
+
# end
|
732
|
+
# end
|
733
|
+
# end
|
734
|
+
#
|
735
|
+
# Models cannot be referred to in Views at this time.
|
736
|
+
module Models
|
737
|
+
autoload :Base,'camping/db'
|
738
|
+
def Y;self;end
|
739
|
+
end
|
740
|
+
|
741
|
+
# Views is an empty module for storing methods which create HTML. The HTML is described
|
742
|
+
# using the Markaby language.
|
743
|
+
#
|
744
|
+
# == Using the layout method
|
745
|
+
#
|
746
|
+
# If your Views module has a <tt>layout</tt> method defined, it will be called with a block
|
747
|
+
# which will insert content from your view.
|
748
|
+
module Views; include Controllers, Helpers end
|
749
|
+
|
750
|
+
# The Mab class wraps Markaby, allowing it to run methods from Camping::Views
|
751
|
+
# and also to replace :href, :action and :src attributes in tags by prefixing the root
|
752
|
+
# path.
|
753
|
+
class Mab < Markaby::Builder
|
754
|
+
include Views
|
755
|
+
def tag!(*g,&b)
|
756
|
+
h=g[-1]
|
757
|
+
[:href,:action,:src].each{|a|(h[a]=self/h[a])rescue 0}
|
758
|
+
super
|
759
|
+
end
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|