bmarzolf-picnic 0.8.0.20090420

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.
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