merb 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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