pirj-sinatra-contrib 1.3.0

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