pirj-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 (75) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +135 -0
  3. data/Rakefile +61 -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/decompile.rb +113 -0
  13. data/lib/sinatra/engine_tracking.rb +96 -0
  14. data/lib/sinatra/extension.rb +95 -0
  15. data/lib/sinatra/json.rb +134 -0
  16. data/lib/sinatra/link_header.rb +132 -0
  17. data/lib/sinatra/namespace.rb +282 -0
  18. data/lib/sinatra/reloader.rb +384 -0
  19. data/lib/sinatra/respond_with.rb +245 -0
  20. data/lib/sinatra/streaming.rb +267 -0
  21. data/lib/sinatra/test_helpers.rb +87 -0
  22. data/sinatra-contrib.gemspec +121 -0
  23. data/spec/capture_spec.rb +80 -0
  24. data/spec/config_file/key_value.yml +6 -0
  25. data/spec/config_file/missing_env.yml +4 -0
  26. data/spec/config_file/with_envs.yml +7 -0
  27. data/spec/config_file/with_nested_envs.yml +11 -0
  28. data/spec/config_file_spec.rb +44 -0
  29. data/spec/content_for/different_key.erb +1 -0
  30. data/spec/content_for/different_key.erubis +1 -0
  31. data/spec/content_for/different_key.haml +2 -0
  32. data/spec/content_for/different_key.slim +2 -0
  33. data/spec/content_for/layout.erb +1 -0
  34. data/spec/content_for/layout.erubis +1 -0
  35. data/spec/content_for/layout.haml +1 -0
  36. data/spec/content_for/layout.slim +1 -0
  37. data/spec/content_for/multiple_blocks.erb +4 -0
  38. data/spec/content_for/multiple_blocks.erubis +4 -0
  39. data/spec/content_for/multiple_blocks.haml +8 -0
  40. data/spec/content_for/multiple_blocks.slim +8 -0
  41. data/spec/content_for/multiple_yields.erb +3 -0
  42. data/spec/content_for/multiple_yields.erubis +3 -0
  43. data/spec/content_for/multiple_yields.haml +3 -0
  44. data/spec/content_for/multiple_yields.slim +3 -0
  45. data/spec/content_for/passes_values.erb +1 -0
  46. data/spec/content_for/passes_values.erubis +1 -0
  47. data/spec/content_for/passes_values.haml +1 -0
  48. data/spec/content_for/passes_values.slim +1 -0
  49. data/spec/content_for/same_key.erb +1 -0
  50. data/spec/content_for/same_key.erubis +1 -0
  51. data/spec/content_for/same_key.haml +2 -0
  52. data/spec/content_for/same_key.slim +2 -0
  53. data/spec/content_for/takes_values.erb +1 -0
  54. data/spec/content_for/takes_values.erubis +1 -0
  55. data/spec/content_for/takes_values.haml +3 -0
  56. data/spec/content_for/takes_values.slim +3 -0
  57. data/spec/content_for_spec.rb +201 -0
  58. data/spec/decompile_spec.rb +44 -0
  59. data/spec/extension_spec.rb +33 -0
  60. data/spec/json_spec.rb +115 -0
  61. data/spec/link_header_spec.rb +100 -0
  62. data/spec/namespace/foo.erb +1 -0
  63. data/spec/namespace/nested/foo.erb +1 -0
  64. data/spec/namespace_spec.rb +623 -0
  65. data/spec/okjson.rb +581 -0
  66. data/spec/reloader/app.rb.erb +40 -0
  67. data/spec/reloader_spec.rb +441 -0
  68. data/spec/respond_with/bar.erb +1 -0
  69. data/spec/respond_with/bar.json.erb +1 -0
  70. data/spec/respond_with/foo.html.erb +1 -0
  71. data/spec/respond_with/not_html.sass +2 -0
  72. data/spec/respond_with_spec.rb +289 -0
  73. data/spec/spec_helper.rb +6 -0
  74. data/spec/streaming_spec.rb +436 -0
  75. metadata +252 -0
@@ -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
@@ -0,0 +1,245 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/json'
3
+
4
+ module Sinatra
5
+ ##
6
+ # = Sinatra::RespondWith
7
+ #
8
+ # This extensions lets Sinatra automatically choose what template to render or
9
+ # action to perform depending on the request's Accept header.
10
+ #
11
+ # Example:
12
+ #
13
+ # # Without Sinatra::RespondWith
14
+ # get '/' do
15
+ # data = { :name => 'example' }
16
+ # request.accept.each do |type|
17
+ # case type
18
+ # when 'text/html'
19
+ # halt haml(:index, :locals => data)
20
+ # when 'text/json'
21
+ # halt data.to_json
22
+ # when 'application/atom+xml'
23
+ # halt nokogiri(:'index.atom', :locals => data)
24
+ # when 'application/xml', 'text/xml'
25
+ # halt nokogiri(:'index.xml', :locals => data)
26
+ # when 'text/plain'
27
+ # halt 'just an example'
28
+ # end
29
+ # end
30
+ # error 406
31
+ # end
32
+ #
33
+ # # With Sinatra::RespondWith
34
+ # get '/' do
35
+ # respond_with :index, :name => 'example' do |f|
36
+ # f.txt { 'just an example' }
37
+ # end
38
+ # end
39
+ #
40
+ # Both helper methods +respond_to+ and +respond_with+ let you define custom
41
+ # handlers like the one above for +text/plain+. +respond_with+ additionally
42
+ # takes a template name and/or an object to offer the following default
43
+ # behavior:
44
+ #
45
+ # * If a template name is given, search for a template called
46
+ # +name.format.engine+ (+index.xml.nokogiri+ in the above example).
47
+ # * If a template name is given, search for a templated called +name.engine+
48
+ # for engines known to result in the requested format (+index.haml+).
49
+ # * If a file extension associated with the mime type is known to Sinatra, and
50
+ # the object responds to +to_extension+, call that method and use the result
51
+ # (+data.to_json+).
52
+ #
53
+ # == Security
54
+ #
55
+ # Since methods are triggered based on client input, this can lead to security
56
+ # issues (but not as seviere as those might apear in the first place: keep in
57
+ # mind that only known file extensions are used). You therefore should limit
58
+ # the possible formats you serve.
59
+ #
60
+ # This is possible with the +provides+ condition:
61
+ #
62
+ # get '/', :provides => [:html, :json, :xml, :atom] do
63
+ # respond_with :index, :name => 'example'
64
+ # end
65
+ #
66
+ # However, since you have to set +provides+ for every route, this extension
67
+ # adds a app global (class method) `respond_to`, that let's you define content
68
+ # types for all routes:
69
+ #
70
+ # respond_to :html, :json, :xml, :atom
71
+ # get('/a') { respond_with :index, :name => 'a' }
72
+ # get('/b') { respond_with :index, :name => 'b' }
73
+ #
74
+ # == Custom Types
75
+ #
76
+ # Use the +on+ method for defining actions for custom types:
77
+ #
78
+ # get '/' do
79
+ # respond_to do |f|
80
+ # f.xml { nokogiri :index }
81
+ # f.on('application/custom') { custom_action }
82
+ # f.on('text/*') { data.to_s }
83
+ # f.on('*/*') { "matches everything" }
84
+ # end
85
+ # end
86
+ #
87
+ # Definition order does not matter.
88
+ module RespondWith
89
+ class Format
90
+ def initialize(app)
91
+ @app, @map, @generic, @default = app, {}, {}, nil
92
+ end
93
+
94
+ def on(type, &block)
95
+ @app.settings.mime_types(type).each do |mime|
96
+ case mime
97
+ when '*/*' then @default = block
98
+ when /^([^\/]+)\/\*$/ then @generic[$1] = block
99
+ else @map[mime] = block
100
+ end
101
+ end
102
+ end
103
+
104
+ def finish
105
+ yield self if block_given?
106
+ mime_type = @app.content_type ||
107
+ @app.request.preferred_type(@map.keys) ||
108
+ @app.request.preferred_type ||
109
+ 'text/html'
110
+ type = mime_type.split(/\s*;\s*/, 2).first
111
+ handlers = [@map[type], @generic[type[/^[^\/]+/]], @default].compact
112
+ handlers.each do |block|
113
+ if result = block.call(type)
114
+ @app.content_type mime_type
115
+ @app.halt result
116
+ end
117
+ end
118
+ @app.halt 406
119
+ end
120
+
121
+ def method_missing(meth, *args, &block)
122
+ return super if args.any? or block.nil? or not @app.mime_type(meth)
123
+ on(meth, &block)
124
+ end
125
+ end
126
+
127
+ module Helpers
128
+ include Sinatra::JSON
129
+
130
+ def respond_with(template, object = nil, &block)
131
+ object, template = template, nil unless Symbol === template
132
+ format = Format.new(self)
133
+ format.on "*/*" do |type|
134
+ exts = settings.ext_map[type]
135
+ exts << :xml if type.end_with? '+xml'
136
+ if template
137
+ args = template_cache.fetch(type, template) { template_for(template, exts) }
138
+ if args.any?
139
+ locals = { :object => object }
140
+ locals.merge! object.to_hash if object.respond_to? :to_hash
141
+ args << { :locals => locals }
142
+ halt send(*args)
143
+ end
144
+ end
145
+ if object
146
+ exts.each do |ext|
147
+ halt json(object) if ext == :json
148
+ next unless meth = "to_#{ext}" and object.respond_to? meth
149
+ halt(*object.send(meth))
150
+ end
151
+ end
152
+ false
153
+ end
154
+ format.finish(&block)
155
+ end
156
+
157
+ def respond_to(&block)
158
+ Format.new(self).finish(&block)
159
+ end
160
+
161
+ private
162
+
163
+ def template_for(name, exts)
164
+ # in production this is cached, so don't worry to much about runtime
165
+ possible = []
166
+ settings.template_engines[:all].each do |engine|
167
+ exts.each { |ext| possible << [engine, "#{name}.#{ext}"] }
168
+ end
169
+ exts.each do |ext|
170
+ settings.template_engines[ext].each { |e| possible << [e, name] }
171
+ end
172
+ possible.each do |engine, template|
173
+ # not exactly like Tilt[engine], but does not trigger a require
174
+ klass = Tilt.mappings[Tilt.normalize(engine)].first
175
+ find_template(settings.views, template, klass) do |file|
176
+ next unless File.exist? file
177
+ return settings.rendering_method(engine) << template.to_sym
178
+ end
179
+ end
180
+ [] # nil or false would not be cached
181
+ end
182
+ end
183
+
184
+ attr_accessor :ext_map
185
+
186
+ def remap_extensions
187
+ ext_map.clear
188
+ Rack::Mime::MIME_TYPES.each { |e,t| ext_map[t] << e[1..-1].to_sym }
189
+ ext_map['text/javascript'] << 'js'
190
+ ext_map['text/xml'] << 'xml'
191
+ end
192
+
193
+ def mime_type(*)
194
+ result = super
195
+ remap_extensions
196
+ result
197
+ end
198
+
199
+ def respond_to(*formats)
200
+ if formats.any?
201
+ @respond_to ||= []
202
+ @respond_to.concat formats
203
+ elsif @respond_to.nil? and superclass.respond_to? :respond_to
204
+ superclass.respond_to
205
+ else
206
+ @respond_to
207
+ end
208
+ end
209
+
210
+ def rendering_method(engine)
211
+ return [engine] if Sinatra::Templates.method_defined? engine
212
+ return [:mab] if engine.to_sym == :markaby
213
+ [:render, :engine]
214
+ end
215
+
216
+ private
217
+
218
+ def compile!(verb, path, block, options = {})
219
+ options[:provides] ||= respond_to if respond_to
220
+ super
221
+ end
222
+
223
+ ENGINES = {
224
+ :css => [:less, :sass, :scss],
225
+ :xml => [:builder, :nokogiri],
226
+ :js => [:coffee],
227
+ :html => [:erb, :erubis, :haml, :slim, :liquid, :radius, :mab, :markdown,
228
+ :textile, :rdoc],
229
+ :all => Sinatra::Templates.instance_methods.map(&:to_sym) + [:mab] -
230
+ [:find_template, :markaby]
231
+ }
232
+
233
+ ENGINES.default = []
234
+
235
+ def self.registered(base)
236
+ base.ext_map = Hash.new { |h,k| h[k] = [] }
237
+ base.set :template_engines, ENGINES.dup
238
+ base.remap_extensions
239
+ base.helpers Helpers
240
+ end
241
+ end
242
+
243
+ register RespondWith
244
+ Delegator.delegate :respond_to
245
+ end