merb-core 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data/LICENSE +1 -1
  2. data/README +3 -3
  3. data/Rakefile +144 -33
  4. data/bin/merb +0 -0
  5. data/bin/merb-specs +0 -0
  6. data/docs/bootloading.dox +1 -0
  7. data/docs/merb-core-call-stack-diagram.mmap +0 -0
  8. data/docs/merb-core-call-stack-diagram.pdf +0 -0
  9. data/docs/merb-core-call-stack-diagram.png +0 -0
  10. data/lib/merb-core.rb +159 -37
  11. data/lib/merb-core/autoload.rb +1 -0
  12. data/lib/merb-core/bootloader.rb +208 -92
  13. data/lib/merb-core/config.rb +20 -6
  14. data/lib/merb-core/controller/abstract_controller.rb +113 -61
  15. data/lib/merb-core/controller/exceptions.rb +28 -13
  16. data/lib/merb-core/controller/merb_controller.rb +73 -44
  17. data/lib/merb-core/controller/mime.rb +25 -7
  18. data/lib/merb-core/controller/mixins/authentication.rb +1 -1
  19. data/lib/merb-core/controller/mixins/controller.rb +44 -8
  20. data/lib/merb-core/controller/mixins/render.rb +191 -128
  21. data/lib/merb-core/controller/mixins/responder.rb +65 -63
  22. data/lib/merb-core/controller/template.rb +103 -54
  23. data/lib/merb-core/core_ext.rb +7 -12
  24. data/lib/merb-core/core_ext/kernel.rb +128 -136
  25. data/lib/merb-core/dispatch/cookies.rb +26 -4
  26. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  27. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
  28. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  29. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +92 -0
  30. data/lib/merb-core/dispatch/dispatcher.rb +156 -224
  31. data/lib/merb-core/dispatch/request.rb +126 -25
  32. data/lib/merb-core/dispatch/router.rb +61 -6
  33. data/lib/merb-core/dispatch/router/behavior.rb +122 -41
  34. data/lib/merb-core/dispatch/router/route.rb +147 -22
  35. data/lib/merb-core/dispatch/session.rb +52 -2
  36. data/lib/merb-core/dispatch/session/cookie.rb +4 -2
  37. data/lib/merb-core/dispatch/session/memcached.rb +38 -27
  38. data/lib/merb-core/dispatch/session/memory.rb +18 -11
  39. data/lib/merb-core/dispatch/worker.rb +28 -0
  40. data/lib/merb-core/gem_ext/erubis.rb +58 -0
  41. data/lib/merb-core/logger.rb +3 -31
  42. data/lib/merb-core/plugins.rb +25 -3
  43. data/lib/merb-core/rack.rb +18 -12
  44. data/lib/merb-core/rack/adapter.rb +10 -8
  45. data/lib/merb-core/rack/adapter/ebb.rb +2 -2
  46. data/lib/merb-core/rack/adapter/irb.rb +31 -21
  47. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  48. data/lib/merb-core/rack/adapter/thin.rb +19 -9
  49. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  50. data/lib/merb-core/rack/application.rb +9 -84
  51. data/lib/merb-core/rack/middleware.rb +26 -0
  52. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  53. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  54. data/lib/merb-core/rack/middleware/static.rb +45 -0
  55. data/lib/merb-core/server.rb +27 -9
  56. data/lib/merb-core/tasks/audit.rake +68 -0
  57. data/lib/merb-core/tasks/merb.rb +1 -0
  58. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -0
  59. data/lib/merb-core/tasks/stats.rake +71 -0
  60. data/lib/merb-core/test.rb +2 -1
  61. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -3
  62. data/lib/merb-core/test/helpers/request_helper.rb +66 -24
  63. data/lib/merb-core/test/matchers/controller_matchers.rb +36 -4
  64. data/lib/merb-core/test/matchers/route_matchers.rb +12 -3
  65. data/lib/merb-core/test/matchers/view_matchers.rb +3 -3
  66. data/lib/merb-core/test/run_specs.rb +1 -0
  67. data/lib/merb-core/test/tasks/spectasks.rb +13 -5
  68. data/lib/merb-core/test/test_ext/string.rb +14 -0
  69. data/lib/merb-core/vendor/facets/dictionary.rb +3 -3
  70. data/lib/merb-core/vendor/facets/inflect.rb +82 -37
  71. data/lib/merb-core/version.rb +2 -2
  72. data/spec/private/config/config_spec.rb +39 -4
  73. data/spec/private/core_ext/kernel_spec.rb +3 -14
  74. data/spec/private/dispatch/bootloader_spec.rb +1 -1
  75. data/spec/private/dispatch/cookies_spec.rb +181 -69
  76. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +0 -2
  77. data/spec/private/dispatch/fixture/app/controllers/foo.rb +0 -2
  78. data/spec/private/dispatch/fixture/config/rack.rb +10 -0
  79. data/spec/private/dispatch/fixture/log/merb_test.log +7054 -1802
  80. data/spec/private/dispatch/route_params_spec.rb +2 -3
  81. data/spec/private/dispatch/session_mixin_spec.rb +47 -0
  82. data/spec/private/plugins/plugin_spec.rb +73 -59
  83. data/spec/private/router/behavior_spec.rb +60 -0
  84. data/spec/private/router/fixture/log/merb_test.log +1693 -0
  85. data/spec/private/router/route_spec.rb +414 -0
  86. data/spec/private/router/router_spec.rb +175 -0
  87. data/spec/private/vendor/facets/plural_spec.rb +564 -0
  88. data/spec/private/vendor/facets/singular_spec.rb +489 -0
  89. data/spec/public/abstract_controller/controllers/cousins.rb +41 -0
  90. data/spec/public/abstract_controller/controllers/helpers.rb +12 -2
  91. data/spec/public/abstract_controller/controllers/partial.rb +17 -2
  92. data/spec/public/abstract_controller/controllers/render.rb +16 -1
  93. data/spec/public/abstract_controller/controllers/views/helpers/capture_eq/index.erb +1 -0
  94. data/spec/public/abstract_controller/controllers/views/helpers/capture_with_args/index.erb +1 -0
  95. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_two_throw_contents/index.erb +1 -0
  96. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/_collection.erb +1 -0
  97. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/index.erb +1 -0
  98. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/_partial.erb +1 -0
  99. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/index.erb +1 -0
  100. data/spec/public/abstract_controller/filter_spec.rb +20 -1
  101. data/spec/public/abstract_controller/helper_spec.rb +10 -2
  102. data/spec/public/abstract_controller/partial_spec.rb +8 -0
  103. data/spec/public/abstract_controller/render_spec.rb +8 -0
  104. data/spec/public/abstract_controller/spec_helper.rb +7 -3
  105. data/spec/public/boot_loader/boot_loader_spec.rb +2 -2
  106. data/spec/public/controller/base_spec.rb +10 -2
  107. data/spec/public/controller/config/init.rb +6 -0
  108. data/spec/public/controller/controllers/authentication.rb +9 -11
  109. data/spec/public/controller/controllers/base.rb +2 -8
  110. data/spec/public/controller/controllers/cookies.rb +16 -0
  111. data/spec/public/controller/controllers/dispatcher.rb +35 -0
  112. data/spec/public/controller/controllers/display.rb +62 -14
  113. data/spec/public/controller/controllers/redirect.rb +36 -0
  114. data/spec/public/controller/controllers/responder.rb +37 -11
  115. data/spec/public/controller/controllers/views/layout/custom_arg.json.erb +1 -0
  116. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.html.erb +1 -0
  117. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.xml.erb +1 -0
  118. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/no_layout.html.erb +1 -0
  119. data/spec/public/controller/cookies_spec.rb +23 -0
  120. data/spec/public/controller/dispatcher_spec.rb +411 -0
  121. data/spec/public/controller/display_spec.rb +43 -10
  122. data/spec/public/controller/redirect_spec.rb +33 -0
  123. data/spec/public/controller/responder_spec.rb +79 -11
  124. data/spec/public/controller/spec_helper.rb +3 -1
  125. data/spec/public/controller/url_spec.rb +10 -0
  126. data/spec/public/core/merb_core_spec.rb +11 -0
  127. data/spec/public/core_ext/fixtures/core_ext_dependency.rb +2 -0
  128. data/spec/public/core_ext/kernel_spec.rb +9 -0
  129. data/spec/public/core_ext/spec_helper.rb +1 -0
  130. data/spec/public/directory_structure/directory/log/merb_test.log +3729 -272
  131. data/spec/public/directory_structure/directory_spec.rb +3 -4
  132. data/spec/public/logger/logger_spec.rb +4 -4
  133. data/spec/public/reloading/directory/log/merb_test.log +288066 -15
  134. data/spec/public/reloading/reload_spec.rb +49 -27
  135. data/spec/public/request/multipart_spec.rb +26 -0
  136. data/spec/public/request/request_spec.rb +21 -2
  137. data/spec/public/router/fixation_spec.rb +27 -0
  138. data/spec/public/router/fixture/log/merb_test.log +30050 -0
  139. data/spec/public/router/nested_matches_spec.rb +97 -0
  140. data/spec/public/router/resource_spec.rb +1 -9
  141. data/spec/public/router/resources_spec.rb +0 -20
  142. data/spec/public/router/spec_helper.rb +27 -9
  143. data/spec/public/router/special_spec.rb +21 -8
  144. data/spec/public/template/template_spec.rb +17 -5
  145. data/spec/public/test/controller_matchers_spec.rb +10 -0
  146. data/spec/public/test/request_helper_spec.rb +29 -0
  147. data/spec/public/test/route_helper_spec.rb +18 -1
  148. data/spec/public/test/route_matchers_spec.rb +28 -1
  149. data/spec/public/test/view_matchers_spec.rb +3 -3
  150. data/spec/spec_helper.rb +56 -12
  151. metadata +89 -47
  152. data/lib/merb-core/core_ext/class.rb +0 -299
  153. data/lib/merb-core/core_ext/hash.rb +0 -426
  154. data/lib/merb-core/core_ext/mash.rb +0 -154
  155. data/lib/merb-core/core_ext/object.rb +0 -147
  156. data/lib/merb-core/core_ext/object_space.rb +0 -14
  157. data/lib/merb-core/core_ext/rubygems.rb +0 -28
  158. data/lib/merb-core/core_ext/set.rb +0 -46
  159. data/lib/merb-core/core_ext/string.rb +0 -89
  160. data/lib/merb-core/core_ext/time.rb +0 -13
  161. data/lib/merb-core/dispatch/exceptions.html.erb +0 -297
  162. data/spec/private/core_ext/class_spec.rb +0 -22
  163. data/spec/private/core_ext/hash_spec.rb +0 -522
  164. data/spec/private/core_ext/object_spec.rb +0 -121
  165. data/spec/private/core_ext/set_spec.rb +0 -26
  166. data/spec/private/core_ext/string_spec.rb +0 -167
  167. data/spec/private/core_ext/time_spec.rb +0 -16
  168. data/spec/private/dispatch/dispatch_spec.rb +0 -26
  169. data/spec/private/dispatch/fixture/log/development.log +0 -1
  170. data/spec/private/dispatch/fixture/log/merb.4000.pid +0 -1
  171. data/spec/private/dispatch/fixture/log/production.log +0 -1
  172. data/spec/private/dispatch/fixture/merb.4000.pid +0 -1
  173. data/spec/private/rack/application_spec.rb +0 -43
  174. data/spec/public/controller/log/merb.4000.pid +0 -1
  175. data/spec/public/directory_structure/directory/log/merb.4000.pid +0 -1
  176. data/spec/public/directory_structure/directory/merb.4000.pid +0 -1
  177. data/spec/public/reloading/directory/log/merb.4000.pid +0 -1
  178. data/spec/public/reloading/directory/merb.4000.pid +0 -1
@@ -1,6 +1,7 @@
1
1
  require 'enumerator'
2
2
  require 'merb-core/controller/mime'
3
3
  require "merb-core/vendor/facets/dictionary"
4
+
4
5
  module Merb
5
6
  # The ResponderMixin adds methods that help you manage what
6
7
  # formats your controllers have available, determine what format(s)
@@ -98,12 +99,13 @@ module Merb
98
99
  module ResponderMixin
99
100
 
100
101
  TYPES = Dictionary.new
102
+ MIMES = {}
101
103
 
102
104
  class ContentTypeAlreadySet < StandardError; end
103
105
 
104
106
  # ==== Parameters
105
107
  # base<Module>:: The module that ResponderMixin was mixed into
106
- def self.included(base) # :nodoc:
108
+ def self.included(base)
107
109
  base.extend(ClassMethods)
108
110
  base.class_eval do
109
111
  class_inheritable_accessor :class_provided_formats
@@ -132,9 +134,7 @@ module Merb
132
134
  #---
133
135
  # @public
134
136
  def provides(*formats)
135
- formats.each do |fmt|
136
- self.class_provided_formats << fmt unless class_provided_formats.include?(fmt)
137
- end
137
+ self.class_provided_formats |= formats
138
138
  end
139
139
 
140
140
  # This class should only provide the formats listed here, despite any
@@ -195,28 +195,6 @@ module Merb
195
195
  @_provided_formats ||= class_provided_formats.dup
196
196
  end
197
197
 
198
- # Sets the provided formats for this action. Usually, you would use a
199
- # combination of provides, only_provides and does_not_provide to manage
200
- # this, but you can set it directly.
201
- #
202
- # ==== Parameters
203
- # *formats<Symbol>:: A list of formats to be passed to provides.
204
- #
205
- # ==== Raises
206
- # Merb::ResponderMixin::ContentTypeAlreadySet::
207
- # Content negotiation already occured, and the content_type is set.
208
- #
209
- # ==== Returns
210
- # Array[Symbol]:: List of formats passed in.
211
- def _set_provided_formats(*formats)
212
- if @_content_type
213
- raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set"
214
- end
215
- @_provided_formats = []
216
- provides(*formats)
217
- end
218
- alias :_provided_formats= :_set_provided_formats
219
-
220
198
  # Adds formats to the list of provided formats for this particular request.
221
199
  # Usually used to add formats to a single action. See also the
222
200
  # controller-level provides that affects all actions in a controller.
@@ -238,9 +216,7 @@ module Merb
238
216
  if @_content_type
239
217
  raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set"
240
218
  end
241
- formats.each do |fmt|
242
- _provided_formats << fmt unless _provided_formats.include?(fmt)
243
- end
219
+ @_provided_formats = self._provided_formats | formats # merges with class_provided_formats if not already
244
220
  end
245
221
 
246
222
  # Sets list of provided formats for this particular request. Usually used
@@ -257,7 +233,8 @@ module Merb
257
233
  #---
258
234
  # @public
259
235
  def only_provides(*formats)
260
- _set_provided_formats(*formats)
236
+ @_provided_formats = []
237
+ provides(*formats)
261
238
  end
262
239
 
263
240
  # Removes formats from the list of provided formats for this particular
@@ -275,8 +252,7 @@ module Merb
275
252
  #---
276
253
  # @public
277
254
  def does_not_provide(*formats)
278
- formats.flatten!
279
- self._provided_formats -= formats
255
+ @_provided_formats -= formats.flatten
280
256
  end
281
257
 
282
258
  # Do the content negotiation:
@@ -285,22 +261,29 @@ module Merb
285
261
  # 3. If it's */*, use the first provided format
286
262
  # 4. Look for one that is provided, in order of request
287
263
  # 5. Raise 406 if none found
288
- def _perform_content_negotiation # :nodoc:
289
- raise Merb::ControllerExceptions::NotAcceptable if _provided_formats.empty?
264
+ def _perform_content_negotiation
290
265
  if (fmt = params[:format]) && !fmt.empty?
291
266
  accepts = [fmt.to_sym]
267
+ elsif request.accept =~ %r{^(text/html|\*/\*)} && _provided_formats.first == :html
268
+ # Handle the common case of text/html and :html provided after checking :format
269
+ return :html
292
270
  else
293
271
  accepts = Responder.parse(request.accept).map {|t| t.to_sym}.compact
294
272
  end
295
- specifics = accepts & _provided_formats
273
+
274
+ # no need to make a bunch of method calls to _provided_formats
275
+ provided_formats = _provided_formats
276
+
277
+ specifics = accepts & provided_formats
296
278
  return specifics.first unless specifics.length == 0
297
- return _provided_formats.first if accepts.include? :all
279
+ return provided_formats.first if accepts.include?(:all) && !provided_formats.empty?
280
+
298
281
  message = "A format (%s) that isn't provided (%s) has been requested. "
299
282
  message += "Make sure the action provides the format, and be "
300
283
  message += "careful of before filters which won't recognize "
301
284
  message += "formats provided within actions."
302
285
  raise Merb::ControllerExceptions::NotAcceptable,
303
- (message % [accepts.join(', '), _provided_formats.join(', ')])
286
+ (message % [accepts.join(', '), provided_formats.join(', ')])
304
287
  end
305
288
 
306
289
  # Returns the output format for this request, based on the
@@ -350,8 +333,18 @@ module Merb
350
333
  def content_type=(type)
351
334
  unless Merb.available_mime_types.has_key?(type)
352
335
  raise Merb::ControllerExceptions::NotAcceptable.new("Unknown content_type for response: #{type}")
353
- end
354
- headers['Content-Type'] = Merb.available_mime_types[type][:request_headers].first
336
+ end
337
+
338
+ mime = Merb.available_mime_types[type]
339
+
340
+ headers["Content-Type"] = mime[:content_type]
341
+
342
+ # merge any format specific response headers
343
+ mime[:response_headers].each { |k,v| headers[k] ||= v }
344
+
345
+ # if given, use a block to finetune any runtime headers
346
+ mime[:response_block].call(self) if mime[:response_block]
347
+
355
348
  @_content_type = type
356
349
  end
357
350
 
@@ -369,19 +362,19 @@ module Merb
369
362
  # ==== Returns
370
363
  # Array[AcceptType]:: The accepted types.
371
364
  def self.parse(accept_header)
372
- list = accept_header.to_s.split(/,/).enum_for(:each_with_index).map do |entry,index|
373
- AcceptType.new(entry,index += 1)
374
- end.sort.uniq
375
- # firefox (and possibly other browsers) send broken default accept headers.
376
- # fix them up by sorting alternate xml forms (namely application/xhtml+xml)
377
- # ahead of pure xml types (application/xml,text/xml).
378
- if app_xml = list.detect{|e| e.super_range == 'application/xml'}
379
- list.select{|e| e.to_s =~ /\+xml/}.each { |acc_type|
380
- list[list.index(acc_type)],list[list.index(app_xml)] =
381
- list[list.index(app_xml)],list[list.index(acc_type)] }
365
+ # FF2 is broken. If we get FF2 headers, use FF3 headers instead.
366
+ if accept_header == "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
367
+ accept_header = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
368
+ end
369
+
370
+ headers = accept_header.split(/,/)
371
+ idx, list = 0, []
372
+ while idx < headers.size
373
+ list << AcceptType.new(headers[idx], idx)
374
+ idx += 1
382
375
  end
383
- list
384
- end
376
+ list.sort
377
+ end
385
378
 
386
379
  end
387
380
 
@@ -396,12 +389,15 @@ module Merb
396
389
  # priority.
397
390
  def initialize(entry,index)
398
391
  @index = index
399
- @media_range, quality = entry.split(/;\s*q=/).map{|a| a.strip }
400
- @type, @sub_type = @media_range.split(/\//)
401
- quality ||= 0.0 if @media_range == '*/*'
402
- @quality = ((quality || 1.0).to_f * 100).to_i
392
+
393
+ entry =~ /\s*([^;\s]*)\s*(;\s*q=\s*(.*))?/
394
+ @media_range, quality = $1, $3
395
+
396
+ @type, @sub_type = @media_range.split(%r{/})
397
+ (quality ||= 0.0) if @media_range == "*/*"
398
+ @quality = quality ? (quality.to_f * 100).to_i : 100
403
399
  end
404
-
400
+
405
401
  # Compares two accept types for sorting purposes.
406
402
  #
407
403
  # ==== Parameters
@@ -412,11 +408,14 @@ module Merb
412
408
  # -1, 0 or 1, depending on whether entry has a lower, equal or higher
413
409
  # priority than the accept type being compared.
414
410
  def <=>(entry)
415
- c = entry.quality <=> quality
416
- c = index <=> entry.index if c == 0
417
- c
411
+ if entry.quality == quality
412
+ @index <=> entry.index
413
+ else
414
+ entry.quality <=> @quality
415
+ end
418
416
  end
419
417
 
418
+
420
419
  # ==== Parameters
421
420
  # entry<AcceptType>:: The accept type to compare.
422
421
  #
@@ -439,9 +438,12 @@ module Merb
439
438
  # Array[String]::
440
439
  # All Accept header values, such as "text/html", that match this type.
441
440
  def synonyms
442
- @syns ||= Merb.available_mime_types.values.map do |e|
443
- e[:request_headers] if e[:request_headers].include?(@media_range)
444
- end.compact.flatten
441
+ return @syns if @syns
442
+ if mime = Merb.available_mime_types[Merb::ResponderMixin::MIMES[@media_range]]
443
+ @syns = mime[:accepts]
444
+ else
445
+ @syns = []
446
+ end
445
447
  end
446
448
 
447
449
  # ==== Returns
@@ -456,7 +458,7 @@ module Merb
456
458
  # Symbol: The type as a symbol, e.g. :html.
457
459
  def to_sym
458
460
  Merb.available_mime_types.select{|k,v|
459
- v[:request_headers] == synonyms || v[:request_headers][0] == synonyms[0]}.flatten.first
461
+ v[:accepts] == synonyms || v[:accepts][0] == synonyms[0]}.flatten.first
460
462
  end
461
463
 
462
464
  # ==== Returns
@@ -29,6 +29,27 @@ module Merb::Template
29
29
  path.gsub(/[^\.a-zA-Z0-9]/, "__").gsub(/\./, "_")
30
30
  end
31
31
 
32
+ # For a given path, get an IO object that responds to #path.
33
+ #
34
+ # This is so that plugins can override this if they provide
35
+ # mechanisms for specifying templates that are not just simple
36
+ # files. The plugin is responsible for ensuring that the fake
37
+ # path provided will work with #template_for, and thus the
38
+ # RenderMixin in general.
39
+ #
40
+ # ==== Parameters
41
+ # path<String>:: A full path to find a template for. This is the
42
+ # path that the RenderMixin assumes it should find the template
43
+ # in.
44
+ #
45
+ # ==== Returns
46
+ # IO#path:: An IO object that responds to path (File or VirtualFile).
47
+ #---
48
+ # @semipublic
49
+ def load_template_io(path)
50
+ File.open(path, "r")
51
+ end
52
+
32
53
  # Get the name of the template method for a particular path.
33
54
  #
34
55
  # ==== Parameters
@@ -44,25 +65,34 @@ module Merb::Template
44
65
 
45
66
  ret =
46
67
  if Merb::Config[:reload_templates]
47
- file = Dir["#{path}.{#{Merb::Template::EXTENSIONS.keys.join(',')}}"].first
48
- METHOD_LIST[path] = file ? inline_template(file) : nil
68
+ file = Dir["#{path}.{#{template_extensions.join(',')}}"].first
69
+ METHOD_LIST[path] = file ? inline_template(load_template_io(file)) : nil
49
70
  else
50
71
  METHOD_LIST[path] ||= begin
51
- file = Dir["#{path}.{#{Merb::Template::EXTENSIONS.keys.join(',')}}"].first
52
- file ? inline_template(file) : nil
72
+ file = Dir["#{path}.{#{template_extensions.join(',')}}"].first
73
+ file ? inline_template(load_template_io(file)) : nil
53
74
  end
54
75
  end
55
76
 
56
77
  ret
57
78
  end
58
79
 
80
+ # Get all known template extensions
81
+ #
82
+ # ==== Returns
83
+ # Array:: Extension strings.
84
+ #---
85
+ # @semipublic
86
+ def template_extensions
87
+ EXTENSIONS.keys
88
+ end
89
+
59
90
  # Takes a template at a particular path and inlines it into a module and
60
91
  # adds it to the METHOD_LIST table to speed lookup later.
61
92
  #
62
93
  # ==== Parameters
63
- # path<String>::
64
- # The full path of the template (minus the templating specifier) to
65
- # inline.
94
+ # io<#path>::
95
+ # An IO that responds to #path (File or VirtualFile)
66
96
  # mod<Module>::
67
97
  # The module to put the compiled method into. Defaults to
68
98
  # Merb::InlineTemplates
@@ -72,10 +102,12 @@ module Merb::Template
72
102
  # must be available to instances of AbstractController that will use it.
73
103
  #---
74
104
  # @public
75
- def inline_template(path, mod = Merb::InlineTemplates)
76
- path = File.expand_path(path)
77
- METHOD_LIST[path.gsub(/\.[^\.]*$/, "")] =
78
- engine_for(path).compile_template(path, template_name(path), mod)
105
+ def inline_template(io, mod = Merb::InlineTemplates)
106
+ path = File.expand_path(io.path)
107
+ ret = METHOD_LIST[path.gsub(/\.[^\.]*$/, "")] =
108
+ engine_for(path).compile_template(io, template_name(path), mod)
109
+ io.close
110
+ ret
79
111
  end
80
112
 
81
113
  # Finds the engine for a particular path.
@@ -122,28 +154,21 @@ module Merb::Template
122
154
 
123
155
  class Erubis
124
156
  # ==== Parameters
125
- # path<String>:: A full path to the template.
157
+ # io<#path>:: An IO containing the full path of the template.
126
158
  # name<String>:: The name of the method that will be created.
127
159
  # mod<Module>:: The module that the compiled method will be placed into.
128
- def self.compile_template(path, name, mod)
129
- template = ::Erubis::Eruby.new(File.read(path))
130
- template.def_method(mod, name, path)
160
+ def self.compile_template(io, name, mod)
161
+ template = ::Erubis::BlockAwareEruby.new(io.read)
162
+
163
+ _old_verbose, $VERBOSE = $VERBOSE, nil
164
+ template.def_method(mod, name, File.expand_path(io.path))
165
+ $VERBOSE = _old_verbose
166
+
131
167
  name
132
168
  end
133
169
 
134
170
  module Mixin
135
171
 
136
- # Provides direct acccess to the buffer for this view context
137
- #
138
- # ==== Parameters
139
- # the_binding<Binding>:: The binding to pass to the buffer.
140
- #
141
- # ==== Returns
142
- # DOC
143
- def _erb_buffer( the_binding )
144
- @_buffer = eval( "_buf", the_binding, __FILE__, __LINE__)
145
- end
146
-
147
172
  # ==== Parameters
148
173
  # *args:: Arguments to pass to the block.
149
174
  # &block:: The template block to call.
@@ -158,30 +183,16 @@ module Merb::Template
158
183
  # <p>Some Foo content!</p>
159
184
  # <% end %>
160
185
  def capture_erb(*args, &block)
161
- # get the buffer from the block's binding
162
- buffer = _erb_buffer( block.binding ) rescue nil
163
-
164
- # If there is no buffer, just call the block and get the contents
165
- if buffer.nil?
166
- block.call(*args)
167
- # If there is a buffer, execute the block, then extract its contents
168
- else
169
- pos = buffer.length
170
- block.call(*args)
171
-
172
- # extract the block
173
- data = buffer[pos..-1]
174
-
175
- # replace it in the original with empty string
176
- buffer[pos..-1] = ''
177
-
178
- data
179
- end
186
+ _old_buf, @_erb_buf = @_erb_buf, ""
187
+ block.call(*args)
188
+ ret = @_erb_buf
189
+ @_erb_buf = _old_buf
190
+ ret
180
191
  end
181
192
 
182
193
  # DOC
183
194
  def concat_erb(string, binding)
184
- _erb_buffer(binding) << string
195
+ @_erb_buf << string
185
196
  end
186
197
 
187
198
  end
@@ -192,14 +203,52 @@ module Merb::Template
192
203
  end
193
204
 
194
205
  module Erubis
195
- module RubyEvaluator
206
+ module BlockAwareEnhancer
207
+ def add_preamble(src)
208
+ src << "_old_buf, @_erb_buf = @_erb_buf, ''; "
209
+ src << "@_engine = 'erb'; "
210
+ end
196
211
 
197
- # DOC
198
- def def_method(object, method_name, filename=nil)
199
- m = object.is_a?(Module) ? :module_eval : :instance_eval
200
- setup = "@_engine = 'erb'"
201
- object.__send__(m, "def #{method_name}(locals={}); #{setup}; #{@src}; end", filename || @filename || '(erubis)')
212
+ def add_postamble(src)
213
+ src << "\n" unless src[-1] == ?\n
214
+ src << "_ret = @_erb_buf; @_erb_buf = _old_buf; _ret.to_s;\n"
202
215
  end
203
-
216
+
217
+ def add_text(src, text)
218
+ src << " @_erb_buf.concat('" << escape_text(text) << "'); "
219
+ end
220
+
221
+ def add_expr_escaped(src, code)
222
+ src << ' @_erb_buf.concat(' << escaped_expr(code) << ');'
223
+ end
224
+
225
+ def add_stmt2(src, code, tailch)
226
+ src << code
227
+ src << " ).to_s; " if tailch == "="
228
+ src << ';' unless code[-1] == ?\n
229
+ end
230
+
231
+ def add_expr_literal(src, code)
232
+ if code =~ /(do|\{)(\s*\|[^|]*\|)?\s*\Z/
233
+ src << ' @_erb_buf.concat( ' << code << "; "
234
+ else
235
+ src << ' @_erb_buf.concat((' << code << ').to_s);'
236
+ end
237
+ end
238
+ end
239
+
240
+ class BlockAwareEruby < Eruby
241
+ include BlockAwareEnhancer
204
242
  end
205
- end
243
+
244
+ # module RubyEvaluator
245
+ #
246
+ # # DOC
247
+ # def def_method(object, method_name, filename=nil)
248
+ # m = object.is_a?(Module) ? :module_eval : :instance_eval
249
+ # setup = "@_engine = 'erb'"
250
+ # object.__send__(m, "def #{method_name}(locals={}); #{setup}; #{@src}; end", filename || @filename || '(erubis)')
251
+ # end
252
+ #
253
+ # end
254
+ end