camping 1.5.180 → 2.0.rc0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|