rubycut-sinatra-contrib 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +136 -0
  3. data/Rakefile +75 -0
  4. data/ideas.md +29 -0
  5. data/lib/sinatra/capture.rb +124 -0
  6. data/lib/sinatra/config_file.rb +167 -0
  7. data/lib/sinatra/content_for.rb +125 -0
  8. data/lib/sinatra/contrib.rb +39 -0
  9. data/lib/sinatra/contrib/all.rb +2 -0
  10. data/lib/sinatra/contrib/setup.rb +53 -0
  11. data/lib/sinatra/contrib/version.rb +17 -0
  12. data/lib/sinatra/cookies.rb +331 -0
  13. data/lib/sinatra/decompile.rb +120 -0
  14. data/lib/sinatra/engine_tracking.rb +96 -0
  15. data/lib/sinatra/extension.rb +95 -0
  16. data/lib/sinatra/json.rb +130 -0
  17. data/lib/sinatra/link_header.rb +132 -0
  18. data/lib/sinatra/multi_route.rb +87 -0
  19. data/lib/sinatra/namespace.rb +284 -0
  20. data/lib/sinatra/reloader.rb +394 -0
  21. data/lib/sinatra/respond_with.rb +249 -0
  22. data/lib/sinatra/streaming.rb +267 -0
  23. data/lib/sinatra/test_helpers.rb +87 -0
  24. data/sinatra-contrib.gemspec +127 -0
  25. data/spec/capture_spec.rb +93 -0
  26. data/spec/config_file/key_value.yml +6 -0
  27. data/spec/config_file/key_value.yml.erb +6 -0
  28. data/spec/config_file/key_value_override.yml +2 -0
  29. data/spec/config_file/missing_env.yml +4 -0
  30. data/spec/config_file/with_envs.yml +7 -0
  31. data/spec/config_file/with_nested_envs.yml +11 -0
  32. data/spec/config_file_spec.rb +63 -0
  33. data/spec/content_for/different_key.erb +1 -0
  34. data/spec/content_for/different_key.erubis +1 -0
  35. data/spec/content_for/different_key.haml +2 -0
  36. data/spec/content_for/different_key.slim +2 -0
  37. data/spec/content_for/layout.erb +1 -0
  38. data/spec/content_for/layout.erubis +1 -0
  39. data/spec/content_for/layout.haml +1 -0
  40. data/spec/content_for/layout.slim +1 -0
  41. data/spec/content_for/multiple_blocks.erb +4 -0
  42. data/spec/content_for/multiple_blocks.erubis +4 -0
  43. data/spec/content_for/multiple_blocks.haml +8 -0
  44. data/spec/content_for/multiple_blocks.slim +8 -0
  45. data/spec/content_for/multiple_yields.erb +3 -0
  46. data/spec/content_for/multiple_yields.erubis +3 -0
  47. data/spec/content_for/multiple_yields.haml +3 -0
  48. data/spec/content_for/multiple_yields.slim +3 -0
  49. data/spec/content_for/passes_values.erb +1 -0
  50. data/spec/content_for/passes_values.erubis +1 -0
  51. data/spec/content_for/passes_values.haml +1 -0
  52. data/spec/content_for/passes_values.slim +1 -0
  53. data/spec/content_for/same_key.erb +1 -0
  54. data/spec/content_for/same_key.erubis +1 -0
  55. data/spec/content_for/same_key.haml +2 -0
  56. data/spec/content_for/same_key.slim +2 -0
  57. data/spec/content_for/takes_values.erb +1 -0
  58. data/spec/content_for/takes_values.erubis +1 -0
  59. data/spec/content_for/takes_values.haml +3 -0
  60. data/spec/content_for/takes_values.slim +3 -0
  61. data/spec/content_for_spec.rb +213 -0
  62. data/spec/cookies_spec.rb +802 -0
  63. data/spec/decompile_spec.rb +44 -0
  64. data/spec/extension_spec.rb +33 -0
  65. data/spec/json_spec.rb +117 -0
  66. data/spec/link_header_spec.rb +100 -0
  67. data/spec/multi_route_spec.rb +60 -0
  68. data/spec/namespace/foo.erb +1 -0
  69. data/spec/namespace/nested/foo.erb +1 -0
  70. data/spec/namespace_spec.rb +676 -0
  71. data/spec/okjson.rb +581 -0
  72. data/spec/reloader/app.rb.erb +40 -0
  73. data/spec/reloader_spec.rb +441 -0
  74. data/spec/respond_with/bar.erb +1 -0
  75. data/spec/respond_with/bar.json.erb +1 -0
  76. data/spec/respond_with/foo.html.erb +1 -0
  77. data/spec/respond_with/not_html.sass +2 -0
  78. data/spec/respond_with_spec.rb +297 -0
  79. data/spec/spec_helper.rb +7 -0
  80. data/spec/streaming_spec.rb +436 -0
  81. metadata +313 -0
@@ -0,0 +1,87 @@
1
+ require 'sinatra/base'
2
+
3
+ module Sinatra
4
+ # = Sinatra::MultiRoute
5
+ #
6
+ # Create multiple routes with one statement.
7
+ #
8
+ # == Usage
9
+ #
10
+ # Use this extension to create a handler for multiple routes:
11
+ #
12
+ # get '/foo', '/bar' do
13
+ # # ...
14
+ # end
15
+ #
16
+ # Or for multiple verbs:
17
+ #
18
+ # route :get, :post, '/' do
19
+ # # ...
20
+ # end
21
+ #
22
+ # Or for multiple verbs and multiple routes:
23
+ #
24
+ # route :get, :post, ['/foo', '/bar'] do
25
+ # # ...
26
+ # end
27
+ #
28
+ # Or even for custom verbs:
29
+ #
30
+ # route 'LIST', '/' do
31
+ # # ...
32
+ # end
33
+ #
34
+ # === Classic Application
35
+ #
36
+ # To use the extension in a classic application all you need to do is require
37
+ # it:
38
+ #
39
+ # require "sinatra"
40
+ # require "sinatra/multi_route"
41
+ #
42
+ # # Your classic application code goes here...
43
+ #
44
+ # === Modular Application
45
+ #
46
+ # To use the extension in a modular application you need to require it, and
47
+ # then, tell the application you will use it:
48
+ #
49
+ # require "sinatra/base"
50
+ # require "sinatra/multi_route"
51
+ #
52
+ # class MyApp < Sinatra::Base
53
+ # register Sinatra::MultiRoute
54
+ #
55
+ # # The rest of your modular application code goes here...
56
+ # end
57
+ #
58
+ module MultiRoute
59
+ def head(*args, &block) super(*route_args(args), &block) end
60
+ def delete(*args, &block) super(*route_args(args), &block) end
61
+ def get(*args, &block) super(*route_args(args), &block) end
62
+ def options(*args, &block) super(*route_args(args), &block) end
63
+ def patch(*args, &block) super(*route_args(args), &block) end
64
+ def post(*args, &block) super(*route_args(args), &block) end
65
+ def put(*args, &block) super(*route_args(args), &block) end
66
+
67
+ def route(*args, &block)
68
+ options = Hash === args.last ? args.pop : {}
69
+ routes = [*args.pop]
70
+ args.each do |verb|
71
+ verb = verb.to_s.upcase if Symbol === verb
72
+ routes.each do |route|
73
+ super(verb, route, options, &block)
74
+ end
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def route_args(args)
81
+ options = Hash === args.last ? args.pop : {}
82
+ [args, options]
83
+ end
84
+ end
85
+
86
+ register MultiRoute
87
+ end
@@ -0,0 +1,284 @@
1
+ require 'backports'
2
+ require 'sinatra/base'
3
+ require 'sinatra/decompile'
4
+
5
+ module Sinatra
6
+
7
+ # = Sinatra::Namespace
8
+ #
9
+ # <tt>Sinatra::Namespace</tt> is an extension that adds namespaces to an
10
+ # application. This namespaces will allow you to share a path prefix for the
11
+ # routes within the namespace, and define filters, conditions and error
12
+ # handlers exclusively for them. Besides that, you can also register helpers
13
+ # and extensions that will be used only within the namespace.
14
+ #
15
+ # == Usage
16
+ #
17
+ # Once you have loaded the extension (see below), you can use the +namespace+
18
+ # method to define namespaces in your application.
19
+ #
20
+ # You can define a namespace by a path prefix:
21
+ #
22
+ # namespace '/blog' do
23
+ # get { haml :blog }
24
+ # get '/:entry_permalink' do
25
+ # @entry = Entry.find_by_permalink!(params[:entry_permalink])
26
+ # haml :entry
27
+ # end
28
+ #
29
+ # # More blog routes...
30
+ # end
31
+ #
32
+ # by a condition:
33
+ #
34
+ # namespace :host_name => 'localhost' do
35
+ # get('/admin/dashboard') { haml :dashboard }
36
+ # get('/admin/login') { haml :login }
37
+ #
38
+ # # More admin routes...
39
+ # end
40
+ #
41
+ # or both:
42
+ #
43
+ # namespace '/admin', :host_name => 'localhost' do
44
+ # get('/dashboard') { haml :dashboard }
45
+ # get('/login') { haml :login }
46
+ # post('/login') { login_user }
47
+ #
48
+ # # More admin routes...
49
+ # end
50
+ #
51
+ # When you define a filter or an error handler, or register an extension or a
52
+ # set of helpers within a namespace, they only affect the routes defined in
53
+ # it. For instance, lets define a before filter to prevent the access of
54
+ # unauthorized users to the admin section of the application:
55
+ #
56
+ # namespace '/admin' do
57
+ # helpers AdminHelpers
58
+ # before { authenticate unless request.path_info == '/admin/login' }
59
+ #
60
+ # get '/dashboard' do
61
+ # # Only authenticated users can access here...
62
+ # haml :dashboard
63
+ # end
64
+ #
65
+ # # More admin routes...
66
+ # end
67
+ #
68
+ # get '/' do
69
+ # # Any user can access here...
70
+ # haml :index
71
+ # end
72
+ #
73
+ # Well, they actually also affect the nested namespaces:
74
+ #
75
+ # namespace '/admin' do
76
+ # helpers AdminHelpers
77
+ # before { authenticate unless request.path_info == '/admin/login' }
78
+ #
79
+ # namespace '/users' do
80
+ # get do
81
+ # # Only authenticated users can access here...
82
+ # @users = User.all
83
+ # haml :users
84
+ # end
85
+ #
86
+ # # More user admin routes...
87
+ # end
88
+ #
89
+ # # More admin routes...
90
+ # end
91
+ #
92
+ # === Classic Application Setup
93
+ #
94
+ # To be able to use namespaces in a classic application all you need to do is
95
+ # require the extension:
96
+ #
97
+ # require "sinatra"
98
+ # require "sinatra/namespace"
99
+ #
100
+ # # The rest of your classic application code goes here...
101
+ #
102
+ # === Modular Application Setup
103
+ #
104
+ # To be able to use namespaces in a modular application all you need to do is
105
+ # require the extension, and then, register it:
106
+ #
107
+ # require "sinatra/base"
108
+ # require "sinatra/namespace"
109
+ #
110
+ # class MyApp < Sinatra::Base
111
+ # register Sinatra::Namespace
112
+ #
113
+ # # The rest of your modular application code goes here...
114
+ # end
115
+ #
116
+ module Namespace
117
+ def self.new(base, pattern, conditions = {}, &block)
118
+ Module.new do
119
+ extend NamespacedMethods
120
+ include InstanceMethods
121
+ @base, @extensions, @errors = base, [], {}
122
+ @pattern, @conditions = compile(pattern, conditions)
123
+ @templates = Hash.new { |h,k| @base.templates[k] }
124
+ namespace = self
125
+ before { extend(@namespace = namespace) }
126
+ class_eval(&block)
127
+ end
128
+ end
129
+
130
+ module InstanceMethods
131
+ def settings
132
+ @namespace
133
+ end
134
+
135
+ def template_cache
136
+ super.fetch(:nested, @namespace) { Tilt::Cache.new }
137
+ end
138
+ end
139
+
140
+ module SharedMethods
141
+ def namespace(pattern, conditions = {}, &block)
142
+ Sinatra::Namespace.new(self, pattern, conditions, &block)
143
+ end
144
+ end
145
+
146
+ module NamespacedMethods
147
+ include SharedMethods
148
+ include Sinatra::Decompile
149
+ attr_reader :base, :templates
150
+
151
+ def self.prefixed(*names)
152
+ names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) }}
153
+ end
154
+
155
+ prefixed :before, :after, :delete, :get, :head, :options, :patch, :post, :put
156
+
157
+ def helpers(*extensions, &block)
158
+ class_eval(&block) if block_given?
159
+ include(*extensions) if extensions.any?
160
+ end
161
+
162
+ def register(*extensions, &block)
163
+ extensions << Module.new(&block) if block_given?
164
+ @extensions += extensions
165
+ extensions.each do |extension|
166
+ extend extension
167
+ extension.registered(self) if extension.respond_to?(:registered)
168
+ end
169
+ end
170
+
171
+ def invoke_hook(name, *args)
172
+ @extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
173
+ end
174
+
175
+ def not_found(&block)
176
+ error(404, &block)
177
+ end
178
+
179
+ def errors
180
+ base.errors.merge(namespace_errors)
181
+ end
182
+
183
+ def namespace_errors
184
+ @errors
185
+ end
186
+
187
+ def error(*codes, &block)
188
+ args = Sinatra::Base.send(:compile!, "ERROR", /^#{@pattern}/, block)
189
+ codes = codes.map { |c| Array(c) }.flatten
190
+ codes << Exception if codes.empty?
191
+ codes.each do |c|
192
+ errors = @errors[c] ||= []
193
+ errors << args
194
+ end
195
+ end
196
+
197
+ def respond_to(*args)
198
+ return @conditions[:provides] || base.respond_to if args.empty?
199
+ @conditions[:provides] = args
200
+ end
201
+
202
+ def set(key, value = self, &block)
203
+ raise ArgumentError, "may not set #{key}" if key != :views
204
+ return key.each { |k,v| set(k, v) } if block.nil? and value == self
205
+ block ||= proc { value }
206
+ singleton_class.send(:define_method, key, &block)
207
+ end
208
+
209
+ def enable(*opts)
210
+ opts.each { |key| set(key, true) }
211
+ end
212
+
213
+ def disable(*opts)
214
+ opts.each { |key| set(key, false) }
215
+ end
216
+
217
+ def template(name, &block)
218
+ filename, line = caller_locations.first
219
+ templates[name] = [block, filename, line.to_i]
220
+ end
221
+
222
+ def layout(name=:layout, &block)
223
+ template name, &block
224
+ end
225
+
226
+ private
227
+
228
+ def app
229
+ base.respond_to?(:base) ? base.base : base
230
+ end
231
+
232
+ def compile(pattern, conditions, default_pattern = nil)
233
+ if pattern.respond_to? :to_hash
234
+ conditions = conditions.merge pattern.to_hash
235
+ pattern = nil
236
+ end
237
+ base_pattern, base_conditions = @pattern, @conditions
238
+ pattern ||= default_pattern
239
+ base_pattern ||= base.pattern if base.respond_to? :pattern
240
+ base_conditions ||= base.conditions if base.respond_to? :conditions
241
+ [ prefixed_path(base_pattern, pattern),
242
+ (base_conditions || {}).merge(conditions) ]
243
+ end
244
+
245
+ def prefixed_path(a, b)
246
+ return a || b || // unless a and b
247
+ a, b = decompile(a), decompile(b) unless a.class == b.class
248
+ a, b = regexpify(a), regexpify(b) unless a.class == b.class
249
+ path = a.class.new "#{a}#{b}"
250
+ path = /^#{path}$/ if path.is_a? Regexp and base == app
251
+ path
252
+ end
253
+
254
+ def regexpify(pattern)
255
+ pattern = Sinatra::Base.send(:compile, pattern).first.inspect
256
+ pattern.gsub! /^\/(\^|\\A)?|(\$|\\Z)?\/$/, ''
257
+ Regexp.new pattern
258
+ end
259
+
260
+ def prefixed(method, pattern = nil, conditions = {}, &block)
261
+ default = '*' if method == :before or method == :after
262
+ pattern, conditions = compile pattern, conditions, default
263
+ result = base.send(method, pattern, conditions, &block)
264
+ invoke_hook :route_added, method.to_s.upcase, pattern, block
265
+ result
266
+ end
267
+
268
+ def method_missing(method, *args, &block)
269
+ base.send(method, *args, &block)
270
+ end
271
+ end
272
+
273
+ module BaseMethods
274
+ include SharedMethods
275
+ end
276
+
277
+ def self.extend_object(base)
278
+ base.extend BaseMethods
279
+ end
280
+ end
281
+
282
+ register Sinatra::Namespace
283
+ Delegator.delegate :namespace
284
+ end
@@ -0,0 +1,394 @@
1
+ require 'sinatra/base'
2
+
3
+ module Sinatra
4
+
5
+ # = Sinatra::Reloader
6
+ #
7
+ # Extension to reload modified files. Useful during development,
8
+ # since it will automatically require files defining routes, filters,
9
+ # error handlers and inline templates, with every incoming request,
10
+ # but only if they have been updated.
11
+ #
12
+ # == Usage
13
+ #
14
+ # === Classic Application
15
+ #
16
+ # To enable the reloader in a classic application all you need to do is
17
+ # require it:
18
+ #
19
+ # require "sinatra"
20
+ # require "sinatra/reloader" if development?
21
+ #
22
+ # # Your classic application code goes here...
23
+ #
24
+ # === Modular Application
25
+ #
26
+ # To enable the reloader in a modular application all you need to do is
27
+ # require it, and then, register it:
28
+ #
29
+ # require "sinatra/base"
30
+ # require "sinatra/reloader"
31
+ #
32
+ # class MyApp < Sinatra::Base
33
+ # configure :development do
34
+ # register Sinatra::Reloader
35
+ # end
36
+ #
37
+ # # Your modular application code goes here...
38
+ # end
39
+ #
40
+ # == Changing the Reloading Policy
41
+ #
42
+ # You can refine the reloading policy with +also_reload+ and
43
+ # +dont_reload+, to customize which files should, and should not, be
44
+ # reloaded, respectively.
45
+ #
46
+ # === Classic Application
47
+ #
48
+ # Simply call the methods:
49
+ #
50
+ # require "sinatra"
51
+ # require "sinatra/reloader" if development?
52
+ #
53
+ # also_reload '/path/to/some/file'
54
+ # dont_reload '/path/to/other/file'
55
+ #
56
+ # # Your classic application code goes here...
57
+ #
58
+ # === Modular Application
59
+ #
60
+ # Call the methods inside the +configure+ block:
61
+ #
62
+ # require "sinatra/base"
63
+ # require "sinatra/reloader"
64
+ #
65
+ # class MyApp < Sinatra::Base
66
+ # configure :development do
67
+ # register Sinatra::Reloader
68
+ # also_reload '/path/to/some/file'
69
+ # dont_reload '/path/to/other/file'
70
+ # end
71
+ #
72
+ # # Your modular application code goes here...
73
+ # end
74
+ #
75
+ module Reloader
76
+
77
+ # Watches a file so it can tell when it has been updated, and what
78
+ # elements does it contain.
79
+ class Watcher
80
+
81
+ # Represents an element of a Sinatra application that may need to
82
+ # be reloaded. An element could be:
83
+ # * a route
84
+ # * a filter
85
+ # * an error handler
86
+ # * a middleware
87
+ # * inline templates
88
+ #
89
+ # Its +representation+ attribute is there to allow to identify the
90
+ # element within an application, that is, to match it with its
91
+ # Sinatra's internal representation.
92
+ class Element < Struct.new(:type, :representation)
93
+ end
94
+
95
+ # Collection of file +Watcher+ that can be associated with a
96
+ # Sinatra application. That way, we can know which files belong
97
+ # to a given application and which files have been modified. It
98
+ # also provides a mechanism to inform a Watcher of the elements
99
+ # defined in the file being watched and if its changes should be
100
+ # ignored.
101
+ class List
102
+ @app_list_map = Hash.new { |hash, key| hash[key] = new }
103
+
104
+ # Returns the +List+ for the application +app+.
105
+ def self.for(app)
106
+ @app_list_map[app]
107
+ end
108
+
109
+ # Creates a new +List+ instance.
110
+ def initialize
111
+ @path_watcher_map = Hash.new do |hash, key|
112
+ hash[key] = Watcher.new(key)
113
+ end
114
+ end
115
+
116
+ # Lets the +Watcher+ for the file located at +path+ know that the
117
+ # +element+ is defined there, and adds the +Watcher+ to the +List+,
118
+ # if it isn't already there.
119
+ def watch(path, element)
120
+ watcher_for(path).elements << element
121
+ end
122
+
123
+ # Tells the +Watcher+ for the file located at +path+ to ignore
124
+ # the file changes, and adds the +Watcher+ to the +List+, if
125
+ # it isn't already there.
126
+ def ignore(path)
127
+ watcher_for(path).ignore
128
+ end
129
+
130
+ # Adds a +Watcher+ for the file located at +path+ to the
131
+ # +List+, if it isn't already there.
132
+ def watcher_for(path)
133
+ @path_watcher_map[File.expand_path(path)]
134
+ end
135
+ alias watch_file watcher_for
136
+
137
+ # Returns an array with all the watchers in the +List+.
138
+ def watchers
139
+ @path_watcher_map.values
140
+ end
141
+
142
+ # Returns an array with all the watchers in the +List+ that
143
+ # have been updated.
144
+ def updated
145
+ watchers.find_all(&:updated?)
146
+ end
147
+ end
148
+
149
+ attr_reader :path, :elements, :mtime
150
+
151
+ # Creates a new +Watcher+ instance for the file located at +path+.
152
+ def initialize(path)
153
+ @path, @elements = path, []
154
+ update
155
+ end
156
+
157
+ # Indicates whether or not the file being watched has been modified.
158
+ def updated?
159
+ !ignore? && !removed? && mtime != File.mtime(path)
160
+ end
161
+
162
+ # Updates the mtime of the file being watched.
163
+ def update
164
+ @mtime = File.mtime(path)
165
+ end
166
+
167
+ # Indicates whether or not the file being watched has inline
168
+ # templates.
169
+ def inline_templates?
170
+ elements.any? { |element| element.type == :inline_templates }
171
+ end
172
+
173
+ # Informs that the modifications to the file being watched
174
+ # should be ignored.
175
+ def ignore
176
+ @ignore = true
177
+ end
178
+
179
+ # Indicates whether or not the modifications to the file being
180
+ # watched should be ignored.
181
+ def ignore?
182
+ !!@ignore
183
+ end
184
+
185
+ # Indicates whether or not the file being watched has been removed.
186
+ def removed?
187
+ !File.exist?(path)
188
+ end
189
+ end
190
+
191
+ # When the extension is registed it extends the Sinatra application
192
+ # +klass+ with the modules +BaseMethods+ and +ExtensionMethods+ and
193
+ # defines a before filter to +perform+ the reload of the modified files.
194
+ def self.registered(klass)
195
+ @reloader_loaded_in ||= {}
196
+ return if @reloader_loaded_in[klass]
197
+
198
+ @reloader_loaded_in[klass] = true
199
+
200
+ klass.extend BaseMethods
201
+ klass.extend ExtensionMethods
202
+ klass.set(:reloader) { klass.development? }
203
+ klass.set(:reload_templates) { klass.reloader? }
204
+ klass.before do
205
+ if klass.reloader?
206
+ if Reloader.thread_safe?
207
+ Thread.exclusive { Reloader.perform(klass) }
208
+ else
209
+ Reloader.perform(klass)
210
+ end
211
+ end
212
+ end
213
+ klass.set(:inline_templates, klass.app_file) if klass == Sinatra::Application
214
+ end
215
+
216
+ # Reloads the modified files, adding, updating and removing the
217
+ # needed elements.
218
+ def self.perform(klass)
219
+ Watcher::List.for(klass).updated.each do |watcher|
220
+ klass.set(:inline_templates, watcher.path) if watcher.inline_templates?
221
+ watcher.elements.each { |element| klass.deactivate(element) }
222
+ $LOADED_FEATURES.delete(watcher.path)
223
+ require watcher.path
224
+ watcher.update
225
+ end
226
+ end
227
+
228
+ # Indicates whether or not we can and need to run thread-safely.
229
+ def self.thread_safe?
230
+ Thread and Thread.list.size > 1 and Thread.respond_to?(:exclusive)
231
+ end
232
+
233
+ # Contains the methods defined in Sinatra::Base that are overriden.
234
+ module BaseMethods
235
+ # Protects Sinatra::Base.run! from being called more than once.
236
+ def run!(*args)
237
+ if settings.reloader?
238
+ super unless running?
239
+ else
240
+ super
241
+ end
242
+ end
243
+
244
+ # Does everything Sinatra::Base#route does, but it also tells the
245
+ # +Watcher::List+ for the Sinatra application to watch the defined
246
+ # route.
247
+ #
248
+ # Note: We are using #compile! so we don't interfere with extensions
249
+ # changing #route.
250
+ def compile!(verb, path, block, options = {})
251
+ source_location = block.respond_to?(:source_location) ?
252
+ block.source_location.first : caller_files[1]
253
+ signature = super
254
+ watch_element(
255
+ source_location, :route, { :verb => verb, :signature => signature }
256
+ )
257
+ signature
258
+ end
259
+
260
+ # Does everything Sinatra::Base#inline_templates= does, but it also
261
+ # tells the +Watcher::List+ for the Sinatra application to watch the
262
+ # inline templates in +file+ or the file who made the call to this
263
+ # method.
264
+ def inline_templates=(file=nil)
265
+ file = (file.nil? || file == true) ?
266
+ (caller_files[1] || File.expand_path($0)) : file
267
+ watch_element(file, :inline_templates)
268
+ super
269
+ end
270
+
271
+ # Does everything Sinatra::Base#use does, but it also tells the
272
+ # +Watcher::List+ for the Sinatra application to watch the middleware
273
+ # being used.
274
+ def use(middleware, *args, &block)
275
+ path = caller_files[1] || File.expand_path($0)
276
+ watch_element(path, :middleware, [middleware, args, block])
277
+ super
278
+ end
279
+
280
+ # Does everything Sinatra::Base#add_filter does, but it also tells
281
+ # the +Watcher::List+ for the Sinatra application to watch the defined
282
+ # filter.
283
+ def add_filter(type, path = nil, options = {}, &block)
284
+ source_location = block.respond_to?(:source_location) ?
285
+ block.source_location.first : caller_files[1]
286
+ result = super
287
+ watch_element(source_location, :"#{type}_filter", filters[type].last)
288
+ result
289
+ end
290
+
291
+ # Does everything Sinatra::Base#error does, but it also tells the
292
+ # +Watcher::List+ for the Sinatra application to watch the defined
293
+ # error handler.
294
+ def error(*codes, &block)
295
+ path = caller_files[1] || File.expand_path($0)
296
+ result = super
297
+ codes.each do |c|
298
+ watch_element(path, :error, :code => c, :handler => @errors[c])
299
+ end
300
+ result
301
+ end
302
+
303
+ # Does everything Sinatra::Base#register does, but it also lets the
304
+ # reloader know that an extension is being registered, because the
305
+ # elements defined in its +registered+ method need a special treatment.
306
+ def register(*extensions, &block)
307
+ start_registering_extension
308
+ result = super
309
+ stop_registering_extension
310
+ result
311
+ end
312
+
313
+ # Does everything Sinatra::Base#register does and then registers the
314
+ # reloader in the +subclass+.
315
+ def inherited(subclass)
316
+ result = super
317
+ subclass.register Sinatra::Reloader
318
+ result
319
+ end
320
+ end
321
+
322
+ # Contains the methods that the extension adds to the Sinatra application.
323
+ module ExtensionMethods
324
+ # Removes the +element+ from the Sinatra application.
325
+ def deactivate(element)
326
+ case element.type
327
+ when :route then
328
+ verb = element.representation[:verb]
329
+ signature = element.representation[:signature]
330
+ (routes[verb] ||= []).delete(signature)
331
+ when :middleware then
332
+ @middleware.delete(element.representation)
333
+ when :before_filter then
334
+ filters[:before].delete(element.representation)
335
+ when :after_filter then
336
+ filters[:after].delete(element.representation)
337
+ when :error then
338
+ code = element.representation[:code]
339
+ handler = element.representation[:handler]
340
+ @errors.delete(code) if @errors[code] == handler
341
+ end
342
+ end
343
+
344
+ # Indicates with a +glob+ which files should be reloaded if they
345
+ # have been modified. It can be called several times.
346
+ def also_reload(*glob)
347
+ Dir[*glob].each { |path| Watcher::List.for(self).watch_file(path) }
348
+ end
349
+
350
+ # Indicates with a +glob+ which files should not be reloaded even if
351
+ # they have been modified. It can be called several times.
352
+ def dont_reload(*glob)
353
+ Dir[*glob].each { |path| Watcher::List.for(self).ignore(path) }
354
+ end
355
+
356
+ private
357
+
358
+ attr_reader :register_path
359
+
360
+ # Indicates an extesion is being registered.
361
+ def start_registering_extension
362
+ @register_path = caller_files[2]
363
+ end
364
+
365
+ # Indicates the extesion has already been registered.
366
+ def stop_registering_extension
367
+ @register_path = nil
368
+ end
369
+
370
+ # Indicates whether or not an extension is being registered.
371
+ def registering_extension?
372
+ !register_path.nil?
373
+ end
374
+
375
+ # Builds a Watcher::Element from +type+ and +representation+ and
376
+ # tells the Watcher::List for the current application to watch it
377
+ # in the file located at +path+.
378
+ #
379
+ # If an extension is being registered, it also tells the list to
380
+ # watch it in the file where the extesion has been registered.
381
+ # This prevents the duplication of the elements added by the
382
+ # extension in its +registered+ method with every reload.
383
+ def watch_element(path, type, representation=nil)
384
+ list = Watcher::List.for(self)
385
+ element = Watcher::Element.new(type, representation)
386
+ list.watch(path, element)
387
+ list.watch(register_path, element) if registering_extension?
388
+ end
389
+ end
390
+ end
391
+
392
+ register Reloader
393
+ Delegator.delegate :also_reload, :dont_reload
394
+ end