camping 1.5.180 → 2.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG +35 -0
  2. data/README +43 -68
  3. data/Rakefile +155 -86
  4. data/bin/camping +64 -246
  5. data/book/01_introduction +19 -0
  6. data/book/02_getting_started +443 -0
  7. data/book/51_upgrading +93 -0
  8. data/doc/api.html +1953 -0
  9. data/doc/book.html +73 -0
  10. data/doc/book/01_introduction.html +57 -0
  11. data/doc/book/02_getting_started.html +573 -0
  12. data/doc/book/51_upgrading.html +146 -0
  13. data/doc/created.rid +1 -0
  14. data/{extras → doc/images}/Camping.gif +0 -0
  15. data/doc/images/loadingAnimation.gif +0 -0
  16. data/{extras → doc/images}/permalink.gif +0 -0
  17. data/doc/index.html +148 -0
  18. data/doc/js/camping.js +79 -0
  19. data/doc/js/jquery.js +32 -0
  20. data/doc/rdoc.css +117 -0
  21. data/examples/blog.rb +280 -181
  22. data/extras/images/badge.gif +0 -0
  23. data/extras/images/boys-life.png +0 -0
  24. data/extras/images/deerputer.png +0 -0
  25. data/extras/images/diagram.png +0 -0
  26. data/extras/images/hill.png +0 -0
  27. data/extras/images/i-wish.png +0 -0
  28. data/extras/images/latl.png +0 -0
  29. data/extras/images/little-wheels.png +0 -0
  30. data/extras/images/square-badge.png +0 -0
  31. data/extras/images/uniform.png +0 -0
  32. data/extras/images/whale-bounce.png +0 -0
  33. data/extras/rdoc/generator/singledarkfish.rb +205 -0
  34. data/extras/rdoc/generator/template/flipbook/images/Camping.gif +0 -0
  35. data/extras/rdoc/generator/template/flipbook/images/loadingAnimation.gif +0 -0
  36. data/extras/rdoc/generator/template/flipbook/images/permalink.gif +0 -0
  37. data/extras/rdoc/generator/template/flipbook/js/camping.js +79 -0
  38. data/extras/rdoc/generator/template/flipbook/js/jquery.js +32 -0
  39. data/extras/rdoc/generator/template/flipbook/page.rhtml +30 -0
  40. data/extras/rdoc/generator/template/flipbook/rdoc.css +117 -0
  41. data/extras/rdoc/generator/template/flipbook/readme.rhtml +31 -0
  42. data/extras/rdoc/generator/template/flipbook/reference.rhtml +71 -0
  43. data/extras/rdoc/generator/template/flipbook/toc.rhtml +43 -0
  44. data/lib/camping-unabridged.rb +420 -481
  45. data/lib/camping.rb +40 -55
  46. data/lib/camping/{db.rb → ar.rb} +5 -8
  47. data/lib/camping/mab.rb +26 -0
  48. data/lib/camping/reloader.rb +175 -147
  49. data/lib/camping/server.rb +178 -0
  50. data/lib/camping/session.rb +34 -121
  51. data/test/apps/env_debug.rb +65 -0
  52. data/test/apps/forms.rb +95 -0
  53. data/test/apps/forward_to_other_controller.rb +60 -0
  54. data/test/apps/migrations.rb +97 -0
  55. data/test/apps/misc.rb +86 -0
  56. data/test/apps/sessions.rb +38 -0
  57. metadata +120 -80
  58. data/doc/camping.1.gz +0 -0
  59. data/examples/campsh.rb +0 -630
  60. data/examples/tepee.rb +0 -242
  61. data/extras/flipbook_rdoc.rb +0 -491
  62. data/lib/camping/fastcgi.rb +0 -244
  63. data/lib/camping/webrick.rb +0 -65
  64. 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
@@ -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
- # The Camping::Session module is designed to be mixed into your application or into specific
80
- # controllers which require sessions. This module defines a <tt>service</tt> method which
81
- # intercepts all requests handed to those controllers.
82
- #
83
- # == Getting Started
84
- #
85
- # To get sessions working for your application:
86
- #
87
- # 1. <tt>require 'camping/session'</tt>
88
- # 2. Mixin the module: <tt>module YourApp; include Camping::Session end</tt>
89
- # 3. In your application's <tt>create</tt> method, add a call to <tt>Camping::Models::Session.create_schema</tt>
90
- # 4. Throughout your application, use the <tt>@state</tt> var like a hash to store your application's data.
91
- #
92
- # If you are unfamiliar with the <tt>create</tt> method, see
93
- # http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
94
- #
95
- # == A Few Notes
96
- #
97
- # * The session ID is stored in a cookie. Look in <tt>@cookies.camping_sid</tt>.
98
- # * The session data is stored in the <tt>sessions</tt> table in your database.
99
- # * All mounted Camping apps using this class will use the same database table.
100
- # * However, your application's data is stored in its own hash.
101
- # * Session data is only saved if it has changed.
102
- module Session
103
- # This <tt>service</tt> method, when mixed into controllers, intercepts requests
104
- # and wraps them with code to start and close the session. If a session isn't found
105
- # in the database it is created. The <tt>@state</tt> variable is set and if it changes,
106
- # it is saved back into the database.
107
- def service(*a)
108
- session = Camping::Models::Session.persist @cookies
109
- app = self.class.name.gsub(/^(\w+)::.+$/, '\1')
110
- @state = (session[app] ||= Camping::H[])
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
@@ -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