bmarzolf-picnic 0.8.0.20090420

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG.txt +1 -0
  2. data/History.txt +78 -0
  3. data/LICENSE.txt +165 -0
  4. data/Manifest.txt +45 -0
  5. data/README.txt +31 -0
  6. data/Rakefile +64 -0
  7. data/lib/picnic/authentication.rb +254 -0
  8. data/lib/picnic/cli.rb +165 -0
  9. data/lib/picnic/conf.rb +135 -0
  10. data/lib/picnic/controllers.rb +4 -0
  11. data/lib/picnic/logger.rb +41 -0
  12. data/lib/picnic/server.rb +99 -0
  13. data/lib/picnic/service_control.rb +274 -0
  14. data/lib/picnic/version.rb +9 -0
  15. data/lib/picnic.rb +11 -0
  16. data/setup.rb +1585 -0
  17. data/test/picnic_test.rb +11 -0
  18. data/test/test_helper.rb +2 -0
  19. data/vendor/camping-2.0.20090420/CHANGELOG +118 -0
  20. data/vendor/camping-2.0.20090420/COPYING +18 -0
  21. data/vendor/camping-2.0.20090420/README +82 -0
  22. data/vendor/camping-2.0.20090420/Rakefile +180 -0
  23. data/vendor/camping-2.0.20090420/bin/camping +97 -0
  24. data/vendor/camping-2.0.20090420/doc/camping.1.gz +0 -0
  25. data/vendor/camping-2.0.20090420/examples/README +5 -0
  26. data/vendor/camping-2.0.20090420/examples/blog.rb +375 -0
  27. data/vendor/camping-2.0.20090420/examples/campsh.rb +629 -0
  28. data/vendor/camping-2.0.20090420/examples/tepee.rb +242 -0
  29. data/vendor/camping-2.0.20090420/extras/Camping.gif +0 -0
  30. data/vendor/camping-2.0.20090420/extras/permalink.gif +0 -0
  31. data/vendor/camping-2.0.20090420/lib/camping/ar/session.rb +132 -0
  32. data/vendor/camping-2.0.20090420/lib/camping/ar.rb +78 -0
  33. data/vendor/camping-2.0.20090420/lib/camping/mab.rb +26 -0
  34. data/vendor/camping-2.0.20090420/lib/camping/reloader.rb +184 -0
  35. data/vendor/camping-2.0.20090420/lib/camping/server.rb +159 -0
  36. data/vendor/camping-2.0.20090420/lib/camping/session.rb +75 -0
  37. data/vendor/camping-2.0.20090420/lib/camping-unabridged.rb +630 -0
  38. data/vendor/camping-2.0.20090420/lib/camping.rb +52 -0
  39. data/vendor/camping-2.0.20090420/setup.rb +1551 -0
  40. data/vendor/camping-2.0.20090420/test/apps/env_debug.rb +65 -0
  41. data/vendor/camping-2.0.20090420/test/apps/forms.rb +95 -0
  42. data/vendor/camping-2.0.20090420/test/apps/misc.rb +86 -0
  43. data/vendor/camping-2.0.20090420/test/apps/sessions.rb +38 -0
  44. data/vendor/camping-2.0.20090420/test/test_camping.rb +54 -0
  45. metadata +140 -0
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/ruby
2
+ $:.unshift File.dirname(__FILE__) + "/../lib"
3
+ %w(rubygems redcloth camping camping/ar acts_as_versioned).each { |lib| require lib }
4
+
5
+ Camping.goes :Tepee
6
+
7
+ module Tepee::Models
8
+
9
+ class Page < Base
10
+ PAGE_LINK = /\[\[([^\]|]*)[|]?([^\]]*)\]\]/
11
+ validates_uniqueness_of :title
12
+ before_save { |r| r.title = r.title.underscore }
13
+ acts_as_versioned
14
+ end
15
+
16
+ class CreateTepee < V 1.0
17
+ def self.up
18
+ create_table :tepee_pages do |t|
19
+ t.column :title, :string, :limit => 255
20
+ t.column :body, :text
21
+ end
22
+ Page.create_versioned_table
23
+ Page.reset_column_information
24
+ end
25
+ def self.down
26
+ drop_table :tepee_pages
27
+ Page.drop_versioned_table
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ module Tepee::Controllers
34
+ class Index < R '/'
35
+ def get
36
+ redirect Show, 'home'
37
+ end
38
+ end
39
+
40
+ class Show < R '/(\w+)', '/(\w+)/(\d+)'
41
+ def get page_name, version = nil
42
+ redirect(Edit, page_name, 1) and return unless @page = Page.find_by_title(page_name)
43
+ @version = (version.nil? or version == @page.version.to_s) ? @page : @page.versions.find_by_version(version)
44
+ render :show
45
+ end
46
+ end
47
+
48
+ class Edit < R '/(\w+)/edit', '/(\w+)/(\d+)/edit'
49
+ def get page_name, version = nil
50
+ @page = Page.find_or_create_by_title(page_name)
51
+ @page = @page.versions.find_by_version(version) unless version.nil? or version == @page.version.to_s
52
+ render :edit
53
+ end
54
+
55
+ def post page_name
56
+ Page.find_or_create_by_title(page_name).update_attributes :body => input.post_body and redirect Show, page_name
57
+ end
58
+ end
59
+
60
+ class Versions < R '/(\w+)/versions'
61
+ def get page_name
62
+ @page = Page.find_or_create_by_title(page_name)
63
+ @versions = @page.versions
64
+ render :versions
65
+ end
66
+ end
67
+
68
+ class List < R '/all/list'
69
+ def get
70
+ @pages = Page.find :all, :order => 'title'
71
+ render :list
72
+ end
73
+ end
74
+
75
+ class Stylesheet < R '/css/tepee.css'
76
+ def get
77
+ @headers['Content-Type'] = 'text/css'
78
+ File.read(__FILE__).gsub(/.*__END__/m, '')
79
+ end
80
+ end
81
+ end
82
+
83
+ module Tepee::Views
84
+ def layout
85
+ html do
86
+ head do
87
+ title 'test'
88
+ link :href=>R(Stylesheet), :rel=>'stylesheet', :type=>'text/css'
89
+ end
90
+ style <<-END, :type => 'text/css'
91
+ body {
92
+ font-family: verdana, arial, sans-serif;
93
+ }
94
+ h1, h2, h3, h4, h5 {
95
+ font-weight: normal;
96
+ }
97
+ p.actions a {
98
+ margin-right: 6px;
99
+ }
100
+ END
101
+ body do
102
+ p do
103
+ small do
104
+ span "welcome to " ; a 'tepee', :href => "http://code.whytheluckystiff.net/svn/camping/trunk/examples/tepee.rb"
105
+ span '. go ' ; a 'home', :href => R(Show, 'home')
106
+ span '. list all ' ; a 'pages', :href => R(List)
107
+ end
108
+ end
109
+ div.content do
110
+ self << yield
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def show
117
+ h1 @page.title
118
+ div { _markup @version.body }
119
+ p.actions do
120
+ _button 'edit', :href => R(Edit, @version.title, @version.version)
121
+ _button 'back', :href => R(Show, @version.title, @version.version-1) unless @version.version == 1
122
+ _button 'next', :href => R(Show, @version.title, @version.version+1) unless @version.version == @page.version
123
+ _button 'current', :href => R(Show, @version.title) unless @version.version == @page.version
124
+ _button 'versions', :href => R(Versions, @page.title)
125
+ end
126
+ end
127
+
128
+ def edit
129
+ h1 @page.title
130
+ form :method => 'post', :action => R(Edit, @page.title) do
131
+ p do
132
+ textarea @page.body, :name => 'post_body', :rows => 50, :cols => 100
133
+ end
134
+ input :type => 'submit', :value=>'change'
135
+ end
136
+ _button 'cancel', :href => R(Show, @page.title, @page.version)
137
+ a 'syntax', :href => 'http://hobix.com/textile/', :target=>'_blank'
138
+ end
139
+
140
+ def list
141
+ h1 'all pages'
142
+ ul { @pages.each { |p| li { a p.title, :href => R(Show, p.title) } } }
143
+ end
144
+
145
+ def versions
146
+ h1 @page.title
147
+ ul do
148
+ @versions.each do |page|
149
+ li do
150
+ span page.version
151
+ _button 'show', :href => R(Show, page.title, page.version)
152
+ _button 'edit', :href => R(Edit, page.title, page.version)
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def _button(text, options={})
159
+ form :method=>:get, :action=>options[:href] do
160
+ input :type=>'submit', :name=>'submit', :value=>text
161
+ end
162
+ end
163
+
164
+ def _markup body
165
+ return '' if body.blank?
166
+ body.gsub!(Tepee::Models::Page::PAGE_LINK) do
167
+ page = title = $1
168
+ title = $2 unless $2.empty?
169
+ page = page.gsub /\W/, '_'
170
+ if Tepee::Models::Page.find(:all, :select => 'title').collect { |p| p.title }.include?(page)
171
+ %Q{<a href="#{self/R(Show, page)}">#{title}</a>}
172
+ else
173
+ %Q{<span>#{title}<a href="#{self/R(Edit, page, 1)}">?</a></span>}
174
+ end
175
+ end
176
+ RedCloth.new(body, [ :hard_breaks ]).to_html
177
+ end
178
+ end
179
+
180
+ def Tepee.create
181
+ Tepee::Models.create_schema :assume => (Tepee::Models::Page.table_exists? ? 1.0 : 0.0)
182
+ end
183
+ __END__
184
+ /** focus **/
185
+ /*
186
+ a:hover:active {
187
+ color: #10bae0;
188
+ }
189
+
190
+ a:not(:hover):active {
191
+ color: #0000ff;
192
+ }
193
+
194
+ *:focus {
195
+ -moz-outline: 2px solid #10bae0 !important;
196
+ -moz-outline-offset: 1px !important;
197
+ -moz-outline-radius: 3px !important;
198
+ }
199
+
200
+ button:focus,
201
+ input[type="reset"]:focus,
202
+ input[type="button"]:focus,
203
+ input[type="submit"]:focus,
204
+ input[type="file"] > input[type="button"]:focus {
205
+ -moz-outline-radius: 5px !important;
206
+ }
207
+
208
+ button:focus::-moz-focus-inner {
209
+ border-color: transparent !important;
210
+ }
211
+
212
+ button::-moz-focus-inner,
213
+ input[type="reset"]::-moz-focus-inner,
214
+ input[type="button"]::-moz-focus-inner,
215
+ input[type="submit"]::-moz-focus-inner,
216
+ input[type="file"] > input[type="button"]::-moz-focus-inner {
217
+ border: 1px dotted transparent !important;
218
+ }
219
+ textarea:focus, button:focus, select:focus, input:focus {
220
+ -moz-outline-offset: -1px !important;
221
+ }
222
+ input[type="radio"]:focus {
223
+ -moz-outline-radius: 12px;
224
+ -moz-outline-offset: 0px !important;
225
+ }
226
+ a:focus {
227
+ -moz-outline-offset: 0px !important;
228
+ }
229
+ */
230
+ form { display: inline; }
231
+
232
+ /** Gradient **/
233
+ small, pre, textarea, textfield, button, input, select {
234
+ color: #4B4B4C !important;
235
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAMAAAAxfD/2AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAtUExURfT09PLy8vHx8fv7+/j4+PX19fn5+fr6+vf39/z8/Pb29vPz8/39/f7+/v///0c8Y4oAAAA5SURBVHjaXMZJDgAgCMDAuouA/3+uHPRiMmlKzmhCFRorLOakVnpnDEpBBDHM8ODs/bz372+PAAMAXIQCfD6uIDsAAAAASUVORK5CYII=) !important;
236
+ background-color: #FFF !important;
237
+ background-repeat: repeat-x !important;
238
+ border: 1px solid #CCC !important;
239
+ }
240
+
241
+ button, input { margin: 3px; }
242
+
@@ -0,0 +1,132 @@
1
+ # == About camping/ar/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::ARSession 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::ARSession module.
13
+ require 'camping'
14
+ require 'camping/ar'
15
+
16
+ module Camping::Models
17
+ # A database table for storing Camping sessions. Contains a unique 32-character hashid, a
18
+ # creation timestamp, and a column of serialized data called <tt>ivars</tt>.
19
+ class Session < Base
20
+ serialize :ivars
21
+ set_primary_key :hashid
22
+
23
+ def []=(k, v) # :nodoc:
24
+ self.ivars[k] = v
25
+ end
26
+ def [](k) # :nodoc:
27
+ self.ivars[k] rescue nil
28
+ end
29
+
30
+ protected
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
37
+
38
+ # Generates a new session ID and creates a row for the new session in the database.
39
+ def self.generate cookies
40
+ sess = Session.create :ivars => Camping::H[]
41
+ cookies.camping_sid = sess.hashid
42
+ sess
43
+ end
44
+
45
+ # Gets the existing session based on the <tt>camping_sid</tt> available in cookies.
46
+ # If none is found, generates a new session.
47
+ def self.persist cookies
48
+ session = nil
49
+ if cookies.camping_sid
50
+ session = Camping::Models::Session.find_by_hashid cookies.camping_sid
51
+ end
52
+ unless session
53
+ session = Camping::Models::Session.generate cookies
54
+ end
55
+ session
56
+ end
57
+
58
+ # Builds the session table in the database. To be used in your application's
59
+ # <tt>create</tt> method.
60
+ #
61
+ # Like so:
62
+ #
63
+ # def Blog.create
64
+ # Camping::Models::Session.create_schema
65
+ # unless Blog::Models::Post.table_exists?
66
+ # ActiveRecord::Schema.define(&Blog::Models.schema)
67
+ # end
68
+ # end
69
+ #
70
+ def self.create_schema
71
+ unless table_exists?
72
+ ActiveRecord::Schema.define do
73
+ create_table :sessions, :force => true, :id => false do |t|
74
+ t.column :hashid, :string, :limit => 32, :null => false
75
+ t.column :created_at, :datetime
76
+ t.column :ivars, :text
77
+ end
78
+ add_index :sessions, [:hashid], :unique => true
79
+ end
80
+ reset_column_information
81
+ end
82
+ end
83
+ end
84
+ Session.partial_updates = false if Session.respond_to?(:partial_updates=)
85
+ end
86
+
87
+ module Camping
88
+ # The Camping::ARSession module is designed to be mixed into your application or into specific
89
+ # controllers which require sessions. This module defines a <tt>service</tt> method which
90
+ # intercepts all requests handed to those controllers.
91
+ #
92
+ # == Getting Started
93
+ #
94
+ # To get sessions working for your application:
95
+ #
96
+ # 1. <tt>require 'camping/session'</tt>
97
+ # 2. Mixin the module: <tt>module YourApp; include Camping::ARSession end</tt>
98
+ # 3. In your application's <tt>create</tt> method, add a call to <tt>Camping::Models::Session.create_schema</tt>
99
+ # 4. Throughout your application, use the <tt>@state</tt> var like a hash to store your application's data.
100
+ #
101
+ # If you are unfamiliar with the <tt>create</tt> method, see
102
+ # http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
103
+ #
104
+ # == A Few Notes
105
+ #
106
+ # * The session ID is stored in a cookie. Look in <tt>@cookies.camping_sid</tt>.
107
+ # * The session data is stored in the <tt>sessions</tt> table in your database.
108
+ # * All mounted Camping apps using this class will use the same database table.
109
+ # * However, your application's data is stored in its own hash.
110
+ # * Session data is only saved if it has changed.
111
+ module ARSession
112
+ # This <tt>service</tt> method, when mixed into controllers, intercepts requests
113
+ # and wraps them with code to start and close the session. If a session isn't found
114
+ # in the database it is created. The <tt>@state</tt> variable is set and if it changes,
115
+ # it is saved back into the database.
116
+ def service(*a)
117
+ session = Camping::Models::Session.persist @cookies
118
+ app = self.class.name.gsub(/^(\w+)::.+$/, '\1')
119
+ @state = (session[app] ||= Camping::H[])
120
+ hash_before = Marshal.dump(@state).hash
121
+ return super(*a)
122
+ ensure
123
+ if session
124
+ hash_after = Marshal.dump(@state).hash
125
+ unless hash_before == hash_after
126
+ session[app] = @state
127
+ session.save
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,78 @@
1
+ class MissingLibrary < Exception #:nodoc: all
2
+ end
3
+ begin
4
+ require 'active_record'
5
+ rescue LoadError => e
6
+ raise MissingLibrary, "ActiveRecord could not be loaded (is it installed?): #{e.message}"
7
+ end
8
+
9
+ $AR_EXTRAS = %{
10
+ Base = ActiveRecord::Base unless const_defined? :Base
11
+
12
+ def Y; ActiveRecord::Base.verify_active_connections!; self; end
13
+
14
+ class SchemaInfo < Base
15
+ end
16
+
17
+ def self.V(n)
18
+ @final = [n, @final.to_i].max
19
+ m = (@migrations ||= [])
20
+ Class.new(ActiveRecord::Migration) do
21
+ meta_def(:version) { n }
22
+ meta_def(:inherited) { |k| m << k }
23
+ end
24
+ end
25
+
26
+ def self.create_schema(opts = {})
27
+ opts[:assume] ||= 0
28
+ opts[:version] ||= @final
29
+ if @migrations
30
+ unless SchemaInfo.table_exists?
31
+ ActiveRecord::Schema.define do
32
+ create_table SchemaInfo.table_name do |t|
33
+ t.column :version, :float
34
+ end
35
+ end
36
+ end
37
+
38
+ si = SchemaInfo.find(:first) || SchemaInfo.new(:version => opts[:assume])
39
+ if si.version < opts[:version]
40
+ @migrations.each do |k|
41
+ k.migrate(:up) if si.version < k.version and k.version <= opts[:version]
42
+ k.migrate(:down) if si.version > k.version and k.version > opts[:version]
43
+ end
44
+ si.update_attributes(:version => opts[:version])
45
+ end
46
+ end
47
+ end
48
+ }
49
+
50
+ module Camping
51
+ module Models
52
+ A = ActiveRecord
53
+ # Base is an alias for ActiveRecord::Base. The big warning I'm going to give you
54
+ # about this: *Base overloads table_name_prefix.* This means that if you have a
55
+ # model class Blog::Models::Post, it's table name will be <tt>blog_posts</tt>.
56
+ #
57
+ # ActiveRecord is not loaded if you never reference this class. The minute you
58
+ # use the ActiveRecord or Camping::Models::Base class, then the ActiveRecord library
59
+ # is loaded.
60
+ Base = A::Base
61
+
62
+ # The default prefix for Camping model classes is the topmost module name lowercase
63
+ # and followed with an underscore.
64
+ #
65
+ # Tepee::Models::Page.table_name_prefix
66
+ # #=> "tepee_pages"
67
+ #
68
+ def Base.table_name_prefix
69
+ "#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'')
70
+ end
71
+ module_eval $AR_EXTRAS
72
+ end
73
+ end
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
+ 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,184 @@
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::Server 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
+ # reloader = Camping::Reloader.new('/path/to/yourapp.rb')
27
+ # blog = reloader.apps[:Blog]
28
+ # wiki = reloader.apps[:Wiki]
29
+ #
30
+ # The <tt>blog</tt> and <tt>wiki</tt> objects will behave exactly like your
31
+ # Blog and Wiki, but they will update themselves if yourapp.rb changes.
32
+ #
33
+ # You can also give Reloader more than one script.
34
+ class Reloader
35
+ attr_reader :scripts
36
+
37
+ # This is a simple wrapper which causes the script to reload (if needed)
38
+ # on any method call. Then the method call will be forwarded to the
39
+ # app.
40
+ class App # :nodoc:
41
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
42
+ attr_accessor :app, :script
43
+
44
+ def initialize(script)
45
+ @script = script
46
+ end
47
+
48
+ # Reloads if needed, before calling the method on the app.
49
+ def method_missing(meth, *args, &blk)
50
+ @script.reload!
51
+ @app.send(meth, *args, &blk)
52
+ end
53
+ end
54
+
55
+ # This class is doing all the hard work; however, it only works on
56
+ # single files. Reloader just wraps up support for multiple scripts
57
+ # and hides away some methods you normally won't need.
58
+ class Script # :nodoc:
59
+ attr_reader :apps, :file, :dir, :extras
60
+
61
+ def initialize(file)
62
+ @file = File.expand_path(file)
63
+ @dir = File.dirname(@file)
64
+ @extras = File.join(@dir, File.basename(@file, ".rb"))
65
+ @mtime = Time.at(0)
66
+ @requires = []
67
+ @apps = {}
68
+ end
69
+
70
+ # Loads the apps availble in this script. Use <tt>apps</tt> to get
71
+ # the loaded apps.
72
+ def load_apps
73
+ all_requires = $LOADED_FEATURES.dup
74
+ all_apps = Camping::Apps.dup
75
+
76
+ begin
77
+ load(@file)
78
+ rescue Exception => e
79
+ puts "!! Error loading #{@file}:"
80
+ puts "#{e.class}: #{e.message}"
81
+ puts e.backtrace
82
+ puts "!! Error loading #{@file}, see backtrace above"
83
+ end
84
+
85
+ @requires = ($LOADED_FEATURES - all_requires).map do |req|
86
+ full = full_path(req)
87
+ full if full == @file or full.index(@extras) == 0
88
+ end
89
+
90
+ @mtime = mtime
91
+
92
+ new_apps = (Camping::Apps - all_apps)
93
+ old_apps = @apps.dup
94
+ @apps = new_apps.inject({}) do |hash, app|
95
+ key = app.name.to_sym
96
+ hash[key] = (old = old_apps[key]) || App.new(self)
97
+ hash[key].app = app
98
+ app.create if app.respond_to?(:create) && !old
99
+ hash
100
+ end
101
+ self
102
+ end
103
+
104
+ # Removes all the apps defined in this script.
105
+ def remove_apps
106
+ @apps.each do |name, app|
107
+ Camping::Apps.delete(app.app)
108
+ Object.send :remove_const, name
109
+ end
110
+ end
111
+
112
+ # Reloads the file if needed. No harm is done by calling this multiple
113
+ # times, so feel free call just to be sure.
114
+ def reload!
115
+ return if @mtime >= mtime
116
+ remove_apps
117
+ load_apps
118
+ end
119
+
120
+ # Checks if both scripts watches the same file.
121
+ def ==(other)
122
+ @file == other.file
123
+ end
124
+
125
+ private
126
+
127
+ def mtime
128
+ (@requires + [@file]).compact.map do |fname|
129
+ File.mtime(fname)
130
+ end.reject{|t| t > Time.now }.max
131
+ end
132
+
133
+ # Figures out the full path of a required file.
134
+ def full_path(req)
135
+ dir = File.expand_path($LOAD_PATH.detect { |l| File.exists?(File.join(l, req)) })
136
+ File.join(dir, req)
137
+ end
138
+ end
139
+
140
+ # Creates the reloader, assigns a +script+ to it and initially loads the
141
+ # application. Pass in the full path to the script, otherwise the script
142
+ # will be loaded relative to the current working directory.
143
+ def initialize(*scripts)
144
+ @scripts = []
145
+ update(*scripts)
146
+ end
147
+
148
+ # Updates the reloader to only use the scripts provided:
149
+ #
150
+ # reloader.update("examples/blog.rb", "examples/wiki.rb")
151
+ def update(*scripts)
152
+ old = @scripts.dup
153
+ clear
154
+ @scripts = scripts.map do |script|
155
+ s = Script.new(script)
156
+ if pos = old.index(s)
157
+ # We already got a script, so we use the old (which might got a mtime)
158
+ old[pos]
159
+ else
160
+ s.load_apps
161
+ end
162
+ end
163
+ end
164
+
165
+ # Removes all the scripts from the reloader.
166
+ def clear
167
+ @scrips = []
168
+ end
169
+
170
+ # Simply calls reload! on all the Script objects.
171
+ def reload!
172
+ @scripts.each { |script| script.reload! }
173
+ end
174
+
175
+ # Returns a Hash of all the apps available in the scripts, where the key
176
+ # would be the name of the app (the one you gave to Camping.goes) and the
177
+ # value would be the app (wrapped inside App).
178
+ def apps
179
+ @scripts.inject({}) do |hash, script|
180
+ hash.merge(script.apps)
181
+ end
182
+ end
183
+ end
184
+ end