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,464 @@
1
+ require 'enumerator'
2
+ require 'merb-core/controller/mime'
3
+ module Merb
4
+ # The ResponderMixin adds methods that help you manage what
5
+ # formats your controllers have available, determine what format(s)
6
+ # the client requested and is capable of handling, and perform
7
+ # content negotiation to pick the proper content format to
8
+ # deliver.
9
+ #
10
+ # If you hear someone say "Use provides" they're talking about the
11
+ # Responder. If you hear someone ask "What happened to respond_to?"
12
+ # it was replaced by provides and the other Responder methods.
13
+ #
14
+ # == A simple example
15
+ #
16
+ # The best way to understand how all of these pieces fit together is
17
+ # with an example. Here's a simple web-service ready resource that
18
+ # provides a list of all the widgets we know about. The widget list is
19
+ # available in 3 formats: :html (the default), plus :xml and :text.
20
+ #
21
+ # class Widgets < Application
22
+ # provides :html # This is the default, but you can
23
+ # # be explicit if you like.
24
+ # provides :xml, :text
25
+ #
26
+ # def index
27
+ # @widgets = Widget.fetch
28
+ # render @widgets
29
+ # end
30
+ # end
31
+ #
32
+ # Let's look at some example requests for this list of widgets. We'll
33
+ # assume they're all GET requests, but that's only to make the examples
34
+ # easier; this works for the full set of RESTful methods.
35
+ #
36
+ # 1. The simplest case, /widgets.html
37
+ # Since the request includes a specific format (.html) we know
38
+ # what format to return. Since :html is in our list of provided
39
+ # formats, that's what we'll return. +render+ will look
40
+ # for an index.html.erb (or another template format
41
+ # like index.html.mab; see the documentation on Template engines)
42
+ #
43
+ # 2. Almost as simple, /widgets.xml
44
+ # This is very similar. They want :xml, we have :xml, so
45
+ # that's what they get. If +render+ doesn't find an
46
+ # index.xml.builder or similar template, it will call +to_xml+
47
+ # on @widgets. This may or may not do something useful, but you can
48
+ # see how it works.
49
+ #
50
+ # 3. A browser request for /widgets
51
+ # This time the URL doesn't say what format is being requested, so
52
+ # we'll look to the HTTP Accept: header. If it's '*/*' (anything),
53
+ # we'll use the first format on our list, :html by default.
54
+ #
55
+ # If it parses to a list of accepted formats, we'll look through
56
+ # them, in order, until we find one we have available. If we find
57
+ # one, we'll use that. Otherwise, we can't fulfill the request:
58
+ # they asked for a format we don't have. So we raise
59
+ # 406: Not Acceptable.
60
+ #
61
+ # == A more complex example
62
+ #
63
+ # Sometimes you don't have the same code to handle each available
64
+ # format. Sometimes you need to load different data to serve
65
+ # /widgets.xml versus /widgets.txt. In that case, you can use
66
+ # +content_type+ to determine what format will be delivered.
67
+ #
68
+ # class Widgets < Application
69
+ # def action1
70
+ # if content_type == :text
71
+ # Widget.load_text_formatted(params[:id])
72
+ # else
73
+ # render
74
+ # end
75
+ # end
76
+ #
77
+ # def action2
78
+ # case content_type
79
+ # when :html
80
+ # handle_html()
81
+ # when :xml
82
+ # handle_xml()
83
+ # when :text
84
+ # handle_text()
85
+ # else
86
+ # render
87
+ # end
88
+ # end
89
+ # end
90
+ #
91
+ # You can do any standard Ruby flow control using +content_type+. If
92
+ # you don't call it yourself, it will be called (triggering content
93
+ # negotiation) by +render+.
94
+ #
95
+ # Once +content_type+ has been called, the output format is frozen,
96
+ # and none of the provides methods can be used.
97
+ module ResponderMixin
98
+
99
+ TYPES = {}
100
+
101
+ class ContentTypeAlreadySet < StandardError; end
102
+
103
+ # ==== Parameters
104
+ # base<Module>:: The module that ResponderMixin was mixed into
105
+ def self.included(base) # :nodoc:
106
+ base.extend(ClassMethods)
107
+ base.class_eval do
108
+ class_inheritable_accessor :class_provided_formats
109
+ self.class_provided_formats = []
110
+ end
111
+ base.reset_provides
112
+ end
113
+
114
+ module ClassMethods
115
+
116
+ # Adds symbols representing formats to the controller's default list of
117
+ # provided_formats. These will apply to every action in the controller,
118
+ # unless modified in the action. If the last argument is a Hash or an
119
+ # Array, these are regarded as arguments to pass to the to_<mime_type>
120
+ # method as needed.
121
+ #
122
+ # ==== Parameters
123
+ # *formats<Symbol>::
124
+ # A list of mime-types that the controller should provide.
125
+ #
126
+ # ==== Returns
127
+ # Array[Symbol]:: List of formats passed in.
128
+ #
129
+ # ==== Examples
130
+ # provides :html, :xml
131
+ #---
132
+ # @public
133
+ def provides(*formats)
134
+ formats.each do |fmt|
135
+ self.class_provided_formats << fmt unless class_provided_formats.include?(fmt)
136
+ end
137
+ end
138
+
139
+ # This class should only provide the formats listed here, despite any
140
+ # other definitions previously or in superclasses.
141
+ #
142
+ # ==== Parameters
143
+ # *formats<Symbol>:: Registered mime-types.
144
+ #
145
+ # ==== Returns
146
+ # Array[Symbol]:: List of formats passed in.
147
+ #
148
+ #---
149
+ # @public
150
+ def only_provides(*formats)
151
+ clear_provides
152
+ provides(*formats)
153
+ end
154
+
155
+ # This class should not provide any of this list of formats, despite any.
156
+ # other definitions previously or in superclasses.
157
+ #
158
+ # ==== Parameters
159
+ # *formats<Symbol>:: Registered mime-types.
160
+ #
161
+ # ==== Returns
162
+ # Array[Symbol]::
163
+ # List of formats that remain after removing the ones not to provide.
164
+ #
165
+ #---
166
+ # @public
167
+ def does_not_provide(*formats)
168
+ self.class_provided_formats -= formats
169
+ end
170
+
171
+ # Clear the list of provides.
172
+ #
173
+ # ==== Returns
174
+ # Array:: An empty Array.
175
+ def clear_provides
176
+ self.class_provided_formats.clear
177
+ end
178
+
179
+ # Reset the list of provides to include only :html.
180
+ #
181
+ # ==== Returns
182
+ # Array[Symbol]:: [:html].
183
+ def reset_provides
184
+ only_provides(:html)
185
+ end
186
+ end
187
+
188
+ # ==== Returns
189
+ # Array[Symbol]::
190
+ # The current list of formats provided for this instance of the
191
+ # controller. It starts with what has been set in the controller (or
192
+ # :html by default) but can be modifed on a per-action basis.
193
+ def _provided_formats
194
+ @_provided_formats ||= class_provided_formats.dup
195
+ end
196
+
197
+ # Sets the provided formats for this action. Usually, you would use a
198
+ # combination of provides, only_provides and does_not_provide to manage
199
+ # this, but you can set it directly.
200
+ #
201
+ # ==== Parameters
202
+ # *formats<Symbol>:: A list of formats to be passed to provides.
203
+ #
204
+ # ==== Raises
205
+ # Merb::ResponderMixin::ContentTypeAlreadySet::
206
+ # Content negotiation already occured, and the content_type is set.
207
+ #
208
+ # ==== Returns
209
+ # Array[Symbol]:: List of formats passed in.
210
+ def _set_provided_formats(*formats)
211
+ if @_content_type
212
+ raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set"
213
+ end
214
+ @_provided_formats = []
215
+ provides(*formats)
216
+ end
217
+ alias :_provided_formats= :_set_provided_formats
218
+
219
+ # Adds formats to the list of provided formats for this particular request.
220
+ # Usually used to add formats to a single action. See also the
221
+ # controller-level provides that affects all actions in a controller.
222
+ #
223
+ # ==== Parameters
224
+ # *formats<Symbol>::
225
+ # A list of formats to add to the per-action list of provided formats.
226
+ #
227
+ # ==== Raises
228
+ # Merb::ResponderMixin::ContentTypeAlreadySet::
229
+ # Content negotiation already occured, and the content_type is set.
230
+ #
231
+ # ==== Returns
232
+ # Array[Symbol]:: List of formats passed in.
233
+ #
234
+ #---
235
+ # @public
236
+ def provides(*formats)
237
+ if @_content_type
238
+ raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set"
239
+ end
240
+ formats.each do |fmt|
241
+ _provided_formats << fmt unless _provided_formats.include?(fmt)
242
+ end
243
+ end
244
+
245
+ # Sets list of provided formats for this particular request. Usually used
246
+ # to limit formats to a single action. See also the controller-level
247
+ # only_provides that affects all actions in a controller.
248
+ #
249
+ # ==== Parameters
250
+ # *formats<Symbol>::
251
+ # A list of formats to use as the per-action list of provided formats.
252
+ #
253
+ # ==== Returns
254
+ # Array[Symbol]:: List of formats passed in.
255
+ #
256
+ #---
257
+ # @public
258
+ def only_provides(*formats)
259
+ _set_provided_formats(*formats)
260
+ end
261
+
262
+ # Removes formats from the list of provided formats for this particular
263
+ # request. Usually used to remove formats from a single action. See
264
+ # also the controller-level does_not_provide that affects all actions in a
265
+ # controller.
266
+ #
267
+ # ==== Parameters
268
+ # *formats<Symbol>:: Registered mime-type
269
+ #
270
+ # ==== Returns
271
+ # Array[Symbol]::
272
+ # List of formats that remain after removing the ones not to provide.
273
+ #
274
+ #---
275
+ # @public
276
+ def does_not_provide(*formats)
277
+ formats.flatten!
278
+ self._provided_formats -= formats
279
+ end
280
+
281
+ # Do the content negotiation:
282
+ # 1. if params[:format] is there, and provided, use it
283
+ # 2. Parse the Accept header
284
+ # 3. If it's */*, use the first provided format
285
+ # 4. Look for one that is provided, in order of request
286
+ # 5. Raise 406 if none found
287
+ def _perform_content_negotiation # :nodoc:
288
+ raise Merb::ControllerExceptions::NotAcceptable if _provided_formats.empty?
289
+ if (fmt = params[:format]) && !fmt.empty?
290
+ accepts = [fmt.to_sym]
291
+ else
292
+ accepts = Responder.parse(request.accept).map {|t| t.to_sym}.compact
293
+ end
294
+ specifics = accepts & _provided_formats
295
+ return specifics.first unless specifics.length == 0
296
+ return _provided_formats.first if accepts.include? :all
297
+ raise Merb::ControllerExceptions::NotAcceptable
298
+ end
299
+
300
+ # Returns the output format for this request, based on the
301
+ # provided formats, <tt>params[:format]</tt> and the client's HTTP
302
+ # Accept header.
303
+ #
304
+ # The first time this is called, it triggers content negotiation
305
+ # and caches the value. Once you call +content_type+ you can
306
+ # not set or change the list of provided formats.
307
+ #
308
+ # Called automatically by +render+, so you should only call it if
309
+ # you need the value, not to trigger content negotiation.
310
+ #
311
+ # ==== Parameters
312
+ # fmt<String>::
313
+ # An optional format to use instead of performing content negotiation.
314
+ # This can be used to pass in the values of opts[:format] from the
315
+ # render function to short-circuit content-negotiation when it's not
316
+ # necessary. This optional parameter should not be considered part
317
+ # of the public API.
318
+ #
319
+ # ==== Returns
320
+ # Symbol:: The content-type that will be used for this controller.
321
+ #
322
+ #---
323
+ # @public
324
+ def content_type(fmt = nil)
325
+ self.content_type = (fmt || _perform_content_negotiation) unless @_content_type
326
+ @_content_type
327
+ end
328
+
329
+ # Sets the content type of the current response to a value based on
330
+ # a passed in key. The Content-Type header will be set to the first
331
+ # registered header for the mime-type.
332
+ #
333
+ # ==== Parameters
334
+ # type<Symbol>:: The content type.
335
+ #
336
+ # ==== Raises
337
+ # ArgumentError:: type is not in the list of registered mime-types.
338
+ #
339
+ # ==== Returns
340
+ # Symbol:: The content-type that was passed in.
341
+ #
342
+ #---
343
+ # @semipublic
344
+ def content_type=(type)
345
+ unless Merb.available_mime_types.has_key?(type)
346
+ raise Merb::ControllerExceptions::NotAcceptable.new("Unknown content_type for response: #{type}")
347
+ end
348
+ headers['Content-Type'] = Merb.available_mime_types[type][:request_headers].first
349
+ @_content_type = type
350
+ end
351
+
352
+ end
353
+
354
+ class Responder
355
+
356
+ protected
357
+
358
+ # Parses the raw accept header into an array of sorted AcceptType objects.
359
+ #
360
+ # ==== Parameters
361
+ # accept_header<~to_s>:: The raw accept header.
362
+ #
363
+ # ==== Returns
364
+ # Array[AcceptType]:: The accepted types.
365
+ def self.parse(accept_header)
366
+ list = accept_header.to_s.split(/,/).enum_for(:each_with_index).map do |entry,index|
367
+ AcceptType.new(entry,index += 1)
368
+ end.sort.uniq
369
+ # firefox (and possibly other browsers) send broken default accept headers.
370
+ # fix them up by sorting alternate xml forms (namely application/xhtml+xml)
371
+ # ahead of pure xml types (application/xml,text/xml).
372
+ if app_xml = list.detect{|e| e.super_range == 'application/xml'}
373
+ list.select{|e| e.to_s =~ /\+xml/}.each { |acc_type|
374
+ list[list.index(acc_type)],list[list.index(app_xml)] =
375
+ list[list.index(app_xml)],list[list.index(acc_type)] }
376
+ end
377
+ list
378
+ end
379
+
380
+ end
381
+
382
+ class AcceptType
383
+
384
+ attr_reader :media_range, :quality, :index, :type, :sub_type
385
+
386
+ # ==== Parameters
387
+ # entry<String>:: The accept type pattern
388
+ # index<Fixnum>::
389
+ # The index used for sorting accept types. A lower value indicates higher
390
+ # priority.
391
+ def initialize(entry,index)
392
+ @index = index
393
+ @media_range, quality = entry.split(/;\s*q=/).map{|a| a.strip }
394
+ @type, @sub_type = @media_range.split(/\//)
395
+ quality ||= 0.0 if @media_range == '*/*'
396
+ @quality = ((quality || 1.0).to_f * 100).to_i
397
+ end
398
+
399
+ # Compares two accept types for sorting purposes.
400
+ #
401
+ # ==== Parameters
402
+ # entry<AcceptType>:: The accept type to compare.
403
+ #
404
+ # ==== Returns
405
+ # Fixnum::
406
+ # -1, 0 or 1, depending on whether entry has a lower, equal or higher
407
+ # priority than the accept type being compared.
408
+ def <=>(entry)
409
+ c = entry.quality <=> quality
410
+ c = index <=> entry.index if c == 0
411
+ c
412
+ end
413
+
414
+ # ==== Parameters
415
+ # entry<AcceptType>:: The accept type to compare.
416
+ #
417
+ # ==== Returns
418
+ # Boolean::
419
+ # True if the accept types are equal, i.e. if the synonyms for this
420
+ # accept type includes the entry media range.
421
+ def eql?(entry)
422
+ synonyms.include?(entry.media_range)
423
+ end
424
+
425
+ # An alias for eql?.
426
+ def ==(entry); eql?(entry); end
427
+
428
+ # ==== Returns
429
+ # Fixnum:: A hash based on the super range.
430
+ def hash; super_range.hash; end
431
+
432
+ # ==== Returns
433
+ # Array[String]::
434
+ # All Accept header values, such as "text/html", that match this type.
435
+ def synonyms
436
+ @syns ||= Merb.available_mime_types.values.map do |e|
437
+ e[:request_headers] if e[:request_headers].include?(@media_range)
438
+ end.compact.flatten
439
+ end
440
+
441
+ # ==== Returns
442
+ # String::
443
+ # The primary media range for this accept type, i.e. either the first
444
+ # synonym or, if none exist, the media range.
445
+ def super_range
446
+ synonyms.first || @media_range
447
+ end
448
+
449
+ # ==== Returns
450
+ # Symbol: The type as a symbol, e.g. :html.
451
+ def to_sym
452
+ Merb.available_mime_types.select{|k,v|
453
+ v[:request_headers] == synonyms || v[:request_headers][0] == synonyms[0]}.flatten.first
454
+ end
455
+
456
+ # ==== Returns
457
+ # String:: The accept type as a string, i.e. the media range.
458
+ def to_s
459
+ @media_range
460
+ end
461
+
462
+ end
463
+
464
+ end