merb-core 0.9.2

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 (246) hide show
  1. data/LICENSE +20 -0
  2. data/README +21 -0
  3. data/Rakefile +285 -0
  4. data/TODO +0 -0
  5. data/bin/merb +8 -0
  6. data/bin/merb-specs +5 -0
  7. data/docs/bootloading.dox +57 -0
  8. data/docs/documentation_standards +40 -0
  9. data/docs/new_render_api +51 -0
  10. data/lib/merb-core.rb +304 -0
  11. data/lib/merb-core/autoload.rb +29 -0
  12. data/lib/merb-core/bootloader.rb +601 -0
  13. data/lib/merb-core/config.rb +284 -0
  14. data/lib/merb-core/constants.rb +43 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +531 -0
  16. data/lib/merb-core/controller/exceptions.rb +257 -0
  17. data/lib/merb-core/controller/merb_controller.rb +214 -0
  18. data/lib/merb-core/controller/mime.rb +88 -0
  19. data/lib/merb-core/controller/mixins/controller.rb +262 -0
  20. data/lib/merb-core/controller/mixins/render.rb +324 -0
  21. data/lib/merb-core/controller/mixins/responder.rb +464 -0
  22. data/lib/merb-core/controller/template.rb +205 -0
  23. data/lib/merb-core/core_ext.rb +12 -0
  24. data/lib/merb-core/core_ext/class.rb +192 -0
  25. data/lib/merb-core/core_ext/hash.rb +422 -0
  26. data/lib/merb-core/core_ext/kernel.rb +304 -0
  27. data/lib/merb-core/core_ext/mash.rb +154 -0
  28. data/lib/merb-core/core_ext/object.rb +136 -0
  29. data/lib/merb-core/core_ext/object_space.rb +14 -0
  30. data/lib/merb-core/core_ext/rubygems.rb +28 -0
  31. data/lib/merb-core/core_ext/set.rb +41 -0
  32. data/lib/merb-core/core_ext/string.rb +69 -0
  33. data/lib/merb-core/dispatch/cookies.rb +92 -0
  34. data/lib/merb-core/dispatch/dispatcher.rb +233 -0
  35. data/lib/merb-core/dispatch/exceptions.html.erb +297 -0
  36. data/lib/merb-core/dispatch/request.rb +560 -0
  37. data/lib/merb-core/dispatch/router.rb +141 -0
  38. data/lib/merb-core/dispatch/router/behavior.rb +777 -0
  39. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  40. data/lib/merb-core/dispatch/router/route.rb +212 -0
  41. data/lib/merb-core/dispatch/session.rb +28 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +166 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +161 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +234 -0
  45. data/lib/merb-core/gem_ext/erubis.rb +19 -0
  46. data/lib/merb-core/logger.rb +230 -0
  47. data/lib/merb-core/plugins.rb +25 -0
  48. data/lib/merb-core/rack.rb +15 -0
  49. data/lib/merb-core/rack/adapter.rb +42 -0
  50. data/lib/merb-core/rack/adapter/ebb.rb +22 -0
  51. data/lib/merb-core/rack/adapter/evented_mongrel.rb +24 -0
  52. data/lib/merb-core/rack/adapter/fcgi.rb +16 -0
  53. data/lib/merb-core/rack/adapter/irb.rb +108 -0
  54. data/lib/merb-core/rack/adapter/mongrel.rb +25 -0
  55. data/lib/merb-core/rack/adapter/runner.rb +27 -0
  56. data/lib/merb-core/rack/adapter/thin.rb +27 -0
  57. data/lib/merb-core/rack/adapter/webrick.rb +35 -0
  58. data/lib/merb-core/rack/application.rb +77 -0
  59. data/lib/merb-core/rack/handler/mongrel.rb +97 -0
  60. data/lib/merb-core/server.rb +184 -0
  61. data/lib/merb-core/test.rb +10 -0
  62. data/lib/merb-core/test/helpers.rb +9 -0
  63. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  64. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  65. data/lib/merb-core/test/helpers/request_helper.rb +257 -0
  66. data/lib/merb-core/test/helpers/route_helper.rb +33 -0
  67. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  68. data/lib/merb-core/test/matchers.rb +9 -0
  69. data/lib/merb-core/test/matchers/controller_matchers.rb +269 -0
  70. data/lib/merb-core/test/matchers/route_matchers.rb +136 -0
  71. data/lib/merb-core/test/matchers/view_matchers.rb +293 -0
  72. data/lib/merb-core/test/run_specs.rb +38 -0
  73. data/lib/merb-core/test/tasks/spectasks.rb +39 -0
  74. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  75. data/lib/merb-core/test/test_ext/object.rb +14 -0
  76. data/lib/merb-core/vendor/facets.rb +2 -0
  77. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  78. data/lib/merb-core/vendor/facets/inflect.rb +211 -0
  79. data/lib/merb-core/version.rb +11 -0
  80. data/spec/private/config/adapter_spec.rb +32 -0
  81. data/spec/private/config/config_spec.rb +139 -0
  82. data/spec/private/config/environment_spec.rb +13 -0
  83. data/spec/private/config/spec_helper.rb +1 -0
  84. data/spec/private/core_ext/hash_spec.rb +506 -0
  85. data/spec/private/core_ext/kernel_spec.rb +46 -0
  86. data/spec/private/core_ext/object_spec.rb +39 -0
  87. data/spec/private/core_ext/set_spec.rb +26 -0
  88. data/spec/private/core_ext/string_spec.rb +9 -0
  89. data/spec/private/dispatch/cookies_spec.rb +107 -0
  90. data/spec/private/dispatch/dispatch_spec.rb +26 -0
  91. data/spec/private/dispatch/fixture/app/controllers/application.rb +4 -0
  92. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +27 -0
  93. data/spec/private/dispatch/fixture/app/controllers/foo.rb +21 -0
  94. data/spec/private/dispatch/fixture/app/helpers/global_helpers.rb +8 -0
  95. data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +37 -0
  96. data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +216 -0
  97. data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +38 -0
  98. data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +40 -0
  99. data/spec/private/dispatch/fixture/app/views/foo/bar.html.erb +0 -0
  100. data/spec/private/dispatch/fixture/app/views/layout/application.html.erb +11 -0
  101. data/spec/private/dispatch/fixture/config/environments/development.rb +6 -0
  102. data/spec/private/dispatch/fixture/config/environments/production.rb +5 -0
  103. data/spec/private/dispatch/fixture/config/environments/test.rb +6 -0
  104. data/spec/private/dispatch/fixture/config/init.rb +45 -0
  105. data/spec/private/dispatch/fixture/config/rack.rb +1 -0
  106. data/spec/private/dispatch/fixture/config/router.rb +35 -0
  107. data/spec/private/dispatch/fixture/log/development.log +1 -0
  108. data/spec/private/dispatch/fixture/log/merb.4000.pid +1 -0
  109. data/spec/private/dispatch/fixture/log/merb_test.log +2040 -0
  110. data/spec/private/dispatch/fixture/log/production.log +1 -0
  111. data/spec/private/dispatch/fixture/merb.4000.pid +1 -0
  112. data/spec/private/dispatch/fixture/public/images/merb.jpg +0 -0
  113. data/spec/private/dispatch/fixture/public/merb.fcgi +4 -0
  114. data/spec/private/dispatch/fixture/public/stylesheets/master.css +119 -0
  115. data/spec/private/dispatch/route_params_spec.rb +24 -0
  116. data/spec/private/dispatch/spec_helper.rb +1 -0
  117. data/spec/private/plugins/plugin_spec.rb +81 -0
  118. data/spec/private/rack/application_spec.rb +43 -0
  119. data/spec/public/DEFINITIONS +11 -0
  120. data/spec/public/abstract_controller/controllers/alt_views/layout/application.erb +1 -0
  121. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_string_controller_layout.erb +1 -0
  122. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_template_controller_layout.erb +1 -0
  123. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/index.erb +1 -0
  124. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/show.erb +1 -0
  125. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
  126. data/spec/public/abstract_controller/controllers/alt_views/partial/basic_partial_with_multiple_roots/_partial.erb +1 -0
  127. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_and_custom_location/index.erb +1 -0
  128. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_inherited/index.erb +1 -0
  129. data/spec/public/abstract_controller/controllers/display.rb +54 -0
  130. data/spec/public/abstract_controller/controllers/filters.rb +167 -0
  131. data/spec/public/abstract_controller/controllers/helpers.rb +31 -0
  132. data/spec/public/abstract_controller/controllers/partial.rb +106 -0
  133. data/spec/public/abstract_controller/controllers/render.rb +86 -0
  134. data/spec/public/abstract_controller/controllers/views/helpers/capture/index.erb +1 -0
  135. data/spec/public/abstract_controller/controllers/views/helpers/concat/index.erb +1 -0
  136. data/spec/public/abstract_controller/controllers/views/layout/alt.erb +1 -0
  137. data/spec/public/abstract_controller/controllers/views/layout/custom.erb +1 -0
  138. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object/index.erb +1 -0
  139. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object_with_action/new.erb +1 -0
  140. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template/index.erb +1 -0
  141. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_app_layout/index.erb +0 -0
  142. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_custom_layout/index.erb +1 -0
  143. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
  144. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/show.erb +1 -0
  145. data/spec/public/abstract_controller/controllers/views/partial/another_directory/_partial.erb +1 -0
  146. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/_partial.erb +1 -0
  147. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/index.erb +1 -0
  148. data/spec/public/abstract_controller/controllers/views/partial/basic_partial_with_multiple_roots/index.erb +1 -0
  149. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_first.erb +1 -0
  150. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_second.erb +1 -0
  151. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/index.erb +1 -0
  152. data/spec/public/abstract_controller/controllers/views/partial/partial_in_another_directory/index.erb +1 -0
  153. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/_collection.erb +1 -0
  154. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/index.erb +1 -0
  155. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/_collection.erb +1 -0
  156. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/index.erb +1 -0
  157. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/_collection.erb +1 -0
  158. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/index.erb +1 -0
  159. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/_variables.erb +1 -0
  160. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/index.erb +1 -0
  161. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/_both.erb +1 -0
  162. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/index.erb +1 -0
  163. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/_with_partial.erb +1 -0
  164. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/index.erb +1 -0
  165. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/_with_partial.erb +1 -0
  166. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/index.erb +1 -0
  167. data/spec/public/abstract_controller/controllers/views/partial/with_partial/_with_partial.erb +1 -0
  168. data/spec/public/abstract_controller/controllers/views/partial/with_partial/index.erb +1 -0
  169. data/spec/public/abstract_controller/controllers/views/test_display/foo.html.erb +1 -0
  170. data/spec/public/abstract_controller/controllers/views/test_render/foo.html.erb +0 -0
  171. data/spec/public/abstract_controller/controllers/views/wonderful/index.erb +1 -0
  172. data/spec/public/abstract_controller/display_spec.rb +33 -0
  173. data/spec/public/abstract_controller/filter_spec.rb +80 -0
  174. data/spec/public/abstract_controller/helper_spec.rb +13 -0
  175. data/spec/public/abstract_controller/partial_spec.rb +53 -0
  176. data/spec/public/abstract_controller/render_spec.rb +70 -0
  177. data/spec/public/abstract_controller/spec_helper.rb +27 -0
  178. data/spec/public/boot_loader/boot_loader_spec.rb +33 -0
  179. data/spec/public/boot_loader/spec_helper.rb +1 -0
  180. data/spec/public/controller/base_spec.rb +31 -0
  181. data/spec/public/controller/controllers/base.rb +41 -0
  182. data/spec/public/controller/controllers/display.rb +40 -0
  183. data/spec/public/controller/controllers/responder.rb +67 -0
  184. data/spec/public/controller/controllers/url.rb +7 -0
  185. data/spec/public/controller/controllers/views/layout/custom.html.erb +1 -0
  186. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.html.erb +1 -0
  187. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.xml.erb +1 -0
  188. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/index.html.erb +1 -0
  189. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/html_default/index.html.erb +1 -0
  190. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/layout/custom.html.erb +1 -0
  191. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.html.erb +1 -0
  192. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.xml.erb +1 -0
  193. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.html.erb +1 -0
  194. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.js.erb +1 -0
  195. data/spec/public/controller/display_spec.rb +34 -0
  196. data/spec/public/controller/log/merb.4000.pid +1 -0
  197. data/spec/public/controller/responder_spec.rb +95 -0
  198. data/spec/public/controller/spec_helper.rb +9 -0
  199. data/spec/public/controller/url_spec.rb +152 -0
  200. data/spec/public/directory_structure/directory/app/controllers/application.rb +3 -0
  201. data/spec/public/directory_structure/directory/app/controllers/base.rb +13 -0
  202. data/spec/public/directory_structure/directory/app/controllers/custom.rb +19 -0
  203. data/spec/public/directory_structure/directory/app/views/base/template.html.erb +1 -0
  204. data/spec/public/directory_structure/directory/app/views/wonderful/template.erb +1 -0
  205. data/spec/public/directory_structure/directory/config/router.rb +3 -0
  206. data/spec/public/directory_structure/directory/log/merb.4000.pid +1 -0
  207. data/spec/public/directory_structure/directory/log/merb_test.log +265 -0
  208. data/spec/public/directory_structure/directory/merb.4000.pid +1 -0
  209. data/spec/public/directory_structure/directory_spec.rb +44 -0
  210. data/spec/public/logger/logger_spec.rb +175 -0
  211. data/spec/public/logger/spec_helper.rb +1 -0
  212. data/spec/public/reloading/directory/app/controllers/application.rb +3 -0
  213. data/spec/public/reloading/directory/app/controllers/reload.rb +6 -0
  214. data/spec/public/reloading/directory/config/init.rb +2 -0
  215. data/spec/public/reloading/directory/log/merb.4000.pid +1 -0
  216. data/spec/public/reloading/directory/log/merb_test.log +59 -0
  217. data/spec/public/reloading/directory/merb.4000.pid +1 -0
  218. data/spec/public/reloading/reload_spec.rb +80 -0
  219. data/spec/public/request/multipart_spec.rb +15 -0
  220. data/spec/public/request/request_spec.rb +207 -0
  221. data/spec/public/router/default_spec.rb +21 -0
  222. data/spec/public/router/deferred_spec.rb +22 -0
  223. data/spec/public/router/namespace_spec.rb +113 -0
  224. data/spec/public/router/nested_resources_spec.rb +34 -0
  225. data/spec/public/router/resource_spec.rb +45 -0
  226. data/spec/public/router/resources_spec.rb +57 -0
  227. data/spec/public/router/spec_helper.rb +72 -0
  228. data/spec/public/router/special_spec.rb +44 -0
  229. data/spec/public/router/string_spec.rb +61 -0
  230. data/spec/public/template/template_spec.rb +92 -0
  231. data/spec/public/template/templates/error.html.erb +2 -0
  232. data/spec/public/template/templates/template.html.erb +1 -0
  233. data/spec/public/template/templates/template.html.myt +1 -0
  234. data/spec/public/test/controller_matchers_spec.rb +378 -0
  235. data/spec/public/test/controllers/controller_assertion_mock.rb +7 -0
  236. data/spec/public/test/controllers/dispatch_controller.rb +11 -0
  237. data/spec/public/test/controllers/spec_helper_controller.rb +30 -0
  238. data/spec/public/test/multipart_request_helper_spec.rb +159 -0
  239. data/spec/public/test/multipart_upload_text_file.txt +1 -0
  240. data/spec/public/test/request_helper_spec.rb +153 -0
  241. data/spec/public/test/route_helper_spec.rb +54 -0
  242. data/spec/public/test/route_matchers_spec.rb +133 -0
  243. data/spec/public/test/view_helper_spec.rb +96 -0
  244. data/spec/public/test/view_matchers_spec.rb +107 -0
  245. data/spec/spec_helper.rb +71 -0
  246. metadata +488 -0
@@ -0,0 +1,88 @@
1
+ module Merb
2
+ class << self
3
+
4
+ # ==== Returns
5
+ # Hash:: The available mime types.
6
+ def available_mime_types
7
+ ResponderMixin::TYPES
8
+ end
9
+
10
+ # Any specific outgoing headers should be included here. These are not
11
+ # the content-type header but anything in addition to it.
12
+ # +transform_method+ should be set to a symbol of the method used to
13
+ # transform a resource into this mime type.
14
+ # For example for the :xml mime type an object might be transformed by
15
+ # calling :to_xml, or for the :js mime type, :to_json.
16
+ # If there is no transform method, use nil.
17
+ #
18
+ # ==== Autogenerated Methods
19
+ # Adding a mime-type adds a render_type method that sets the content
20
+ # type and calls render.
21
+ #
22
+ # By default this does: def render_all, def render_yaml, def render_text,
23
+ # def render_html, def render_xml, def render_js, and def render_yaml
24
+ #
25
+ # ==== Parameters
26
+ # key<Symbol>:: The name of the mime-type. This is used by the provides API
27
+ # transform_method<~to_s>::
28
+ # The associated method to call on objects to convert them to the
29
+ # appropriate mime-type. For instance, :json would use :to_json as its
30
+ # transform_method.
31
+ # values<Array[String]>::
32
+ # A list of possible values sent in the Accept header, such as text/html,
33
+ # that should be associated with this content-type.
34
+ # new_response_headers<Hash>::
35
+ # The response headers to set for the the mime type.
36
+ def add_mime_type(key, transform_method, values, new_response_headers = {})
37
+ enforce!(key => Symbol, values => Array)
38
+ ResponderMixin::TYPES.update(key =>
39
+ {:request_headers => values,
40
+ :transform_method => transform_method,
41
+ :response_headers => new_response_headers })
42
+
43
+ Merb::RenderMixin.class_eval <<-EOS, __FILE__, __LINE__
44
+ def render_#{key}(thing = nil, opts = {})
45
+ self.content_type = :#{key}
46
+ render thing, opts
47
+ end
48
+ EOS
49
+ end
50
+
51
+ # Removes a MIME-type from the mime-type list.
52
+ #
53
+ # ==== Parameters
54
+ # key<Symbol>:: The key that represents the mime-type to remove.
55
+ #
56
+ # ==== Note
57
+ # :all is the key for */*; It can't be removed.
58
+ def remove_mime_type(key)
59
+ return false if key == :all
60
+ ResponderMixin::TYPES.delete(key)
61
+ end
62
+
63
+ # ==== Parameters
64
+ # key<Symbol>:: The key that represents the mime-type.
65
+ #
66
+ # ==== Returns
67
+ # Symbol:: The transform method for the mime type, e.g. :to_json.
68
+ #
69
+ # ==== Raises
70
+ # ArgumentError:: The requested mime type is not valid.
71
+ def mime_transform_method(key)
72
+ raise ArgumentError, ":#{key} is not a valid MIME-type" unless ResponderMixin::TYPES.key?(key)
73
+ ResponderMixin::TYPES[key][:transform_method]
74
+ end
75
+
76
+ # The mime-type for a particular inbound Accepts header.
77
+ #
78
+ # ==== Parameters
79
+ # header<String>:: The name of the header to find the mime-type for.
80
+ #
81
+ # ==== Returns
82
+ # Hash:: The mime type information.
83
+ def mime_by_request_header(header)
84
+ available_mime_types.find {|key,info| info[request_headers].include?(header)}.first
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,262 @@
1
+ module Merb
2
+ # Module that is mixed in to all implemented controllers.
3
+ module ControllerMixin
4
+ # Renders the block given as a parameter using chunked encoding.
5
+ #
6
+ # ==== Parameters
7
+ # &blk::
8
+ # A block that, when called, will use send_chunks to send chunks of data
9
+ # down to the server. The chunking will terminate once the block returns.
10
+ #
11
+ # ==== Examples
12
+ # def stream
13
+ # prefix = '<p>'
14
+ # suffix = "</p>\r\n"
15
+ # render_chunked do
16
+ # IO.popen("cat /tmp/test.log") do |io|
17
+ # done = false
18
+ # until done
19
+ # sleep 0.3
20
+ # line = io.gets.chomp
21
+ #
22
+ # if line == 'EOF'
23
+ # done = true
24
+ # else
25
+ # send_chunk(prefix + line + suffix)
26
+ # end
27
+ # end
28
+ # end
29
+ # end
30
+ # end
31
+ def render_chunked(&blk)
32
+ must_support_streaming!
33
+ headers['Transfer-Encoding'] = 'chunked'
34
+ Proc.new { |response|
35
+ @response = response
36
+ response.send_status_no_connection_close('')
37
+ response.send_header
38
+ blk.call
39
+ response.write("0\r\n\r\n")
40
+ }
41
+ end
42
+
43
+ # Writes a chunk from +render_chunked+ to the response that is sent back to
44
+ # the client. This should only be called within a +render_chunked+ block.
45
+ #
46
+ # ==== Parameters
47
+ # data<String>:: a chunk of data to return.
48
+ def send_chunk(data)
49
+ @response.write('%x' % data.size + "\r\n")
50
+ @response.write(data + "\r\n")
51
+ end
52
+
53
+ # ==== Parameters
54
+ # &blk::
55
+ # A proc that should get called outside the mutex, and which will return
56
+ # the value to render.
57
+ #
58
+ # ==== Returns
59
+ # Proc::
60
+ # A block that Mongrel can call later, allowing Merb to release the
61
+ # thread lock and render another request.
62
+ def render_deferred(&blk)
63
+ must_support_streaming!
64
+ Proc.new {|response|
65
+ result = blk.call
66
+ response.send_status(result.length)
67
+ response.send_header
68
+ response.write(result)
69
+ }
70
+ end
71
+
72
+ # Renders the passed in string, then calls the block outside the mutex and
73
+ # after the string has been returned to the client.
74
+ #
75
+ # ==== Parameters
76
+ # str<String>:: A +String+ to return to the client.
77
+ # &blk:: A block that should get called once the string has been returned.
78
+ #
79
+ # ==== Returns
80
+ # Proc::
81
+ # A block that Mongrel can call after returning the string to the user.
82
+ def render_then_call(str, &blk)
83
+ must_support_streaming!
84
+ Proc.new {|response|
85
+ response.send_status(str.length)
86
+ response.send_header
87
+ response.write(str)
88
+ blk.call
89
+ }
90
+ end
91
+
92
+ # ==== Parameters
93
+ # url<String>::
94
+ # URL to redirect to. It can be either a relative or fully-qualified URL.
95
+ #
96
+ # ==== Returns
97
+ # String:: Explanation of redirect.
98
+ #
99
+ # ==== Examples
100
+ # redirect("/posts/34")
101
+ # redirect("http://www.merbivore.com/")
102
+ def redirect(url)
103
+ Merb.logger.info("Redirecting to: #{url}")
104
+ self.status = 302
105
+ headers['Location'] = url
106
+ "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
107
+ end
108
+
109
+ # Sends a file over HTTP. When given a path to a file, it will set the
110
+ # right headers so that the static file is served directly.
111
+ #
112
+ # ==== Parameters
113
+ # file<String>:: Path to file to send to the client.
114
+ # opts<Hash>:: Options for sending the file (see below).
115
+ #
116
+ # ==== Options (opts)
117
+ # :disposition<String>::
118
+ # The disposition of the file send. Defaults to "attachment".
119
+ # :filename<String>::
120
+ # The name to use for the file. Defaults to the filename of file.
121
+ # :type<String>:: The content type.
122
+ #
123
+ # ==== Returns
124
+ # IO:: An I/O stream for the file.
125
+ def send_file(file, opts={})
126
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
127
+ disposition = opts[:disposition].dup || 'attachment'
128
+ disposition << %(; filename="#{opts[:filename] ? opts[:filename] : File.basename(file)}")
129
+ headers.update(
130
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
131
+ 'Content-Disposition' => disposition,
132
+ 'Content-Transfer-Encoding' => 'binary'
133
+ )
134
+ File.open(file)
135
+ end
136
+
137
+ # Send binary data over HTTP to the user as a file download. May set content type,
138
+ # apparent file name, and specify whether to show data inline or download as an attachment.
139
+ #
140
+ # ==== Parameters
141
+ # data<String>:: Path to file to send to the client.
142
+ # opts<Hash>:: Options for sending the data (see below).
143
+ #
144
+ # ==== Options (opts)
145
+ # :disposition<String>::
146
+ # The disposition of the file send. Defaults to "attachment".
147
+ # :filename<String>::
148
+ # The name to use for the file. Defaults to the filename of file.
149
+ # :type<String>:: The content type.
150
+ def send_data(data, opts={})
151
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
152
+ disposition = opts[:disposition].dup || 'attachment'
153
+ disposition << %(; filename="#{opts[:filename]}") if opts[:filename]
154
+ headers.update(
155
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
156
+ 'Content-Disposition' => disposition,
157
+ 'Content-Transfer-Encoding' => 'binary'
158
+ )
159
+ data
160
+ end
161
+
162
+ # Streams a file over HTTP.
163
+ #
164
+ # ==== Parameters
165
+ # opts<Hash>:: Options for the file streaming (see below).
166
+ # &stream::
167
+ # A block that, when called, will return an object that responds to
168
+ # +get_lines+ for streaming.
169
+ #
170
+ # ==== Options
171
+ # :disposition<String>::
172
+ # The disposition of the file send. Defaults to "attachment".
173
+ # :type<String>:: The content type.
174
+ # :content_length<Numeric>:: The length of the content to send.
175
+ # :filename<String>:: The name to use for the streamed file.
176
+ #
177
+ # ==== Examples
178
+ # stream_file({ :filename => file_name, :type => content_type,
179
+ # :content_length => content_length }) do |response|
180
+ # AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk|
181
+ # response.write chunk
182
+ # end
183
+ # end
184
+ def stream_file(opts={}, &stream)
185
+ must_support_streaming!
186
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
187
+ disposition = opts[:disposition].dup || 'attachment'
188
+ disposition << %(; filename="#{opts[:filename]}")
189
+ response.headers.update(
190
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
191
+ 'Content-Disposition' => disposition,
192
+ 'Content-Transfer-Encoding' => 'binary',
193
+ 'CONTENT-LENGTH' => opts[:content_length]
194
+ )
195
+ response.send_status(opts[:content_length])
196
+ response.send_header
197
+ stream
198
+ end
199
+
200
+ # Uses the nginx specific +X-Accel-Redirect+ header to send a file directly
201
+ # from nginx. For more information, see the nginx wiki:
202
+ # http://wiki.codemongers.com/NginxXSendfile
203
+ #
204
+ # ==== Parameters
205
+ # file<String>:: Path to file to send to the client.
206
+ def nginx_send_file(file)
207
+ headers['X-Accel-Redirect'] = File.expand_path(file)
208
+ return
209
+ end
210
+
211
+ # Sets a cookie to be included in the response. This method is used
212
+ # primarily internally in Merb.
213
+ #
214
+ # If you need to set a cookie, then use the +cookies+ hash.
215
+ #
216
+ # ==== Parameters
217
+ # name<~to_s>:: A name for the cookie.
218
+ # value<~to_s>:: A value for the cookie.
219
+ # expires<~gmtime:~strftime>:: An expiration time for the cookie.
220
+ def set_cookie(name, value, expires)
221
+ (headers['Set-Cookie'] ||=[]) << (Merb::Const::SET_COOKIE % [
222
+ name.to_s,
223
+ ::Merb::Request.escape(value.to_s),
224
+ # Cookie expiration time must be GMT. See RFC 2109
225
+ expires.gmtime.strftime(Merb::Const::COOKIE_EXPIRATION_FORMAT)
226
+ ])
227
+ end
228
+
229
+ # Marks a cookie as deleted and gives it an expires stamp in the past. This
230
+ # method is used primarily internally in Merb.
231
+ #
232
+ # Use the +cookies+ hash to manipulate cookies instead.
233
+ #
234
+ # ==== Parameters
235
+ # name<~to_s>:: A name for the cookie to delete.
236
+ def delete_cookie(name)
237
+ set_cookie(name, nil, Merb::Const::COOKIE_EXPIRED_TIME)
238
+ end
239
+
240
+ # Escapes the string representation of +obj+ and escapes it for use in XML.
241
+ #
242
+ # ==== Parameter
243
+ # obj<~to_s>:: The object to escape for use in XML.
244
+ #
245
+ # ==== Returns
246
+ # String:: The escaped object.
247
+ def escape_xml(obj)
248
+ Erubis::XmlHelper.escape_xml(obj.to_s)
249
+ end
250
+ alias h escape_xml
251
+ alias html_escape escape_xml
252
+
253
+ private
254
+ # Checks whether streaming is supported by the current Rack adapter.
255
+ #
256
+ # ==== Raises
257
+ # NotImplemented:: The Rack adapter doens't support streaming.
258
+ def must_support_streaming!
259
+ raise(NotImplemented, "Current Rack adapter does not support streaming") unless request.env['rack.streaming']
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,324 @@
1
+ module Merb::RenderMixin
2
+ # So we can do raise TemplateNotFound
3
+ include Merb::ControllerExceptions
4
+
5
+ # ==== Parameters
6
+ # base<Module>:: Module that is including RenderMixin (probably a controller)
7
+ def self.included(base)
8
+ base.class_eval do
9
+ class_inheritable_accessor :_layout, :_cached_templates
10
+ end
11
+ end
12
+
13
+ # Render the specified item, with the specified options.
14
+ #
15
+ # ==== Parameters
16
+ # thing<String, Symbol, nil>::
17
+ # The thing to render. This will default to the current action
18
+ # opts<Hash>:: An options hash (see below)
19
+ #
20
+ # ==== Options (opts)
21
+ # :format<Symbol>:: A registered mime-type format
22
+ # :template<String>::
23
+ # The path to the template relative to the template root
24
+ # :status<~to_i>::
25
+ # The status to send to the client. Typically, this would be an integer
26
+ # (200), or a Merb status code (Accepted)
27
+ # :layout<~to_s>::
28
+ # A layout to use instead of the default. This should be relative to the
29
+ # layout root. By default, the layout will be either the controller_name or
30
+ # application. If you want to use an alternative content-type than the one
31
+ # that the base template was rendered as, you will need to do :layout =>
32
+ # "foo.#{content_type}" (i.e. "foo.json")
33
+ #
34
+ # ==== Returns
35
+ # String:: The rendered template, including layout, if appropriate.
36
+ #
37
+ # ==== Raises
38
+ # TemplateNotFound:: There is no template for the specified location.
39
+ #
40
+ # ==== Alternatives
41
+ # If you pass a Hash as the first parameter, it will be moved to opts and
42
+ # "thing" will be the current action
43
+ #---
44
+ # @public
45
+ def render(thing = nil, opts = {})
46
+ # render :format => :xml means render nil, :format => :xml
47
+ opts, thing = thing, nil if thing.is_a?(Hash)
48
+
49
+ # If you don't specify a thing to render, assume they want to render the current action
50
+ thing ||= action_name.to_sym
51
+
52
+ # Content negotiation
53
+ opts[:format] ? (self.content_type = opts[:format]) : content_type
54
+
55
+ # Handle options (:status)
56
+ _handle_options!(opts)
57
+
58
+ # Do we have a template to try to render?
59
+ if thing.is_a?(Symbol) || opts[:template]
60
+
61
+ template_method, template_location = _template_for(thing, content_type, controller_name, opts)
62
+
63
+ # Raise an error if there's no template
64
+ raise TemplateNotFound, "No template found at #{template_location}.*" \
65
+ unless template_method && self.respond_to?(template_method)
66
+
67
+ # Call the method in question and throw the content for later consumption by the layout
68
+ throw_content(:for_layout, self.send(template_method))
69
+
70
+ # Do we have a string to render?
71
+ elsif thing.is_a?(String)
72
+
73
+ # Throw it for later consumption by the layout
74
+ throw_content(:for_layout, thing)
75
+ end
76
+
77
+ # If we find a layout, use it. Otherwise, just render the content thrown for layout.
78
+ layout = opts[:layout] != false && _get_layout(opts[:layout])
79
+ layout ? send(layout) : catch_content(:for_layout)
80
+ end
81
+
82
+ # Renders an object using to registered transform method based on the
83
+ # negotiated content-type, if a template does not exist. For instance, if the
84
+ # content-type is :json, Merb will first look for current_action.json.*.
85
+ # Failing that, it will run object.to_json.
86
+ #
87
+ # ==== Parameter
88
+ # object<Object>::
89
+ # An object that responds_to? the transform method registered for the
90
+ # negotiated mime-type.
91
+ # thing<String, Symbol>::
92
+ # The thing to attempt to render via #render before calling the transform
93
+ # method on the object. Defaults to nil.
94
+ # opts<Hash>:: An options hash that will be passed on to #render
95
+ #
96
+ # ==== Returns
97
+ # String::
98
+ # The rendered template or if no template is found, the transformed object.
99
+ #
100
+ # ==== Raises
101
+ # NotAcceptable::
102
+ # If there is no transform method for the specified mime-type or the object
103
+ # does not respond to the transform method.
104
+ #
105
+ # ==== Alternatives
106
+ # A string in the second parameter will be interpreted as a template:
107
+ # display @object, "path/to/foo"
108
+ # #=> display @object, nil, :template => "path/to/foo"
109
+ #
110
+ # A hash in the second parameters will be interpreted as opts:
111
+ # display @object, :layout => "zoo"
112
+ # #=> display @object, nil, :layout => "zoo"
113
+ #
114
+ # ==== Note
115
+ # The transformed object will not be used in a layout unless a :layout is
116
+ # explicitly passed in the opts.
117
+ def display(object, thing = nil, opts = {})
118
+ # display @object, "path/to/foo" means display @object, nil, :template => "path/to/foo"
119
+ # display @object, :template => "path/to/foo" means display @object, nil, :template => "path/to/foo"
120
+ opts[:template], thing = thing, nil if thing.is_a?(String) || thing.is_a?(Hash)
121
+
122
+ # Try to render without the object
123
+ render(thing || action_name.to_sym, opts)
124
+
125
+ # If the render fails (i.e. a template was not found)
126
+ rescue TemplateNotFound
127
+
128
+ # Figure out what to transform and raise NotAcceptable unless there's a transform method assigned
129
+ transform = Merb.mime_transform_method(content_type)
130
+ raise NotAcceptable unless transform && object.respond_to?(transform)
131
+
132
+ # Throw the transformed object for later consumption by the layout
133
+ throw_content(:for_layout, object.send(transform))
134
+
135
+ # Only use a layout if one was specified
136
+ if opts[:layout]
137
+ # Look for the layout under the default layout directly. If it's not found, reraise
138
+ # the TemplateNotFound error
139
+ template = _template_location(opts[:layout], layout.index(".") ? content_type : nil, "layout")
140
+ layout = _template_for(_template_root / template) ||
141
+ (raise TemplateNotFound, "No layout found at #{_template_root / template}.*")
142
+
143
+ # If the layout was found, call it
144
+ send(layout)
145
+
146
+ # Otherwise, just render the transformed object
147
+ else
148
+ catch_content(:for_layout)
149
+ end
150
+ end
151
+
152
+ # Render a partial template.
153
+ #
154
+ # ==== Parameters
155
+ # template<~to_s>::
156
+ # The path to the template, relative to the current controller or the
157
+ # template root. If the template contains a "/", Merb will search for it
158
+ # relative to the template root; otherwise, Merb will search for it
159
+ # relative to the current controller.
160
+ # opts<Hash>:: A hash of options (see below)
161
+ #
162
+ # ==== Options (opts)
163
+ # :with<Object, Array>::
164
+ # An object or an array of objects that will be passed into the partial.
165
+ # :as<~to_sym>:: The local name of the :with Object inside of the partial.
166
+ # :format<Symbol>:: The mime format that you want the partial to be in (:js, :html, etc.)
167
+ # others::
168
+ # A Hash object names and values that will be the local names and values
169
+ # inside the partial.
170
+ #
171
+ # ==== Example
172
+ # partial :foo, :hello => @object
173
+ #
174
+ # The "_foo" partial will be called, relative to the current controller,
175
+ # with a local variable of +hello+ inside of it, assigned to @object.
176
+ def partial(template, opts={})
177
+
178
+ # partial :foo becomes "#{controller_name}/_foo"
179
+ # partial "foo/bar" becomes "foo/_bar"
180
+ template = template.to_s
181
+ kontroller = (m = template.match(/.*(?=\/)/)) ? m[0] : controller_name
182
+ template = "_#{File.basename(template)}"
183
+
184
+ template_method, template_location = _template_for(template, opts.delete(:format) || content_type, kontroller)
185
+
186
+ (@_old_partial_locals ||= []).push @_merb_partial_locals
187
+
188
+ if opts.key?(:with)
189
+ with = opts.delete(:with)
190
+ as = opts.delete(:as) || template_location.match(%r[.*/_([^\.]*)])[1]
191
+ @_merb_partial_locals = opts
192
+ sent_template = [with].flatten.map do |temp|
193
+ @_merb_partial_locals[as.to_sym] = temp
194
+ send(template_method)
195
+ end.join
196
+ else
197
+ @_merb_partial_locals = opts
198
+ sent_template = send(template_method)
199
+ end
200
+ @_merb_partial_locals = @_old_partial_locals.pop
201
+ sent_template
202
+ end
203
+
204
+ # Take the options hash and handle it as appropriate.
205
+ #
206
+ # ==== Parameters
207
+ # opts<Hash>:: The options hash that was passed into render.
208
+ #
209
+ # ==== Options
210
+ # :status<~to_i>::
211
+ # The status of the response will be set to opts[:status].to_i
212
+ #
213
+ # ==== Returns
214
+ # Hash:: The options hash that was passed in.
215
+ def _handle_options!(opts)
216
+ self.status = opts[:status].to_i if opts[:status]
217
+ opts
218
+ end
219
+
220
+ # Get the layout that should be used. The content-type will be appended to
221
+ # the layout unless the layout already contains a "." in it.
222
+ #
223
+ # If no layout was passed in, this method will look for one with the same
224
+ # name as the controller, and finally one in "application.#{content_type}".
225
+ #
226
+ # ==== Parameters
227
+ # layout<~to_s>:: A layout, relative to the layout root. Defaults to nil.
228
+ #
229
+ # ==== Returns
230
+ # String:: The method name that corresponds to the found layout.
231
+ #
232
+ # ==== Raises
233
+ # TemplateNotFound::
234
+ # If a layout was specified (either via layout in the class or by passing
235
+ # one in to this method), and not found. No error will be raised if no
236
+ # layout was specified, and the default layouts were not found.
237
+ def _get_layout(layout = nil)
238
+ if _layout && !layout
239
+ layout = _layout.instance_of?(Symbol) && self.respond_to?(_layout, true) ? send(_layout) : _layout
240
+ end
241
+ layout = layout.to_s if layout
242
+
243
+ # If a layout was provided, throw an error if it's not found
244
+ if layout
245
+ template_method, template_location = _template_for(layout, layout.index(".") ? nil : content_type, "layout")
246
+ raise TemplateNotFound, "No layout found at #{template_location}" unless template_method
247
+ template_method
248
+
249
+ # If a layout was not provided, try the default locations
250
+ else
251
+ template, location = _template_for(controller_name, content_type, "layout")
252
+ template, location = _template_for("application", content_type, "layout") unless template
253
+ template
254
+ end
255
+ end
256
+
257
+ # Iterate over the template roots in reverse order, and return the template
258
+ # and template location of the first match.
259
+ #
260
+ # ==== Parameters
261
+ # thing<Object>:: The controller action.
262
+ # content_type<~to_s>:: The content type. Defaults to nil.
263
+ # controller<~to_s>:: The name of the controller. Defaults to nil.
264
+ #
265
+ # ==== Options (opts)
266
+ # :template<String>::
267
+ # The location of the template to use. Defaults to whatever matches this
268
+ # thing, content_type and controller.
269
+ #
270
+ # ==== Returns
271
+ # Array[Symbol, String]::
272
+ # A pair consisting of the template method and location.
273
+ def _template_for(thing, content_type, controller=nil, opts={})
274
+ template_method = nil
275
+ template_location = nil
276
+
277
+ self.class._template_roots.reverse_each do |root, template_location|
278
+ template_location = root / (opts[:template] || self.send(template_location, thing, content_type, controller))
279
+ template_method = Merb::Template.template_for(template_location)
280
+ break if template_method && self.respond_to?(template_method)
281
+ end
282
+
283
+ [template_method, template_location]
284
+ end
285
+
286
+ # Called in templates to get at content thrown in another template. The
287
+ # results of rendering a template are automatically thrown into :for_layout,
288
+ # so catch_content or catch_content(:for_layout) can be used inside layouts
289
+ # to get the content rendered by the action template.
290
+ #
291
+ # ==== Parameters
292
+ # obj<Object>:: The key in the thrown_content hash. Defaults to :for_layout.
293
+ #---
294
+ # @public
295
+ def catch_content(obj = :for_layout)
296
+ @_caught_content[obj]
297
+ end
298
+
299
+ # Called in templates to store up content for later use. Takes a string
300
+ # and/or a block. First, the string is evaluated, and then the block is
301
+ # captured using the capture() helper provided by the template languages. The
302
+ # two are concatenated together.
303
+ #
304
+ # ==== Parameters
305
+ # obj<Object>:: The key in the thrown_content hash.
306
+ # string<String>:: Textual content. Defaults to nil.
307
+ # &block:: A block to be evaluated and concatenated to string.
308
+ #
309
+ # ==== Raises
310
+ # ArgumentError:: Neither string nor block given.
311
+ #
312
+ # ==== Example
313
+ # throw_content(:foo, "Foo")
314
+ # catch_content(:foo) #=> "Foo"
315
+ #---
316
+ # @public
317
+ def throw_content(obj, string = nil, &block)
318
+ unless string || block_given?
319
+ raise ArgumentError, "You must pass a block or a string into throw_content"
320
+ end
321
+ @_caught_content[obj] = string.to_s << (block_given? ? capture(&block) : "")
322
+ end
323
+
324
+ end