merb-core 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (274) hide show
  1. data/CHANGELOG +136 -2
  2. data/CONTRIBUTORS +6 -0
  3. data/PUBLIC_CHANGELOG +38 -0
  4. data/Rakefile +38 -30
  5. data/lib/merb-core.rb +88 -23
  6. data/lib/merb-core/bootloader.rb +235 -51
  7. data/lib/merb-core/config.rb +121 -36
  8. data/lib/merb-core/controller/abstract_controller.rb +59 -36
  9. data/lib/merb-core/controller/exceptions.rb +2 -15
  10. data/lib/merb-core/controller/merb_controller.rb +44 -1
  11. data/lib/merb-core/controller/mime.rb +4 -0
  12. data/lib/merb-core/controller/mixins/controller.rb +38 -21
  13. data/lib/merb-core/controller/mixins/render.rb +44 -29
  14. data/lib/merb-core/controller/mixins/responder.rb +3 -31
  15. data/lib/merb-core/controller/template.rb +45 -21
  16. data/lib/merb-core/core_ext/kernel.rb +60 -32
  17. data/lib/merb-core/dispatch/default_exception/default_exception.rb +2 -2
  18. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +3 -1
  19. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +71 -67
  20. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +7 -3
  21. data/lib/merb-core/dispatch/dispatcher.rb +3 -3
  22. data/lib/merb-core/dispatch/request.rb +56 -9
  23. data/lib/merb-core/dispatch/router.rb +159 -133
  24. data/lib/merb-core/dispatch/router/behavior.rb +462 -703
  25. data/lib/merb-core/dispatch/router/cached_proc.rb +3 -3
  26. data/lib/merb-core/dispatch/router/resources.rb +289 -0
  27. data/lib/merb-core/dispatch/router/route.rb +514 -294
  28. data/lib/merb-core/dispatch/session.rb +4 -2
  29. data/lib/merb-core/logger.rb +213 -202
  30. data/lib/merb-core/plugins.rb +9 -1
  31. data/lib/merb-core/rack.rb +3 -1
  32. data/lib/merb-core/rack/adapter.rb +7 -4
  33. data/lib/merb-core/rack/adapter/abstract.rb +188 -0
  34. data/lib/merb-core/rack/adapter/ebb.rb +12 -13
  35. data/lib/merb-core/rack/adapter/evented_mongrel.rb +2 -15
  36. data/lib/merb-core/rack/adapter/irb.rb +3 -2
  37. data/lib/merb-core/rack/adapter/mongrel.rb +22 -15
  38. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +4 -16
  39. data/lib/merb-core/rack/adapter/thin.rb +21 -22
  40. data/lib/merb-core/rack/adapter/thin_turbo.rb +4 -11
  41. data/lib/merb-core/rack/adapter/webrick.rb +54 -18
  42. data/lib/merb-core/rack/application.rb +4 -4
  43. data/lib/merb-core/rack/handler/mongrel.rb +12 -13
  44. data/lib/merb-core/rack/middleware/csrf.rb +1 -1
  45. data/lib/merb-core/rack/stream_wrapper.rb +41 -0
  46. data/lib/merb-core/server.rb +157 -90
  47. data/lib/merb-core/tasks/gem_management.rb +267 -0
  48. data/lib/merb-core/tasks/merb.rb +1 -0
  49. data/lib/merb-core/tasks/merb_rake_helper.rb +48 -34
  50. data/lib/merb-core/tasks/stats.rake +1 -1
  51. data/lib/merb-core/test.rb +9 -3
  52. data/lib/merb-core/test/helpers.rb +1 -0
  53. data/lib/merb-core/test/helpers/mock_request_helper.rb +393 -0
  54. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -2
  55. data/lib/merb-core/test/helpers/request_helper.rb +40 -372
  56. data/lib/merb-core/test/helpers/route_helper.rb +16 -2
  57. data/lib/merb-core/test/matchers.rb +1 -0
  58. data/lib/merb-core/test/matchers/controller_matchers.rb +4 -247
  59. data/lib/merb-core/test/matchers/request_matchers.rb +140 -0
  60. data/lib/merb-core/test/matchers/view_matchers.rb +22 -4
  61. data/lib/merb-core/test/run_specs.rb +118 -26
  62. data/lib/merb-core/test/test_ext/rspec.rb +181 -0
  63. data/lib/merb-core/version.rb +1 -9
  64. metadata +10 -342
  65. data/docs/bootloading.dox +0 -58
  66. data/docs/documentation_standards +0 -40
  67. data/docs/merb-core-call-stack-diagram.mmap +0 -0
  68. data/docs/merb-core-call-stack-diagram.pdf +0 -0
  69. data/docs/merb-core-call-stack-diagram.png +0 -0
  70. data/docs/new_render_api +0 -51
  71. data/lib/merb-core/vendor/facets.rb +0 -2
  72. data/lib/merb-core/vendor/facets/dictionary.rb +0 -433
  73. data/lib/merb-core/vendor/facets/inflect.rb +0 -342
  74. data/spec/private/config/adapter_spec.rb +0 -32
  75. data/spec/private/config/config_spec.rb +0 -202
  76. data/spec/private/config/environment_spec.rb +0 -13
  77. data/spec/private/config/merb_spec.rb +0 -34
  78. data/spec/private/config/spec_helper.rb +0 -1
  79. data/spec/private/core_ext/kernel_spec.rb +0 -159
  80. data/spec/private/dispatch/bootloader_spec.rb +0 -24
  81. data/spec/private/dispatch/fixture/app/controllers/application.rb +0 -4
  82. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +0 -25
  83. data/spec/private/dispatch/fixture/app/controllers/foo.rb +0 -19
  84. data/spec/private/dispatch/fixture/app/helpers/global_helpers.rb +0 -8
  85. data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +0 -37
  86. data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +0 -216
  87. data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +0 -38
  88. data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +0 -40
  89. data/spec/private/dispatch/fixture/app/views/foo/bar.html.erb +0 -0
  90. data/spec/private/dispatch/fixture/app/views/layout/application.html.erb +0 -11
  91. data/spec/private/dispatch/fixture/config/black_hole.rb +0 -12
  92. data/spec/private/dispatch/fixture/config/environments/development.rb +0 -6
  93. data/spec/private/dispatch/fixture/config/environments/production.rb +0 -5
  94. data/spec/private/dispatch/fixture/config/environments/test.rb +0 -6
  95. data/spec/private/dispatch/fixture/config/init.rb +0 -45
  96. data/spec/private/dispatch/fixture/config/rack.rb +0 -11
  97. data/spec/private/dispatch/fixture/config/router.rb +0 -35
  98. data/spec/private/dispatch/fixture/log/merb_test.log +0 -8839
  99. data/spec/private/dispatch/fixture/public/images/merb.jpg +0 -0
  100. data/spec/private/dispatch/fixture/public/merb.fcgi +0 -4
  101. data/spec/private/dispatch/fixture/public/stylesheets/master.css +0 -119
  102. data/spec/private/dispatch/route_params_spec.rb +0 -23
  103. data/spec/private/dispatch/spec_helper.rb +0 -1
  104. data/spec/private/router/behavior_spec.rb +0 -60
  105. data/spec/private/router/fixture/log/merb_test.log +0 -35
  106. data/spec/private/router/route_spec.rb +0 -418
  107. data/spec/private/router/router_spec.rb +0 -183
  108. data/spec/private/vendor/facets/plural_spec.rb +0 -564
  109. data/spec/private/vendor/facets/singular_spec.rb +0 -489
  110. data/spec/public/DEFINITIONS +0 -11
  111. data/spec/public/abstract_controller/controllers/alt_views/layout/application.erb +0 -1
  112. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_string_controller_layout.erb +0 -1
  113. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_template_controller_layout.erb +0 -1
  114. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/index.erb +0 -1
  115. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/show.erb +0 -1
  116. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +0 -1
  117. data/spec/public/abstract_controller/controllers/alt_views/partial/basic_partial_with_multiple_roots/_partial.erb +0 -1
  118. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_and_custom_location/index.erb +0 -1
  119. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_inherited/index.erb +0 -1
  120. data/spec/public/abstract_controller/controllers/cousins.rb +0 -41
  121. data/spec/public/abstract_controller/controllers/display.rb +0 -60
  122. data/spec/public/abstract_controller/controllers/filters.rb +0 -260
  123. data/spec/public/abstract_controller/controllers/helpers.rb +0 -41
  124. data/spec/public/abstract_controller/controllers/partial.rb +0 -121
  125. data/spec/public/abstract_controller/controllers/render.rb +0 -113
  126. data/spec/public/abstract_controller/controllers/views/helpers/capture/index.erb +0 -1
  127. data/spec/public/abstract_controller/controllers/views/helpers/capture_eq/index.erb +0 -1
  128. data/spec/public/abstract_controller/controllers/views/helpers/capture_with_args/index.erb +0 -1
  129. data/spec/public/abstract_controller/controllers/views/helpers/concat/index.erb +0 -1
  130. data/spec/public/abstract_controller/controllers/views/layout/alt.erb +0 -1
  131. data/spec/public/abstract_controller/controllers/views/layout/custom.erb +0 -1
  132. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object/index.erb +0 -1
  133. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object_with_action/new.erb +0 -1
  134. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template/index.erb +0 -1
  135. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_app_layout/index.erb +0 -0
  136. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_custom_layout/index.erb +0 -1
  137. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +0 -1
  138. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/show.erb +0 -1
  139. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_two_throw_contents/index.erb +0 -1
  140. data/spec/public/abstract_controller/controllers/views/partial/another_directory/_partial.erb +0 -1
  141. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/_partial.erb +0 -1
  142. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/index.erb +0 -1
  143. data/spec/public/abstract_controller/controllers/views/partial/basic_partial_with_multiple_roots/index.erb +0 -1
  144. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_first.erb +0 -1
  145. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_second.erb +0 -1
  146. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/index.erb +0 -1
  147. data/spec/public/abstract_controller/controllers/views/partial/partial_in_another_directory/index.erb +0 -1
  148. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/_collection.erb +0 -1
  149. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/index.erb +0 -1
  150. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/_collection.erb +0 -1
  151. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/index.erb +0 -1
  152. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/_collection.erb +0 -1
  153. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/index.erb +0 -1
  154. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/_collection.erb +0 -1
  155. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/index.erb +0 -1
  156. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/_variables.erb +0 -1
  157. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/index.erb +0 -1
  158. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/_both.erb +0 -1
  159. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/index.erb +0 -1
  160. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/_partial.erb +0 -1
  161. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/index.erb +0 -1
  162. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/_with_partial.erb +0 -1
  163. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/index.erb +0 -1
  164. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/_with_partial.erb +0 -1
  165. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/index.erb +0 -1
  166. data/spec/public/abstract_controller/controllers/views/partial/with_partial/_with_partial.erb +0 -1
  167. data/spec/public/abstract_controller/controllers/views/partial/with_partial/index.erb +0 -1
  168. data/spec/public/abstract_controller/controllers/views/test_display/foo.html.erb +0 -1
  169. data/spec/public/abstract_controller/controllers/views/test_render/foo.html.erb +0 -0
  170. data/spec/public/abstract_controller/controllers/views/wonderful/index.erb +0 -1
  171. data/spec/public/abstract_controller/display_spec.rb +0 -37
  172. data/spec/public/abstract_controller/filter_spec.rb +0 -136
  173. data/spec/public/abstract_controller/helper_spec.rb +0 -21
  174. data/spec/public/abstract_controller/partial_spec.rb +0 -61
  175. data/spec/public/abstract_controller/render_spec.rb +0 -90
  176. data/spec/public/abstract_controller/spec_helper.rb +0 -31
  177. data/spec/public/boot_loader/boot_loader_spec.rb +0 -33
  178. data/spec/public/boot_loader/spec_helper.rb +0 -1
  179. data/spec/public/controller/authentication_spec.rb +0 -174
  180. data/spec/public/controller/base_spec.rb +0 -88
  181. data/spec/public/controller/conditional_get_spec.rb +0 -100
  182. data/spec/public/controller/config/init.rb +0 -6
  183. data/spec/public/controller/controllers/authentication.rb +0 -74
  184. data/spec/public/controller/controllers/base.rb +0 -65
  185. data/spec/public/controller/controllers/conditional_get.rb +0 -35
  186. data/spec/public/controller/controllers/cookies.rb +0 -36
  187. data/spec/public/controller/controllers/dispatcher.rb +0 -35
  188. data/spec/public/controller/controllers/display.rb +0 -118
  189. data/spec/public/controller/controllers/redirect.rb +0 -36
  190. data/spec/public/controller/controllers/responder.rb +0 -93
  191. data/spec/public/controller/controllers/url.rb +0 -7
  192. data/spec/public/controller/controllers/views/layout/custom.html.erb +0 -1
  193. data/spec/public/controller/controllers/views/layout/custom_arg.html.erb +0 -1
  194. data/spec/public/controller/controllers/views/layout/custom_arg.json.erb +0 -1
  195. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.html.erb +0 -1
  196. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.xml.erb +0 -1
  197. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.html.erb +0 -1
  198. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.xml.erb +0 -1
  199. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/index.html.erb +0 -1
  200. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/no_layout.html.erb +0 -1
  201. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template_argument/index.html.erb +0 -1
  202. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/html_default/index.html.erb +0 -1
  203. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/layout/custom.html.erb +0 -1
  204. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.html.erb +0 -1
  205. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.xml.erb +0 -1
  206. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.html.erb +0 -1
  207. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.js.erb +0 -1
  208. data/spec/public/controller/cookies_spec.rb +0 -95
  209. data/spec/public/controller/dispatcher_spec.rb +0 -410
  210. data/spec/public/controller/display_spec.rb +0 -84
  211. data/spec/public/controller/redirect_spec.rb +0 -33
  212. data/spec/public/controller/responder_spec.rb +0 -169
  213. data/spec/public/controller/spec_helper.rb +0 -13
  214. data/spec/public/controller/url_spec.rb +0 -255
  215. data/spec/public/core/merb_core_spec.rb +0 -45
  216. data/spec/public/core_ext/fixtures/core_ext_dependency.rb +0 -2
  217. data/spec/public/core_ext/kernel_spec.rb +0 -88
  218. data/spec/public/core_ext/spec_helper.rb +0 -1
  219. data/spec/public/directory_structure/directory/app/controllers/application.rb +0 -3
  220. data/spec/public/directory_structure/directory/app/controllers/base.rb +0 -13
  221. data/spec/public/directory_structure/directory/app/controllers/custom.rb +0 -19
  222. data/spec/public/directory_structure/directory/app/views/base/template.html.erb +0 -1
  223. data/spec/public/directory_structure/directory/app/views/wonderful/template.erb +0 -1
  224. data/spec/public/directory_structure/directory/config/router.rb +0 -3
  225. data/spec/public/directory_structure/directory/log/merb_test.log +0 -4588
  226. data/spec/public/directory_structure/directory_spec.rb +0 -43
  227. data/spec/public/logger/logger_spec.rb +0 -181
  228. data/spec/public/logger/spec_helper.rb +0 -1
  229. data/spec/public/rack/conditinal_get_middleware_spec.rb +0 -127
  230. data/spec/public/rack/csrf_middleware_spec.rb +0 -70
  231. data/spec/public/rack/rack_middleware_spec.rb +0 -99
  232. data/spec/public/rack/shared_example_groups.rb +0 -35
  233. data/spec/public/reloading/directory/app/controllers/application.rb +0 -3
  234. data/spec/public/reloading/directory/app/controllers/reload.rb +0 -6
  235. data/spec/public/reloading/directory/config/init.rb +0 -2
  236. data/spec/public/reloading/directory/log/merb_test.log +0 -288231
  237. data/spec/public/reloading/reload_spec.rb +0 -103
  238. data/spec/public/request/multipart_spec.rb +0 -41
  239. data/spec/public/request/request_spec.rb +0 -250
  240. data/spec/public/router/default_spec.rb +0 -21
  241. data/spec/public/router/deferred_spec.rb +0 -22
  242. data/spec/public/router/fixation_spec.rb +0 -49
  243. data/spec/public/router/fixture/log/merb_test.log +0 -524
  244. data/spec/public/router/namespace_spec.rb +0 -113
  245. data/spec/public/router/nested_matches_spec.rb +0 -97
  246. data/spec/public/router/nested_resources_spec.rb +0 -41
  247. data/spec/public/router/resource_spec.rb +0 -37
  248. data/spec/public/router/resources_spec.rb +0 -82
  249. data/spec/public/router/spec_helper.rb +0 -90
  250. data/spec/public/router/special_spec.rb +0 -61
  251. data/spec/public/router/string_spec.rb +0 -61
  252. data/spec/public/session/controllers/sessions.rb +0 -56
  253. data/spec/public/session/cookie_session_spec.rb +0 -73
  254. data/spec/public/session/memcached_session_spec.rb +0 -31
  255. data/spec/public/session/memory_session_spec.rb +0 -28
  256. data/spec/public/session/multiple_sessions_spec.rb +0 -74
  257. data/spec/public/session/no_session_spec.rb +0 -12
  258. data/spec/public/session/session_spec.rb +0 -106
  259. data/spec/public/template/template_spec.rb +0 -104
  260. data/spec/public/template/templates/error.html.erb +0 -2
  261. data/spec/public/template/templates/template.html.erb +0 -1
  262. data/spec/public/template/templates/template.html.myt +0 -1
  263. data/spec/public/test/controller_matchers_spec.rb +0 -412
  264. data/spec/public/test/controllers/controller_assertion_mock.rb +0 -7
  265. data/spec/public/test/controllers/dispatch_controller.rb +0 -11
  266. data/spec/public/test/controllers/spec_helper_controller.rb +0 -39
  267. data/spec/public/test/multipart_request_helper_spec.rb +0 -159
  268. data/spec/public/test/multipart_upload_text_file.txt +0 -1
  269. data/spec/public/test/request_helper_spec.rb +0 -269
  270. data/spec/public/test/route_helper_spec.rb +0 -78
  271. data/spec/public/test/route_matchers_spec.rb +0 -166
  272. data/spec/public/test/view_helper_spec.rb +0 -96
  273. data/spec/public/test/view_matchers_spec.rb +0 -183
  274. data/spec/spec_helper.rb +0 -121
@@ -5,7 +5,7 @@ module Merb
5
5
  class CachedProc
6
6
  @@index = 0
7
7
  @@list = []
8
-
8
+
9
9
  attr_accessor :cache, :index
10
10
 
11
11
  # ==== Parameters
@@ -13,13 +13,13 @@ module Merb
13
13
  def initialize(cache)
14
14
  @cache, @index = cache, CachedProc.register(self)
15
15
  end
16
-
16
+
17
17
  # ==== Returns
18
18
  # String:: The CachedProc object in a format embeddable within a string.
19
19
  def to_s
20
20
  "CachedProc[#{@index}].cache"
21
21
  end
22
-
22
+
23
23
  class << self
24
24
 
25
25
  # ==== Parameters
@@ -0,0 +1,289 @@
1
+ module Merb
2
+ class Router
3
+
4
+ module Resources
5
+ # Behavior#+resources+ is a route helper for defining a collection of
6
+ # RESTful resources. It yields to a block for child routes.
7
+ #
8
+ # ==== Parameters
9
+ # name<String, Symbol>:: The name of the resources
10
+ # options<Hash>::
11
+ # Ovverides and parameters to be associated with the route
12
+ #
13
+ # ==== Options (options)
14
+ # :namespace<~to_s>: The namespace for this route.
15
+ # :name_prefix<~to_s>:
16
+ # A prefix for the named routes. If a namespace is passed and there
17
+ # isn't a name prefix, the namespace will become the prefix.
18
+ # :controller<~to_s>: The controller for this route
19
+ # :collection<~to_s>: Special settings for the collections routes
20
+ # :member<Hash>:
21
+ # Special settings and resources related to a specific member of this
22
+ # resource.
23
+ # :keys<Array>:
24
+ # A list of the keys to be used instead of :id with the resource in the order of the url.
25
+ # :singular<Symbol>
26
+ #
27
+ # ==== Block parameters
28
+ # next_level<Behavior>:: The child behavior.
29
+ #
30
+ # ==== Returns
31
+ # Array::
32
+ # Routes which will define the specified RESTful collection of resources
33
+ #
34
+ # ==== Examples
35
+ #
36
+ # r.resources :posts # will result in the typical RESTful CRUD
37
+ # # lists resources
38
+ # # GET /posts/?(\.:format)? :action => "index"
39
+ # # GET /posts/index(\.:format)? :action => "index"
40
+ #
41
+ # # shows new resource form
42
+ # # GET /posts/new :action => "new"
43
+ #
44
+ # # creates resource
45
+ # # POST /posts/?(\.:format)?, :action => "create"
46
+ #
47
+ # # shows resource
48
+ # # GET /posts/:id(\.:format)? :action => "show"
49
+ #
50
+ # # shows edit form
51
+ # # GET /posts/:id/edit :action => "edit"
52
+ #
53
+ # # updates resource
54
+ # # PUT /posts/:id(\.:format)? :action => "update"
55
+ #
56
+ # # shows deletion confirmation page
57
+ # # GET /posts/:id/delete :action => "delete"
58
+ #
59
+ # # destroys resources
60
+ # # DELETE /posts/:id(\.:format)? :action => "destroy"
61
+ #
62
+ # # Nesting resources
63
+ # r.resources :posts do |posts|
64
+ # posts.resources :comments
65
+ # end
66
+ #---
67
+ # @public
68
+ def resources(name, *args, &block)
69
+ name = name.to_s
70
+ options = extract_options_from_args!(args) || {}
71
+ singular = options[:singular] ? options[:singular].to_s : Extlib::Inflection.singularize(name)
72
+ klass = args.first ? args.first.to_s : Extlib::Inflection.classify(singular)
73
+ keys = [ options.delete(:keys) || options.delete(:key) || :id ].flatten
74
+ params = { :controller => options.delete(:controller) || name }
75
+ collection = options.delete(:collection) || {}
76
+ member = { :edit => :get, :delete => :get }.merge(options.delete(:member) || {})
77
+
78
+ # Try pulling :namespace out of options for backwards compatibility
79
+ options[:name_prefix] ||= nil # Don't use a name_prefix if not needed
80
+ options[:resource_prefix] ||= nil # Don't use a resource_prefix if not needed
81
+ options[:controller_prefix] ||= options.delete(:namespace)
82
+
83
+ self.namespace(name, options).to(params) do |resource|
84
+ root_keys = keys.map { |k| ":#{k}" }.join("/")
85
+
86
+ # => index
87
+ resource.match("(/index)(.:format)", :method => :get).to(:action => "index").
88
+ name(name).register_resource(name)
89
+
90
+ # => create
91
+ resource.match("(.:format)", :method => :post).to(:action => "create")
92
+
93
+ # => new
94
+ resource.match("/new(.:format)", :method => :get).to(:action => "new").
95
+ name("new", singular).register_resource(name, "new")
96
+
97
+ # => user defined collection routes
98
+ collection.each_pair do |action, method|
99
+ action = action.to_s
100
+ resource.match("/#{action}(.:format)", :method => method).to(:action => "#{action}").
101
+ name(action, name).register_resource(name, action)
102
+ end
103
+
104
+ # => show
105
+ resource.match("/#{root_keys}(.:format)", :method => :get).to(:action => "show").
106
+ name(singular).register_resource(klass)
107
+
108
+ # => user defined member routes
109
+ member.each_pair do |action, method|
110
+ action = action.to_s
111
+ resource.match("/#{root_keys}/#{action}(.:format)", :method => method).
112
+ to(:action => "#{action}").name(action, singular).register_resource(klass, action)
113
+ end
114
+
115
+ # => update
116
+ resource.match("/#{root_keys}(.:format)", :method => :put).
117
+ to(:action => "update")
118
+
119
+ # => destroy
120
+ resource.match("/#{root_keys}(.:format)", :method => :delete).
121
+ to(:action => "destroy")
122
+
123
+ if block_given?
124
+ nested_keys = keys.map do |k|
125
+ k.to_s == "id" ? ":#{singular}_id" : ":#{k}"
126
+ end.join("/")
127
+
128
+ # Procs for building the extra collection/member resource routes
129
+ placeholder = Router.resource_routes[ [@options[:resource_prefix], klass].flatten.compact ]
130
+ builders = {}
131
+
132
+ builders[:collection] = lambda do |action, to, method|
133
+ resource.before(placeholder).match("/#{action}(.:format)", :method => method).
134
+ to(:action => to).name(action, name).register_resource(name, action)
135
+ end
136
+
137
+ builders[:member] = lambda do |action, to, method|
138
+ resource.match("/#{root_keys}/#{action}(.:format)", :method => method).
139
+ to(:action => to).name(action, singular).register_resource(klass, action)
140
+ end
141
+
142
+ resource.options(:name_prefix => singular, :resource_prefix => klass).
143
+ match("/#{nested_keys}").resource_block(builders, &block)
144
+ end
145
+ end # namespace
146
+ end # resources
147
+
148
+ # Behavior#+resource+ is a route helper for defining a singular RESTful
149
+ # resource. It yields to a block for child routes.
150
+ #
151
+ # ==== Parameters
152
+ # name<String, Symbol>:: The name of the resource.
153
+ # options<Hash>::
154
+ # Overides and parameters to be associated with the route.
155
+ #
156
+ # ==== Options (options)
157
+ # :namespace<~to_s>: The namespace for this route.
158
+ # :name_prefix<~to_s>:
159
+ # A prefix for the named routes. If a namespace is passed and there
160
+ # isn't a name prefix, the namespace will become the prefix.
161
+ # :controller<~to_s>: The controller for this route
162
+ #
163
+ # ==== Block parameters
164
+ # next_level<Behavior>:: The child behavior.
165
+ #
166
+ # ==== Returns
167
+ # Array:: Routes which define a RESTful single resource.
168
+ #
169
+ # ==== Examples
170
+ #
171
+ # r.resource :account # will result in the typical RESTful CRUD
172
+ # # shows new resource form
173
+ # # GET /account/new :action => "new"
174
+ #
175
+ # # creates resource
176
+ # # POST /account/?(\.:format)?, :action => "create"
177
+ #
178
+ # # shows resource
179
+ # # GET /account/(\.:format)? :action => "show"
180
+ #
181
+ # # shows edit form
182
+ # # GET /account//edit :action => "edit"
183
+ #
184
+ # # updates resource
185
+ # # PUT /account/(\.:format)? :action => "update"
186
+ #
187
+ # # shows deletion confirmation page
188
+ # # GET /account//delete :action => "delete"
189
+ #
190
+ # # destroys resources
191
+ # # DELETE /account/(\.:format)? :action => "destroy"
192
+ #
193
+ # You can optionally pass :namespace and :controller to refine the routing
194
+ # or pass a block to nest resources.
195
+ #
196
+ # r.resource :account, :namespace => "admin" do |account|
197
+ # account.resources :preferences, :controller => "settings"
198
+ # end
199
+ # ---
200
+ # @public
201
+ def resource(name, *args, &block)
202
+ name = name.to_s
203
+ options = extract_options_from_args!(args) || {}
204
+ params = { :controller => options.delete(:controller) || name.pluralize }
205
+ member = { :new => :get, :edit => :get, :delete => :get }.merge(options.delete(:member) || {})
206
+
207
+ options[:name_prefix] ||= nil # Don't use a name_prefix if not needed
208
+ options[:resource_prefix] ||= nil # Don't use a resource_prefix if not needed
209
+ options[:controller_prefix] ||= options.delete(:namespace)
210
+
211
+ self.namespace(name, options).to(params) do |resource|
212
+ # => show
213
+ resource.match("(.:format)", :method => :get).to(:action => "show").
214
+ name(name).register_resource(name)
215
+
216
+ # => create
217
+ resource.match("(.:format)", :method => :post).to(:action => "create")
218
+
219
+ # => update
220
+ resource.match("(.:format)", :method => :put).to(:action => "update")
221
+
222
+ # => destroy
223
+ resource.match("(.:format)", :method => :delete).to(:action => "destroy")
224
+
225
+ member.each_pair do |action, method|
226
+ action = action.to_s
227
+ resource.match("/#{action}(.:format)", :method => method).to(:action => action).
228
+ name(action, name).register_resource(name, action)
229
+ end
230
+
231
+ if block_given?
232
+ builders = {}
233
+
234
+ builders[:member] = lambda do |action, to, method|
235
+ resource.match("/#{action}(.:format)", :method => method).to(:action => to).
236
+ name(action, name).register_resource(name, action)
237
+ end
238
+
239
+ resource.options(:name_prefix => name, :resource_prefix => name).
240
+ resource_block(builders, &block)
241
+ end
242
+ end
243
+ end
244
+
245
+ protected
246
+
247
+ def register_resource(*key)
248
+ key = [@options[:resource_prefix], key].flatten.compact
249
+ @route.resource = key
250
+ self
251
+ end
252
+
253
+ def resource_block(builders, &block)
254
+ behavior = ResourceBehavior.new(builders, @proxy, @conditions, @params, @defaults, @identifiers, @options, @blocks)
255
+ with_behavior_context(behavior, &block)
256
+ end
257
+
258
+ end # Resources
259
+
260
+ class Behavior
261
+ include Resources
262
+ end
263
+
264
+ # Adding the collection and member methods to behavior
265
+ class ResourceBehavior < Behavior #:nodoc:
266
+
267
+ def initialize(builders, *args)
268
+ super(*args)
269
+ @collection = builders[:collection]
270
+ @member = builders[:member]
271
+ end
272
+
273
+ def collection(action, options = {})
274
+ action = action.to_s
275
+ method = options[:method]
276
+ to = options[:to] || action
277
+ @collection[action, to, method]
278
+ end
279
+
280
+ def member(action, options = {})
281
+ action = action.to_s
282
+ method = options[:method]
283
+ to = options[:to] || action
284
+ @member[action, to, method]
285
+ end
286
+
287
+ end
288
+ end
289
+ end
@@ -1,348 +1,568 @@
1
- require 'merb-core/controller/mixins/responder'
2
1
  module Merb
3
2
 
4
3
  class Router
5
- # Route instances incapsulate information about particular route
6
- # definition. Route definition ties
7
- # number of conditions (URL match, HTTP request method) with
8
- # resulting hash of route parameters:
9
- # controller, action, format and named parameters
10
- # from the URL.
11
- #
12
- # The following routes definition:
13
- #
14
- # Merb::Router.prepare do |r|
15
- # r.match("api/:action/:token.:format").to(:controller => "dev").fixatable
16
- # end
17
- #
18
- # maps URL matching pattern to controller named "dev"
19
- # and specifies fixation for that route. Path and request method are
20
- # route conditions, controller name, action name, format and
21
- # value of segment we decided to call :token are route parameters.
22
- #
23
- # ==== How route definitions are used.
24
- #
25
- # When routes are compiled, each route produces
26
- # a string with eval-able if/elsif condition statement.
27
- # This statement together with others constructs body
28
- # of Merb::Router.match method.
29
- # Condition statements are Ruby code in form of string.
30
- #
31
- # ==== Segments.
32
- #
33
- # Route definitions use conventional syntax for named parameters.
34
- # This splits route path into segments. Static (not changing) segments
35
- # represented internally as strings, named parameters are stored
36
- # as symbols and called symbol segments. Symbol segments
37
- # map to groups in regular expression in resulting condition statement.
38
- #
39
- # ==== Route conditions.
40
- #
41
- # Because route conditions include path matching,
42
- # regular expression is created from string that uses
43
- # :segment format to fetch groups and assign them to
44
- # named parameters. This regular expression is used
45
- # to produce compiled statement mentioned above.
46
- #
47
- # Route conditions may also include
48
- # user agent. Symbol segments
49
- #
50
- # Here is example of Route conditions:
51
- # {
52
- # :path => /^\/continents\/?(\.([^\/.,;?]+))?$/,
53
- # :method => /^get$/
54
- # }
55
- #
56
- #
57
- # ==== Route parameters.
58
- #
59
- # Route parameters is a Hash with controller name,
60
- # action name and parameters key/value pairs.
61
- # It is then merged with request.params hash.
62
- #
63
- # Example of route parameters:
64
- #
65
- # {
66
- # :action => "\"index\"",
67
- # :format => "path2",
68
- # :controller => "\"continents\""
69
- # }
70
- #
71
- # Router takes first matching route and uses it's parameters
72
- # to dispatch request to certain controller and action.
73
- #
74
- # ==== Behavior
75
- #
76
- # Each route has utility collaborator called behavior
77
- # that incapsulates additional information about route
78
- # (like namespace or if route is deferred) and also
79
- # provides utility methods.
80
- #
81
- # ==== Route registration.
82
- #
83
- # When route is added to set of routes, it is called route
84
- # registration. Registred route knows it's index in routes set.
85
- #
86
- # ==== Fixation
87
- # Fixatable routes allow setting of session key from GET params
88
- # found in incoming request. This is very useful to allow certain
89
- # URLs to be used by rich media applications and other kinds
90
- # of clients that have no other way of passing session identifier.
91
- #
92
- # ==== Conditional block.
93
- # Conditional block is anonymous function that is evaluated
94
- # when deferred routes are processed. Unless route is deferred,
95
- # it has no condition block.
96
- class Route
97
- attr_reader :conditions, :conditional_block
98
- attr_reader :params, :behavior, :segments, :index, :symbol
99
-
100
- # ==== Parameters
101
- # conditions<Hash>:: Conditions for the route.
102
- # params<Hash>:: Parameters for the route.
103
- # behavior<Merb::Router::Behavior>::
104
- # The associated behavior. Defaults to nil.
105
- # &conditional_block::
106
- # A block with the conditions to be met for the route to take effect.
107
- def initialize(conditions, params, behavior = nil, &conditional_block)
108
- @conditions, @params, @behavior = conditions, params, behavior
109
- @conditional_block = conditional_block
110
- @fixation=false
111
- if @behavior && (path = @behavior.merged_original_conditions[:path])
112
- @segments = segments_from_path(path)
4
+ # This entire class is private and should never be accessed outside of
5
+ # Merb::Router and Behavior
6
+ class Route #:nodoc:
7
+ SEGMENT_REGEXP = /(:([a-z](_?[a-z0-9])*))/
8
+ OPTIONAL_SEGMENT_REGEX = /^.*?([\(\)])/i
9
+ SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
10
+ JUST_BRACKETS = /\[(\d+)\]/
11
+ SEGMENT_CHARACTERS = "[^\/.,;?]".freeze
12
+
13
+ attr_reader :conditions, :params, :segments
14
+ attr_reader :index, :variables, :name
15
+ attr_accessor :fixation
16
+
17
+ def initialize(conditions, params, deferred_procs, options = {})
18
+ @conditions, @params = conditions, params
19
+
20
+ if options[:redirects]
21
+ @redirects = true
22
+ @redirect_status = @params[:status]
23
+ @redirect_url = @params[:url]
24
+ @defaults = {}
25
+ else
26
+ @defaults = options[:defaults] || {}
113
27
  end
28
+
29
+ # @conditional_block = conditional_block
30
+
31
+ @identifiers = options[:identifiers]
32
+ @deferred_procs = deferred_procs
33
+ @segments = []
34
+ @symbol_conditions = {}
35
+ @placeholders = {}
36
+ compile
114
37
  end
115
38
 
116
- # ==== Returns
117
- # Boolean::
118
- # Does the router specify a redirect?
119
- def redirects?
120
- behavior.redirects?
39
+ def regexp?
40
+ @regexp
41
+ end
42
+
43
+ def allow_fixation?
44
+ @fixation
121
45
  end
122
46
 
123
- # ==== Returns
124
- # Integer::
125
- # The status code to use if the route redirects
126
- def redirect_status
127
- behavior.redirect_status
47
+ def to_s
48
+ regexp? ?
49
+ "/#{conditions[:path].source}/" :
50
+ segment_level_to_s(segments)
128
51
  end
129
52
 
130
- # ==== Returns
131
- # String::
132
- # The URL to redirect to if the route redirects
133
- def redirect_url
134
- behavior.redirect_url
53
+ alias_method :inspect, :to_s
54
+
55
+ # Appends self to Merb::Router.routes
56
+ def register
57
+ @index = Merb::Router.routes.size
58
+ Merb::Router.routes << self
59
+ self
135
60
  end
136
61
 
137
- # ==== Returns
138
- # Boolean:: True if fixation is allowed.
139
- def allow_fixation?
140
- @fixation
62
+ # Inserts self to Merb::Router.routes at the specified index.
63
+ def register_at(index)
64
+ @index = index
65
+ Merb::Router.routes.insert(index, self)
66
+ self
67
+ end
68
+
69
+ # Sets the route as a resource route with the given key as the
70
+ # lookup key.
71
+ def resource=(key)
72
+ Router.resource_routes[key] = self
73
+ key
74
+ end
75
+
76
+ def name=(name)
77
+ @name = name.to_sym
78
+ Router.named_routes[@name] = self
79
+ @name
80
+ end
81
+
82
+ # === Compiled method ===
83
+ def generate(args = [], defaults = {})
84
+ raise GenerationError, "Cannot generate regexp Routes" if regexp?
85
+
86
+ params = extract_options_from_args!(args) || { }
87
+
88
+ # Support for anonymous params
89
+ unless args.empty?
90
+ # First, let's determine which variables are missing
91
+ variables = @variables - params.keys
92
+
93
+ raise GenerationError, "The route has #{@variables.length} variables: #{@variables.inspect}" if args.length > variables.length
94
+
95
+ args.each_with_index do |param, i|
96
+ params[variables[i]] ||= param
97
+ end
98
+ end
99
+
100
+ uri = @generator[params, defaults] or raise GenerationError, "Named route #{name} could not be generated with #{params.inspect}"
101
+ uri = Merb::Config[:path_prefix] + uri if Merb::Config[:path_prefix]
102
+ uri
141
103
  end
142
104
 
143
- # ==== Parameters
144
- # enabled<Boolean>:: True enables fixation on the route.
145
- def fixatable(enable=true)
146
- @fixation = enable
147
- self
105
+ def compiled_statement(first)
106
+ els_if = first ? ' if ' : ' elsif '
107
+
108
+ code = ""
109
+ code << els_if << condition_statements.join(" && ") << "\n"
110
+
111
+ # First, we need to always return the value of the
112
+ # deferred block if it explicitly matched the route
113
+ if @redirects && @deferred_procs.any?
114
+ code << " return [#{@index.inspect}, block_result] if request.matched?" << "\n"
115
+ code << " request.redirects!" << "\n"
116
+ code << " [#{@index.inspect}, { :url => #{@redirect_url.inspect}, :status => #{@redirect_status.inspect} }]" << "\n"
117
+ elsif @redirects
118
+ code << " request.redirects!" << "\n"
119
+ code << " [#{@index.inspect}, { :url => #{@redirect_url.inspect}, :status => #{@redirect_status.inspect} }]" << "\n"
120
+ elsif @deferred_procs.any?
121
+ code << " [#{@index.inspect}, block_result]" << "\n"
122
+ else
123
+ code << " [#{@index.inspect}, #{params_as_string}]" << "\n"
124
+ end
148
125
  end
149
126
 
150
- # Concatenates all route segments and returns result.
151
- # Symbol segments have colon preserved.
152
- #
153
- # ==== Returns
154
- # String:: The route as a string, e.g. "admin/:controller/:id".
155
- def to_s
156
- (segments || []).inject('') do |str,seg|
157
- str << (seg.is_a?(Symbol) ? ":#{seg}" : seg)
127
+ private
128
+
129
+ # === Compilation ===
130
+
131
+ def compile
132
+ compile_conditions
133
+ compile_params
134
+ @generator = Generator.new(@segments, @symbol_conditions, @identifiers).compiled
135
+ end
136
+
137
+ # The Generator class handles compiling the route down to a lambda that
138
+ # can generate the URL from a params hash and a default params hash.
139
+ class Generator #:nodoc:
140
+
141
+ def initialize(segments, symbol_conditions, identifiers)
142
+ @segments = segments
143
+ @symbol_conditions = symbol_conditions
144
+ @identifiers = identifiers
145
+ @stack = []
146
+ @opt_segment_count = 0
147
+ @opt_segment_stack = [[]]
148
+ end
149
+
150
+ def compiled
151
+ ruby = ""
152
+ ruby << "lambda do |params, defaults|\n"
153
+ ruby << " fragment = params.delete(:fragment)\n"
154
+ ruby << " query_params = params.dup\n"
155
+
156
+ with(@segments) do
157
+ ruby << " include_defaults = true\n"
158
+ ruby << " return unless url = #{block_for_level}\n"
159
+ end
160
+
161
+ ruby << " query_params.delete_if { |key, value| value.nil? }\n"
162
+ ruby << " unless query_params.empty?\n"
163
+ ruby << ' url << "?#{Merb::Request.params_to_query_string(query_params)}"' << "\n"
164
+ ruby << " end\n"
165
+ ruby << ' url << "##{fragment}" if fragment' << "\n"
166
+ ruby << " url\n"
167
+ ruby << "end\n"
168
+
169
+ eval(ruby)
170
+ end
171
+
172
+ private
173
+
174
+ # Cleans up methods a bunch. We don't need to pass the current segment
175
+ # level around everywhere anymore. It's kept track for us in the stack.
176
+ def with(segments, &block)
177
+ @stack.push(segments)
178
+ retval = yield
179
+ @stack.pop
180
+ retval
181
+ end
182
+
183
+ def segments
184
+ @stack.last || []
185
+ end
186
+
187
+ def symbol_segments
188
+ segments.flatten.select { |s| s.is_a?(Symbol) }
189
+ end
190
+
191
+ def current_segments
192
+ segments.select { |s| s.is_a?(Symbol) }
193
+ end
194
+
195
+ def nested_segments
196
+ segments.select { |s| s.is_a?(Array) }.flatten.select { |s| s.is_a?(Symbol) }
197
+ end
198
+
199
+ def block_for_level
200
+ ruby = ""
201
+ ruby << "if #{segment_level_matches_conditions}\n"
202
+ ruby << " #{remove_used_segments_in_query_path}\n"
203
+ ruby << " #{generate_optional_segments}\n"
204
+ ruby << %{ "#{combine_required_and_optional_segments}"\n}
205
+ ruby << "end"
206
+ end
207
+
208
+ def check_if_defaults_should_be_included
209
+ ruby = ""
210
+ ruby << "include_defaults = "
211
+ symbol_segments.each { |s| ruby << "params[#{s.inspect}] || " }
212
+ ruby << "false"
213
+ end
214
+
215
+ # --- Not so pretty ---
216
+ def segment_level_matches_conditions
217
+ conditions = current_segments.map do |segment|
218
+ condition = "(cached_#{segment} = params[#{segment.inspect}] || include_defaults && defaults[#{segment.inspect}])"
219
+
220
+ if @symbol_conditions[segment] && @symbol_conditions[segment].is_a?(Regexp)
221
+ condition << " =~ #{@symbol_conditions[segment].inspect}"
222
+ elsif @symbol_conditions[segment]
223
+ condition << " == #{@symbol_conditions[segment].inspect}"
224
+ end
225
+
226
+ condition
227
+ end
228
+
229
+ conditions << "true" if conditions.empty?
230
+ conditions.join(" && ")
231
+ end
232
+
233
+ def remove_used_segments_in_query_path
234
+ "#{current_segments.inspect}.each { |s| query_params.delete(s) }"
235
+ end
236
+
237
+ def generate_optional_segments
238
+ optionals = []
239
+
240
+ segments.each_with_index do |segment, i|
241
+ if segment.is_a?(Array) && segment.any? { |s| !s.is_a?(String) }
242
+ with(segment) do
243
+ @opt_segment_stack.last << (optional_name = "_optional_segments_#{@opt_segment_count += 1}")
244
+ @opt_segment_stack.push []
245
+ optionals << "#{check_if_defaults_should_be_included}\n"
246
+ optionals << "#{optional_name} = #{block_for_level}"
247
+ @opt_segment_stack.pop
248
+ end
249
+ end
250
+ end
251
+
252
+ optionals.join("\n")
253
+ end
254
+
255
+ def combine_required_and_optional_segments
256
+ bits = ""
257
+
258
+ segments.each_with_index do |segment, i|
259
+ bits << case
260
+ when segment.is_a?(String) then segment
261
+ when segment.is_a?(Symbol) then '#{param_for_route(cached_' + segment.to_s + ')}'
262
+ when segment.is_a?(Array) && segment.any? { |s| !s.is_a?(String) } then "\#{#{@opt_segment_stack.last.shift}}"
263
+ else ""
264
+ end
265
+ end
266
+
267
+ bits
268
+ end
269
+
270
+ def param_for_route(param)
271
+ case param
272
+ when String, Symbol, Numeric, TrueClass, FalseClass, NilClass
273
+ param
274
+ else
275
+ _, identifier = @identifiers.find { |klass, _| param.is_a?(klass) }
276
+ identifier ? param.send(identifier) : param
277
+ end
158
278
  end
279
+
159
280
  end
160
281
 
161
- # Registers the route in the Router.routes array.
162
- # After registration route has index.
163
- def register
164
- @index = Router.routes.size
165
- Router.routes << self
166
- self
282
+ # === Conditions ===
283
+
284
+ def compile_conditions
285
+ @original_conditions = conditions.dup
286
+
287
+ if path = conditions[:path]
288
+ path = [path].flatten.compact
289
+ if path = compile_path(path)
290
+ conditions[:path] = Regexp.new("^#{path}$")
291
+ else
292
+ conditions.delete(:path)
293
+ end
294
+ end
167
295
  end
168
296
 
169
- # ==== Returns
170
- # Array:: All the symbols in the segments array.
171
- def symbol_segments
172
- (segments || []).select{ |s| s.is_a?(Symbol) }
297
+ # The path is passed in as an array of different parts. We basically have
298
+ # to concat all the parts together, then parse the path and extract the
299
+ # variables. However, if any of the parts are a regular expression, then
300
+ # we abort the parsing and just convert it to a regexp.
301
+ def compile_path(path)
302
+ @segments = []
303
+ compiled = ""
304
+
305
+ return nil if path.nil? || path.empty?
306
+
307
+ path.each do |part|
308
+ case part
309
+ when Regexp
310
+ @regexp = true
311
+ @segments = []
312
+ compiled << part.source.sub(/^\^/, '').sub(/\$$/, '')
313
+ when String
314
+ segments = segments_with_optionals_from_string(part.dup)
315
+ compile_path_segments(compiled, segments)
316
+ # Concat the segments
317
+ unless regexp?
318
+ if @segments[-1].is_a?(String) && segments[0].is_a?(String)
319
+ @segments[-1] << segments.shift
320
+ end
321
+ @segments.concat segments
322
+ end
323
+ else
324
+ raise ArgumentError.new("A route path can only be specified as a String or Regexp")
325
+ end
326
+ end
327
+
328
+ unless regexp?
329
+ @variables = @segments.flatten.select { |s| s.is_a?(Symbol) }
330
+ compiled.gsub!(%r[/+], '/')
331
+ compiled.gsub!(%r[(.+)/$], '\1')
332
+ end
333
+
334
+ compiled
173
335
  end
174
336
 
175
- # Turn a path into string and symbol segments so it can be reconstructed,
176
- # as in the case of a named route.
177
- #
178
- # ==== Parameters
179
- # path<String>:: The path to split into segments.
180
- #
181
- # ==== Returns
182
- # Array:: The Symbol and String segments for the path.
183
- def segments_from_path(path)
184
- # Remove leading ^ and trailing $ from each segment (left-overs from regexp joining)
185
- strip = proc { |str| str.gsub(/^\^/, '').gsub(/\$$/, '') }
337
+ # Simple nested parenthesis parser
338
+ def segments_with_optionals_from_string(path, nest_level = 0)
186
339
  segments = []
340
+
341
+ # Extract all the segments at this parenthesis level
342
+ while segment = path.slice!(OPTIONAL_SEGMENT_REGEX)
343
+ # Append the segments that we came across so far
344
+ # at this level
345
+ segments.concat segments_from_string(segment[0..-2]) if segment.length > 1
346
+ # If the parenthesis that we came across is an opening
347
+ # then we need to jump to the higher level
348
+ if segment[-1,1] == '('
349
+ segments << segments_with_optionals_from_string(path, nest_level + 1)
350
+ else
351
+ # Throw an error if we can't actually go back down (aka syntax error)
352
+ raise "There are too many closing parentheses" if nest_level == 0
353
+ return segments
354
+ end
355
+ end
356
+
357
+ # Save any last bit of the string that didn't match the original regex
358
+ segments.concat segments_from_string(path) unless path.empty?
359
+
360
+ # Throw an error if the string should not actually be done (aka syntax error)
361
+ raise "You have too many opening parentheses" unless nest_level == 0
362
+
363
+ segments
364
+ end
365
+
366
+ def segments_from_string(path)
367
+ segments = []
368
+
187
369
  while match = (path.match(SEGMENT_REGEXP))
188
- segments << strip[match.pre_match] unless match.pre_match.empty?
370
+ segments << match.pre_match unless match.pre_match.empty?
189
371
  segments << match[2].intern
190
- path = strip[match.post_match]
372
+ path = match.post_match
191
373
  end
192
- segments << strip[path] unless path.empty?
374
+
375
+ segments << path unless path.empty?
193
376
  segments
194
377
  end
195
378
 
196
- # Names this route in Router. Name must be a Symbol.
197
- #
198
- # ==== Parameters
199
- # symbol<Symbol>:: The name of the route.
200
- #
201
- # ==== Raises
202
- # ArgumentError:: symbol is not a Symbol.
203
- def name(symbol = nil)
204
- raise ArgumentError unless (@symbol = symbol).is_a?(Symbol)
205
- Router.named_routes[@symbol] = self
379
+ # --- Yeah, this could probably be refactored
380
+ def compile_path_segments(compiled, segments)
381
+ segments.each do |segment|
382
+ case segment
383
+ when String
384
+ compiled << Regexp.escape(segment)
385
+ when Symbol
386
+ condition = (@symbol_conditions[segment] ||= @conditions.delete(segment))
387
+ compiled << compile_segment_condition(condition)
388
+ # Create a param for the Symbol segment if none already exists
389
+ @params[segment] = "#{segment.inspect}" unless @params.has_key?(segment)
390
+ @placeholders[segment] ||= capturing_parentheses_count(compiled)
391
+ when Array
392
+ compiled << "(?:"
393
+ compile_path_segments(compiled, segment)
394
+ compiled << ")?"
395
+ else
396
+ raise ArgumentError, "conditions[:path] segments can only be a Strings, Symbols, or Arrays"
397
+ end
398
+ end
206
399
  end
207
400
 
208
- # ==== Returns
209
- # Boolean::
210
- # True if this route is a regexp, i.e. its behavior or one of the
211
- # behavior's ancestors is a regexp.
212
- def regexp?
213
- @regexp ||= behavior.regexp? || behavior.ancestors.any? { |a| a.regexp? }
401
+ # Handles anchors in Regexp conditions
402
+ def compile_segment_condition(condition)
403
+ return "(#{SEGMENT_CHARACTERS}+)" unless condition
404
+ return "(#{condition})" unless condition.is_a?(Regexp)
405
+
406
+ condition = condition.source
407
+ # Handle the start anchor
408
+ condition = if condition =~ /^\^/
409
+ condition[1..-1]
410
+ else
411
+ "#{SEGMENT_CHARACTERS}*#{condition}"
412
+ end
413
+ # Handle the end anchor
414
+ condition = if condition =~ /\$$/
415
+ condition[0..-2]
416
+ else
417
+ "#{condition}#{SEGMENT_CHARACTERS}*"
418
+ end
419
+
420
+ "(#{condition})"
421
+ end
422
+
423
+ def compile_params
424
+ # Loop through each param and compile it
425
+ @defaults.merge(@params).each do |key, value|
426
+ if value.nil?
427
+ @params.delete(key)
428
+ elsif value.is_a?(String)
429
+ @params[key] = compile_param(value)
430
+ else
431
+ @params[key] = value.inspect
432
+ end
433
+ end
214
434
  end
215
435
 
216
- # Generates URL using route segments and given parameters.
217
- # If parameter value responds to :to_param, it is called.
218
- #
219
- # ==== Parameters
220
- # params<Hash>:: Optional parameters for the route.
221
- # fallback<Hash>:: Optional parameters for the fallback route.
222
- #
223
- # ==== Returns
224
- # String::
225
- # The URL corresponding to the params, using the stored route segments
226
- # for reconstruction of the URL.
227
- def generate(params = {}, fallback = {})
228
- raise "Cannot generate regexp Routes" if regexp?
229
- query_params = params.dup if params.is_a? Hash
230
- url = @segments.map do |segment|
231
- value =
232
- if segment.is_a? Symbol
233
- if params.is_a? Hash
234
- if segment.to_s =~ /_id/ && params[:id].respond_to?(segment)
235
- params[segment] = params[:id].send(segment)
236
- end
237
- query_params.delete segment
238
- params[segment] || fallback[segment]
436
+ # This was pretty much a copy / paste from the old router
437
+ def compile_param(value)
438
+ result = []
439
+ match = true
440
+ while match
441
+ if match = SEGMENT_REGEXP_WITH_BRACKETS.match(value)
442
+ result << match.pre_match.inspect unless match.pre_match.empty?
443
+ placeholder_key = match[1][1..-1].intern
444
+ if match[2] # has brackets, e.g. :path[2]
445
+ result << "#{placeholder_key}#{match[3]}"
446
+ else # no brackets, e.g. a named placeholder such as :controller
447
+ if place = @placeholders[placeholder_key]
448
+ # result << "(path#{place} || )" # <- Defaults
449
+ with_defaults = ["(path#{place}"]
450
+ with_defaults << " || #{@defaults[placeholder_key].inspect}" if @defaults[placeholder_key]
451
+ with_defaults << ")"
452
+ result << with_defaults.join
239
453
  else
240
- if segment == :id && params.respond_to?(:to_param)
241
- params.to_param
242
- elsif segment == :id && params.is_a?(Fixnum)
243
- params
244
- elsif params.respond_to?(segment)
245
- params.send(segment)
246
- else
247
- fallback[segment]
248
- end
454
+ raise GenerationError, "Placeholder not found while compiling routes: #{placeholder_key.inspect}. Add it to the conditions part of the route."
249
455
  end
250
- elsif segment.respond_to? :to_s
251
- segment
252
- else
253
- raise "Segment type '#{segment.class}' can't be converted to a string"
254
456
  end
255
- (value.respond_to?(:to_param) ? value.to_param : value).to_s.unescape_regexp
256
- end.join
257
- if query_params && format = query_params.delete(:format)
258
- format = fallback[:format] if format == :current
259
- url += ".#{format}"
457
+ value = match.post_match
458
+ elsif match = JUST_BRACKETS.match(value)
459
+ result << match.pre_match.inspect unless match.pre_match.empty?
460
+ result << "path#{match[1]}"
461
+ value = match.post_match
462
+ else
463
+ result << value.inspect unless value.empty?
464
+ end
260
465
  end
261
- if query_params
262
- fragment = query_params.delete(:fragment)
466
+
467
+ result.join(' + ').gsub("\\_", "_")
468
+ end
469
+
470
+ def condition_statements
471
+ statements = []
472
+
473
+ # First, let's build the conditions for the regular
474
+ conditions.each_pair do |key, value|
475
+ statements << case value
476
+ when Regexp
477
+ captures = ""
478
+
479
+ if (max = capturing_parentheses_count(value)) > 0
480
+ captures << (1..max).to_a.map { |n| "#{key}#{n}" }.join(", ")
481
+ captures << " = "
482
+ captures << (1..max).to_a.map { |n| "$#{n}" }.join(", ")
483
+ end
484
+
485
+ # Note: =~ is slightly faster than .match
486
+ %{(#{value.inspect} =~ cached_#{key} #{' && ((' + captures + ') || true)' unless captures.empty?})}
487
+ when Array
488
+ %{(#{arrays_to_regexps(value).inspect} =~ cached_#{key})}
489
+ else
490
+ %{(cached_#{key} == #{value.inspect})}
491
+ end
263
492
  end
264
- if query_params && !query_params.empty?
265
- url += "?" + Merb::Request.params_to_query_string(query_params)
493
+
494
+ # The first one is special, so let's extract it
495
+ if first = @deferred_procs.first
496
+ deferred = ""
497
+ deferred << "(block_result = "
498
+ deferred << "request._process_block_return("
499
+ deferred << "#{first}.call(request, #{params_as_string})"
500
+ deferred << ")"
501
+ deferred << ")"
502
+
503
+ # Let's build the rest of them now
504
+ if @deferred_procs.length > 1
505
+ deferred << deferred_condition_statement(@deferred_procs[1..-1])
506
+ end
507
+
508
+ statements << deferred
266
509
  end
267
- if fragment
268
- url += "##{fragment}"
510
+
511
+ statements
512
+ end
513
+
514
+ # (request.matched? || ((block_result = process(proc.call))))
515
+ def deferred_condition_statement(deferred)
516
+ if current = deferred.first
517
+ html = " && (request.matched? || ("
518
+ html << "(block_result = "
519
+ html << "request._process_block_return("
520
+ html << "#{current}.call(request, block_result)"
521
+ html << ")"
522
+ html << ")"
523
+ html << "#{deferred_condition_statement(deferred[1..-1])}"
524
+ html << "))"
269
525
  end
270
- url
271
526
  end
272
527
 
273
- # Generates and returns if statement used to
274
- # construct final condition statement of the route.
275
- #
276
- # ==== Params
277
- # params_as_string<String>::
278
- # The params hash as a string, e.g. ":foo => 'bar'".
279
- #
280
- # ==== Returns
281
- # Array:: All the conditions as eval'able strings.
282
- def if_conditions(params_as_string)
283
- cond = []
284
- condition_string = proc do |key, value, regexp_string|
285
- max = Behavior.count_parens_up_to(value.source, value.source.size)
286
- captures = max == 0 ? "" : (1..max).to_a.map{ |n| "#{key}#{n}" }.join(", ") + " = " +
287
- (1..max).to_a.map{ |n| "$#{n}"}.join(", ")
288
- " (#{value.inspect} =~ #{regexp_string}) #{" && (" + captures + ")" unless captures.empty?}"
289
- end
290
- @conditions.each_pair do |key, value|
291
-
292
- # Note: =~ is slightly faster than .match
293
- cond << case key
294
- when :path then condition_string[key, value, "cached_path"]
295
- when :method then condition_string[key, value, "cached_method"]
296
- else condition_string[key, value, "request.#{key}.to_s"]
297
- end
528
+ def params_as_string
529
+ elements = params.keys.map do |k|
530
+ "#{k.inspect} => #{params[k]}"
298
531
  end
299
- if @conditional_block
300
- str = " # #{@conditional_block.inspect.scan(/@([^>]+)/).flatten.first}\n"
301
- str << " (block_result = #{CachedProc.new(@conditional_block)}.call(request, params.merge({#{params_as_string}})))" if @conditional_block
302
- cond << str
303
- end
304
- cond
532
+ "{#{elements.join(', ')}}"
305
533
  end
306
534
 
307
- # Compiles the route to a form used by Merb::Router. This form sometimes
308
- # referred as condition statement of the route.
309
- #
310
- # ==== Parameters
311
- # first<Boolean>::
312
- # True if this is the first route in set of routes. Defaults to false.
313
- #
314
- # ==== Returns
315
- # String:: The code corresponding to the route in a form suited for eval.
316
- def compile(first = false)
317
- code = ""
318
- default_params = { :action => "index" }
319
- get_value = proc do |key|
320
- if default_params.has_key?(key) && params[key][0] != ?"
321
- "#{params[key]} || \"#{default_params[key]}\""
535
+ # ---------- Utilities ----------
536
+
537
+ def arrays_to_regexps(condition)
538
+ return condition unless condition.is_a?(Array)
539
+
540
+ source = condition.map do |value|
541
+ value = if value.is_a?(Regexp)
542
+ value.source
322
543
  else
323
- "#{params[key]}"
544
+ "^#{Regexp.escape(value.to_s)}$"
324
545
  end
546
+ "(?:#{value})"
325
547
  end
326
- params_as_string = params.keys.map { |k| "#{k.inspect} => #{get_value[k]}" }.join(', ')
327
- code << " els" unless first
328
- code << "if # #{@behavior.merged_original_conditions.inspect} \n"
329
- code << if_conditions(params_as_string).join(" && ") << "\n"
330
- code << " # then\n"
331
- if @conditional_block
332
- code << " [#{@index.inspect}, block_result]\n"
333
- else
334
- code << " [#{@index.inspect}, {#{params_as_string}}]\n"
548
+
549
+ Regexp.compile(source.join('|'))
550
+ end
551
+
552
+ def segment_level_to_s(segments)
553
+ (segments || []).inject('') do |str, seg|
554
+ str << case seg
555
+ when String then seg
556
+ when Symbol then ":#{seg}"
557
+ when Array then "(#{segment_level_to_s(seg)})"
558
+ end
335
559
  end
336
560
  end
337
561
 
338
- # Prints a trace of the behavior for this route.
339
- def behavior_trace
340
- if @behavior
341
- puts @behavior.send(:ancestors).reverse.map{|a| a.inspect}.join("\n"); puts @behavior.inspect; puts
342
- else
343
- puts "No behavior to trace #{self}"
344
- end
562
+ def capturing_parentheses_count(regexp)
563
+ regexp = regexp.source if regexp.is_a?(Regexp)
564
+ regexp.scan(/(?!\\)[(](?!\?[#=:!>-imx])/).length
345
565
  end
346
- end # Route
347
- end
566
+ end
567
+ end
348
568
  end