picnic 0.5.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.
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class PicnicTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_truth
9
+ assert true
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/picnic'
@@ -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
+