merb 0.4.2 → 0.5.0

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 (108) hide show
  1. data/README +21 -14
  2. data/Rakefile +157 -108
  3. data/SVN_REVISION +1 -0
  4. data/app_generators/merb/templates/Rakefile +20 -4
  5. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +1 -1
  6. data/app_generators/merb/templates/config/boot.rb +1 -1
  7. data/app_generators/merb/templates/config/dependencies.rb +3 -3
  8. data/app_generators/merb/templates/config/merb.yml +5 -0
  9. data/app_generators/merb/templates/config/merb_init.rb +3 -3
  10. data/app_generators/merb/templates/script/destroy +3 -0
  11. data/app_generators/merb/templates/script/generate +1 -1
  12. data/app_generators/merb/templates/spec/spec_helper.rb +2 -2
  13. data/app_generators/merb/templates/test/test_helper.rb +1 -1
  14. data/app_generators/merb_plugin/merb_plugin_generator.rb +4 -0
  15. data/bin/merb +1 -3
  16. data/lib/merb.rb +144 -76
  17. data/lib/merb/abstract_controller.rb +6 -5
  18. data/lib/merb/assets.rb +119 -0
  19. data/lib/merb/boot_loader.rb +217 -0
  20. data/lib/merb/caching.rb +1 -1
  21. data/lib/merb/caching/action_cache.rb +1 -1
  22. data/lib/merb/caching/fragment_cache.rb +1 -1
  23. data/lib/merb/caching/store/file_cache.rb +1 -1
  24. data/lib/merb/config.rb +290 -0
  25. data/lib/merb/controller.rb +5 -5
  26. data/lib/merb/core_ext/get_args.rb +1 -0
  27. data/lib/merb/core_ext/hash.rb +182 -169
  28. data/lib/merb/core_ext/kernel.rb +57 -26
  29. data/lib/merb/dispatcher.rb +6 -6
  30. data/lib/merb/drb_server.rb +1 -1
  31. data/lib/merb/generators/merb_generator_helpers.rb +7 -6
  32. data/lib/merb/logger.rb +1 -1
  33. data/lib/merb/mail_controller.rb +3 -4
  34. data/lib/merb/mailer.rb +2 -2
  35. data/lib/merb/mixins/basic_authentication.rb +2 -2
  36. data/lib/merb/mixins/controller.rb +1 -1
  37. data/lib/merb/mixins/general_controller.rb +13 -20
  38. data/lib/merb/mixins/inline_partial.rb +32 -0
  39. data/lib/merb/mixins/render.rb +3 -3
  40. data/lib/merb/mixins/responder.rb +1 -1
  41. data/lib/merb/mixins/view_context.rb +159 -33
  42. data/lib/merb/mongrel_handler.rb +9 -9
  43. data/lib/merb/plugins.rb +1 -1
  44. data/lib/merb/request.rb +25 -1
  45. data/lib/merb/router.rb +264 -226
  46. data/lib/merb/server.rb +66 -560
  47. data/lib/merb/session/cookie_store.rb +14 -13
  48. data/lib/merb/session/mem_cache_session.rb +20 -10
  49. data/lib/merb/session/memory_session.rb +21 -11
  50. data/lib/merb/template.rb +2 -2
  51. data/lib/merb/template/erubis.rb +3 -33
  52. data/lib/merb/template/haml.rb +8 -3
  53. data/lib/merb/test/fake_request.rb +8 -3
  54. data/lib/merb/test/helper.rb +66 -22
  55. data/lib/merb/test/rspec.rb +9 -155
  56. data/lib/merb/test/rspec_matchers/controller_matchers.rb +117 -0
  57. data/lib/merb/test/rspec_matchers/markup_matchers.rb +98 -0
  58. data/lib/merb/upload_handler.rb +2 -1
  59. data/lib/merb/version.rb +38 -3
  60. data/lib/merb/view_context.rb +1 -2
  61. data/lib/tasks/merb.rake +11 -11
  62. data/merb_generators/part_controller/USAGE +5 -0
  63. data/merb_generators/part_controller/part_controller_generator.rb +27 -0
  64. data/merb_generators/part_controller/templates/controller.rb +8 -0
  65. data/merb_generators/part_controller/templates/helper.rb +5 -0
  66. data/merb_generators/part_controller/templates/index.html.erb +3 -0
  67. data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +1 -1
  68. data/script/destroy +14 -0
  69. data/script/generate +14 -0
  70. data/spec/fixtures/controllers/dispatch_spec_controllers.rb +9 -1
  71. data/spec/fixtures/controllers/render_spec_controllers.rb +5 -5
  72. data/spec/fixtures/models/router_spec_models.rb +10 -0
  73. data/spec/merb/abstract_controller_spec.rb +2 -2
  74. data/spec/merb/assets_spec.rb +207 -0
  75. data/spec/merb/caching_spec.rb +2 -2
  76. data/spec/merb/controller_spec.rb +7 -2
  77. data/spec/merb/cookie_store_spec.rb +1 -1
  78. data/spec/merb/core_ext/class_spec.rb +97 -0
  79. data/spec/merb/core_ext/enumerable_spec.rb +27 -0
  80. data/spec/merb/core_ext/hash_spec.rb +251 -0
  81. data/spec/merb/core_ext/inflector_spec.rb +34 -0
  82. data/spec/merb/core_ext/kernel_spec.rb +25 -0
  83. data/spec/merb/core_ext/numeric_spec.rb +26 -0
  84. data/spec/merb/core_ext/object_spec.rb +47 -0
  85. data/spec/merb/core_ext/string_spec.rb +22 -0
  86. data/spec/merb/core_ext/symbol_spec.rb +7 -0
  87. data/spec/merb/dependency_spec.rb +22 -0
  88. data/spec/merb/dispatch_spec.rb +23 -12
  89. data/spec/merb/fake_request_spec.rb +8 -0
  90. data/spec/merb/generator_spec.rb +140 -21
  91. data/spec/merb/handler_spec.rb +5 -5
  92. data/spec/merb/mail_controller_spec.rb +3 -3
  93. data/spec/merb/render_spec.rb +1 -1
  94. data/spec/merb/responder_spec.rb +3 -3
  95. data/spec/merb/router_spec.rb +260 -191
  96. data/spec/merb/server_spec.rb +5 -5
  97. data/spec/merb/upload_handler_spec.rb +7 -0
  98. data/spec/merb/version_spec.rb +33 -0
  99. data/spec/merb/view_context_spec.rb +217 -59
  100. data/spec/spec_generator_helper.rb +15 -0
  101. data/spec/spec_helper.rb +5 -3
  102. data/spec/spec_helpers/url_shared_behaviour.rb +5 -7
  103. metadata +32 -7
  104. data/lib/merb/caching/store/memcache.rb +0 -20
  105. data/lib/merb/mixins/form_control.rb +0 -332
  106. data/lib/patch +0 -69
  107. data/spec/merb/core_ext_spec.rb +0 -464
  108. data/spec/merb/form_control_mixin_spec.rb +0 -431
@@ -370,7 +370,7 @@ module Merb
370
370
  module Rest
371
371
 
372
372
  TYPES = {}
373
- RESPONSE_HEADERS = {}
373
+ RESPONSE_HEADERS = Hash.new([])
374
374
  TRANSFORM_METHODS = {}
375
375
  TRANSFORM_METHOD_DEFAULTS = {}
376
376
 
@@ -2,6 +2,9 @@ module Merb
2
2
  # The ViewContextMixin module provides a number of helper methods to views for
3
3
  # linking to assets and other pages, dealing with JavaScript, and caching.
4
4
  module ViewContextMixin
5
+
6
+ include Merb::Assets::AssetHelpers
7
+
5
8
  # :section: Accessing Assets
6
9
  # Merb provides views with convenience methods for links images and other assets.
7
10
 
@@ -52,8 +55,8 @@ module Merb
52
55
  if img =~ %r{^https?://}
53
56
  ''
54
57
  else
55
- if Merb::Server.config[:path_prefix]
56
- Merb::Server.config[:path_prefix] + '/images/'
58
+ if Merb::Config[:path_prefix]
59
+ Merb::Config[:path_prefix] + '/images/'
57
60
  else
58
61
  '/images/'
59
62
  end
@@ -162,6 +165,77 @@ module Merb
162
165
  #
163
166
  # See each method's documentation for more information.
164
167
 
168
+ # :section: Bundling Asset Files
169
+ #
170
+ # The key to making a fast web application is to reduce both the amount of
171
+ # data transfered and the number of client-server interactions. While having
172
+ # many small, module Javascript or stylesheet files aids in the development
173
+ # process, your web application will benefit from bundling those assets in
174
+ # the production environment.
175
+ #
176
+ # An asset bundle is a set of asset files which are combined into a single
177
+ # file. This reduces the number of requests required to render a page, and
178
+ # can reduce the amount of data transfer required if you're using gzip
179
+ # encoding.
180
+ #
181
+ # Asset bundling is always enabled in production mode, and can be optionally
182
+ # enabled in all environments by setting the <tt>:bundle_assets</tt> value
183
+ # in <tt>config/merb.yml</tt> to +true+.
184
+ #
185
+ # ==== Examples
186
+ #
187
+ # In the development environment, this:
188
+ #
189
+ # js_include_tag :prototype, :lowpro, :bundle => true
190
+ #
191
+ # will produce two <script> elements. In the production mode, however, the
192
+ # two files will be concatenated in the order given into a single file,
193
+ # <tt>all.js</tt>, in the <tt>public/javascripts</tt> directory.
194
+ #
195
+ # To specify a different bundle name:
196
+ #
197
+ # css_include_tag :typography, :whitespace, :bundle => :base
198
+ # css_include_tag :header, :footer, :bundle => "content"
199
+ # css_include_tag :lightbox, :images, :bundle => "lb.css"
200
+ #
201
+ # (<tt>base.css</tt>, <tt>content.css</tt>, and <tt>lb.css</tt> will all be
202
+ # created in the <tt>public/stylesheets</tt> directory.)
203
+ #
204
+ # == Callbacks
205
+ #
206
+ # To use a Javascript or CSS compressor, like JSMin or YUI Compressor:
207
+ #
208
+ # Merb::Assets::JavascriptAssetBundler.add_callback do |filename|
209
+ # system("/usr/local/bin/yui-compress #{filename}")
210
+ # end
211
+ #
212
+ # Merb::Assets::StylesheetAssetBundler.add_callback do |filename|
213
+ # system("/usr/local/bin/css-min #{filename}")
214
+ # end
215
+ #
216
+ # These blocks will be run after a bundle is created.
217
+ #
218
+ # == Bundling Required Assets
219
+ #
220
+ # Combining the +require_css+ and +require_js+ helpers with bundling can be
221
+ # problematic. You may want to separate out the common assets for your
222
+ # application -- Javascript frameworks, common CSS, etc. -- and bundle those
223
+ # in a "base" bundle. Then, for each section of your site, bundle the
224
+ # required assets into a section-specific bundle.
225
+ #
226
+ # <b>N.B.: If you bundle an inconsistent set of assets with the same name,
227
+ # you will have inconsistent results. Be thorough and test often.</b>
228
+ #
229
+ # ==== Example
230
+ #
231
+ # In your application layout:
232
+ #
233
+ # js_include_tag :prototype, :lowpro, :bundle => :base
234
+ #
235
+ # In your controller layout:
236
+ #
237
+ # require_js :bundle => :posts
238
+
165
239
  # The require_js method can be used to require any JavaScript
166
240
  # file anywhere in your templates. Regardless of how many times
167
241
  # a single script is included with require_js, Merb will only include
@@ -182,7 +256,7 @@ module Merb
182
256
  @required_js |= js
183
257
  end
184
258
 
185
- # The require_ccs method can be used to require any CSS
259
+ # The require_css method can be used to require any CSS
186
260
  # file anywhere in your templates. Regardless of how many times
187
261
  # a single stylesheet is included with require_css, Merb will only include
188
262
  # it once in the header.
@@ -204,7 +278,13 @@ module Merb
204
278
 
205
279
  # A method used in the layout of an application to create +<script>+ tags to include JavaScripts required in
206
280
  # in templates and subtemplates using require_js.
207
- #
281
+ #
282
+ # ==== Options
283
+ # bundle:: The name of the bundle the scripts should be combined into.
284
+ # If +nil+ or +false+, the bundle is not created. If +true+, a
285
+ # bundle named <tt>all.js</tt> is created. Otherwise,
286
+ # <tt>:bundle</tt> is treated as an asset name.
287
+ #
208
288
  # ==== Examples
209
289
  # # my_action.herb has a call to require_js 'jquery'
210
290
  # # File: layout/application.html.erb
@@ -218,14 +298,20 @@ module Merb
218
298
  # # <script src="/javascripts/effects.js" type="text/javascript"></script>
219
299
  # # <script src="/javascripts/validation.js" type="text/javascript"></script>
220
300
  #
221
- def include_required_js
301
+ def include_required_js(options = {})
222
302
  return '' if @required_js.nil?
223
- js_include_tag(*@required_js)
303
+ js_include_tag(*(@required_js + [options]))
224
304
  end
225
305
 
226
306
  # A method used in the layout of an application to create +<link>+ tags for CSS stylesheets required in
227
307
  # in templates and subtemplates using require_css.
228
- #
308
+ #
309
+ # ==== Options
310
+ # bundle:: The name of the bundle the stylesheets should be combined into.
311
+ # If +nil+ or +false+, the bundle is not created. If +true+, a
312
+ # bundle named <tt>all.css</tt> is created. Otherwise,
313
+ # <tt>:bundle</tt> is treated as an asset name.
314
+ #
229
315
  # ==== Examples
230
316
  # # my_action.herb has a call to require_css 'style'
231
317
  # # File: layout/application.html.erb
@@ -238,15 +324,21 @@ module Merb
238
324
  # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
239
325
  # # <link href="/stylesheets/ie-specific.css" media="all" rel="Stylesheet" type="text/css"/>
240
326
  #
241
- def include_required_css
327
+ def include_required_css(options = {})
242
328
  return '' if @required_css.nil?
243
- css_include_tag(*@required_css)
329
+ css_include_tag(*(@required_css + [options]))
244
330
  end
245
331
 
246
332
  # The js_include_tag method will create a JavaScript
247
333
  # +<include>+ tag for each script named in the arguments, appending
248
334
  # '.js' if it is left out of the call.
249
- #
335
+ #
336
+ # ==== Options
337
+ # bundle:: The name of the bundle the scripts should be combined into.
338
+ # If +nil+ or +false+, the bundle is not created. If +true+, a
339
+ # bundle named <tt>all.js</tt> is created. Otherwise,
340
+ # <tt>:bundle</tt> is treated as an asset name.
341
+ #
250
342
  # ==== Examples
251
343
  # js_include_tag 'jquery'
252
344
  # # => <script src="/javascripts/jquery.js" type="text/javascript"></script>
@@ -263,46 +355,80 @@ module Merb
263
355
  # # <script src="/javascripts/validation.js" type="text/javascript"></script>
264
356
  #
265
357
  def js_include_tag(*scripts)
358
+ options = scripts.last.is_a?(Hash) ? scripts.pop : {}
266
359
  return nil if scripts.empty?
267
- include_tag = ""
268
- scripts.each do |script|
269
- script = script.to_s
270
- url = "/javascripts/#{script =~ /\.js$/ ? script : script + '.js'}"
271
- url = Merb::Server.config[:path_prefix] + url if Merb::Server.config[:path_prefix]
272
- include_tag << %Q|<script src="#{url}" type="text/javascript">//</script>\n|
360
+
361
+ if (bundle_name = options[:bundle]) && Merb::Assets.bundle? && scripts.size > 1
362
+ bundler = Merb::Assets::JavascriptAssetBundler.new(bundle_name, *scripts)
363
+ bundled_asset = bundler.bundle!
364
+ return js_include_tag(bundled_asset)
365
+ end
366
+
367
+ tags = ""
368
+
369
+ for script in scripts
370
+ attrs = {
371
+ :src => asset_path(:javascript, script),
372
+ :type => "text/javascript"
373
+ }
374
+ tags << %Q{<script #{attrs.to_xml_attributes}>//</script>}
273
375
  end
274
- include_tag
376
+
377
+ return tags
275
378
  end
276
379
 
277
380
  # The css_include_tag method will create a CSS stylesheet
278
381
  # +<link>+ tag for each stylesheet named in the arguments, appending
279
382
  # '.css' if it is left out of the call.
383
+ #
384
+ # ==== Options
385
+ # bundle:: The name of the bundle the stylesheets should be combined into.
386
+ # If +nil+ or +false+, the bundle is not created. If +true+, a
387
+ # bundle named <tt>all.css</tt> is created. Otherwise,
388
+ # <tt>:bundle</tt> is treated as an asset name.
389
+ # media:: The media attribute for the generated link element. Defaults
390
+ # to <tt>:all</tt>.
280
391
  #
281
392
  # ==== Examples
282
393
  # css_include_tag 'style'
283
- # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
394
+ # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
284
395
  #
285
396
  # css_include_tag 'style.css', 'layout'
286
- # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
287
- # # <link href="/stylesheets/layout.css" media="all" rel="Stylesheet" type="text/css"/>
397
+ # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
398
+ # # <link href="/stylesheets/layout.css" media="all" rel="Stylesheet" type="text/css" />
288
399
  #
289
400
  # css_include_tag :menu
290
- # # => <link href="/stylesheets/menu.css" media="all" rel="Stylesheet" type="text/css"/>
401
+ # # => <link href="/stylesheets/menu.css" media="all" rel="Stylesheet" type="text/css" />
291
402
  #
292
403
  # css_include_tag :style, :screen
293
- # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
294
- # # <link href="/stylesheets/screen.css" media="all" rel="Stylesheet" type="text/css"/>
295
- #
296
- def css_include_tag(*scripts)
297
- return nil if scripts.empty?
298
- include_tag = ""
299
- scripts.each do |script|
300
- script = script.to_s
301
- url = "/stylesheets/#{script =~ /\.css$/ ? script : script + '.css'}"
302
- url = Merb::Server.config[:path_prefix] + url if Merb::Server.config[:path_prefix]
303
- include_tag << %Q|<link href="#{url}" media="all" rel="Stylesheet" type="text/css"/>\n|
404
+ # # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
405
+ # # <link href="/stylesheets/screen.css" media="all" rel="Stylesheet" type="text/css" />
406
+ #
407
+ # css_include_tag :style, :media => :print
408
+ # # => <link href="/stylesheets/style.css" media="print" rel="Stylesheet" type="text/css" />
409
+ def css_include_tag(*stylesheets)
410
+ options = stylesheets.last.is_a?(Hash) ? stylesheets.pop : {}
411
+ return nil if stylesheets.empty?
412
+
413
+ if (bundle_name = options[:bundle]) && Merb::Assets.bundle? && stylesheets.size > 1
414
+ bundler = Merb::Assets::StylesheetAssetBundler.new(bundle_name, *stylesheets)
415
+ bundled_asset = bundler.bundle!
416
+ return css_include_tag(bundled_asset)
304
417
  end
305
- include_tag
418
+
419
+ tags = ""
420
+
421
+ for stylesheet in stylesheets
422
+ attrs = {
423
+ :href => asset_path(:stylesheet, stylesheet),
424
+ :type => "text/css",
425
+ :rel => "Stylesheet",
426
+ :media => options[:media] || :all
427
+ }
428
+ tags << %Q{<link #{attrs.to_xml_attributes} />}
429
+ end
430
+
431
+ return tags
306
432
  end
307
433
 
308
434
  # :section: Caching
@@ -49,14 +49,14 @@ class MerbHandler < Mongrel::HttpHandler
49
49
  start = Time.now
50
50
  benchmarks = {}
51
51
 
52
- MERB_LOGGER.info("\nRequest: REQUEST_URI: #{
52
+ Merb.logger.info("\nRequest: REQUEST_URI: #{
53
53
  request.params[Mongrel::Const::REQUEST_URI]} (#{Time.now.strftime("%Y-%m-%d %H:%M:%S")})")
54
54
 
55
55
  # Truncate the request URI if there's a path prefix so that an app can be
56
56
  # hosted inside a subdirectory, for example.
57
57
  if @@path_prefix
58
58
  if request.params[Mongrel::Const::PATH_INFO] =~ @@path_prefix
59
- MERB_LOGGER.info("Path prefix #{@@path_prefix.inspect} removed from PATH_INFO and REQUEST_URI.")
59
+ Merb.logger.info("Path prefix #{@@path_prefix.inspect} removed from PATH_INFO and REQUEST_URI.")
60
60
  request.params[Mongrel::Const::PATH_INFO].sub!(@@path_prefix, '')
61
61
  request.params[Mongrel::Const::REQUEST_URI].sub!(@@path_prefix, '')
62
62
  path_info = request.params[Mongrel::Const::PATH_INFO]
@@ -75,11 +75,11 @@ class MerbHandler < Mongrel::HttpHandler
75
75
 
76
76
  if get_or_head && @files.can_serve(path_info)
77
77
  # File exists as-is so serve it up
78
- MERB_LOGGER.info("Serving static file: #{path_info}")
78
+ Merb.logger.info("Serving static file: #{path_info}")
79
79
  @files.process(request,response)
80
80
  elsif get_or_head && @files.can_serve(page_cached)
81
81
  # Possible cached page, serve it up
82
- MERB_LOGGER.info("Serving static file: #{page_cached}")
82
+ Merb.logger.info("Serving static file: #{page_cached}")
83
83
  request.params[Mongrel::Const::PATH_INFO] = page_cached
84
84
  @files.process(request,response)
85
85
  else
@@ -89,7 +89,7 @@ class MerbHandler < Mongrel::HttpHandler
89
89
  benchmarks[:controller] = controller.class.to_s
90
90
  benchmarks[:action] = action
91
91
 
92
- MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{benchmarks[:setup_time]} seconds")
92
+ Merb.logger.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{benchmarks[:setup_time]} seconds")
93
93
 
94
94
  sendfile, clength = nil
95
95
  response.status = controller.status
@@ -111,7 +111,7 @@ class MerbHandler < Mongrel::HttpHandler
111
111
  if @mongrel_x_sendfile
112
112
  # we want to emulate X-Sendfile header internally in mongrel
113
113
  benchmarks[:sendfile_time] = Time.now - start
114
- MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{
114
+ Merb.logger.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{
115
115
  benchmarks[:sendfile_time]} seconds")
116
116
  file_status = File.stat(sendfile)
117
117
  response.status = 200
@@ -152,7 +152,7 @@ class MerbHandler < Mongrel::HttpHandler
152
152
  total_request_time = Time.now - start
153
153
  benchmarks[:total_request_time] = total_request_time
154
154
 
155
- MERB_LOGGER.info("Request Times: #{benchmarks.inspect}\nResponse status: #{response.status}\nComplete Request took: #{total_request_time} seconds, #{1.0/total_request_time} Requests/Second\n\n")
155
+ Merb.logger.info("Request Times: #{benchmarks.inspect}\nResponse status: #{response.status}\nComplete Request took: #{total_request_time} seconds, #{1.0/total_request_time} Requests/Second\n\n")
156
156
  end
157
157
  rescue Object => e
158
158
  # if an exception is raised here then something is
@@ -161,8 +161,8 @@ class MerbHandler < Mongrel::HttpHandler
161
161
  response.send_status(500)
162
162
  response.send_header
163
163
  response.write("500 Internal Server Error")
164
- MERB_LOGGER.error(Merb.exception(e))
164
+ Merb.logger.error(Merb.exception(e))
165
165
  ensure
166
- MERB_LOGGER.flush
166
+ Merb.logger.flush
167
167
  end
168
168
  end
@@ -1,7 +1,7 @@
1
1
  module Merb
2
2
  module Plugins
3
3
  def self.config
4
- @config ||= File.exists?(MERB_ROOT / "config" / "plugins.yml") ? YAML.load(File.read(MERB_ROOT / "config" / "plugins.yml")) || {} : {}
4
+ @config ||= File.exists?(Merb.root / "config" / "plugins.yml") ? YAML.load(File.read(Merb.root / "config" / "plugins.yml")) || {} : {}
5
5
  end
6
6
 
7
7
  @rakefiles = []
@@ -328,6 +328,30 @@ module Merb
328
328
  end
329
329
 
330
330
  class << self
331
+ # Escapes +s+ for use in a URL.
332
+ #
333
+ # ==== Parameter
334
+ #
335
+ # +s+ - String to URL escape.
336
+ #
337
+ def escape(s)
338
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
339
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
340
+ }.tr(' ', '+')
341
+ end
342
+
343
+ # Unescapes a string (i.e., reverse URL escaping).
344
+ #
345
+ # ==== Parameter
346
+ #
347
+ # +s+ - String to unescape.
348
+ #
349
+ def unescape(s)
350
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
351
+ [$1.delete('%')].pack('H*')
352
+ }
353
+ end
354
+
331
355
  # parses a query string or the payload of a POST
332
356
  # request into the params hash. So for example:
333
357
  # /foo?bar=nik&post[title]=heya&post[body]=whatever
@@ -335,7 +359,7 @@ module Merb
335
359
  # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}}
336
360
  def query_parse(qs, d = '&;')
337
361
  (qs||'').split(/[#{d}] */n).inject({}) { |h,p|
338
- normalize_params(h, *Mongrel::HttpRequest.unescape(p).split('=',2))
362
+ normalize_params(h, *unescape(p).split('=',2))
339
363
  }.to_mash
340
364
  end
341
365
 
@@ -1,31 +1,30 @@
1
1
  module Merb
2
-
3
2
  class Router
4
3
  SEGMENT_REGEXP = /(:([a-z_][a-z0-9_]*|:))/
5
4
  SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
6
5
  JUST_BRACKETS = /\[(\d+)\]/
7
6
  PARENTHETICAL_SEGMENT_STRING = "([^\/.,;?]+)".freeze
8
-
7
+
9
8
  @@named_routes = {}
10
9
  @@routes = []
11
10
  cattr_accessor :routes, :named_routes
12
-
11
+
13
12
  class << self
14
13
  def append(&block)
15
14
  prepare(@@routes, [], &block)
16
15
  end
17
-
16
+
18
17
  def prepend(&block)
19
18
  prepare([], @@routes, &block)
20
19
  end
21
-
20
+
22
21
  def prepare(first = [], last = [], &block)
23
22
  @@routes = []
24
- yield Behavior.new({}, {:action => 'index'}) # defaults
23
+ yield Behavior.new({}, { :action => 'index' }) # defaults
25
24
  @@routes = first + @@routes + last
26
25
  compile
27
26
  end
28
-
27
+
29
28
  def compiled_statement
30
29
  @@compiled_statement = "lambda { |request, params|\n"
31
30
  @@compiled_statement << " cached_path = request.path\n cached_method = request.method.to_s\n "
@@ -35,31 +34,37 @@ module Merb
35
34
  @@compiled_statement << " end\n"
36
35
  @@compiled_statement << "}"
37
36
  end
38
-
37
+
39
38
  def compile
40
39
  meta_def(:match, &eval(compiled_statement))
41
40
  end
42
-
41
+
43
42
  def generate(name, params = {}, fallback = {})
44
- raise "Named route not found: #{name}" unless @@named_routes.has_key? name
45
- @@named_routes[name].generate(params, fallback).sub(/\/$/, '').squeeze("/")
43
+ name = name.to_sym
44
+ unless @@named_routes.key? name
45
+ raise "Named route not found: #{name}"
46
+ else
47
+ @@named_routes[name].generate(params, fallback)
48
+ end
46
49
  end
47
50
  end # self
48
-
51
+
49
52
  # Cache procs for future reference in eval statement
50
53
  class CachedProc
51
54
  @@index = 0
52
55
  @@list = []
53
-
56
+
54
57
  attr_accessor :cache, :index
55
-
58
+
56
59
  def initialize(cache)
57
60
  @cache, @index = cache, CachedProc.register(self)
58
61
  end
59
-
62
+
60
63
  # Make each CachedProc object embeddable within a string
61
- def to_s() "CachedProc[#{@index}].cache" end
62
-
64
+ def to_s
65
+ "CachedProc[#{@index}].cache"
66
+ end
67
+
63
68
  class << self
64
69
  def register(cached_code)
65
70
  CachedProc[@@index] = cached_code
@@ -70,11 +75,11 @@ module Merb
70
75
  def [](index) @@list[index] end
71
76
  end
72
77
  end # CachedProc
73
-
78
+
74
79
  class Route
75
80
  attr_reader :conditions, :conditional_block
76
81
  attr_reader :params, :behavior, :segments, :index, :symbol
77
-
82
+
78
83
  def initialize(conditions, params, behavior = nil, &conditional_block)
79
84
  @conditions, @params, @behavior = conditions, params, behavior
80
85
  @conditional_block = conditional_block
@@ -82,31 +87,25 @@ module Merb
82
87
  @segments = segments_from_path(path)
83
88
  end
84
89
  end
85
-
90
+
86
91
  def to_s
87
- r = ""
88
- segments.each {|s|
89
- if s.is_a?(Symbol)
90
- r << ":#{s}"
91
- else
92
- r << s
93
- end
94
- }
95
- r
92
+ segments.inject('') do |str,seg|
93
+ str << (seg.is_a?(Symbol) ? ":#{seg}" : seg)
94
+ end
96
95
  end
97
-
96
+
98
97
  # Registers itself in the Router.routes array
99
98
  def register
100
99
  @index = Router.routes.size
101
100
  Router.routes << self
102
101
  self
103
102
  end
104
-
103
+
105
104
  # Get the symbols out of the segments array
106
105
  def symbol_segments
107
106
  segments.select{ |s| s.is_a?(Symbol) }
108
107
  end
109
-
108
+
110
109
  # Turn a path into string and symbol segments so it can be reconstructed, as in the
111
110
  # case of a named route.
112
111
  def segments_from_path(path)
@@ -121,17 +120,17 @@ module Merb
121
120
  segments << strip[path] unless path.empty?
122
121
  segments
123
122
  end
124
-
123
+
125
124
  # Name this route
126
125
  def name(symbol = nil)
127
- @symbol = symbol
126
+ raise ArgumentError unless (@symbol = symbol).is_a?(Symbol)
128
127
  Router.named_routes[@symbol] = self
129
128
  end
130
-
129
+
131
130
  def regexp?
132
- behavior.regexp? || behavior.send(:ancestors).any?{ |a| a.regexp? }
131
+ behavior.regexp? || behavior.send(:ancestors).any? { |a| a.regexp? }
133
132
  end
134
-
133
+
135
134
  # Given a hash of +params+, returns a string using the stored route segments
136
135
  # for reconstruction of the URL.
137
136
  def generate(params = {}, fallback = {})
@@ -141,7 +140,9 @@ module Merb
141
140
  if params.is_a? Hash
142
141
  params[segment] || fallback[segment]
143
142
  else
144
- if params.respond_to?(segment)
143
+ if segment == :id && params.respond_to?(:to_param)
144
+ params.to_param
145
+ elsif params.respond_to?(segment)
145
146
  params.send(segment)
146
147
  else
147
148
  fallback[segment]
@@ -155,7 +156,7 @@ module Merb
155
156
  (value.respond_to?(:to_param) ? value.to_param : value).to_s
156
157
  end.join
157
158
  end
158
-
159
+
159
160
  def if_conditions(params_as_string)
160
161
  cond = []
161
162
  condition_string = proc do |key, value, regexp_string|
@@ -169,7 +170,7 @@ module Merb
169
170
  " (#{value.inspect} =~ #{regexp_string}" + captures + ")"
170
171
  end
171
172
  @conditions.each_pair do |key, value|
172
-
173
+
173
174
  # Note: =~ is slightly faster than .match
174
175
  cond << case key
175
176
  when :path then condition_string[key, value, "cached_path"]
@@ -184,10 +185,10 @@ module Merb
184
185
  end
185
186
  cond
186
187
  end
187
-
188
+
188
189
  def compile(first = false)
189
190
  code = ""
190
- default_params = {:action => "index"}
191
+ default_params = { :action => "index" }
191
192
  get_value = proc do |key|
192
193
  if default_params.has_key?(key) && params[key][0] != ?"
193
194
  "#{params[key]} || \"#{default_params[key]}\""
@@ -195,7 +196,7 @@ module Merb
195
196
  "#{params[key]}"
196
197
  end
197
198
  end
198
- params_as_string = params.keys.map{|k| "#{k.inspect} => #{get_value[k]}"}.join(", ")
199
+ params_as_string = params.keys.map { |k| "#{k.inspect} => #{get_value[k]}" }.join(', ')
199
200
  code << " els" unless first
200
201
  code << "if # #{@behavior.merged_original_conditions.inspect}\n "
201
202
  code << if_conditions(params_as_string).join(" &&\n ") << "\n"
@@ -206,7 +207,7 @@ module Merb
206
207
  code << " [#{@index.inspect}, {#{params_as_string}}]\n"
207
208
  end
208
209
  end
209
-
210
+
210
211
  def behavior_trace
211
212
  if @behavior
212
213
  puts @behavior.send(:ancestors).reverse.map{|a| a.inspect}.join("\n"); puts @behavior.inspect; puts
@@ -215,30 +216,65 @@ module Merb
215
216
  end
216
217
  end
217
218
  end # Route
218
-
219
- # The Behavior class is an interim route-building class that ties pattern-matching +conditions+ to
220
- # output parameters, +params+.
219
+
220
+ # The Behavior class is an interim route-building class that ties
221
+ # pattern-matching +conditions+ to output parameters, +params+.
221
222
  class Behavior
222
223
  attr_reader :placeholders, :conditions, :params
223
224
  attr_accessor :parent
224
-
225
+
226
+ class << self
227
+ # Count the number of open parentheses in +string+, up to and including +pos+
228
+ def count_parens_up_to(string, pos)
229
+ string[0..pos].gsub(/[^\(]/, '').size
230
+ end
231
+
232
+ # Concatenate strings and remove regexp end caps
233
+ def concat_without_endcaps(string1, string2)
234
+ return nil if !string1 and !string2
235
+ return string1 if string2.nil?
236
+ return string2 if string1.nil?
237
+ s1 = string1[-1] == ?$ ? string1[0..-2] : string1
238
+ s2 = string2[0] == ?^ ? string2[1..-1] : string2
239
+ s1 + s2
240
+ end
241
+
242
+ # Join an array's elements into a string using " + " as a joiner, and
243
+ # surround string elements in quotes.
244
+ def array_to_code(arr)
245
+ code = ''
246
+ arr.each_with_index do |part, i|
247
+ code << ' + ' if i > 0
248
+ case part
249
+ when Symbol
250
+ code << part.to_s
251
+ when String
252
+ code << %{"#{part}"}
253
+ else
254
+ raise "Don't know how to compile array part: #{part.class} [#{i}]"
255
+ end
256
+ end
257
+ code
258
+ end
259
+ end # class << self
260
+
225
261
  def initialize(conditions = {}, params = {}, parent = nil)
262
+ # Must wait until after deducing placeholders to set @params !
226
263
  @conditions, @params, @parent = conditions, {}, parent
227
264
  @placeholders = {}
228
265
  stringify_conditions
229
266
  copy_original_conditions
230
267
  deduce_placeholders
231
- # Must wait until after deducing placeholders to set @params
232
- @params.merge!(params)
268
+ @params.merge! params
233
269
  end
234
-
270
+
235
271
  def add(path, params = {})
236
272
  match(path).to(params)
237
273
  end
238
-
274
+
239
275
  # Matches a +path+ and any number of optional request methods as conditions of a route.
240
276
  # Alternatively, +path+ can be a hash of conditions, in which case +conditions+ is ignored.
241
- # Yields 'self' so that sub-matching may occur.
277
+ # Yields a new instance so that sub-matching may occur.
242
278
  def match(path = '', conditions = {}, &block)
243
279
  if path.is_a? Hash
244
280
  conditions = path
@@ -247,28 +283,32 @@ module Merb
247
283
  end
248
284
  match_without_path(conditions, &block)
249
285
  end
250
-
286
+
251
287
  def match_without_path(conditions = {})
252
288
  new_behavior = self.class.new(conditions, {}, self)
253
289
  conditions.delete :path if ['', '^$'].include?(conditions[:path])
254
290
  yield new_behavior if block_given?
255
291
  new_behavior
256
292
  end
257
-
293
+
258
294
  def to_route(params = {}, &conditional_block)
259
- @params.merge!(params)
260
- Route.new(compiled_conditions, compiled_params, self, &conditional_block)
295
+ @params.merge! params
296
+ Route.new compiled_conditions, compiled_params, self, &conditional_block
261
297
  end
262
-
298
+
263
299
  # Creates a Route from one or more Behavior objects, unless a +block+ is passed in.
264
300
  # If a block is passed in, a Behavior object is yielded and further .to operations
265
301
  # may be called in the block. For example:
302
+ #
266
303
  # r.match('/:controller/:id).to(:action => 'show')
304
+ #
267
305
  # vs.
268
- # r.to(:controller => "simple") do |simple|
269
- # simple.match('/test').to(:action => 'index')
270
- # simple.match('/other').to(:action => 'other')
306
+ #
307
+ # r.to :controller => 'simple' do |s|
308
+ # s.match('/test').to(:action => 'index')
309
+ # s.match('/other').to(:action => 'other')
271
310
  # end
311
+ #
272
312
  def to(params = {}, &block)
273
313
  if block_given?
274
314
  new_behavior = self.class.new({}, params, self)
@@ -278,129 +318,148 @@ module Merb
278
318
  to_route(params).register
279
319
  end
280
320
  end
281
-
282
- # Takes a block and stores it for defered conditional routes. The block takes the
283
- # +request+ object and the +params+ hash as parameters and should return a hash of params.
284
- # For example:
321
+
322
+ # Takes a block and stores it for defered conditional routes.
323
+ # The block takes the +request+ object and the +params+ hash as parameters
324
+ # and should return a hash of params.
325
+ #
285
326
  # r.defer_to do |request, params|
286
- # params.merge(:controller => 'here', :action => 'there') if External.says_so?
327
+ # params.merge :controller => 'here', :action => 'there'
287
328
  # end
329
+ #
288
330
  def defer_to(params = {}, &conditional_block)
289
331
  Router.routes << (route = to_route(params, &conditional_block))
290
332
  route
291
333
  end
292
-
334
+
293
335
  def default_routes(params = {}, &block)
294
- match(%r[/:controller(/:action(/:id)?)?(\.:format)?]).to(params, &block)
336
+ match(%r{/:controller(/:action(/:id)?)?(\.:format)?}).to(params, &block)
295
337
  end
296
-
297
- def to_resources(params = {}, &block)
298
- many_behaviors_to(resources_behaviors, params, &block)
338
+
339
+ def namespace(name_or_path, &block)
340
+ yield self.class.new(:namespace => name_or_path.to_s)
299
341
  end
300
-
342
+
301
343
  def resources(name, options = {})
302
- # singular = name.singularize
303
- next_level = match("/#{name}")
304
- behaviors = []
305
-
306
- singular = name.to_s.singularize
344
+ namespace = options[:namespace] || merged_params[:namespace] || conditions[:namespace]
345
+
346
+ match_path = namespace ? "/#{namespace}/#{name}" : "/#{name}"
347
+
348
+ next_level = match match_path
307
349
 
308
- namespace = options[:namespace] || merged_params[:namespace]
350
+ name_prefix = options.delete :name_prefix
309
351
 
310
- if options[:name_prefix].nil? && namespace != nil
311
- options[:name_prefix] = "#{namespace}_"
352
+ if name_prefix.nil? && !namespace.nil?
353
+ name_prefix = namespace_to_name_prefix namespace
312
354
  end
313
355
 
314
- name_prefix = options.delete(:name_prefix)
315
-
316
-
317
- route_plural_name = "#{name_prefix}#{name}"
318
- route_singular_name = "#{name_prefix}#{singular}"
319
-
320
- member = options.delete(:member)
321
- collection = options.delete(:collection)
322
-
323
- # Add optional member actions
324
-
325
- member.each_pair do |action, methods|
326
- conditions = {:path => %r[^/:id[/;]#{action}$], :method => /^(#{[methods].flatten.join("|")})$/}
327
- behaviors << Behavior.new(conditions, {:action => action.to_s}, next_level)
328
- next_level.match("/:id/#{action}").
329
- to_route.name(:"#{action}_#{route_singular_name}")
330
- end if member
331
-
332
- # Add optional collection actions
333
- collection.each_pair do |action, methods|
334
- conditions = {:path => %r[^[/;]#{action}$], :method => /^(#{[methods].flatten.join("|")})$/}
335
- behaviors << Behavior.new(conditions, {:action => action.to_s}, next_level)
336
- next_level.match("/#{action}").to_route.name(:"#{action}_#{route_plural_name}")
337
- end if collection
338
-
339
356
  options[:controller] ||= merged_params[:controller] || name.to_s
357
+
358
+ singular = name.to_s.singularize
359
+
360
+ route_plural_name = "#{name_prefix}#{name}"
361
+ route_singular_name = "#{name_prefix}#{singular}"
362
+
363
+ behaviors = []
364
+
365
+ if member = options.delete(:member)
366
+ member.each_pair do |action, methods|
367
+ behaviors << Behavior.new(
368
+ { :path => %r{^/:id[/;]#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
369
+ { :action => action.to_s }, next_level
370
+ )
371
+ next_level.match("/:id/#{action}").to_route.name(:"#{action}_#{route_singular_name}")
372
+ end
373
+ end
374
+
375
+ if collection = options.delete(:collection)
376
+ collection.each_pair do |action, methods|
377
+ behaviors << Behavior.new(
378
+ { :path => %r{^[/;]#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
379
+ { :action => action.to_s }, next_level
380
+ )
381
+ next_level.match("/#{action}").to_route.name(:"#{action}_#{route_plural_name}")
382
+ end
383
+ end
384
+
340
385
  routes = many_behaviors_to(behaviors + next_level.send(:resources_behaviors), options)
341
-
386
+
342
387
  # Add names to some routes
343
- next_level.match("").to_route.name(:"#{route_plural_name}")
344
- next_level.match("/:id").to_route.name(:"#{route_singular_name}")
345
- next_level.match("/new").to_route.name(:"new_#{route_singular_name}")
346
- next_level.match("/:id/edit").to_route.name(:"edit_#{route_singular_name}")
347
- next_level.match("/:action/:id").to_route.name(:"custom_#{route_singular_name}")
348
-
388
+ [['', :"#{route_plural_name}"],
389
+ ['/:id', :"#{route_singular_name}"],
390
+ ['/new', :"new_#{route_singular_name}"],
391
+ ['/:id/edit', :"edit_#{route_singular_name}"],
392
+ ['/:action/:id', :"custom_#{route_singular_name}"]
393
+ ].each do |path,name|
394
+ next_level.match(path).to_route.name(name)
395
+ end
396
+
349
397
  yield next_level.match("/:#{singular}_id") if block_given?
398
+
350
399
  routes
351
400
  end
352
-
353
- def to_resource(params = {}, &block)
354
- many_behaviors_to(resource_behaviors, params, &block)
355
- end
356
-
401
+
357
402
  def resource(name, options = {})
358
- next_level = match("/#{name}")
359
-
403
+ namespace = options[:namespace] || merged_params[:namespace] || conditions[:namespace]
404
+ match_path = namespace ? "/#{namespace}/#{name}" : "/#{name}"
405
+ next_level = match match_path
406
+
360
407
  options[:controller] ||= merged_params[:controller] || name.to_s
361
- name_prefix = options.delete(:name_prefix)
362
- routes = next_level.to_resource(options)
363
-
364
- namespace = options[:namespace] || merged_params[:namespace]
365
408
 
366
- if options[:name_prefix].nil? && namespace != nil
367
- options[:name_prefix] = "#{namespace}_"
409
+ # Do not pass :name_prefix option on to to_resource
410
+ name_prefix = options.delete :name_prefix
411
+
412
+ if name_prefix.nil? && !namespace.nil?
413
+ name_prefix = namespace_to_name_prefix namespace
368
414
  end
369
415
 
416
+ routes = next_level.to_resource options
417
+
370
418
  route_name = "#{name_prefix}#{name}"
371
-
372
- next_level.match("").to_route.name(:"#{route_name}")
373
- next_level.match("/new").to_route.name(:"new_#{route_name}")
374
- next_level.match("/edit").to_route.name(:"edit_#{route_name}")
375
-
419
+
420
+ next_level.match('').to_route.name(:"#{route_name}")
421
+ next_level.match('/new').to_route.name(:"new_#{route_name}")
422
+ next_level.match('/edit').to_route.name(:"edit_#{route_name}")
423
+
376
424
  yield next_level if block_given?
425
+
377
426
  routes
378
427
  end
379
-
428
+
429
+ def to_resources(params = {}, &block)
430
+ many_behaviors_to resources_behaviors, params, &block
431
+ end
432
+
433
+ def to_resource(params = {}, &block)
434
+ many_behaviors_to resource_behaviors, params, &block
435
+ end
436
+
380
437
  def merged_original_conditions
381
438
  if parent.nil?
382
439
  @original_conditions
383
440
  else
384
441
  merged_so_far = parent.merged_original_conditions
385
- path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path])
386
- path ?
387
- merged_so_far.merge(@original_conditions).merge(:path => path) :
442
+ if path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path])
443
+ merged_so_far.merge(@original_conditions).merge(:path => path)
444
+ else
388
445
  merged_so_far.merge(@original_conditions)
446
+ end
389
447
  end
390
448
  end
391
-
449
+
392
450
  def merged_conditions
393
451
  if parent.nil?
394
452
  @conditions
395
453
  else
396
454
  merged_so_far = parent.merged_conditions
397
- path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path])
398
- path ?
399
- merged_so_far.merge(@conditions).merge(:path => path) :
455
+ if path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path])
456
+ merged_so_far.merge(@conditions).merge(:path => path)
457
+ else
400
458
  merged_so_far.merge(@conditions)
459
+ end
401
460
  end
402
461
  end
403
-
462
+
404
463
  def merged_params
405
464
  if parent.nil?
406
465
  @params
@@ -408,75 +467,82 @@ module Merb
408
467
  parent.merged_params.merge(@params)
409
468
  end
410
469
  end
411
-
470
+
412
471
  def merged_placeholders
413
472
  placeholders = {}
414
473
  (ancestors.reverse + [self]).each do |a|
415
- a.placeholders.each_pair do |key, pair|
474
+ a.placeholders.each_pair do |k, pair|
416
475
  param, place = pair
417
- placeholders[key] = [param, place + (param == :path ? a.total_previous_captures : 0)]
476
+ placeholders[k] = [param, place + (param == :path ? a.total_previous_captures : 0)]
418
477
  end
419
478
  end
420
479
  placeholders
421
480
  end
422
-
481
+
423
482
  def inspect
424
- "[captures: #{path_captures}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]"
483
+ "[captures: #{path_captures.inspect}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]"
425
484
  end
426
-
485
+
427
486
  def regexp?
428
487
  @conditions_have_regexp
429
488
  end
430
-
489
+
431
490
  protected
491
+ def namespace_to_name_prefix(name_or_path)
492
+ name_or_path.to_s.tr('/', '_') + '_'
493
+ end
494
+
432
495
  def resources_behaviors(parent = self)
433
496
  [
434
- Behavior.new({:path => %r[^/?(\.:format)?$], :method => :get}, {:action => "index"}, parent),
435
- Behavior.new({:path => %r[^/index(\.:format)?$], :method => :get}, {:action => "index"}, parent),
436
- Behavior.new({:path => %r[^/new$], :method => :get}, {:action => "new"}, parent),
437
- Behavior.new({:path => %r[^/?(\.:format)?$], :method => :post}, {:action => "create"}, parent),
438
- Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :get}, {:action => "show"}, parent),
439
- Behavior.new({:path => %r[^/:id[;/]edit$], :method => :get}, {:action => "edit"}, parent),
440
- Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :put}, {:action => "update"}, parent),
441
- Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :delete}, {:action => "destroy"}, parent)
497
+ Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :get }, { :action => "index" }, parent),
498
+ Behavior.new({ :path => %r[^/index(\.:format)?$], :method => :get }, { :action => "index" }, parent),
499
+ Behavior.new({ :path => %r[^/new$], :method => :get }, { :action => "new" }, parent),
500
+ Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :post }, { :action => "create" }, parent),
501
+ Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :get }, { :action => "show" }, parent),
502
+ Behavior.new({ :path => %r[^/:id[;/]edit$], :method => :get }, { :action => "edit" }, parent),
503
+ Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :put }, { :action => "update" }, parent),
504
+ Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :delete }, { :action => "destroy" }, parent)
442
505
  ]
443
506
  end
444
-
507
+
445
508
  def resource_behaviors(parent = self)
446
509
  [
447
- Behavior.new({:path => %r{^[;/]new$}, :method => :get}, {:action => "new"}, parent),
448
- Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :post}, {:action => "create"}, parent),
449
- Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :get}, {:action => "show"}, parent),
450
- Behavior.new({:path => %r{^[;/]edit$}, :method => :get}, {:action => "edit"}, parent),
451
- Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :put}, {:action => "update"}, parent),
452
- Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :delete}, {:action => "destroy"}, parent)
510
+ Behavior.new({ :path => %r{^[;/]new$}, :method => :get }, { :action => "new" }, parent),
511
+ Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :post }, { :action => "create" }, parent),
512
+ Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :get }, { :action => "show" }, parent),
513
+ Behavior.new({ :path => %r{^[;/]edit$}, :method => :get }, { :action => "edit" }, parent),
514
+ Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :put }, { :action => "update" }, parent),
515
+ Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :delete }, { :action => "destroy" }, parent)
453
516
  ]
454
517
  end
455
-
518
+
456
519
  # Creates a series of routes from an array of Behavior objects.
457
520
  # You can pass in optional +params+, and an optional block that will be
458
521
  # passed along to the #to method.
459
522
  def many_behaviors_to(behaviors, params = {}, &conditional_block)
460
- routes = []
461
- behaviors.each { |b| routes << b.to(params, &conditional_block) }
462
- routes
523
+ behaviors.map { |b| b.to params, &conditional_block }
463
524
  end
464
-
525
+
465
526
  # Convert conditions to regular expression string sources for consistency
466
527
  def stringify_conditions
467
528
  @conditions_have_regexp = false
468
- @conditions.each_pair do |key, value|
529
+ @conditions.each_pair do |k,v|
469
530
  # TODO: Other Regexp special chars
470
- @conditions[key] = case value
471
- when String then "^" + value.escape_regexp + "$"
472
- when Symbol then "^" + value.to_s + "$"
473
- when Regexp:
474
- @conditions_have_regexp = true
475
- value.source
531
+
532
+ @conditions[k] = case v
533
+ when String,Symbol
534
+ if k == :namespace
535
+ v.to_s
536
+ else
537
+ "^#{v.to_s.escape_regexp}$"
476
538
  end
539
+ when Regexp
540
+ @conditions_have_regexp = true
541
+ v.source
542
+ end
477
543
  end
478
544
  end
479
-
545
+
480
546
  def copy_original_conditions
481
547
  @original_conditions = {}
482
548
  @conditions.each_pair do |key, value|
@@ -484,60 +550,66 @@ module Merb
484
550
  end
485
551
  @original_conditions
486
552
  end
487
-
553
+
488
554
  def deduce_placeholders
489
555
  @conditions.each_pair do |match_key, source|
490
556
  while match = SEGMENT_REGEXP.match(source)
491
- source.sub!(SEGMENT_REGEXP, PARENTHETICAL_SEGMENT_STRING)
492
- unless match[2] == ":" # No need to store anonymous place holders
557
+ source.sub! SEGMENT_REGEXP, PARENTHETICAL_SEGMENT_STRING
558
+ unless match[2] == ':' # No need to store anonymous place holders
493
559
  placeholder_key = match[2].intern
494
560
  @params[placeholder_key] = "#{match[1]}"
495
- @placeholders[placeholder_key] = [match_key, Behavior.count_parens_up_to(source, match.offset(1)[0])]
561
+ @placeholders[placeholder_key] = [
562
+ match_key, Behavior.count_parens_up_to(source, match.offset(1)[0])
563
+ ]
496
564
  end
497
565
  end
498
566
  end
499
567
  end
500
-
568
+
501
569
  def ancestors(list = [])
502
570
  if parent.nil?
503
571
  list
504
572
  else
505
573
  list.push parent
506
- parent.ancestors(list)
574
+ parent.ancestors list
507
575
  list
508
576
  end
509
577
  end
510
-
578
+
511
579
  # Count the number of regexp captures in the :path condition
512
580
  def path_captures
513
581
  return 0 unless conditions[:path]
514
582
  Behavior.count_parens_up_to(conditions[:path], conditions[:path].size)
515
583
  end
516
-
584
+
517
585
  def total_previous_captures
518
586
  ancestors.map{|a| a.path_captures}.inject(0){|sum, n| sum + n}
519
587
  end
520
-
588
+
521
589
  # def merge_with_ancestors
522
590
  # self.class.new(merged_conditions, merged_params)
523
591
  # end
524
-
592
+
525
593
  def compiled_conditions(conditions = merged_conditions)
526
- compiled = {}
527
- conditions.each { |key, value| compiled[key] = Regexp.new(value) }
528
- compiled
594
+ conditions.inject({}) do |compiled,(k,v)|
595
+ compiled.merge k => Regexp.new(v)
596
+ end
529
597
  end
530
-
598
+
531
599
  # Compiles the params hash into 'eval'-able form.
532
- # For example:
600
+ #
533
601
  # @params = {:controller => "admin/:controller"}
602
+ #
534
603
  # Could become:
535
- # {:controller => "'admin/' + matches[:path][1]"}
604
+ #
605
+ # { :controller => "'admin/' + matches[:path][1]" }
536
606
  #
537
607
  def compiled_params(params = merged_params, placeholders = merged_placeholders)
538
608
  compiled = {}
539
609
  params.each_pair do |key, value|
540
- raise ArgumentError, "param value must be string (#{value.inspect})" unless value.is_a? String
610
+ unless value.is_a? String
611
+ raise ArgumentError, "param value must be string (#{value.inspect})"
612
+ end
541
613
  result = []
542
614
  value = value.dup
543
615
  match = true
@@ -568,41 +640,7 @@ module Merb
568
640
  end
569
641
  compiled
570
642
  end
571
-
572
- public
573
- # Count the number of open parentheses in +string+, up to and including +pos+
574
- def self.count_parens_up_to(string, pos)
575
- string[0..pos].gsub(/[^\(]/, "").size
576
- end
577
-
578
- # Concatenate strings and remove regexp end caps
579
- def self.concat_without_endcaps(string1, string2)
580
- return nil if !string1 and !string2
581
- return string1 if string2.nil?
582
- return string2 if string1.nil?
583
- s1 = string1[-1] == ?$ ? string1[0..-2] : string1
584
- s2 = string2[0] == ?^ ? string2[1..-1] : string2
585
- s1 + s2
586
- end
587
-
588
- # Join an array's elements into a string using " + " as a joiner, and
589
- # surround string elements in quotes.
590
- def self.array_to_code(arr)
591
- code = ""
592
- arr.each_with_index do |part, i|
593
- code << " + " if i > 0
594
- case part
595
- when Symbol
596
- code << "#{part}"
597
- when String
598
- code << "\"" + part + "\""
599
- else
600
- raise "Don't know how to compile array part: #{part.class} [#{i}]"
601
- end
602
- end
603
- code
604
- end
605
643
  end # Behavior
606
-
644
+
607
645
  end
608
646
  end