wycats-merb-core 0.9.8

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