sinatra-contrib 1.3.0

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 (79) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +135 -0
  3. data/Rakefile +75 -0
  4. data/ideas.md +29 -0
  5. data/lib/sinatra/capture.rb +42 -0
  6. data/lib/sinatra/config_file.rb +151 -0
  7. data/lib/sinatra/content_for.rb +111 -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 +45 -0
  12. data/lib/sinatra/cookies.rb +331 -0
  13. data/lib/sinatra/decompile.rb +113 -0
  14. data/lib/sinatra/engine_tracking.rb +96 -0
  15. data/lib/sinatra/extension.rb +95 -0
  16. data/lib/sinatra/json.rb +134 -0
  17. data/lib/sinatra/link_header.rb +132 -0
  18. data/lib/sinatra/multi_route.rb +81 -0
  19. data/lib/sinatra/namespace.rb +282 -0
  20. data/lib/sinatra/reloader.rb +384 -0
  21. data/lib/sinatra/respond_with.rb +245 -0
  22. data/lib/sinatra/streaming.rb +267 -0
  23. data/lib/sinatra/test_helpers.rb +87 -0
  24. data/sinatra-contrib.gemspec +125 -0
  25. data/spec/capture_spec.rb +80 -0
  26. data/spec/config_file/key_value.yml +6 -0
  27. data/spec/config_file/missing_env.yml +4 -0
  28. data/spec/config_file/with_envs.yml +7 -0
  29. data/spec/config_file/with_nested_envs.yml +11 -0
  30. data/spec/config_file_spec.rb +44 -0
  31. data/spec/content_for/different_key.erb +1 -0
  32. data/spec/content_for/different_key.erubis +1 -0
  33. data/spec/content_for/different_key.haml +2 -0
  34. data/spec/content_for/different_key.slim +2 -0
  35. data/spec/content_for/layout.erb +1 -0
  36. data/spec/content_for/layout.erubis +1 -0
  37. data/spec/content_for/layout.haml +1 -0
  38. data/spec/content_for/layout.slim +1 -0
  39. data/spec/content_for/multiple_blocks.erb +4 -0
  40. data/spec/content_for/multiple_blocks.erubis +4 -0
  41. data/spec/content_for/multiple_blocks.haml +8 -0
  42. data/spec/content_for/multiple_blocks.slim +8 -0
  43. data/spec/content_for/multiple_yields.erb +3 -0
  44. data/spec/content_for/multiple_yields.erubis +3 -0
  45. data/spec/content_for/multiple_yields.haml +3 -0
  46. data/spec/content_for/multiple_yields.slim +3 -0
  47. data/spec/content_for/passes_values.erb +1 -0
  48. data/spec/content_for/passes_values.erubis +1 -0
  49. data/spec/content_for/passes_values.haml +1 -0
  50. data/spec/content_for/passes_values.slim +1 -0
  51. data/spec/content_for/same_key.erb +1 -0
  52. data/spec/content_for/same_key.erubis +1 -0
  53. data/spec/content_for/same_key.haml +2 -0
  54. data/spec/content_for/same_key.slim +2 -0
  55. data/spec/content_for/takes_values.erb +1 -0
  56. data/spec/content_for/takes_values.erubis +1 -0
  57. data/spec/content_for/takes_values.haml +3 -0
  58. data/spec/content_for/takes_values.slim +3 -0
  59. data/spec/content_for_spec.rb +201 -0
  60. data/spec/cookies_spec.rb +782 -0
  61. data/spec/decompile_spec.rb +44 -0
  62. data/spec/extension_spec.rb +33 -0
  63. data/spec/json_spec.rb +115 -0
  64. data/spec/link_header_spec.rb +100 -0
  65. data/spec/multi_route_spec.rb +45 -0
  66. data/spec/namespace/foo.erb +1 -0
  67. data/spec/namespace/nested/foo.erb +1 -0
  68. data/spec/namespace_spec.rb +623 -0
  69. data/spec/okjson.rb +581 -0
  70. data/spec/reloader/app.rb.erb +40 -0
  71. data/spec/reloader_spec.rb +441 -0
  72. data/spec/respond_with/bar.erb +1 -0
  73. data/spec/respond_with/bar.json.erb +1 -0
  74. data/spec/respond_with/foo.html.erb +1 -0
  75. data/spec/respond_with/not_html.sass +2 -0
  76. data/spec/respond_with_spec.rb +289 -0
  77. data/spec/spec_helper.rb +6 -0
  78. data/spec/streaming_spec.rb +436 -0
  79. metadata +256 -0
@@ -0,0 +1,81 @@
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 even for custom verbs:
23
+ #
24
+ # route 'LIST', '/' do
25
+ # # ...
26
+ # end
27
+ #
28
+ # === Classic Application
29
+ #
30
+ # To use the extension in a classic application all you need to do is require
31
+ # it:
32
+ #
33
+ # require "sinatra"
34
+ # require "sinatra/multi_route"
35
+ #
36
+ # # Your classic application code goes here...
37
+ #
38
+ # === Modular Application
39
+ #
40
+ # To use the extension in a modular application you need to require it, and
41
+ # then, tell the application you will use it:
42
+ #
43
+ # require "sinatra/base"
44
+ # require "sinatra/multi_route"
45
+ #
46
+ # class MyApp < Sinatra::Base
47
+ # register Sinatra::MultiRoute
48
+ #
49
+ # # The rest of your modular application code goes here...
50
+ # end
51
+ #
52
+ module MultiRoute
53
+ def head(*args, &block) super(*route_args(args), &block) end
54
+ def delete(*args, &block) super(*route_args(args), &block) end
55
+ def get(*args, &block) super(*route_args(args), &block) end
56
+ def options(*args, &block) super(*route_args(args), &block) end
57
+ def patch(*args, &block) super(*route_args(args), &block) end
58
+ def post(*args, &block) super(*route_args(args), &block) end
59
+ def put(*args, &block) super(*route_args(args), &block) end
60
+
61
+ def route(*args, &block)
62
+ options = Hash === args.last ? args.pop : {}
63
+ routes = [*args.pop]
64
+ args.each do |verb|
65
+ verb = verb.to_s.upcase if Symbol === verb
66
+ routes.each do |route|
67
+ super(verb, route, options, &block)
68
+ end
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def route_args(args)
75
+ options = Hash === args.last ? args.pop : {}
76
+ [args, options]
77
+ end
78
+ end
79
+
80
+ register MultiRoute
81
+ end
@@ -0,0 +1,282 @@
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 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 = 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
+
139
+ def error_block!(*keys)
140
+ if block = keys.inject(nil) { |b,k| b ||= @namespace.errors[k] }
141
+ instance_eval(&block)
142
+ else
143
+ super
144
+ end
145
+ end
146
+ end
147
+
148
+ module SharedMethods
149
+ def namespace(pattern, conditions = {}, &block)
150
+ Sinatra::Namespace.new(self, pattern, conditions, &block)
151
+ end
152
+ end
153
+
154
+ module NamespacedMethods
155
+ include SharedMethods
156
+ include Sinatra::Decompile
157
+ attr_reader :base, :templates
158
+
159
+ def self.prefixed(*names)
160
+ names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) }}
161
+ end
162
+
163
+ prefixed :before, :after, :delete, :get, :head, :options, :patch, :post, :put
164
+
165
+ def helpers(*extensions, &block)
166
+ class_eval(&block) if block_given?
167
+ include(*extensions) if extensions.any?
168
+ end
169
+
170
+ def register(*extensions, &block)
171
+ extensions << Module.new(&block) if block_given?
172
+ @extensions += extensions
173
+ extensions.each do |extension|
174
+ extend extension
175
+ extension.registered(self) if extension.respond_to?(:registered)
176
+ end
177
+ end
178
+
179
+ def invoke_hook(name, *args)
180
+ @extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
181
+ end
182
+
183
+ def errors
184
+ @errors ||= {}
185
+ end
186
+
187
+ def not_found(&block)
188
+ error(404, &block)
189
+ end
190
+
191
+ def error(codes = Exception, &block)
192
+ [*codes].each { |c| errors[c] = block }
193
+ end
194
+
195
+ def respond_to(*args)
196
+ return @conditions[:provides] || base.respond_to if args.empty?
197
+ @conditions[:provides] = args
198
+ end
199
+
200
+ def set(key, value = self, &block)
201
+ raise ArgumentError, "may not set #{key}" if key != :views
202
+ return key.each { |k,v| set(k, v) } if block.nil? and value == self
203
+ block ||= proc { value }
204
+ singleton_class.send(:define_method, key, &block)
205
+ end
206
+
207
+ def enable(*opts)
208
+ opts.each { |key| set(key, true) }
209
+ end
210
+
211
+ def disable(*opts)
212
+ opts.each { |key| set(key, false) }
213
+ end
214
+
215
+ def template(name, &block)
216
+ filename, line = caller_locations.first
217
+ templates[name] = [block, filename, line.to_i]
218
+ end
219
+
220
+ def layout(name=:layout, &block)
221
+ template name, &block
222
+ end
223
+
224
+ private
225
+
226
+ def app
227
+ base.respond_to?(:base) ? base.base : base
228
+ end
229
+
230
+ def compile(pattern, conditions, default_pattern = nil)
231
+ if pattern.respond_to? :to_hash
232
+ conditions = conditions.merge pattern.to_hash
233
+ pattern = nil
234
+ end
235
+ base_pattern, base_conditions = @pattern, @conditions
236
+ pattern ||= default_pattern
237
+ base_pattern ||= base.pattern if base.respond_to? :pattern
238
+ base_conditions ||= base.conditions if base.respond_to? :conditions
239
+ [ prefixed_path(base_pattern, pattern),
240
+ (base_conditions || {}).merge(conditions) ]
241
+ end
242
+
243
+ def prefixed_path(a, b)
244
+ return a || b || // unless a and b
245
+ a, b = decompile(a), decompile(b) unless a.class == b.class
246
+ a, b = regexpify(a), regexpify(b) unless a.class == b.class
247
+ path = a.class.new "#{a}#{b}"
248
+ path = /^#{path}$/ if path.is_a? Regexp and base == app
249
+ path
250
+ end
251
+
252
+ def regexpify(pattern)
253
+ pattern = Sinatra::Base.send(:compile, pattern).first.inspect
254
+ pattern.gsub! /^\/(\^|\\A)?|(\$|\\Z)?\/$/, ''
255
+ Regexp.new pattern
256
+ end
257
+
258
+ def prefixed(method, pattern = nil, conditions = {}, &block)
259
+ default = '*' if method == :before or method == :after
260
+ pattern, conditions = compile pattern, conditions, default
261
+ result = base.send(method, pattern, conditions, &block)
262
+ invoke_hook :route_added, method.to_s.upcase, pattern, block
263
+ result
264
+ end
265
+
266
+ def method_missing(meth, *args, &block)
267
+ base.send(meth, *args, &block)
268
+ end
269
+ end
270
+
271
+ module BaseMethods
272
+ include SharedMethods
273
+ end
274
+
275
+ def self.extend_object(base)
276
+ base.extend BaseMethods
277
+ end
278
+ end
279
+
280
+ register Sinatra::Namespace
281
+ Delegator.delegate :namespace
282
+ end
@@ -0,0 +1,384 @@
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 realoader 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 realoader 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 contains.
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 the elements
99
+ # defined in the file being watched and if it 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 localted 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 file being watched mtime.
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
+ end
214
+
215
+ # Reloads the modified files, adding, updating and removing the
216
+ # needed elements.
217
+ def self.perform(klass)
218
+ Watcher::List.for(klass).updated.each do |watcher|
219
+ klass.set(:inline_templates, watcher.path) if watcher.inline_templates?
220
+ watcher.elements.each { |element| klass.deactivate(element) }
221
+ $LOADED_FEATURES.delete(watcher.path)
222
+ require watcher.path
223
+ watcher.update
224
+ end
225
+ end
226
+
227
+ # Indicates whether or not we can and need to run thread-safely.
228
+ def self.thread_safe?
229
+ Thread and Thread.list.size > 1 and Thread.respond_to?(:exclusive)
230
+ end
231
+
232
+ # Contains the methods defined in Sinatra::Base that are overriden.
233
+ module BaseMethods
234
+ # Does everything Sinatra::Base#route does, but it also tells the
235
+ # +Watcher::List+ for the Sinatra application to watch the defined
236
+ # route.
237
+ #
238
+ # Note: We are using #compile! so we don't interfere with extensions
239
+ # changing #route.
240
+ def compile!(verb, path, block, options = {})
241
+ source_location = block.respond_to?(:source_location) ?
242
+ block.source_location.first : caller_files[1]
243
+ signature = super
244
+ watch_element(
245
+ source_location, :route, { :verb => verb, :signature => signature }
246
+ )
247
+ signature
248
+ end
249
+
250
+ # Does everything Sinatra::Base#inline_templates= does, but it also
251
+ # tells the +Watcher::List+ for the Sinatra application to watch the
252
+ # inline templates in +file+ or the file who made the call to this
253
+ # method.
254
+ def inline_templates=(file=nil)
255
+ file = (file.nil? || file == true) ?
256
+ (caller_files[1] || File.expand_path($0)) : file
257
+ watch_element(file, :inline_templates)
258
+ super
259
+ end
260
+
261
+ # Does everything Sinatra::Base#use does, but it also tells the
262
+ # +Watcher::List+ for the Sinatra application to watch the middleware
263
+ # being used.
264
+ def use(middleware, *args, &block)
265
+ path = caller_files[1] || File.expand_path($0)
266
+ watch_element(path, :middleware, [middleware, args, block])
267
+ super
268
+ end
269
+
270
+ # Does everything Sinatra::Base#add_filter does, but it also tells
271
+ # the +Watcher::List+ for the Sinatra application to watch the defined
272
+ # filter.
273
+ def add_filter(type, path = nil, options = {}, &block)
274
+ source_location = block.respond_to?(:source_location) ?
275
+ block.source_location.first : caller_files[1]
276
+ result = super
277
+ watch_element(source_location, :"#{type}_filter", filters[type].last)
278
+ result
279
+ end
280
+
281
+ # Does everything Sinatra::Base#error does, but it also tells the
282
+ # +Watcher::List+ for the Sinatra application to watch the defined
283
+ # error handler.
284
+ def error(*codes, &block)
285
+ path = caller_files[1] || File.expand_path($0)
286
+ result = super
287
+ codes.each do |c|
288
+ watch_element(path, :error, :code => c, :handler => @errors[c])
289
+ end
290
+ result
291
+ end
292
+
293
+ # Does everything Sinatra::Base#register does, but it also lets the
294
+ # reloader know that an extension is being registered, because the
295
+ # elements defined in its +registered+ method need a special treatment.
296
+ def register(*extensions, &block)
297
+ start_registering_extension
298
+ result = super
299
+ stop_registering_extension
300
+ result
301
+ end
302
+
303
+ # Does everything Sinatra::Base#register does and then registers the
304
+ # reloader in the +subclass+.
305
+ def inherited(subclass)
306
+ result = super
307
+ subclass.register Sinatra::Reloader
308
+ result
309
+ end
310
+ end
311
+
312
+ # Contains the methods that the extension adds to the Sinatra application.
313
+ module ExtensionMethods
314
+ # Removes the +element+ from the Sinatra application.
315
+ def deactivate(element)
316
+ case element.type
317
+ when :route then
318
+ verb = element.representation[:verb]
319
+ signature = element.representation[:signature]
320
+ (routes[verb] ||= []).delete(signature)
321
+ when :middleware then
322
+ @middleware.delete(element.representation)
323
+ when :before_filter then
324
+ filters[:before].delete(element.representation)
325
+ when :after_filter then
326
+ filters[:after].delete(element.representation)
327
+ when :error then
328
+ code = element.representation[:code]
329
+ handler = element.representation[:handler]
330
+ @errors.delete(code) if @errors[code] == handler
331
+ end
332
+ end
333
+
334
+ # Indicates with a +glob+ which files should be reloaded if they
335
+ # have been modified. It can be called several times.
336
+ def also_reload(glob)
337
+ Dir[glob].each { |path| Watcher::List.for(self).watch_file(path) }
338
+ end
339
+
340
+ # Indicates with a +glob+ which files should not be reloaded even if
341
+ # they have been modified. It can be called several times.
342
+ def dont_reload(glob)
343
+ Dir[glob].each { |path| Watcher::List.for(self).ignore(path) }
344
+ end
345
+
346
+ private
347
+
348
+ attr_reader :register_path
349
+
350
+ # Indicates an extesion is being registered.
351
+ def start_registering_extension
352
+ @register_path = caller_files[2]
353
+ end
354
+
355
+ # Indicates the extesion has already been registered.
356
+ def stop_registering_extension
357
+ @register_path = nil
358
+ end
359
+
360
+ # Indicates whether or not an extension is being registered.
361
+ def registering_extension?
362
+ !register_path.nil?
363
+ end
364
+
365
+ # Builds a Watcher::Element from +type+ and +representation+ and
366
+ # tells the Watcher::List for the current application to watch it
367
+ # in the file located at +path+.
368
+ #
369
+ # If an extension is being registered, it also tells the list to
370
+ # watch it in the file where the extesion has been registered.
371
+ # This prevents the duplication of the elements added by the
372
+ # extension in its +registered+ method with every reload.
373
+ def watch_element(path, type, representation=nil)
374
+ list = Watcher::List.for(self)
375
+ element = Watcher::Element.new(type, representation)
376
+ list.watch(path, element)
377
+ list.watch(register_path, element) if registering_extension?
378
+ end
379
+ end
380
+ end
381
+
382
+ register Reloader
383
+ Delegator.delegate :also_reload, :dont_reload
384
+ end