merb 0.3.7 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. data/README +25 -26
  2. data/Rakefile +48 -36
  3. data/app_generators/merb/USAGE +5 -0
  4. data/app_generators/merb/merb_generator.rb +107 -0
  5. data/app_generators/merb/templates/Rakefile +99 -0
  6. data/{examples/skeleton/dist → app_generators/merb/templates}/app/controllers/application.rb +1 -1
  7. data/app_generators/merb/templates/app/controllers/exceptions.rb +13 -0
  8. data/{examples/skeleton/dist → app_generators/merb/templates}/app/helpers/global_helper.rb +0 -0
  9. data/{examples/skeleton/dist/app/mailers → app_generators/merb/templates/app/mailers/views}/layout/application.erb +0 -0
  10. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +207 -0
  11. data/app_generators/merb/templates/app/views/exceptions/not_acceptable.html.erb +38 -0
  12. data/app_generators/merb/templates/app/views/exceptions/not_found.html.erb +40 -0
  13. data/app_generators/merb/templates/app/views/layout/application.html.erb +11 -0
  14. data/app_generators/merb/templates/config/boot.rb +11 -0
  15. data/app_generators/merb/templates/config/dependencies.rb +41 -0
  16. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/development.rb +0 -0
  17. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/production.rb +0 -0
  18. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/test.rb +0 -0
  19. data/app_generators/merb/templates/config/merb.yml +64 -0
  20. data/app_generators/merb/templates/config/merb_init.rb +16 -0
  21. data/app_generators/merb/templates/config/plugins.yml +1 -0
  22. data/app_generators/merb/templates/config/router.rb +32 -0
  23. data/{lib/merb/core_ext/merb_array.rb → app_generators/merb/templates/config/upload.conf} +0 -0
  24. data/app_generators/merb/templates/public/images/merb.jpg +0 -0
  25. data/app_generators/merb/templates/public/merb.fcgi +6 -0
  26. data/app_generators/merb/templates/public/stylesheets/master.css +119 -0
  27. data/app_generators/merb/templates/script/destroy +28 -0
  28. data/app_generators/merb/templates/script/generate +28 -0
  29. data/{examples/skeleton → app_generators/merb/templates}/script/stop_merb +0 -0
  30. data/app_generators/merb/templates/script/win_script.cmd +1 -0
  31. data/app_generators/merb/templates/spec/spec.opts +6 -0
  32. data/app_generators/merb/templates/spec/spec_helper.rb +10 -0
  33. data/app_generators/merb/templates/test/test_helper.rb +13 -0
  34. data/app_generators/merb_plugin/USAGE +5 -0
  35. data/app_generators/merb_plugin/merb_plugin_generator.rb +64 -0
  36. data/app_generators/merb_plugin/templates/LICENSE +20 -0
  37. data/app_generators/merb_plugin/templates/README +4 -0
  38. data/app_generators/merb_plugin/templates/Rakefile +35 -0
  39. data/app_generators/merb_plugin/templates/TODO +5 -0
  40. data/app_generators/merb_plugin/templates/merbtasks.rb +6 -0
  41. data/app_generators/merb_plugin/templates/sampleplugin.rb +10 -0
  42. data/app_generators/merb_plugin/templates/sampleplugin_spec.rb +7 -0
  43. data/app_generators/merb_plugin/templates/spec_helper.rb +2 -0
  44. data/bin/merb +1 -1
  45. data/lib/autotest/discover.rb +3 -0
  46. data/lib/autotest/merb_rspec.rb +79 -0
  47. data/lib/merb.rb +72 -93
  48. data/lib/merb/{merb_abstract_controller.rb → abstract_controller.rb} +28 -5
  49. data/lib/merb/caching/action_cache.rb +65 -29
  50. data/lib/merb/caching/fragment_cache.rb +9 -4
  51. data/lib/merb/caching/store/file_cache.rb +22 -14
  52. data/lib/merb/caching/store/memory_cache.rb +26 -8
  53. data/lib/merb/{merb_constants.rb → constants.rb} +9 -7
  54. data/lib/merb/controller.rb +178 -0
  55. data/lib/merb/core_ext.rb +13 -11
  56. data/lib/merb/core_ext/array.rb +0 -0
  57. data/lib/merb/core_ext/{merb_class.rb → class.rb} +0 -0
  58. data/lib/merb/core_ext/{merb_enumerable.rb → enumerable.rb} +0 -0
  59. data/lib/merb/core_ext/get_args.rb +52 -0
  60. data/lib/merb/core_ext/{merb_hash.rb → hash.rb} +40 -11
  61. data/lib/merb/core_ext/{merb_inflections.rb → inflections.rb} +0 -0
  62. data/lib/merb/core_ext/{merb_inflector.rb → inflector.rb} +1 -1
  63. data/lib/merb/core_ext/{merb_kernel.rb → kernel.rb} +56 -3
  64. data/lib/merb/core_ext/mash.rb +88 -0
  65. data/lib/merb/core_ext/{merb_module.rb → module.rb} +0 -0
  66. data/lib/merb/core_ext/{merb_numeric.rb → numeric.rb} +0 -0
  67. data/lib/merb/core_ext/{merb_object.rb → object.rb} +10 -47
  68. data/lib/merb/core_ext/string.rb +56 -0
  69. data/lib/merb/core_ext/{merb_symbol.rb → symbol.rb} +0 -0
  70. data/lib/merb/dispatcher.rb +109 -0
  71. data/lib/merb/{merb_drb_server.rb → drb_server.rb} +0 -0
  72. data/lib/merb/erubis_ext.rb +10 -0
  73. data/lib/merb/exceptions.rb +173 -0
  74. data/lib/merb/generators/merb_app/merb_app.rb +5 -25
  75. data/lib/merb/generators/merb_generator_helpers.rb +317 -0
  76. data/lib/merb/generators/merb_plugin.rb +19 -0
  77. data/lib/merb/logger.rb +65 -0
  78. data/lib/merb/{merb_mail_controller.rb → mail_controller.rb} +102 -49
  79. data/lib/merb/{merb_mailer.rb → mailer.rb} +31 -27
  80. data/lib/merb/mixins/{basic_authentication_mixin.rb → basic_authentication.rb} +3 -3
  81. data/lib/merb/mixins/{controller_mixin.rb → controller.rb} +131 -112
  82. data/lib/merb/mixins/{erubis_capture_mixin.rb → erubis_capture.rb} +12 -21
  83. data/lib/merb/mixins/{form_control_mixin.rb → form_control.rb} +6 -12
  84. data/lib/merb/mixins/render.rb +401 -0
  85. data/lib/merb/mixins/responder.rb +378 -0
  86. data/lib/merb/mixins/{view_context_mixin.rb → view_context.rb} +65 -10
  87. data/lib/merb/mixins/web_controller.rb +29 -0
  88. data/lib/merb/{merb_handler.rb → mongrel_handler.rb} +59 -38
  89. data/lib/merb/part_controller.rb +19 -0
  90. data/lib/merb/plugins.rb +16 -0
  91. data/lib/merb/rack_adapter.rb +37 -0
  92. data/lib/merb/request.rb +421 -0
  93. data/lib/merb/router.rb +576 -0
  94. data/lib/merb/{merb_server.rb → server.rb} +275 -71
  95. data/lib/merb/session.rb +10 -10
  96. data/lib/merb/session/cookie_store.rb +125 -0
  97. data/lib/merb/session/{merb_mem_cache_session.rb → mem_cache_session.rb} +22 -9
  98. data/lib/merb/session/{merb_memory_session.rb → memory_session.rb} +15 -11
  99. data/lib/merb/template.rb +35 -8
  100. data/lib/merb/template/erubis.rb +16 -10
  101. data/lib/merb/template/haml.rb +33 -20
  102. data/lib/merb/template/markaby.rb +16 -14
  103. data/lib/merb/template/xml_builder.rb +8 -4
  104. data/lib/merb/test/{merb_fake_request.rb → fake_request.rb} +11 -5
  105. data/lib/merb/test/helper.rb +31 -0
  106. data/lib/merb/test/hpricot.rb +136 -0
  107. data/lib/merb/test/{merb_multipart.rb → multipart.rb} +1 -1
  108. data/lib/merb/test/rspec.rb +93 -0
  109. data/lib/merb/{merb_upload_handler.rb → upload_handler.rb} +5 -6
  110. data/lib/merb/{merb_upload_progress.rb → upload_progress.rb} +1 -1
  111. data/lib/merb/{merb_view_context.rb → view_context.rb} +27 -42
  112. data/lib/{merb_tasks.rb → tasks.rb} +0 -0
  113. data/lib/tasks/merb.rake +21 -11
  114. data/merb_default_generators/model/USAGE +0 -0
  115. data/merb_default_generators/model/model_generator.rb +16 -0
  116. data/merb_default_generators/model/templates/new_model_template.erb +5 -0
  117. data/merb_default_generators/resource_controller/USAGE +0 -0
  118. data/merb_default_generators/resource_controller/resource_controller_generator.rb +26 -0
  119. data/merb_default_generators/resource_controller/templates/controller.rb +30 -0
  120. data/merb_default_generators/resource_controller/templates/edit.html.erb +1 -0
  121. data/merb_default_generators/resource_controller/templates/helper.rb +5 -0
  122. data/merb_default_generators/resource_controller/templates/index.html.erb +1 -0
  123. data/merb_default_generators/resource_controller/templates/new.html.erb +1 -0
  124. data/merb_default_generators/resource_controller/templates/show.html.erb +1 -0
  125. data/merb_generators/controller/USAGE +5 -0
  126. data/merb_generators/controller/controller_generator.rb +16 -0
  127. data/merb_generators/controller/templates/controller.rb +8 -0
  128. data/merb_generators/controller/templates/helper.rb +5 -0
  129. data/merb_generators/controller/templates/index.html.erb +3 -0
  130. data/merb_generators/resource/USAGE +0 -0
  131. data/merb_generators/resource/resource_generator.rb +60 -0
  132. data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +67 -0
  133. data/rspec_generators/merb_controller_test/templates/controller_spec.rb +8 -0
  134. data/rspec_generators/merb_controller_test/templates/edit_spec.rb +12 -0
  135. data/rspec_generators/merb_controller_test/templates/helper_spec.rb +5 -0
  136. data/rspec_generators/merb_controller_test/templates/index_spec.rb +12 -0
  137. data/rspec_generators/merb_controller_test/templates/new_spec.rb +12 -0
  138. data/rspec_generators/merb_controller_test/templates/show_spec.rb +5 -0
  139. data/rspec_generators/merb_model_test/merb_model_test_generator.rb +26 -0
  140. data/rspec_generators/merb_model_test/templates/model_spec_template.erb +7 -0
  141. data/script/destroy +14 -0
  142. data/script/generate +14 -0
  143. data/test_unit_generators/merb_controller_test/merb_controller_test_generator.rb +53 -0
  144. data/test_unit_generators/merb_controller_test/templates/functional_test.rb +17 -0
  145. data/test_unit_generators/merb_controller_test/templates/helper_test.rb +9 -0
  146. data/test_unit_generators/merb_model_test/merb_model_test_generator.rb +29 -0
  147. data/test_unit_generators/merb_model_test/templates/model_test_unit_template.erb +9 -0
  148. metadata +172 -94
  149. data/examples/README_EXAMPLES +0 -10
  150. data/examples/skeleton/Rakefile +0 -68
  151. data/examples/skeleton/dist/app/views/layout/application.herb +0 -12
  152. data/examples/skeleton/dist/conf/database.yml +0 -23
  153. data/examples/skeleton/dist/conf/merb.yml +0 -57
  154. data/examples/skeleton/dist/conf/merb_init.rb +0 -24
  155. data/examples/skeleton/dist/conf/router.rb +0 -22
  156. data/examples/skeleton/dist/conf/upload.conf +0 -5
  157. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +0 -14
  158. data/examples/skeleton/script/new_migration +0 -21
  159. data/lib/merb/core_ext/merb_string.rb +0 -18
  160. data/lib/merb/merb_controller.rb +0 -206
  161. data/lib/merb/merb_dispatcher.rb +0 -87
  162. data/lib/merb/merb_exceptions.rb +0 -319
  163. data/lib/merb/merb_part_controller.rb +0 -42
  164. data/lib/merb/merb_plugins.rb +0 -293
  165. data/lib/merb/merb_request.rb +0 -165
  166. data/lib/merb/merb_router.rb +0 -309
  167. data/lib/merb/merb_yaml_store.rb +0 -31
  168. data/lib/merb/mixins/render_mixin.rb +0 -283
  169. data/lib/merb/mixins/responder_mixin.rb +0 -159
  170. data/lib/merb/session/merb_ar_session.rb +0 -131
  171. data/lib/merb/vendor/paginator/README.txt +0 -84
  172. data/lib/merb/vendor/paginator/paginator.rb +0 -124
  173. data/lib/tasks/db.rake +0 -55
@@ -0,0 +1,378 @@
1
+ require 'enumerator'
2
+
3
+ module Merb
4
+ class << self
5
+ # Provides the currently implemented mime types as a hash
6
+ def available_mime_types
7
+ ResponderMixin::Rest::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
+ # +tranform_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
+ def add_mime_type(key,transform_method, values,new_response_headers = {})
18
+ raise ArgumentError unless key.is_a?(Symbol) && values.is_a?(Array)
19
+ ResponderMixin::Rest::TYPES.update(key => values)
20
+ add_response_headers!(key, new_response_headers)
21
+ ResponderMixin::Rest::TRANSFORM_METHODS.merge!(key => transform_method)
22
+ end
23
+
24
+ def remove_mime_type(key)
25
+ key == :all ? false : ResponderMixin::Rest::TYPES.delete(key)
26
+ end
27
+
28
+ def mime_transform_method(key)
29
+ ResponderMixin::Rest::TRANSFORM_METHODS[key]
30
+ end
31
+
32
+
33
+ # Adds outgoing headers to a mime type. This can be done with the Merb.add_mime_type method
34
+ # or directly here.
35
+ # ===Example
36
+ # {{[
37
+ # Merb.outgoing_headers!(:xml => { :Encoding => "UTF-8" })
38
+ # ]}}
39
+ #
40
+ # This method is destructive on any already defined outgoing headers
41
+ def add_response_headers!(key, values = {})
42
+ raise ArgumentError unless key.is_a?(Symbol) && values.is_a?(Hash)
43
+ response_headers[key] = values
44
+ end
45
+
46
+ def response_headers
47
+ ResponderMixin::Rest::RESPONSE_HEADERS
48
+ end
49
+
50
+ # Completely removes any headers set that are additional to the content-type header.
51
+ def remove_response_headers!(key)
52
+ raise ArgumentError unless key.is_a?(Symbol)
53
+ response_headers[key] = {}
54
+ end
55
+
56
+ # Sets the mime types and outgoing headers to their original states
57
+ def reset_default_mime_types!
58
+ available_mime_types.clear
59
+ response_headers.clear
60
+ Merb.add_mime_type(:all,nil,%w[*/*])
61
+ Merb.add_mime_type(:yaml,:to_yaml,%w[application/x-yaml text/yaml])
62
+ Merb.add_mime_type(:text,:to_text,%w[text/plain])
63
+ Merb.add_mime_type(:html,nil,%w[text/html application/xhtml+xml application/html])
64
+ Merb.add_mime_type(:xml,:to_xml,%w[application/xml text/xml application/x-xml], :Encoding => "UTF-8")
65
+ Merb.add_mime_type(:js,:to_json,%w[ text/javascript application/javascript application/x-javascript])
66
+ Merb.add_mime_type(:json,:to_json,%w[application/json text/x-json ])
67
+ end
68
+
69
+ end
70
+
71
+ # The ResponderMixin adds methods that help you manage what
72
+ # formats your controllers have available, determine what format(s)
73
+ # the client requested and is capable of handling, and perform
74
+ # content negotiation to pick the proper content format to
75
+ # deliver.
76
+ #
77
+ # If you hear someone say "Use provides" they're talking about the
78
+ # Responder. If you hear someone ask "What happened to respond_to?"
79
+ # it was replaced by provides and the other Responder methods.
80
+ #
81
+ # == A simple example
82
+ #
83
+ # The best way to understand how all of these pieces fit together is
84
+ # with an example. Here's a simple web-service ready resource that
85
+ # provides a list of all the widgets we know about. The widget list is
86
+ # available in 3 formats: :html (the default), plus :xml and :text.
87
+ #
88
+ # class Widgets < Application
89
+ # provides :html # This is the default, but you can
90
+ # # be explicit if you like.
91
+ # provides :xml, :text
92
+ #
93
+ # def index
94
+ # @widgets = Widget.fetch
95
+ # render @widgets
96
+ # end
97
+ # end
98
+ #
99
+ # Let's look at some example requests for this list of widgets. We'll
100
+ # assume they're all GET requests, but that's only to make the examples
101
+ # easier; this works for the full set of RESTful methods.
102
+ #
103
+ # 1. The simplest case, /widgets.html
104
+ # Since the request includes a specific format (.html) we know
105
+ # what format to return. Since :html is in our list of provided
106
+ # formats, that's what we'll return. +render+ will look
107
+ # for an index.html.erb (or another template format
108
+ # like index.html.mab; see the documentation on Template engines)
109
+ #
110
+ # 2. Almost as simple, /widgets.xml
111
+ # This is very similar. They want :xml, we have :xml, so
112
+ # that's what they get. If +render+ doesn't find an
113
+ # index.xml.builder or similar template, it will call +to_xml+
114
+ # on @widgets. This may or may not do something useful, but you can
115
+ # see how it works.
116
+ #
117
+ # 3. A browser request for /widgets
118
+ # This time the URL doesn't say what format is being requested, so
119
+ # we'll look to the HTTP Accept: header. If it's '*/*' (anything),
120
+ # we'll use the first format on our list, :html by default.
121
+ #
122
+ # If it parses to a list of accepted formats, we'll look through
123
+ # them, in order, until we find one we have available. If we find
124
+ # one, we'll use that. Otherwise, we can't fulfill the request:
125
+ # they asked for a format we don't have. So we raise
126
+ # 406: Not Acceptable.
127
+ #
128
+ # == A more complex example
129
+ #
130
+ # Sometimes you don't have the same code to handle each available
131
+ # format. Sometimes you need to load different data to serve
132
+ # /widgets.xml versus /widgets.txt. In that case, you can use
133
+ # +content_type+ to determine what format will be delivered.
134
+ #
135
+ # class Widgets < Application
136
+ # def action1
137
+ # if content_type == :text
138
+ # Widget.load_text_formatted(params[:id])
139
+ # else
140
+ # render
141
+ # end
142
+ # end
143
+ #
144
+ # def action2
145
+ # case content_type
146
+ # when :html
147
+ # handle_html()
148
+ # when :xml
149
+ # handle_xml()
150
+ # when :text
151
+ # handle_text()
152
+ # else
153
+ # render
154
+ # end
155
+ # end
156
+ # end
157
+ #
158
+ # You can do any standard Ruby flow control using +content_type+. If
159
+ # you don't call it yourself, it will be called (triggering content
160
+ # negotiation) by +render+.
161
+ #
162
+ # Once +content_type+ has been called, the output format is frozen,
163
+ # and none of the provides methods can be used.
164
+ module ResponderMixin
165
+
166
+ def self.included(base) # :nodoc:
167
+ base.extend(ClassMethods)
168
+ base.class_eval { class_inheritable_accessor :class_provided_formats }
169
+ base.class_provided_formats = [:html]
170
+ end
171
+
172
+ module ClassMethods
173
+
174
+ # Adds symbols representing formats to the controller's
175
+ # default list of provided_formats. These will apply to
176
+ # every action in the controller, unless modified in the action.
177
+ def provides(*formats)
178
+ formats.each {|fmt|
179
+ self.class_provided_formats << fmt unless class_provided_formats.include?(fmt)
180
+ }
181
+ end
182
+
183
+ # Overwrites the controller's list of provided_formats. These
184
+ # will apply to every action in the controller, unless modified
185
+ # in the action.
186
+ def only_provides(*formats)
187
+ self.class_provided_formats = formats
188
+ end
189
+
190
+ # Removes formats from the controller's
191
+ # default list of provided_formats. These will apply to
192
+ # every action in the controller, unless modified in the action.
193
+ def does_not_provide(*formats)
194
+ self.class_provided_formats -= formats
195
+ end
196
+ end
197
+
198
+ # Returns the current list of formats provided for this instance
199
+ # of the controller. It starts with what has been set in the controller
200
+ # (or :html by default) but can be modifed on a per-action basis.
201
+ def provided_formats
202
+ @_provided_formats ||= class_provided_formats
203
+ end
204
+
205
+ # Sets the provided formats for this action. Usually, you would
206
+ # use a combination of +provides+, +only_provides+ and +does_not_provide+
207
+ # to manage this, but you can set it directly.
208
+ def provided_formats=(*formats)
209
+ raise "Cannot modify provided_formats because content_type has already been set" if content_type_set?
210
+ @_provided_formats = formats.flatten
211
+ end
212
+
213
+ # Adds formats to the list of provided formats for this particular
214
+ # request. Usually used to add formats to a single action. See also
215
+ # the controller-level provides that affects all actions in a controller.
216
+ def provides(*formats)
217
+ formats.each {|fmt|
218
+ self.provided_formats += [fmt] unless provided_formats.include?(fmt)
219
+ }
220
+ end
221
+
222
+ # Sets list of provided formats for this particular
223
+ # request. Usually used to limit formats to a single action. See also
224
+ # the controller-level provides that affects all actions in a controller.
225
+ def only_provides(*formats)
226
+ self.provided_formats = formats.flatten
227
+ end
228
+
229
+ # Removes formats from the list of provided formats for this particular
230
+ # request. Usually used to remove formats from a single action. See
231
+ # also the controller-level provides that affects all actions in a
232
+ # controller.
233
+ def does_not_provide(*formats)
234
+ self.provided_formats -= formats.flatten
235
+ end
236
+
237
+ # Do the content negotiation:
238
+ # 1. if params[:format] is there, and provided, use it
239
+ # 2. Parse the Accept header
240
+ # 3. If it's */*, use the first provided format
241
+ # 4. Look for one that is provided, in order of request
242
+ # 5. Raise 406 if none found
243
+ def perform_content_negotiation # :nodoc:
244
+ raise Merb::ControllerExceptions::NotAcceptable if provided_formats.empty?
245
+ if fmt = params[:format]
246
+ if provided_formats.include?(fmt.to_sym)
247
+ fmt.to_sym
248
+ else
249
+ raise Merb::ControllerExceptions::NotAcceptable
250
+ end
251
+ else
252
+ accepts = Rest::Responder.parse(request.accept).
253
+ collect {|t| t.to_sym}
254
+ if accepts.include?(:all)
255
+ provided_formats.first
256
+ else
257
+ accepts.each do |type|
258
+ return type if provided_formats.include?(type)
259
+ end
260
+ raise Merb::ControllerExceptions::NotAcceptable
261
+ end
262
+ end
263
+ end
264
+
265
+ # Checks to see if content negotiation has already been performed.
266
+ # If it has, you can no longer modify the list of provided formats.
267
+ def content_type_set?
268
+ !@_content_type.nil?
269
+ end
270
+
271
+ # Returns the output format for this request, based on the
272
+ # provided formats, <tt>params[:format]</tt> and the client's HTTP
273
+ # Accept header.
274
+ #
275
+ # The first time this is called, it triggers content negotiation
276
+ # and caches the value. Once you call +content_type+ you can
277
+ # not set or change the list of provided formats.
278
+ #
279
+ # Called automatically by +render+, so you should only call it if
280
+ # you need the value, not to trigger content negotiation.
281
+ def content_type
282
+ unless content_type_set?
283
+ @_content_type = perform_content_negotiation
284
+ raise Merb::ControllerExceptions::NotAcceptable.new("Unknown content_type for response: #{@_content_type}") unless
285
+ Merb.available_mime_types.has_key?(@_content_type)
286
+ headers['Content-Type'] = Merb.available_mime_types[@_content_type].first
287
+ end
288
+ @_content_type
289
+ end
290
+
291
+ # Sets the output content_type for this request. Normally you
292
+ # should use +provides+, +does_not_provide+ and +only_provides+
293
+ # and then let the content negotiation process determine the proper
294
+ # content_type. However, in some circumstances you may want to
295
+ # set it directly, or override what content negotiation picks.
296
+ def content_type=(new_type)
297
+ @_content_type = new_type
298
+ end
299
+
300
+ module Rest
301
+
302
+ TYPES = {}
303
+ RESPONSE_HEADERS = {}
304
+ TRANSFORM_METHODS = {}
305
+
306
+ class Responder
307
+
308
+ protected
309
+
310
+ def self.parse(accept_header)
311
+ # parse the raw accept header into a unique, sorted array of AcceptType objects
312
+ list = accept_header.to_s.split(/,/).enum_for(:each_with_index).map do |entry,index|
313
+ AcceptType.new(entry,index += 1)
314
+ end.sort.uniq
315
+ # firefox (and possibly other browsers) send broken default accept headers.
316
+ # fix them up by sorting alternate xml forms (namely application/xhtml+xml)
317
+ # ahead of pure xml types (application/xml,text/xml).
318
+ if app_xml = list.detect{|e| e.super_range == 'application/xml'}
319
+ list.select{|e| e.to_s =~ /\+xml/}.each { |acc_type|
320
+ list[list.index(acc_type)],list[list.index(app_xml)] =
321
+ list[list.index(app_xml)],list[list.index(acc_type)] }
322
+ end
323
+ list
324
+ end
325
+
326
+ end
327
+
328
+ class AcceptType
329
+
330
+ attr_reader :media_range, :quality, :index, :type, :sub_type
331
+
332
+ def initialize(entry,index)
333
+ @index = index
334
+ @media_range, quality = entry.split(/;\s*q=/).map{|a| a.strip }
335
+ @type, @sub_type = @media_range.split(/\//)
336
+ quality ||= 0.0 if @media_range == '*/*'
337
+ @quality = ((quality || 1.0).to_f * 100).to_i
338
+ end
339
+
340
+ def <=>(entry)
341
+ c = entry.quality <=> quality
342
+ c = index <=> entry.index if c == 0
343
+ c
344
+ end
345
+
346
+ def eql?(entry)
347
+ synonyms.include?(entry.media_range)
348
+ end
349
+
350
+ def ==(entry); eql?(entry); end
351
+
352
+ def hash; super_range.hash; end
353
+
354
+ def synonyms
355
+ @syns ||= TYPES.values.select{|e| e.include?(@media_range)}.flatten
356
+ end
357
+
358
+ def super_range
359
+ synonyms.first || @media_range
360
+ end
361
+
362
+ def to_sym
363
+ TYPES.select{|k,v|
364
+ v == synonyms || v[0] == synonyms[0]}.flatten.first
365
+ end
366
+
367
+ def to_s
368
+ @media_range
369
+ end
370
+
371
+ end
372
+
373
+ end
374
+
375
+ end
376
+ reset_default_mime_types!
377
+
378
+ end
@@ -22,7 +22,8 @@ module Merb
22
22
  # # => <a href="blog/show/13">The Entry Title</a>
23
23
  #
24
24
  def link_to(name, url='', opts={})
25
- %{<a href="#{url}" #{opts.map{|k,v| "#{k}=\"#{v}\""}.join(' ')}>#{name}</a>}
25
+ opts[:href] ||= url
26
+ %{<a #{ opts.to_xml_attributes }>#{name}</a>}
26
27
  end
27
28
 
28
29
  # Creates an image tag with the +src+ attribute set to the +img+ argument. The path
@@ -44,11 +45,14 @@ module Merb
44
45
  # image_tag('foo.gif', :path => '/files/')
45
46
  # # => <img src='/files/foo.gif' />
46
47
  #
48
+ # image_tag('http://test.com/foo.gif')
49
+ # # => <img src="http://test.com/foo.gif">
47
50
  def image_tag(img, opts={})
48
- opts[:path] ||= '/images/'
49
- %{<img src="#{opts.delete(:path) + img}" #{opts.map{|k,v| "#{k}=\"#{v}\""}.join(' ')} />}
51
+ opts[:path] ||= (img =~ %r{^https?://}) ? '' : '/images/'
52
+ opts[:src] ||= opts.delete(:path) + img
53
+ %{<img #{ opts.to_xml_attributes } />}
50
54
  end
51
-
55
+
52
56
  # :section: JavaScript related functions
53
57
  #
54
58
 
@@ -107,7 +111,7 @@ module Merb
107
111
  # need to include a call to include_required_js and include_required_css.
108
112
  #
109
113
  # ==== Examples
110
- # # File: app/views/layouts/application.herb
114
+ # # File: app/views/layouts/application.html.erb
111
115
  #
112
116
  # <html>
113
117
  # <head>
@@ -194,18 +198,19 @@ module Merb
194
198
  #
195
199
  # ==== Examples
196
200
  # # my_action.herb has a call to require_js 'jquery'
197
- # # File: layout/application.herb
201
+ # # File: layout/application.html.erb
198
202
  # include_required_js
199
203
  # # => <script src="/javascripts/jquery.js" type="text/javascript"></script>
200
204
  #
201
205
  # # my_action.herb has a call to require_js 'jquery', 'effects', 'validation'
202
- # # File: layout/application.herb
206
+ # # File: layout/application.html.erb
203
207
  # include_required_js
204
208
  # # => <script src="/javascripts/jquery.js" type="text/javascript"></script>
205
209
  # # <script src="/javascripts/effects.js" type="text/javascript"></script>
206
210
  # # <script src="/javascripts/validation.js" type="text/javascript"></script>
207
211
  #
208
212
  def include_required_js
213
+ return '' if @required_js.nil?
209
214
  js_include_tag(*@required_js)
210
215
  end
211
216
 
@@ -214,18 +219,19 @@ module Merb
214
219
  #
215
220
  # ==== Examples
216
221
  # # my_action.herb has a call to require_css 'style'
217
- # # File: layout/application.herb
222
+ # # File: layout/application.html.erb
218
223
  # include_required_css
219
224
  # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
220
225
  #
221
226
  # # my_action.herb has a call to require_js 'style', 'ie-specific'
222
- # # File: layout/application.herb
227
+ # # File: layout/application.html.erb
223
228
  # include_required_css
224
229
  # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
225
230
  # # <link href="/stylesheets/ie-specific.css" media="all" rel="Stylesheet" type="text/css"/>
226
231
  #
227
232
  def include_required_css
228
- css_link_tag(*@required_css)
233
+ return '' if @required_css.nil?
234
+ css_include_tag(*@required_css)
229
235
  end
230
236
 
231
237
  # The js_include_tag method will create a JavaScript
@@ -319,5 +325,54 @@ module Merb
319
325
  ::Merb::Caching::Fragment.put(name, buffer[pos..-1])
320
326
  end
321
327
  end
328
+
329
+ # Calling throw_content stores the block of markup for later use.
330
+ # Subsequently, you can make calls to it by name with <tt>catch_content</tt>
331
+ # in another template or in the layout.
332
+ #
333
+ # Example:
334
+ #
335
+ # <% throw_content :header do %>
336
+ # alert('hello world')
337
+ # <% end %>
338
+ #
339
+ # You can use catch_content :header anywhere in your templates.
340
+ #
341
+ # <%= catch_content :header %>
342
+ def throw_content(name, content = nil, &block)
343
+ controller.thrown_content[name] << ( content || "" ) << capture( &block )
344
+ end
345
+
346
+ # Concat will concatenate text directly to the buffer of the template.
347
+ # The binding must be supplied in order to obtian the buffer. This can be called directly in the
348
+ # template as
349
+ # concat( "text", binding )
350
+ #
351
+ # or from a helper method that accepts a block as
352
+ # concat( "text", block.binding )
353
+ def concat( string, binding )
354
+ _buffer( binding ) << string
355
+ end
356
+
357
+ # Creates the opening tag with attributes for the provided +name+
358
+ # attrs is a hash where all members will be mapped to key="value"
359
+ #
360
+ # Note: This tag will need to be closed
361
+ def open_tag(name, attrs = nil)
362
+ "<#{name}#{(' ' + attrs.to_html_attributes) if attrs && !attrs.empty?}>"
363
+ end
364
+
365
+ # Creates a closing tag
366
+ def close_tag(name)
367
+ "</#{name}>"
368
+ end
369
+
370
+ # Creates a self closing tag. Like <br/> or <img src="..."/>
371
+ #
372
+ # +name+ : the name of the tag to create
373
+ # +attrs+ : a hash where all members will be mapped to key="value"
374
+ def self_closing_tag(name, attrs = nil)
375
+ "<#{name}#{' ' + attrs.to_html_attributes if attrs}/>"
376
+ end
322
377
  end
323
378
  end