picnic 0.7.1 → 0.8.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.
- data/History.txt +10 -0
- data/Manifest.txt +30 -13
- data/Rakefile +2 -0
- data/lib/picnic.rb +5 -120
- data/lib/picnic/authentication.rb +40 -4
- data/lib/picnic/cli.rb +86 -43
- data/lib/picnic/conf.rb +45 -43
- data/lib/picnic/logger.rb +41 -0
- data/lib/picnic/server.rb +99 -0
- data/lib/picnic/version.rb +2 -2
- data/picnic.gemspec +44 -0
- data/vendor/{camping-1.5.180 → camping-2.0.20090212}/CHANGELOG +17 -10
- data/vendor/{camping-1.5.180 → camping-2.0.20090212}/COPYING +0 -0
- data/vendor/{camping-1.5.180 → camping-2.0.20090212}/README +2 -2
- data/vendor/{camping-1.5.180 → camping-2.0.20090212}/Rakefile +62 -5
- data/vendor/camping-2.0.20090212/bin/camping +99 -0
- data/vendor/camping-2.0.20090212/doc/camping.1.gz +0 -0
- data/vendor/camping-2.0.20090212/examples/README +5 -0
- data/vendor/camping-2.0.20090212/examples/blog.rb +375 -0
- data/vendor/camping-2.0.20090212/examples/campsh.rb +629 -0
- data/vendor/camping-2.0.20090212/examples/tepee.rb +242 -0
- data/vendor/camping-2.0.20090212/extras/Camping.gif +0 -0
- data/vendor/camping-2.0.20090212/extras/flipbook_rdoc.rb +491 -0
- data/vendor/camping-2.0.20090212/extras/permalink.gif +0 -0
- data/vendor/{camping-1.5.180 → camping-2.0.20090212}/lib/camping-unabridged.rb +168 -294
- data/vendor/camping-2.0.20090212/lib/camping.rb +54 -0
- data/vendor/{camping-1.5.180/lib/camping/db.rb → camping-2.0.20090212/lib/camping/ar.rb} +4 -4
- data/vendor/{camping-1.5.180/lib/camping → camping-2.0.20090212/lib/camping/ar}/session.rb +23 -14
- data/vendor/camping-2.0.20090212/lib/camping/mab.rb +26 -0
- data/vendor/camping-2.0.20090212/lib/camping/reloader.rb +163 -0
- data/vendor/camping-2.0.20090212/lib/camping/server.rb +158 -0
- data/vendor/camping-2.0.20090212/lib/camping/session.rb +74 -0
- data/vendor/camping-2.0.20090212/setup.rb +1551 -0
- data/vendor/camping-2.0.20090212/test/apps/env_debug.rb +65 -0
- data/vendor/camping-2.0.20090212/test/apps/forms.rb +95 -0
- data/vendor/camping-2.0.20090212/test/apps/misc.rb +86 -0
- data/vendor/camping-2.0.20090212/test/apps/sessions.rb +38 -0
- data/vendor/camping-2.0.20090212/test/test_camping.rb +54 -0
- metadata +43 -16
- data/lib/picnic/postambles.rb +0 -292
- data/lib/picnic/utils.rb +0 -36
- data/vendor/camping-1.5.180/lib/camping.rb +0 -57
- data/vendor/camping-1.5.180/lib/camping/fastcgi.rb +0 -244
- data/vendor/camping-1.5.180/lib/camping/reloader.rb +0 -163
- data/vendor/camping-1.5.180/lib/camping/webrick.rb +0 -65
@@ -0,0 +1,54 @@
|
|
1
|
+
%w[uri stringio rack].map{|l|require l};class Object;def meta_def m,&b
|
2
|
+
(class<<self;self end).send:define_method,m,&b end end;module Camping;C=self
|
3
|
+
S=IO.read(__FILE__)rescue nil;P="<h1>Cam\ping Problem!</h1><h2>%s</h2>"
|
4
|
+
U=Rack::Utils;Apps=[];class H<Hash
|
5
|
+
def method_missing m,*a;m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end
|
6
|
+
undef id,type;end;module Helpers;def R c,*g
|
7
|
+
p,h=/\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"unless u=c.urls.find{|x|
|
8
|
+
break x if x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a|
|
9
|
+
x.sub p,U.escape((a[a.class.primary_key]rescue a))})}
|
10
|
+
h.any?? u+"?"+U.build_query(h[0]):u end;def / p
|
11
|
+
p[0]==?/?@root+p:p end;def URL c='/',*a;c=R(c, *a) if c.respond_to?:urls
|
12
|
+
c=self/c;c=@request.url[/.{8,}?(?=\/)/]+c if c[0]==?/;URI c end
|
13
|
+
end;module Base;attr_accessor:input,:cookies,:headers,:body,:status,:root
|
14
|
+
def render v,*a,&b;mab(/^_/!~v.to_s){send(v,*a,&b)} end
|
15
|
+
def mab l=nil,&b;m=Mab.new({},self);s=m.capture(&b)
|
16
|
+
s=m.capture{layout{s}} if l && m.respond_to?(:layout);s end
|
17
|
+
def r s,b,h={};b,h=h,b if Hash===b;@status=s;
|
18
|
+
@headers.merge!(h);@body=b;end;def redirect *a;r 302,'','Location'=>URL(*a).
|
19
|
+
to_s;end;def r404 p=env.PATH;r 404,P%"#{p} not found"end;def r500 k,m,x
|
20
|
+
r 500,P%"#{k}.#{m}"+"<h3>#{x.class} #{x.message}: <ul>#{x.
|
21
|
+
backtrace.map{|b|"<li>#{b}</li>"}}</ul></h3>"end;def r501 m=@method
|
22
|
+
r 501,P%"#{m.upcase} not implemented"end;def to_a
|
23
|
+
@response.body=@body.respond_to?(:each)?@body:""
|
24
|
+
@response.status=@status;@response.headers.merge!(@headers)
|
25
|
+
@cookies.each{|k,v|v={:value=>v,:path=>self/"/"} if String===v
|
26
|
+
@response.set_cookie(k,v) if @request.cookies[k]!=v}
|
27
|
+
@response.to_a;end;def initialize(env)
|
28
|
+
@request,@response,@env=Rack::Request.new(env),Rack::Response.new,env
|
29
|
+
@root,@input,@cookies,@headers,@status=
|
30
|
+
@env.SCRIPT_NAME.sub(/\/$/,''),H[@request.params],
|
31
|
+
H[@request.cookies],@response.headers,@response.status
|
32
|
+
@input.each{|k,v|if k[-2..-1]=="[]";@input[k[0..-3]]=
|
33
|
+
@input.delete(k)elsif k=~/(.*)\[([^\]]+)\]$/
|
34
|
+
(@input[$1]||=H[])[$2]=@input.delete(k)end};end;def service *a
|
35
|
+
r=catch(:halt){send(@env.REQUEST_METHOD.downcase,*a)};@body||=r
|
36
|
+
self;end;end;module Controllers;@r=[];class<<self;def r;@r end;def R *u;r=@r
|
37
|
+
Class.new{meta_def(:urls){u};meta_def(:inherited){|x|r<<x}}end
|
38
|
+
def D p,m;p='/'if !p||!p[0]
|
39
|
+
r.map{|k|k.urls.map{|x|return(k.instance_method(m)rescue nil)?
|
40
|
+
[k,m,*$~[1..-1]]:[I,'r501',m]if p=~/^#{x}\/?$/}};[I,'r404',p] end
|
41
|
+
N=H.new{|_,x|x.downcase}.merge! "N"=>'(\d+)',"X"=>'(\w+)',"Index"=>''
|
42
|
+
def M;def M;end;constants.map{|c|k=const_get(c)
|
43
|
+
k.send:include,C,Base,Helpers,Models;@r=[k]+r if r-[k]==r
|
44
|
+
k.meta_def(:urls){["/#{c.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}"]
|
45
|
+
}if !k.respond_to?:urls}end end;class I<R()
|
46
|
+
end; end;X=Controllers;class<<self;def goes m
|
47
|
+
Apps<<eval(S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING) end;def call(
|
48
|
+
e)X.M;e=H[e.to_hash];k,m,*a=X.D e.PATH_INFO,(e.REQUEST_METHOD||'get').downcase
|
49
|
+
e.REQUEST_METHOD=m;k.new(e).service(*a).to_a;end
|
50
|
+
def method_missing m,c,*a;X.M;h=Hash===a[-1]?H[a.pop]:{};e=
|
51
|
+
H[h[:env]||{}].merge!({'rack.input'=>StringIO.new,'REQUEST_METHOD'=>m.to_s})
|
52
|
+
k=X.const_get(c).new(H[e]);k.send("input=",h[:input])if h[:input]
|
53
|
+
k.service(*a);end;end;module Views;include X,Helpers end;module Models
|
54
|
+
autoload:Base,'camping/ar';def Y;self;end end;autoload:Mab,'camping/mab';C end
|
@@ -71,8 +71,8 @@ module Camping
|
|
71
71
|
module_eval $AR_EXTRAS
|
72
72
|
end
|
73
73
|
end
|
74
|
-
Camping::S.sub!
|
75
|
-
Camping::S.sub!
|
76
|
-
Camping::Apps.each do |
|
77
|
-
|
74
|
+
Camping::S.sub! /autoload\s*:Base\s*,\s*['"]camping\/ar['"]/, ""
|
75
|
+
Camping::S.sub! /def\s*Y[;\s]*self[;\s]*end/, $AR_EXTRAS
|
76
|
+
Camping::Apps.each do |c|
|
77
|
+
c::Models.module_eval $AR_EXTRAS
|
78
78
|
end
|
@@ -1,22 +1,25 @@
|
|
1
|
-
# == About camping/session.rb
|
1
|
+
# == About camping/ar/session.rb
|
2
2
|
#
|
3
3
|
# This file contains two modules which supply basic sessioning to your Camping app.
|
4
4
|
# Again, we're dealing with a pretty little bit of code: approx. 60 lines.
|
5
5
|
#
|
6
6
|
# * Camping::Models::Session is a module which adds a single <tt>sessions</tt> table
|
7
7
|
# to your database.
|
8
|
-
# * Camping::
|
8
|
+
# * Camping::ARSession is a module which you will mix into your application (or into
|
9
9
|
# specific controllers which require sessions) to supply a <tt>@state</tt> variable
|
10
10
|
# you can use in controllers and views.
|
11
11
|
#
|
12
|
-
# For a basic tutorial, see the *Getting Started* section of the Camping::
|
12
|
+
# For a basic tutorial, see the *Getting Started* section of the Camping::ARSession module.
|
13
13
|
require 'camping'
|
14
|
+
require 'camping/ar'
|
14
15
|
|
15
16
|
module Camping::Models
|
16
17
|
# A database table for storing Camping sessions. Contains a unique 32-character hashid, a
|
17
18
|
# creation timestamp, and a column of serialized data called <tt>ivars</tt>.
|
18
19
|
class Session < Base
|
19
20
|
serialize :ivars
|
21
|
+
set_primary_key :hashid
|
22
|
+
|
20
23
|
def []=(k, v) # :nodoc:
|
21
24
|
self.ivars[k] = v
|
22
25
|
end
|
@@ -24,13 +27,17 @@ class Session < Base
|
|
24
27
|
self.ivars[k] rescue nil
|
25
28
|
end
|
26
29
|
|
30
|
+
protected
|
27
31
|
RAND_CHARS = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']
|
32
|
+
def before_create
|
33
|
+
rand_max = RAND_CHARS.size
|
34
|
+
sid = (0...32).inject("") { |ret,_| ret << RAND_CHARS[rand(rand_max)] }
|
35
|
+
write_attribute('hashid', sid)
|
36
|
+
end
|
28
37
|
|
29
38
|
# Generates a new session ID and creates a row for the new session in the database.
|
30
39
|
def self.generate cookies
|
31
|
-
|
32
|
-
sid = (0...32).inject("") { |ret,_| ret << RAND_CHARS[rand(rand_max)] }
|
33
|
-
sess = Session.create :hashid => sid, :ivars => Camping::H[]
|
40
|
+
sess = Session.create :ivars => Camping::H[]
|
34
41
|
cookies.camping_sid = sess.hashid
|
35
42
|
sess
|
36
43
|
end
|
@@ -38,6 +45,7 @@ class Session < Base
|
|
38
45
|
# Gets the existing session based on the <tt>camping_sid</tt> available in cookies.
|
39
46
|
# If none is found, generates a new session.
|
40
47
|
def self.persist cookies
|
48
|
+
session = nil
|
41
49
|
if cookies.camping_sid
|
42
50
|
session = Camping::Models::Session.find_by_hashid cookies.camping_sid
|
43
51
|
end
|
@@ -62,21 +70,22 @@ class Session < Base
|
|
62
70
|
def self.create_schema
|
63
71
|
unless table_exists?
|
64
72
|
ActiveRecord::Schema.define do
|
65
|
-
create_table :sessions, :force => true do |t|
|
66
|
-
|
67
|
-
t.column :hashid, :string, :limit => 32
|
73
|
+
create_table :sessions, :force => true, :id => false do |t|
|
74
|
+
t.column :hashid, :string, :limit => 32, :null => false
|
68
75
|
t.column :created_at, :datetime
|
69
76
|
t.column :ivars, :text
|
70
77
|
end
|
78
|
+
add_index :sessions, [:hashid], :unique => true
|
71
79
|
end
|
72
80
|
reset_column_information
|
73
81
|
end
|
74
82
|
end
|
75
83
|
end
|
84
|
+
Session.partial_updates = false if Session.respond_to?(:partial_updates=)
|
76
85
|
end
|
77
86
|
|
78
87
|
module Camping
|
79
|
-
# The Camping::
|
88
|
+
# The Camping::ARSession module is designed to be mixed into your application or into specific
|
80
89
|
# controllers which require sessions. This module defines a <tt>service</tt> method which
|
81
90
|
# intercepts all requests handed to those controllers.
|
82
91
|
#
|
@@ -85,7 +94,7 @@ module Camping
|
|
85
94
|
# To get sessions working for your application:
|
86
95
|
#
|
87
96
|
# 1. <tt>require 'camping/session'</tt>
|
88
|
-
# 2. Mixin the module: <tt>module YourApp; include Camping::
|
97
|
+
# 2. Mixin the module: <tt>module YourApp; include Camping::ARSession end</tt>
|
89
98
|
# 3. In your application's <tt>create</tt> method, add a call to <tt>Camping::Models::Session.create_schema</tt>
|
90
99
|
# 4. Throughout your application, use the <tt>@state</tt> var like a hash to store your application's data.
|
91
100
|
#
|
@@ -99,7 +108,7 @@ module Camping
|
|
99
108
|
# * All mounted Camping apps using this class will use the same database table.
|
100
109
|
# * However, your application's data is stored in its own hash.
|
101
110
|
# * Session data is only saved if it has changed.
|
102
|
-
module
|
111
|
+
module ARSession
|
103
112
|
# This <tt>service</tt> method, when mixed into controllers, intercepts requests
|
104
113
|
# and wraps them with code to start and close the session. If a session isn't found
|
105
114
|
# in the database it is created. The <tt>@state</tt> variable is set and if it changes,
|
@@ -109,7 +118,8 @@ module Session
|
|
109
118
|
app = self.class.name.gsub(/^(\w+)::.+$/, '\1')
|
110
119
|
@state = (session[app] ||= Camping::H[])
|
111
120
|
hash_before = Marshal.dump(@state).hash
|
112
|
-
|
121
|
+
return super(*a)
|
122
|
+
ensure
|
113
123
|
if session
|
114
124
|
hash_after = Marshal.dump(@state).hash
|
115
125
|
unless hash_before == hash_after
|
@@ -117,7 +127,6 @@ module Session
|
|
117
127
|
session.save
|
118
128
|
end
|
119
129
|
end
|
120
|
-
s
|
121
130
|
end
|
122
131
|
end
|
123
132
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class MissingLibrary < Exception #:nodoc: all
|
2
|
+
end
|
3
|
+
begin
|
4
|
+
require 'markaby'
|
5
|
+
rescue LoadError => e
|
6
|
+
raise MissingLibrary, "Markaby could not be loaded (is it installed?): #{e.message}"
|
7
|
+
end
|
8
|
+
|
9
|
+
$MAB_CODE = %{
|
10
|
+
# The Mab class wraps Markaby, allowing it to run methods from Camping::Views
|
11
|
+
# and also to replace :href, :action and :src attributes in tags by prefixing the root
|
12
|
+
# path.
|
13
|
+
class Mab < Markaby::Builder
|
14
|
+
include Views
|
15
|
+
def tag!(*g,&b)
|
16
|
+
h=g[-1]
|
17
|
+
[:href,:action,:src].map{|a|(h[a]&&=self/h[a])rescue 0}
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
}
|
22
|
+
|
23
|
+
Camping::S.sub! /autoload\s*:Mab\s*,\s*['"]camping\/mab['"]/, $MAB_CODE
|
24
|
+
Camping::Apps.each do |c|
|
25
|
+
c.module_eval $MAB_CODE
|
26
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module Camping
|
2
|
+
# == The Camping Reloader
|
3
|
+
#
|
4
|
+
# Camping apps are generally small and predictable. Many Camping apps are
|
5
|
+
# contained within a single file. Larger apps are split into a handful of
|
6
|
+
# other Ruby libraries within the same directory.
|
7
|
+
#
|
8
|
+
# Since Camping apps (and their dependencies) are loaded with Ruby's require
|
9
|
+
# method, there is a record of them in $LOADED_FEATURES. Which leaves a
|
10
|
+
# perfect space for this class to manage auto-reloading an app if any of its
|
11
|
+
# immediate dependencies changes.
|
12
|
+
#
|
13
|
+
# == Wrapping Your Apps
|
14
|
+
#
|
15
|
+
# Since bin/camping and the Camping::FastCGI class already use the Reloader,
|
16
|
+
# you probably don't need to hack it on your own. But, if you're rolling your
|
17
|
+
# own situation, here's how.
|
18
|
+
#
|
19
|
+
# Rather than this:
|
20
|
+
#
|
21
|
+
# require 'yourapp'
|
22
|
+
#
|
23
|
+
# Use this:
|
24
|
+
#
|
25
|
+
# require 'camping/reloader'
|
26
|
+
# Camping::Reloader.new('/path/to/yourapp.rb')
|
27
|
+
#
|
28
|
+
# The reloader will take care of requiring the app and monitoring all files
|
29
|
+
# for alterations.
|
30
|
+
class Reloader
|
31
|
+
attr_accessor :klass, :mtime, :mount, :requires
|
32
|
+
|
33
|
+
# Creates the reloader, assigns a +script+ to it and initially loads the
|
34
|
+
# application. Pass in the full path to the script, otherwise the script
|
35
|
+
# will be loaded relative to the current working directory.
|
36
|
+
def initialize(script)
|
37
|
+
@script = File.expand_path(script)
|
38
|
+
@mount = File.basename(script, '.rb')
|
39
|
+
@requires = nil
|
40
|
+
load_app
|
41
|
+
end
|
42
|
+
|
43
|
+
# Find the application, based on the script name.
|
44
|
+
def find_app(title)
|
45
|
+
@klass = Object.const_get(Object.constants.grep(/^#{title}$/i)[0]) rescue nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# If the file isn't found, if we need to remove the app from the global
|
49
|
+
# namespace, this will be sure to do so and set @klass to nil.
|
50
|
+
def remove_app
|
51
|
+
if @klass
|
52
|
+
Camping::Apps.delete(@klass)
|
53
|
+
Object.send :remove_const, @klass.name
|
54
|
+
@klass = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Loads (or reloads) the application. The reloader will take care of calling
|
59
|
+
# this for you. You can certainly call it yourself if you feel it's warranted.
|
60
|
+
def load_app
|
61
|
+
title = File.basename(@script)[/^([\w_]+)/,1].gsub /_/,''
|
62
|
+
begin
|
63
|
+
all_requires = $LOADED_FEATURES.dup
|
64
|
+
load @script
|
65
|
+
@requires = ($LOADED_FEATURES - all_requires).select do |req|
|
66
|
+
req.index(File.basename(@script) + "/") == 0 || req.index(title + "/") == 0
|
67
|
+
end
|
68
|
+
rescue Exception => e
|
69
|
+
puts "!! trouble loading #{title.inspect}: [#{e.class}] #{e.message}"
|
70
|
+
puts e.backtrace.join("\n")
|
71
|
+
find_app title
|
72
|
+
remove_app
|
73
|
+
return
|
74
|
+
end
|
75
|
+
|
76
|
+
@mtime = mtime
|
77
|
+
find_app title
|
78
|
+
unless @klass and @klass.const_defined? :C
|
79
|
+
puts "!! trouble loading #{title.inspect}: not a Camping app, no #{title.capitalize} module found"
|
80
|
+
remove_app
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
Reloader.conditional_connect
|
85
|
+
@klass.create if @klass.respond_to? :create
|
86
|
+
puts "** #{title.inspect} app loaded"
|
87
|
+
@klass
|
88
|
+
end
|
89
|
+
|
90
|
+
# The timestamp of the most recently modified app dependency.
|
91
|
+
def mtime
|
92
|
+
((@requires || []) + [@script]).map do |fname|
|
93
|
+
fname = fname.gsub(/^#{Regexp::quote File.dirname(@script)}\//, '')
|
94
|
+
begin
|
95
|
+
File.mtime(File.join(File.dirname(@script), fname))
|
96
|
+
rescue Errno::ENOENT
|
97
|
+
remove_app
|
98
|
+
@mtime
|
99
|
+
end
|
100
|
+
end.reject{|t| t > Time.now }.max
|
101
|
+
end
|
102
|
+
|
103
|
+
# Conditional reloading of the app. This gets called on each request and
|
104
|
+
# only reloads if the modification times on any of the files is updated.
|
105
|
+
def reload_app
|
106
|
+
return if @klass and @mtime and mtime <= @mtime
|
107
|
+
|
108
|
+
if @requires
|
109
|
+
@requires.each { |req| $LOADED_FEATURES.delete(req) }
|
110
|
+
end
|
111
|
+
remove_app
|
112
|
+
load_app
|
113
|
+
end
|
114
|
+
|
115
|
+
# Conditionally reloads (using reload_app.) Then passes the request through
|
116
|
+
# to the wrapped Camping app.
|
117
|
+
def call(*a)
|
118
|
+
reload_app
|
119
|
+
if @klass
|
120
|
+
@klass.call(*a)
|
121
|
+
else
|
122
|
+
Camping.call(*a)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns source code for the main script in the application.
|
127
|
+
def view_source
|
128
|
+
File.read(@script)
|
129
|
+
end
|
130
|
+
|
131
|
+
class << self
|
132
|
+
attr_writer :database, :log
|
133
|
+
|
134
|
+
def conditional_connect
|
135
|
+
# If database models are present, `autoload?` will return nil.
|
136
|
+
unless Camping::Models.autoload? :Base
|
137
|
+
if @database and @database[:adapter] == 'sqlite3'
|
138
|
+
begin
|
139
|
+
require 'sqlite3_api'
|
140
|
+
rescue LoadError
|
141
|
+
puts "!! Your SQLite3 adapter isn't a compiled extension."
|
142
|
+
abort "!! Please check out http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for tips."
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
case @log
|
147
|
+
when Logger
|
148
|
+
Camping::Models::Base.logger = @log
|
149
|
+
when String
|
150
|
+
require 'logger'
|
151
|
+
Camping::Models::Base.logger = Logger.new(@log == "-" ? STDOUT : @log)
|
152
|
+
end
|
153
|
+
|
154
|
+
Camping::Models::Base.establish_connection @database if @database
|
155
|
+
|
156
|
+
if Camping::Models.const_defined?(:Session)
|
157
|
+
Camping::Models::Session.create_schema
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'irb'
|
2
|
+
require 'rack'
|
3
|
+
require 'camping/reloader'
|
4
|
+
|
5
|
+
module Camping::Server
|
6
|
+
class Base < Hash
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :paths
|
10
|
+
attr_accessor :conf
|
11
|
+
|
12
|
+
def initialize(conf, paths = [])
|
13
|
+
unless conf.database
|
14
|
+
raise "!! No home directory found. Please specify a database file, see --help."
|
15
|
+
end
|
16
|
+
|
17
|
+
@conf = conf
|
18
|
+
Camping::Reloader.database = conf.database
|
19
|
+
Camping::Reloader.log = conf.log
|
20
|
+
|
21
|
+
@paths = []
|
22
|
+
paths.each { |script| add_app script }
|
23
|
+
# TODO exception instead of abort()
|
24
|
+
# abort("** No apps successfully loaded") unless self.detect { |app| app.klass }
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_app(path)
|
29
|
+
@paths << path
|
30
|
+
if File.directory? path
|
31
|
+
Dir[File.join(path, '*.rb')].each { |s| insert_app(s)}
|
32
|
+
else
|
33
|
+
insert_app(path)
|
34
|
+
end
|
35
|
+
# TODO check to see if the application is created or not... exception perhaps?
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_new_scripts
|
39
|
+
self.values.each { |app| app.reload_app }
|
40
|
+
@paths.each do |path|
|
41
|
+
Dir[File.join(path, '*.rb')].each do |script|
|
42
|
+
smount = File.basename(script, '.rb')
|
43
|
+
next if detect { |x| x.mount == smount }
|
44
|
+
|
45
|
+
puts "** Discovered new #{script}"
|
46
|
+
# TODO hmm. the next should be handled by the add_app thingy
|
47
|
+
app = insert_app(script)
|
48
|
+
next unless app
|
49
|
+
|
50
|
+
yield app
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
self.values.sort! { |x, y| x.mount <=> y.mount }
|
55
|
+
end
|
56
|
+
def index_page
|
57
|
+
welcome = "You are Camping"
|
58
|
+
apps = self
|
59
|
+
<<-HTML
|
60
|
+
<html>
|
61
|
+
<head>
|
62
|
+
<title>#{welcome}</title>
|
63
|
+
<style type="text/css">
|
64
|
+
body {
|
65
|
+
font-family: verdana, arial, sans-serif;
|
66
|
+
padding: 10px 40px;
|
67
|
+
margin: 0;
|
68
|
+
}
|
69
|
+
h1, h2, h3, h4, h5, h6 {
|
70
|
+
font-family: utopia, georgia, serif;
|
71
|
+
}
|
72
|
+
</style>
|
73
|
+
</head>
|
74
|
+
<body>
|
75
|
+
<h1>#{welcome}</h1>
|
76
|
+
<p>Good day. These are the Camping apps you've mounted.</p>
|
77
|
+
<ul>
|
78
|
+
#{apps.values.select{|app|app.klass}.map do |app|
|
79
|
+
"<li><h3 style=\"display: inline\"><a href=\"/#{app.mount}\">#{app.klass.name}</a></h3><small> / <a href=\"/code/#{app.mount}\">View source</a></small></li>"
|
80
|
+
end.join("\n")}
|
81
|
+
</ul>
|
82
|
+
</body>
|
83
|
+
</html>
|
84
|
+
HTML
|
85
|
+
end
|
86
|
+
|
87
|
+
def each(&b)
|
88
|
+
self.values.each(&b)
|
89
|
+
end
|
90
|
+
|
91
|
+
# for RSpec tests
|
92
|
+
def apps
|
93
|
+
self.values
|
94
|
+
end
|
95
|
+
|
96
|
+
def start
|
97
|
+
handler, conf = case @conf.server
|
98
|
+
when "console"
|
99
|
+
ARGV.clear
|
100
|
+
IRB.start
|
101
|
+
exit
|
102
|
+
when "mongrel"
|
103
|
+
puts "** Starting Mongrel on #{@conf.host}:#{@conf.port}"
|
104
|
+
[Rack::Handler::Mongrel, {:Port => @conf.port, :Host => @conf.host}]
|
105
|
+
when "webrick"
|
106
|
+
[Rack::Handler::WEBrick, {:Port => @conf.port, :BindAddress => @conf.host}]
|
107
|
+
end
|
108
|
+
|
109
|
+
rapp = if apps.length > 1
|
110
|
+
hash = {
|
111
|
+
"/" => proc {|env|[200,{'Content-Type'=>'text/html'},index_page]}
|
112
|
+
}
|
113
|
+
apps.each do |app|
|
114
|
+
hash["/#{app.mount}"] = app
|
115
|
+
hash["/code/#{app.mount}"] = proc do |env|
|
116
|
+
[200,{'Content-Type'=>'text/plain'},app.view_source]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
Rack::URLMap.new(hash)
|
120
|
+
else
|
121
|
+
apps.first
|
122
|
+
end
|
123
|
+
rapp = Rack::Lint.new(rapp)
|
124
|
+
rapp = XSendfile.new(rapp)
|
125
|
+
rapp = Rack::ShowExceptions.new(rapp)
|
126
|
+
handler.run(rapp, conf)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def insert_app(script)
|
132
|
+
self[script] = Camping::Reloader.new(script)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# A Rack middleware for reading X-Sendfile. Should only be used in
|
137
|
+
# development.
|
138
|
+
class XSendfile
|
139
|
+
|
140
|
+
HEADERS = [
|
141
|
+
"X-Sendfile",
|
142
|
+
"X-Accel-Redirect",
|
143
|
+
"X-LIGHTTPD-send-file"
|
144
|
+
]
|
145
|
+
|
146
|
+
def initialize(app)
|
147
|
+
@app = app
|
148
|
+
end
|
149
|
+
|
150
|
+
def call(env)
|
151
|
+
status, headers, body = @app.call(env)
|
152
|
+
if path = headers.values_at(*HEADERS).compact.first
|
153
|
+
body = File.read(path)
|
154
|
+
end
|
155
|
+
[status, headers, body]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|