camping 1.5.180 → 2.0.rc0
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.
- data/CHANGELOG +35 -0
- data/README +43 -68
- data/Rakefile +155 -86
- data/bin/camping +64 -246
- data/book/01_introduction +19 -0
- data/book/02_getting_started +443 -0
- data/book/51_upgrading +93 -0
- data/doc/api.html +1953 -0
- data/doc/book.html +73 -0
- data/doc/book/01_introduction.html +57 -0
- data/doc/book/02_getting_started.html +573 -0
- data/doc/book/51_upgrading.html +146 -0
- data/doc/created.rid +1 -0
- data/{extras → doc/images}/Camping.gif +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/{extras → doc/images}/permalink.gif +0 -0
- data/doc/index.html +148 -0
- data/doc/js/camping.js +79 -0
- data/doc/js/jquery.js +32 -0
- data/doc/rdoc.css +117 -0
- data/examples/blog.rb +280 -181
- data/extras/images/badge.gif +0 -0
- data/extras/images/boys-life.png +0 -0
- data/extras/images/deerputer.png +0 -0
- data/extras/images/diagram.png +0 -0
- data/extras/images/hill.png +0 -0
- data/extras/images/i-wish.png +0 -0
- data/extras/images/latl.png +0 -0
- data/extras/images/little-wheels.png +0 -0
- data/extras/images/square-badge.png +0 -0
- data/extras/images/uniform.png +0 -0
- data/extras/images/whale-bounce.png +0 -0
- data/extras/rdoc/generator/singledarkfish.rb +205 -0
- data/extras/rdoc/generator/template/flipbook/images/Camping.gif +0 -0
- data/extras/rdoc/generator/template/flipbook/images/loadingAnimation.gif +0 -0
- data/extras/rdoc/generator/template/flipbook/images/permalink.gif +0 -0
- data/extras/rdoc/generator/template/flipbook/js/camping.js +79 -0
- data/extras/rdoc/generator/template/flipbook/js/jquery.js +32 -0
- data/extras/rdoc/generator/template/flipbook/page.rhtml +30 -0
- data/extras/rdoc/generator/template/flipbook/rdoc.css +117 -0
- data/extras/rdoc/generator/template/flipbook/readme.rhtml +31 -0
- data/extras/rdoc/generator/template/flipbook/reference.rhtml +71 -0
- data/extras/rdoc/generator/template/flipbook/toc.rhtml +43 -0
- data/lib/camping-unabridged.rb +420 -481
- data/lib/camping.rb +40 -55
- data/lib/camping/{db.rb → ar.rb} +5 -8
- data/lib/camping/mab.rb +26 -0
- data/lib/camping/reloader.rb +175 -147
- data/lib/camping/server.rb +178 -0
- data/lib/camping/session.rb +34 -121
- data/test/apps/env_debug.rb +65 -0
- data/test/apps/forms.rb +95 -0
- data/test/apps/forward_to_other_controller.rb +60 -0
- data/test/apps/migrations.rb +97 -0
- data/test/apps/misc.rb +86 -0
- data/test/apps/sessions.rb +38 -0
- metadata +120 -80
- data/doc/camping.1.gz +0 -0
- data/examples/campsh.rb +0 -630
- data/examples/tepee.rb +0 -242
- data/extras/flipbook_rdoc.rb +0 -491
- data/lib/camping/fastcgi.rb +0 -244
- data/lib/camping/webrick.rb +0 -65
- data/test/test_xhtml_trans.rb +0 -55
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'irb'
|
2
|
+
require 'rack'
|
3
|
+
require 'camping/reloader'
|
4
|
+
|
5
|
+
# == The Camping Server (for development)
|
6
|
+
#
|
7
|
+
# Camping includes a pretty nifty server which is built for development.
|
8
|
+
# It follows these rules:
|
9
|
+
#
|
10
|
+
# * Load all Camping apps in a directory or a file.
|
11
|
+
# * Load new apps that appear in that directory or that file.
|
12
|
+
# * Mount those apps according to their name. (e.g. Blog is mounted at /blog.)
|
13
|
+
# * Run each app's <tt>create</tt> method upon startup.
|
14
|
+
# * Reload the app if its modification time changes.
|
15
|
+
# * Reload the app if it requires any files under the same directory and one
|
16
|
+
# of their modification times changes.
|
17
|
+
# * Support the X-Sendfile header.
|
18
|
+
#
|
19
|
+
# Run it like this:
|
20
|
+
#
|
21
|
+
# camping examples/ # Mounts all apps in that directory
|
22
|
+
# camping blog.rb # Mounts Blog at /
|
23
|
+
#
|
24
|
+
# And visit http://localhost:3301/ in your browser.
|
25
|
+
class Camping::Server
|
26
|
+
attr_reader :reloader
|
27
|
+
attr_accessor :conf
|
28
|
+
|
29
|
+
def initialize(conf, paths)
|
30
|
+
@conf = conf
|
31
|
+
@paths = paths
|
32
|
+
@reloader = Camping::Reloader.new
|
33
|
+
connect(@conf.database) if @conf.database
|
34
|
+
end
|
35
|
+
|
36
|
+
def connect(db)
|
37
|
+
unless Camping.autoload?(:Models)
|
38
|
+
Camping::Models::Base.establish_connection(db)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_scripts
|
43
|
+
scripts = @paths.map do |path|
|
44
|
+
case
|
45
|
+
when File.file?(path)
|
46
|
+
path
|
47
|
+
when File.directory?(path)
|
48
|
+
Dir[File.join(path, '*.rb')]
|
49
|
+
end
|
50
|
+
end.flatten.compact
|
51
|
+
@reloader.update(*scripts)
|
52
|
+
end
|
53
|
+
|
54
|
+
def index_page(apps)
|
55
|
+
welcome = "You are Camping"
|
56
|
+
header = <<-HTML
|
57
|
+
<html>
|
58
|
+
<head>
|
59
|
+
<title>#{welcome}</title>
|
60
|
+
<style type="text/css">
|
61
|
+
body {
|
62
|
+
font-family: verdana, arial, sans-serif;
|
63
|
+
padding: 10px 40px;
|
64
|
+
margin: 0;
|
65
|
+
}
|
66
|
+
h1, h2, h3, h4, h5, h6 {
|
67
|
+
font-family: utopia, georgia, serif;
|
68
|
+
}
|
69
|
+
</style>
|
70
|
+
</head>
|
71
|
+
<body>
|
72
|
+
<h1>#{welcome}</h1>
|
73
|
+
HTML
|
74
|
+
footer = '</body></html>'
|
75
|
+
main = if apps.empty?
|
76
|
+
"<p>Good day. I'm sorry, but I could not find any Camping apps. "\
|
77
|
+
"You might want to take a look at the console to see if any errors "\
|
78
|
+
"have been raised.</p>"
|
79
|
+
else
|
80
|
+
"<p>Good day. These are the Camping apps you've mounted.</p><ul>" +
|
81
|
+
apps.map do |mount, app|
|
82
|
+
"<li><h3 style=\"display: inline\"><a href=\"/#{mount}\">#{app}</a></h3><small> / <a href=\"/code/#{mount}\">View source</a></small></li>"
|
83
|
+
end.join("\n") + '</ul>'
|
84
|
+
end
|
85
|
+
|
86
|
+
header + main + footer
|
87
|
+
end
|
88
|
+
|
89
|
+
def app
|
90
|
+
reload!
|
91
|
+
all_apps = apps
|
92
|
+
rapp = case all_apps.length
|
93
|
+
when 0
|
94
|
+
proc{|env|[200,{'Content-Type'=>'text/html'},index_page([])]}
|
95
|
+
when 1
|
96
|
+
apps.values.first
|
97
|
+
else
|
98
|
+
hash = {
|
99
|
+
"/" => proc {|env|[200,{'Content-Type'=>'text/html'},index_page(all_apps)]}
|
100
|
+
}
|
101
|
+
all_apps.each do |mount, wrapp|
|
102
|
+
# We're doing @reloader.reload! ourself, so we don't need the wrapper.
|
103
|
+
app = wrapp.app
|
104
|
+
hash["/#{mount}"] = app
|
105
|
+
hash["/code/#{mount}"] = proc do |env|
|
106
|
+
[200,{'Content-Type'=>'text/plain','X-Sendfile'=>wrapp.script.file},'']
|
107
|
+
end
|
108
|
+
end
|
109
|
+
Rack::URLMap.new(hash)
|
110
|
+
end
|
111
|
+
rapp = Rack::ContentLength.new(rapp)
|
112
|
+
rapp = Rack::Lint.new(rapp)
|
113
|
+
rapp = XSendfile.new(rapp)
|
114
|
+
rapp = Rack::ShowExceptions.new(rapp)
|
115
|
+
end
|
116
|
+
|
117
|
+
def apps
|
118
|
+
@reloader.apps.inject({}) do |h, (mount, wrapp)|
|
119
|
+
h[mount.to_s.downcase] = wrapp
|
120
|
+
h
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def call(env)
|
125
|
+
app.call(env)
|
126
|
+
end
|
127
|
+
|
128
|
+
def start
|
129
|
+
handler, conf = case @conf.server
|
130
|
+
when "console"
|
131
|
+
puts "** Starting console"
|
132
|
+
reload!
|
133
|
+
this = self; eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { this.reload!; nil }
|
134
|
+
ARGV.clear
|
135
|
+
IRB.start
|
136
|
+
exit
|
137
|
+
when "mongrel"
|
138
|
+
puts "** Starting Mongrel on #{@conf.host}:#{@conf.port}"
|
139
|
+
[Rack::Handler::Mongrel, {:Port => @conf.port, :Host => @conf.host}]
|
140
|
+
when "webrick"
|
141
|
+
puts "** Starting WEBrick on #{@conf.host}:#{@conf.port}"
|
142
|
+
[Rack::Handler::WEBrick, {:Port => @conf.port, :BindAddress => @conf.host}]
|
143
|
+
end
|
144
|
+
reload!
|
145
|
+
handler.run(self, conf)
|
146
|
+
end
|
147
|
+
|
148
|
+
def reload!
|
149
|
+
find_scripts
|
150
|
+
@reloader.reload!
|
151
|
+
end
|
152
|
+
|
153
|
+
# A Rack middleware for reading X-Sendfile. Should only be used in
|
154
|
+
# development.
|
155
|
+
class XSendfile
|
156
|
+
|
157
|
+
HEADERS = [
|
158
|
+
"X-Sendfile",
|
159
|
+
"X-Accel-Redirect",
|
160
|
+
"X-LIGHTTPD-send-file"
|
161
|
+
]
|
162
|
+
|
163
|
+
def initialize(app)
|
164
|
+
@app = app
|
165
|
+
end
|
166
|
+
|
167
|
+
def call(env)
|
168
|
+
status, headers, body = @app.call(env)
|
169
|
+
headers = Rack::Utils::HeaderHash.new(headers)
|
170
|
+
if header = HEADERS.detect { |header| headers.include?(header) }
|
171
|
+
path = headers[header]
|
172
|
+
body = File.read(path)
|
173
|
+
headers['Content-Length'] = body.length.to_s
|
174
|
+
end
|
175
|
+
[status, headers, body]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/lib/camping/session.rb
CHANGED
@@ -1,123 +1,36 @@
|
|
1
|
-
# == About camping/session.rb
|
2
|
-
#
|
3
|
-
# This file contains two modules which supply basic sessioning to your Camping app.
|
4
|
-
# Again, we're dealing with a pretty little bit of code: approx. 60 lines.
|
5
|
-
#
|
6
|
-
# * Camping::Models::Session is a module which adds a single <tt>sessions</tt> table
|
7
|
-
# to your database.
|
8
|
-
# * Camping::Session is a module which you will mix into your application (or into
|
9
|
-
# specific controllers which require sessions) to supply a <tt>@state</tt> variable
|
10
|
-
# you can use in controllers and views.
|
11
|
-
#
|
12
|
-
# For a basic tutorial, see the *Getting Started* section of the Camping::Session module.
|
13
|
-
require 'camping'
|
14
|
-
|
15
|
-
module Camping::Models
|
16
|
-
# A database table for storing Camping sessions. Contains a unique 32-character hashid, a
|
17
|
-
# creation timestamp, and a column of serialized data called <tt>ivars</tt>.
|
18
|
-
class Session < Base
|
19
|
-
serialize :ivars
|
20
|
-
def []=(k, v) # :nodoc:
|
21
|
-
self.ivars[k] = v
|
22
|
-
end
|
23
|
-
def [](k) # :nodoc:
|
24
|
-
self.ivars[k] rescue nil
|
25
|
-
end
|
26
|
-
|
27
|
-
RAND_CHARS = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']
|
28
|
-
|
29
|
-
# Generates a new session ID and creates a row for the new session in the database.
|
30
|
-
def self.generate cookies
|
31
|
-
rand_max = RAND_CHARS.size
|
32
|
-
sid = (0...32).inject("") { |ret,_| ret << RAND_CHARS[rand(rand_max)] }
|
33
|
-
sess = Session.create :hashid => sid, :ivars => Camping::H[]
|
34
|
-
cookies.camping_sid = sess.hashid
|
35
|
-
sess
|
36
|
-
end
|
37
|
-
|
38
|
-
# Gets the existing session based on the <tt>camping_sid</tt> available in cookies.
|
39
|
-
# If none is found, generates a new session.
|
40
|
-
def self.persist cookies
|
41
|
-
if cookies.camping_sid
|
42
|
-
session = Camping::Models::Session.find_by_hashid cookies.camping_sid
|
43
|
-
end
|
44
|
-
unless session
|
45
|
-
session = Camping::Models::Session.generate cookies
|
46
|
-
end
|
47
|
-
session
|
48
|
-
end
|
49
|
-
|
50
|
-
# Builds the session table in the database. To be used in your application's
|
51
|
-
# <tt>create</tt> method.
|
52
|
-
#
|
53
|
-
# Like so:
|
54
|
-
#
|
55
|
-
# def Blog.create
|
56
|
-
# Camping::Models::Session.create_schema
|
57
|
-
# unless Blog::Models::Post.table_exists?
|
58
|
-
# ActiveRecord::Schema.define(&Blog::Models.schema)
|
59
|
-
# end
|
60
|
-
# end
|
61
|
-
#
|
62
|
-
def self.create_schema
|
63
|
-
unless table_exists?
|
64
|
-
ActiveRecord::Schema.define do
|
65
|
-
create_table :sessions, :force => true do |t|
|
66
|
-
t.column :id, :integer, :null => false
|
67
|
-
t.column :hashid, :string, :limit => 32
|
68
|
-
t.column :created_at, :datetime
|
69
|
-
t.column :ivars, :text
|
70
|
-
end
|
71
|
-
end
|
72
|
-
reset_column_information
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
1
|
module Camping
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
# ==
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
hash_before = Marshal.dump(@state).hash
|
112
|
-
s = super(*a)
|
113
|
-
if session
|
114
|
-
hash_after = Marshal.dump(@state).hash
|
115
|
-
unless hash_before == hash_after
|
116
|
-
session[app] = @state
|
117
|
-
session.save
|
118
|
-
end
|
119
|
-
end
|
120
|
-
s
|
2
|
+
# == Getting Started
|
3
|
+
#
|
4
|
+
# To get sessions working for your application:
|
5
|
+
# 1. <tt>require 'camping/session'</tt>
|
6
|
+
# 2. Mixin the module: <tt>include Camping::Session</tt>
|
7
|
+
# 3. Define a secret (and keep it secret): <tt>secret "SECRET!"</tt>
|
8
|
+
# 4. Throughout your application, use the <tt>@state</tt> var like a hash
|
9
|
+
# to store your application's data.
|
10
|
+
#
|
11
|
+
# require 'camping/session' # 1
|
12
|
+
#
|
13
|
+
# module Nuts
|
14
|
+
# include Camping::Session # 2
|
15
|
+
# secret "Oh yeah!" # 3
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# == Other backends
|
19
|
+
#
|
20
|
+
# Camping only ships with session-cookies. However, the <tt>@state</tt>
|
21
|
+
# variable is simply a shortcut for <tt>@env['rack.session']</tt>. Therefore
|
22
|
+
# you can also use any middleware which sets this variable:
|
23
|
+
#
|
24
|
+
# module Nuts
|
25
|
+
# use Rack::Session::Memcache
|
26
|
+
# end
|
27
|
+
module Session
|
28
|
+
def self.included(app)
|
29
|
+
key = "#{app}.state".downcase
|
30
|
+
secret = [__FILE__, File.mtime(__FILE__)].join(":")
|
31
|
+
|
32
|
+
app.meta_def(:secret) { |val| secret.replace(val) }
|
33
|
+
app.use Rack::Session::Cookie, :key => key, :secret => secret
|
121
34
|
end
|
122
|
-
end
|
123
|
-
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin require "rubygems" rescue LoadError end
|
4
|
+
require "camping"
|
5
|
+
|
6
|
+
Camping.goes :EnvDebug
|
7
|
+
|
8
|
+
module EnvDebug
|
9
|
+
module Controllers
|
10
|
+
class ShowEnv < R '/', '/(.*)'
|
11
|
+
def get(extra = nil)
|
12
|
+
@extra = extra
|
13
|
+
render :show_env
|
14
|
+
end
|
15
|
+
alias post get
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Views
|
20
|
+
def layout
|
21
|
+
html do
|
22
|
+
head{ title C }
|
23
|
+
body do
|
24
|
+
ul do
|
25
|
+
li{ a "show env", :href=>R(ShowEnv)}
|
26
|
+
end
|
27
|
+
p { yield }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def _print_hash(hash)
|
33
|
+
hash.keys.sort.each do |key|
|
34
|
+
value = hash[key]
|
35
|
+
pre "%30s: %s" % [key.inspect, value.inspect]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def show_env
|
40
|
+
b "extra: #{@extra.inspect}"
|
41
|
+
h2 "@status : #{@status.inspect}"
|
42
|
+
h2 "@method : #{@method.inspect}"
|
43
|
+
h2 "@root : #{@root.inspect}"
|
44
|
+
h2 "@env :"
|
45
|
+
_print_hash @env
|
46
|
+
h2 "@input : "
|
47
|
+
_print_hash @input
|
48
|
+
h2 "@headers :"
|
49
|
+
_print_hash @headers
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# For CGI
|
56
|
+
if $0 == __FILE__
|
57
|
+
EnvDebug.create if EnvDebug.respond_to? :create
|
58
|
+
if ARGV.any?
|
59
|
+
require 'camping/fastcgi'
|
60
|
+
#Dir.chdir('/var/camping/blog/')
|
61
|
+
Camping::FastCGI.start(EnvDebug)
|
62
|
+
else
|
63
|
+
puts EnvDebug.run
|
64
|
+
end
|
65
|
+
end
|
data/test/apps/forms.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "camping"
|
2
|
+
|
3
|
+
Camping.goes :Forms
|
4
|
+
|
5
|
+
module Forms
|
6
|
+
module Controllers
|
7
|
+
class Index < R '/'
|
8
|
+
def get; render :index end
|
9
|
+
end
|
10
|
+
class GetForm
|
11
|
+
def get; render :get_form; end
|
12
|
+
end
|
13
|
+
class GetFormResult
|
14
|
+
def get; render :form_result; end
|
15
|
+
end
|
16
|
+
class PostForm
|
17
|
+
def get; render :post_form; end
|
18
|
+
def post; render :form_result; end
|
19
|
+
end
|
20
|
+
class FileForm
|
21
|
+
def get; render :file_form; end
|
22
|
+
def post; render :form_result; end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Views
|
27
|
+
def layout
|
28
|
+
html do
|
29
|
+
head{ title C }
|
30
|
+
body do
|
31
|
+
ul do
|
32
|
+
li{ a "index", :href=>R(Index)}
|
33
|
+
li{ a "get form", :href=>R(GetForm)}
|
34
|
+
li{ a "post form", :href=>R(PostForm)}
|
35
|
+
li{ a "file form", :href=>R(FileForm)}
|
36
|
+
end
|
37
|
+
p { yield }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def form_result
|
43
|
+
p @input.inspect
|
44
|
+
end
|
45
|
+
|
46
|
+
def index
|
47
|
+
h1 "Welcome on the Camping test app"
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_form
|
51
|
+
form :action=>R(GetFormResult), :method=>:get do
|
52
|
+
label "Give me your name!", :for=>:name
|
53
|
+
input :type=>:text, :name=>:name; br
|
54
|
+
input :type=>:text, :name=>"arr[]"; br
|
55
|
+
input :type=>:text, :name=>"arr[]"; br
|
56
|
+
input :type=>:text, :name=>"hash[x]"; br
|
57
|
+
input :type=>:text, :name=>"hash[y]"; br
|
58
|
+
|
59
|
+
input :type=>:submit
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def post_form
|
64
|
+
form :action=>R(PostForm), :method=>:post do
|
65
|
+
label "Give me your name!", :for=>:name
|
66
|
+
input :type=>:text, :name=>:name; br
|
67
|
+
input :type=>:text, :name=>"arr[]"; br
|
68
|
+
input :type=>:text, :name=>"arr[]"; br
|
69
|
+
input :type=>:text, :name=>"hash[x]"; br
|
70
|
+
input :type=>:text, :name=>"hash[y]"; br
|
71
|
+
|
72
|
+
input :type=>:submit
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def file_form
|
77
|
+
form :action=>R(FileForm), :method=>:post, :enctype=>"multipart/form-data" do
|
78
|
+
input :type=>:text, :name=>"arr"
|
79
|
+
input :type=>:text, :name=>"arr"; br
|
80
|
+
input :type=>:file, :name=>"first_file"; br
|
81
|
+
input :type=>:file, :name=>"files[]"; br
|
82
|
+
input :type=>:file, :name=>"files[]"; br
|
83
|
+
input :type=>:submit
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# For CGI
|
92
|
+
if $0 == __FILE__
|
93
|
+
Forms.create
|
94
|
+
Forms.run
|
95
|
+
end
|