sinatra-contrib 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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