picnic 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|