picnic 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+