thorero 0.9.4.4 → 0.9.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (298) hide show
  1. data/LICENSE +1 -1
  2. data/README +21 -0
  3. data/Rakefile +275 -108
  4. data/TODO +0 -0
  5. data/bin/merb +12 -0
  6. data/bin/merb-specs +5 -0
  7. data/docs/bootloading.dox +58 -0
  8. data/docs/documentation_standards +40 -0
  9. data/docs/merb-core-call-stack-diagram.mmap +0 -0
  10. data/docs/merb-core-call-stack-diagram.pdf +0 -0
  11. data/docs/merb-core-call-stack-diagram.png +0 -0
  12. data/docs/new_render_api +51 -0
  13. data/lib/merb-core.rb +603 -0
  14. data/lib/merb-core/autoload.rb +32 -0
  15. data/lib/merb-core/bootloader.rb +708 -0
  16. data/lib/merb-core/config.rb +303 -0
  17. data/lib/merb-core/constants.rb +43 -0
  18. data/lib/merb-core/controller/abstract_controller.rb +578 -0
  19. data/lib/merb-core/controller/exceptions.rb +302 -0
  20. data/lib/merb-core/controller/merb_controller.rb +256 -0
  21. data/lib/merb-core/controller/mime.rb +106 -0
  22. data/lib/merb-core/controller/mixins/authentication.rb +87 -0
  23. data/lib/merb-core/controller/mixins/controller.rb +290 -0
  24. data/lib/merb-core/controller/mixins/render.rb +481 -0
  25. data/lib/merb-core/controller/mixins/responder.rb +472 -0
  26. data/lib/merb-core/controller/template.rb +254 -0
  27. data/lib/merb-core/core_ext.rb +8 -0
  28. data/lib/merb-core/core_ext/kernel.rb +319 -0
  29. data/lib/merb-core/dispatch/cookies.rb +91 -0
  30. data/lib/merb-core/dispatch/dispatcher.rb +278 -0
  31. data/lib/merb-core/dispatch/exceptions.html.erb +303 -0
  32. data/lib/merb-core/dispatch/request.rb +603 -0
  33. data/lib/merb-core/dispatch/router.rb +179 -0
  34. data/lib/merb-core/dispatch/router/behavior.rb +867 -0
  35. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  36. data/lib/merb-core/dispatch/router/route.rb +321 -0
  37. data/lib/merb-core/dispatch/session.rb +78 -0
  38. data/lib/merb-core/dispatch/session/cookie.rb +168 -0
  39. data/lib/merb-core/dispatch/session/memcached.rb +184 -0
  40. data/lib/merb-core/dispatch/session/memory.rb +241 -0
  41. data/lib/merb-core/dispatch/worker.rb +28 -0
  42. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  43. data/lib/{extlib → merb-core}/logger.rb +2 -2
  44. data/lib/merb-core/plugins.rb +59 -0
  45. data/lib/merb-core/rack.rb +21 -0
  46. data/lib/merb-core/rack/adapter.rb +44 -0
  47. data/lib/merb-core/rack/adapter/ebb.rb +25 -0
  48. data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
  49. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  50. data/lib/merb-core/rack/adapter/irb.rb +118 -0
  51. data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
  52. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  53. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  54. data/lib/merb-core/rack/adapter/thin.rb +39 -0
  55. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  56. data/lib/merb-core/rack/adapter/webrick.rb +36 -0
  57. data/lib/merb-core/rack/application.rb +18 -0
  58. data/lib/merb-core/rack/handler/mongrel.rb +97 -0
  59. data/lib/merb-core/rack/middleware.rb +26 -0
  60. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  61. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  62. data/lib/merb-core/rack/middleware/static.rb +45 -0
  63. data/lib/merb-core/server.rb +252 -0
  64. data/lib/merb-core/tasks/audit.rake +68 -0
  65. data/lib/merb-core/tasks/merb.rb +1 -0
  66. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -0
  67. data/lib/merb-core/test.rb +11 -0
  68. data/lib/merb-core/test/helpers.rb +9 -0
  69. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  70. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  71. data/lib/merb-core/test/helpers/request_helper.rb +344 -0
  72. data/lib/merb-core/test/helpers/route_helper.rb +33 -0
  73. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  74. data/lib/merb-core/test/matchers.rb +9 -0
  75. data/lib/merb-core/test/matchers/controller_matchers.rb +319 -0
  76. data/lib/merb-core/test/matchers/route_matchers.rb +136 -0
  77. data/lib/merb-core/test/matchers/view_matchers.rb +335 -0
  78. data/lib/merb-core/test/run_specs.rb +47 -0
  79. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  80. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  81. data/lib/merb-core/test/test_ext/object.rb +14 -0
  82. data/lib/merb-core/test/test_ext/string.rb +14 -0
  83. data/lib/merb-core/vendor/facets.rb +2 -0
  84. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  85. data/lib/merb-core/vendor/facets/inflect.rb +345 -0
  86. data/lib/merb-core/version.rb +11 -0
  87. data/spec/private/config/adapter_spec.rb +32 -0
  88. data/spec/private/config/config_spec.rb +202 -0
  89. data/spec/private/config/environment_spec.rb +13 -0
  90. data/spec/private/config/spec_helper.rb +1 -0
  91. data/spec/private/core_ext/kernel_spec.rb +169 -0
  92. data/spec/private/dispatch/bootloader_spec.rb +24 -0
  93. data/spec/private/dispatch/cookies_spec.rb +107 -0
  94. data/spec/private/dispatch/dispatch_spec.rb +35 -0
  95. data/spec/private/dispatch/fixture/app/controllers/application.rb +4 -0
  96. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +27 -0
  97. data/spec/private/dispatch/fixture/app/controllers/foo.rb +21 -0
  98. data/spec/private/dispatch/fixture/app/helpers/global_helpers.rb +8 -0
  99. data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +37 -0
  100. data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +216 -0
  101. data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +38 -0
  102. data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +40 -0
  103. data/spec/private/dispatch/fixture/app/views/foo/bar.html.erb +0 -0
  104. data/spec/private/dispatch/fixture/app/views/layout/application.html.erb +11 -0
  105. data/spec/private/dispatch/fixture/config/black_hole.rb +12 -0
  106. data/spec/private/dispatch/fixture/config/environments/development.rb +6 -0
  107. data/spec/private/dispatch/fixture/config/environments/production.rb +5 -0
  108. data/spec/private/dispatch/fixture/config/environments/test.rb +6 -0
  109. data/spec/private/dispatch/fixture/config/init.rb +45 -0
  110. data/spec/private/dispatch/fixture/config/rack.rb +11 -0
  111. data/spec/private/dispatch/fixture/config/router.rb +35 -0
  112. data/spec/private/dispatch/fixture/log/merb_test.log +1874 -0
  113. data/spec/private/dispatch/fixture/public/images/merb.jpg +0 -0
  114. data/spec/private/dispatch/fixture/public/merb.fcgi +4 -0
  115. data/spec/private/dispatch/fixture/public/stylesheets/master.css +119 -0
  116. data/spec/private/dispatch/route_params_spec.rb +24 -0
  117. data/spec/private/dispatch/session_mixin_spec.rb +47 -0
  118. data/spec/private/dispatch/spec_helper.rb +1 -0
  119. data/spec/private/plugins/plugin_spec.rb +166 -0
  120. data/spec/private/rack/application_spec.rb +49 -0
  121. data/spec/private/router/behavior_spec.rb +60 -0
  122. data/spec/private/router/fixture/log/merb_test.log +139 -0
  123. data/spec/private/router/route_spec.rb +414 -0
  124. data/spec/private/router/router_spec.rb +175 -0
  125. data/spec/private/vendor/facets/plural_spec.rb +564 -0
  126. data/spec/private/vendor/facets/singular_spec.rb +489 -0
  127. data/spec/public/DEFINITIONS +11 -0
  128. data/spec/public/abstract_controller/controllers/alt_views/layout/application.erb +1 -0
  129. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_string_controller_layout.erb +1 -0
  130. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_template_controller_layout.erb +1 -0
  131. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/index.erb +1 -0
  132. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/show.erb +1 -0
  133. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
  134. data/spec/public/abstract_controller/controllers/alt_views/partial/basic_partial_with_multiple_roots/_partial.erb +1 -0
  135. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_and_custom_location/index.erb +1 -0
  136. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_inherited/index.erb +1 -0
  137. data/spec/public/abstract_controller/controllers/cousins.rb +41 -0
  138. data/spec/public/abstract_controller/controllers/display.rb +54 -0
  139. data/spec/public/abstract_controller/controllers/filters.rb +193 -0
  140. data/spec/public/abstract_controller/controllers/helpers.rb +41 -0
  141. data/spec/public/abstract_controller/controllers/partial.rb +121 -0
  142. data/spec/public/abstract_controller/controllers/render.rb +113 -0
  143. data/spec/public/abstract_controller/controllers/views/helpers/capture/index.erb +1 -0
  144. data/spec/public/abstract_controller/controllers/views/helpers/capture_eq/index.erb +1 -0
  145. data/spec/public/abstract_controller/controllers/views/helpers/capture_with_args/index.erb +1 -0
  146. data/spec/public/abstract_controller/controllers/views/helpers/concat/index.erb +1 -0
  147. data/spec/public/abstract_controller/controllers/views/layout/alt.erb +1 -0
  148. data/spec/public/abstract_controller/controllers/views/layout/custom.erb +1 -0
  149. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object/index.erb +1 -0
  150. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object_with_action/new.erb +1 -0
  151. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template/index.erb +1 -0
  152. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_app_layout/index.erb +0 -0
  153. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_custom_layout/index.erb +1 -0
  154. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
  155. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/show.erb +1 -0
  156. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_two_throw_contents/index.erb +1 -0
  157. data/spec/public/abstract_controller/controllers/views/partial/another_directory/_partial.erb +1 -0
  158. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/_partial.erb +1 -0
  159. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/index.erb +1 -0
  160. data/spec/public/abstract_controller/controllers/views/partial/basic_partial_with_multiple_roots/index.erb +1 -0
  161. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_first.erb +1 -0
  162. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_second.erb +1 -0
  163. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/index.erb +1 -0
  164. data/spec/public/abstract_controller/controllers/views/partial/partial_in_another_directory/index.erb +1 -0
  165. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/_collection.erb +1 -0
  166. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/index.erb +1 -0
  167. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/_collection.erb +1 -0
  168. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/index.erb +1 -0
  169. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/_collection.erb +1 -0
  170. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/index.erb +1 -0
  171. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/_collection.erb +1 -0
  172. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/index.erb +1 -0
  173. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/_variables.erb +1 -0
  174. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/index.erb +1 -0
  175. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/_both.erb +1 -0
  176. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/index.erb +1 -0
  177. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/_partial.erb +1 -0
  178. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/index.erb +1 -0
  179. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/_with_partial.erb +1 -0
  180. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/index.erb +1 -0
  181. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/_with_partial.erb +1 -0
  182. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/index.erb +1 -0
  183. data/spec/public/abstract_controller/controllers/views/partial/with_partial/_with_partial.erb +1 -0
  184. data/spec/public/abstract_controller/controllers/views/partial/with_partial/index.erb +1 -0
  185. data/spec/public/abstract_controller/controllers/views/test_display/foo.html.erb +1 -0
  186. data/spec/public/abstract_controller/controllers/views/test_render/foo.html.erb +0 -0
  187. data/spec/public/abstract_controller/controllers/views/wonderful/index.erb +1 -0
  188. data/spec/public/abstract_controller/display_spec.rb +33 -0
  189. data/spec/public/abstract_controller/filter_spec.rb +106 -0
  190. data/spec/public/abstract_controller/helper_spec.rb +21 -0
  191. data/spec/public/abstract_controller/partial_spec.rb +61 -0
  192. data/spec/public/abstract_controller/render_spec.rb +90 -0
  193. data/spec/public/abstract_controller/spec_helper.rb +31 -0
  194. data/spec/public/boot_loader/boot_loader_spec.rb +33 -0
  195. data/spec/public/boot_loader/spec_helper.rb +1 -0
  196. data/spec/public/controller/authentication_spec.rb +103 -0
  197. data/spec/public/controller/base_spec.rb +36 -0
  198. data/spec/public/controller/controllers/authentication.rb +45 -0
  199. data/spec/public/controller/controllers/base.rb +36 -0
  200. data/spec/public/controller/controllers/display.rb +118 -0
  201. data/spec/public/controller/controllers/redirect.rb +30 -0
  202. data/spec/public/controller/controllers/responder.rb +93 -0
  203. data/spec/public/controller/controllers/url.rb +7 -0
  204. data/spec/public/controller/controllers/views/layout/custom.html.erb +1 -0
  205. data/spec/public/controller/controllers/views/layout/custom_arg.html.erb +1 -0
  206. data/spec/public/controller/controllers/views/layout/custom_arg.json.erb +1 -0
  207. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.html.erb +1 -0
  208. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.xml.erb +1 -0
  209. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.html.erb +1 -0
  210. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.xml.erb +1 -0
  211. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/index.html.erb +1 -0
  212. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/no_layout.html.erb +1 -0
  213. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template_argument/index.html.erb +1 -0
  214. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/html_default/index.html.erb +1 -0
  215. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/layout/custom.html.erb +1 -0
  216. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.html.erb +1 -0
  217. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.xml.erb +1 -0
  218. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.html.erb +1 -0
  219. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.js.erb +1 -0
  220. data/spec/public/controller/display_spec.rb +84 -0
  221. data/spec/public/controller/redirect_spec.rb +27 -0
  222. data/spec/public/controller/responder_spec.rb +163 -0
  223. data/spec/public/controller/spec_helper.rb +11 -0
  224. data/spec/public/controller/url_spec.rb +180 -0
  225. data/spec/public/core/merb_core_spec.rb +45 -0
  226. data/spec/public/core_ext/class_spec.rb +91 -0
  227. data/spec/public/core_ext/fixtures/core_ext_dependency.rb +2 -0
  228. data/spec/public/core_ext/kernel_spec.rb +9 -0
  229. data/spec/public/core_ext/spec_helper.rb +1 -0
  230. data/spec/public/directory_structure/directory/app/controllers/application.rb +3 -0
  231. data/spec/public/directory_structure/directory/app/controllers/base.rb +13 -0
  232. data/spec/public/directory_structure/directory/app/controllers/custom.rb +19 -0
  233. data/spec/public/directory_structure/directory/app/views/base/template.html.erb +1 -0
  234. data/spec/public/directory_structure/directory/app/views/wonderful/template.erb +1 -0
  235. data/spec/public/directory_structure/directory/config/router.rb +3 -0
  236. data/spec/public/directory_structure/directory/log/merb_test.log +562 -0
  237. data/spec/public/directory_structure/directory_spec.rb +44 -0
  238. data/spec/public/logger/logger_spec.rb +181 -0
  239. data/spec/public/logger/spec_helper.rb +1 -0
  240. data/spec/public/reloading/directory/app/controllers/application.rb +3 -0
  241. data/spec/public/reloading/directory/app/controllers/reload.rb +6 -0
  242. data/spec/public/reloading/directory/config/init.rb +2 -0
  243. data/spec/public/reloading/directory/log/merb_test.log +138 -0
  244. data/spec/public/reloading/reload_spec.rb +103 -0
  245. data/spec/public/request/multipart_spec.rb +41 -0
  246. data/spec/public/request/request_spec.rb +228 -0
  247. data/spec/public/router/default_spec.rb +21 -0
  248. data/spec/public/router/deferred_spec.rb +22 -0
  249. data/spec/public/router/fixation_spec.rb +27 -0
  250. data/spec/public/router/fixture/log/merb_test.log +1556 -0
  251. data/spec/public/router/namespace_spec.rb +113 -0
  252. data/spec/public/router/nested_matches_spec.rb +97 -0
  253. data/spec/public/router/nested_resources_spec.rb +41 -0
  254. data/spec/public/router/resource_spec.rb +37 -0
  255. data/spec/public/router/resources_spec.rb +82 -0
  256. data/spec/public/router/spec_helper.rb +90 -0
  257. data/spec/public/router/special_spec.rb +61 -0
  258. data/spec/public/router/string_spec.rb +61 -0
  259. data/spec/public/template/template_spec.rb +104 -0
  260. data/spec/public/template/templates/error.html.erb +2 -0
  261. data/spec/public/template/templates/template.html.erb +1 -0
  262. data/spec/public/template/templates/template.html.myt +1 -0
  263. data/spec/public/test/controller_matchers_spec.rb +402 -0
  264. data/spec/public/test/controllers/controller_assertion_mock.rb +7 -0
  265. data/spec/public/test/controllers/dispatch_controller.rb +11 -0
  266. data/spec/public/test/controllers/spec_helper_controller.rb +38 -0
  267. data/spec/public/test/multipart_request_helper_spec.rb +159 -0
  268. data/spec/public/test/multipart_upload_text_file.txt +1 -0
  269. data/spec/public/test/request_helper_spec.rb +221 -0
  270. data/spec/public/test/route_helper_spec.rb +71 -0
  271. data/spec/public/test/route_matchers_spec.rb +162 -0
  272. data/spec/public/test/view_helper_spec.rb +96 -0
  273. data/spec/public/test/view_matchers_spec.rb +183 -0
  274. data/spec/spec_helper.rb +68 -0
  275. metadata +493 -41
  276. data/README.txt +0 -3
  277. data/lib/extlib.rb +0 -32
  278. data/lib/extlib/assertions.rb +0 -8
  279. data/lib/extlib/blank.rb +0 -42
  280. data/lib/extlib/class.rb +0 -175
  281. data/lib/extlib/hash.rb +0 -410
  282. data/lib/extlib/hook.rb +0 -366
  283. data/lib/extlib/inflection.rb +0 -141
  284. data/lib/extlib/lazy_array.rb +0 -106
  285. data/lib/extlib/mash.rb +0 -143
  286. data/lib/extlib/module.rb +0 -37
  287. data/lib/extlib/object.rb +0 -165
  288. data/lib/extlib/object_space.rb +0 -13
  289. data/lib/extlib/pathname.rb +0 -5
  290. data/lib/extlib/pooling.rb +0 -233
  291. data/lib/extlib/rubygems.rb +0 -38
  292. data/lib/extlib/simple_set.rb +0 -39
  293. data/lib/extlib/string.rb +0 -132
  294. data/lib/extlib/struct.rb +0 -8
  295. data/lib/extlib/tasks/release.rb +0 -9
  296. data/lib/extlib/time.rb +0 -12
  297. data/lib/extlib/version.rb +0 -3
  298. data/lib/extlib/virtual_file.rb +0 -10
@@ -0,0 +1,179 @@
1
+ require 'merb-core/dispatch/router/cached_proc'
2
+ require 'merb-core/dispatch/router/behavior'
3
+ require 'merb-core/dispatch/router/route'
4
+ require 'merb-core/controller/mixins/responder'
5
+ module Merb
6
+ # Router stores route definitions and finds first
7
+ # that matches incoming request URL.
8
+ #
9
+ # Then information from route is used by dispatcher to
10
+ # call action on the controller.
11
+ #
12
+ # ==== Routes compilation.
13
+ #
14
+ # Most interesting method of Router (and heart of
15
+ # route matching machinery) is match method generated
16
+ # on fly from routes definitions. It is called routes
17
+ # compilation. Generated match method body contains
18
+ # one if/elsif statement that picks first matching route
19
+ # definition and sets values to named parameters of the route.
20
+ #
21
+ # Compilation is synchronized by mutex.
22
+ class Router
23
+ SEGMENT_REGEXP = /(:([a-z_][a-z0-9_]*|:))/
24
+ SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
25
+ JUST_BRACKETS = /\[(\d+)\]/
26
+ PARENTHETICAL_SEGMENT_STRING = "([^\/.,;?]+)".freeze
27
+
28
+ @@named_routes = {}
29
+ @@routes = []
30
+ @@compiler_mutex = Mutex.new
31
+ cattr_accessor :routes, :named_routes
32
+
33
+ class << self
34
+
35
+ # Clear all routes.
36
+ def reset!
37
+ self.routes, self.named_routes = [], {}
38
+ end
39
+
40
+ # Appends the generated routes to the current routes.
41
+ #
42
+ # ==== Parameters
43
+ # &block::
44
+ # A block that generates new routes when yielded a new Behavior.
45
+ def append(&block)
46
+ prepare(@@routes, [], &block)
47
+ end
48
+
49
+ # Prepends the generated routes to the current routes.
50
+ #
51
+ # ==== Parameters
52
+ # &block::
53
+ # A block that generates new routes when yielded a new Behavior.
54
+ def prepend(&block)
55
+ prepare([], @@routes, &block)
56
+ end
57
+
58
+ # Prepares new routes and adds them to existing routes.
59
+ #
60
+ # ==== Parameters
61
+ # first<Array>:: An array of routes to add before the generated routes.
62
+ # last<Array>:: An array of routes to add after the generated routes.
63
+ # &block:: A block that generates new routes.
64
+ #
65
+ # ==== Block parameters (&block)
66
+ # new_behavior<Behavior>:: Behavior for child routes.
67
+ def prepare(first = [], last = [], &block)
68
+ @@routes = []
69
+ yield Behavior.new({}, { :action => 'index' }) # defaults
70
+ @@routes = first + @@routes + last
71
+ compile
72
+ end
73
+
74
+ # Capture any new routes that have been added within the block.
75
+ #
76
+ # This utility method lets you track routes that have been added;
77
+ # it doesn't affect how/which routes are added.
78
+ #
79
+ # &block:: A context in which routes are generated.
80
+ def capture(&block)
81
+ routes_before, named_route_keys_before = self.routes.dup, self.named_routes.keys
82
+ yield
83
+ [self.routes - routes_before, self.named_routes.except(*named_route_keys_before)]
84
+ end
85
+
86
+ # ==== Returns
87
+ # String:: A routing lambda statement generated from the routes.
88
+ def compiled_statement
89
+ @@compiler_mutex.synchronize do
90
+ @@compiled_statement = "def match(request)\n"
91
+ @@compiled_statement << " params = request.params\n"
92
+ @@compiled_statement << " cached_path = request.path\n cached_method = request.method.to_s\n "
93
+ @@routes.each_with_index { |route, i| @@compiled_statement << route.compile(i == 0) }
94
+ @@compiled_statement << " else\n [nil, {}]\n"
95
+ @@compiled_statement << " end\n"
96
+ @@compiled_statement << "end"
97
+ end
98
+ end
99
+
100
+ # Defines the match function for this class based on the
101
+ # compiled_statement.
102
+ def compile
103
+ puts "compiled route: #{compiled_statement}" if $DEBUG
104
+ eval(compiled_statement, binding, "Generated Code for Router#match(#{__FILE__}:#{__LINE__})", 1)
105
+ end
106
+
107
+ # Generates a URL based on passed options.
108
+ #
109
+ # ==== Parameters
110
+ # name<~to_sym, Hash>:: The name of the route to generate.
111
+ # params<Hash, Fixnum, Object>:: The params to use in the route generation.
112
+ # fallback<Hash>:: Parameters for generating a fallback URL.
113
+ #
114
+ # ==== Returns
115
+ # String:: The generated URL.
116
+ #
117
+ # ==== Alternatives
118
+ # If name is a hash, it will be merged with params and passed on to
119
+ # generate_for_default_route along with fallback.
120
+ def generate(name, params = {}, fallback = {})
121
+ params.reject! { |k,v| v.nil? } if params.is_a? Hash
122
+ if name.is_a? Hash
123
+ name.reject! { |k,v| v.nil? }
124
+ return generate_for_default_route(name.merge(params), fallback)
125
+ end
126
+ name = name.to_sym
127
+ unless @@named_routes.key? name
128
+ raise "Named route not found: #{name}"
129
+ else
130
+ @@named_routes[name].generate(params, fallback)
131
+ end
132
+ end
133
+
134
+ # Generates a URL based on the default route scheme of
135
+ # "/:controller/:action/:id.:format".
136
+ #
137
+ # ==== Parameters
138
+ # params<Hash>::
139
+ # The primary parameters to create the route from (see below).
140
+ # fallback<Hash>:: Fallback parameters. Same options as params.
141
+ #
142
+ # ==== Options (params)
143
+ # :controller<~to_s>:: The controller name. Required.
144
+ # :action<~to_s>:: The action name. Required.
145
+ # :id<~to_s>:: The ID for use in the action.
146
+ # :format<~to_s>:: The format of the preferred response.
147
+ #
148
+ # ==== Returns
149
+ # String:: The generated URL.
150
+ def generate_for_default_route(params, fallback)
151
+ query_params = params.reject do |k,v|
152
+ [:controller, :action, :id, :format].include?(k.to_sym)
153
+ end
154
+
155
+ controller = params[:controller] || fallback[:controller]
156
+ raise "Controller Not Specified" unless controller
157
+ url = "/#{controller}"
158
+
159
+ if params[:action] || params[:id] || params[:format] || !query_params.empty?
160
+ action = params[:action] || fallback[:action]
161
+ raise "Action Not Specified" unless action
162
+ url += "/#{action}"
163
+ end
164
+ if params[:id]
165
+ url += "/#{params[:id]}"
166
+ end
167
+ if format = params[:format]
168
+ format = fallback[:format] if format == :current
169
+ url += ".#{format}"
170
+ end
171
+ unless query_params.empty?
172
+ url += "?" + Merb::Request.params_to_query_string(query_params)
173
+ end
174
+ url
175
+ end
176
+ end # self
177
+
178
+ end
179
+ end
@@ -0,0 +1,867 @@
1
+ module Merb
2
+
3
+ class Router
4
+
5
+ # The Behavior class is an interim route-building class that ties
6
+ # pattern-matching +conditions+ to output parameters, +params+.
7
+ #---
8
+ # @public
9
+ class Behavior
10
+ attr_reader :placeholders, :conditions, :params, :redirect_url, :redirect_status
11
+ attr_accessor :parent
12
+ @@parent_resources = []
13
+ class << self
14
+
15
+ # ==== Parameters
16
+ # string<String>:: The string in which to count parentheses.
17
+ # pos<Fixnum>:: The last character for counting.
18
+ #
19
+ # ==== Returns
20
+ # Fixnum::
21
+ # The number of open parentheses in string, up to and including pos.
22
+ def count_parens_up_to(string, pos)
23
+ string[0..pos].gsub(/[^\(]/, '').size
24
+ end
25
+
26
+ # ==== Parameters
27
+ # string1<String>:: The string to concatenate with.
28
+ # string2<String>:: The string to concatenate.
29
+ #
30
+ # ==== Returns
31
+ # String:: the concatenated string with regexp end caps removed.
32
+ def concat_without_endcaps(string1, string2)
33
+ return nil if !string1 and !string2
34
+ return string1 if string2.nil?
35
+ return string2 if string1.nil?
36
+ s1 = string1[-1] == ?$ ? string1[0..-2] : string1
37
+ s2 = string2[0] == ?^ ? string2[1..-1] : string2
38
+ s1 + s2
39
+ end
40
+
41
+ # ==== Parameters
42
+ # arr<Array>:: The array to convert to a code string.
43
+ #
44
+ # ==== Returns
45
+ # String::
46
+ # The arr's elements converted to string and joined with " + ", with
47
+ # any string elements surrounded by quotes.
48
+ def array_to_code(arr)
49
+ code = ''
50
+ arr.each_with_index do |part, i|
51
+ code << ' + ' if i > 0
52
+ case part
53
+ when Symbol
54
+ code << part.to_s
55
+ when String
56
+ code << %{"#{part}"}
57
+ else
58
+ raise "Don't know how to compile array part: #{part.class} [#{i}]"
59
+ end
60
+ end
61
+ code
62
+ end
63
+ end # class << self
64
+
65
+ # ==== Parameters
66
+ # conditions<Hash>::
67
+ # Conditions to be met for this behavior to take effect.
68
+ # params<Hash>::
69
+ # Hash describing the course action to take (Behavior) when the
70
+ # conditions match. The values of the +params+ keys must be Strings.
71
+ # parent<Behavior, Nil>::
72
+ # The parent of this Behavior. Defaults to nil.
73
+ def initialize(conditions = {}, params = {}, parent = nil)
74
+ # Must wait until after deducing placeholders to set @params !
75
+ @conditions, @params, @parent = conditions, {}, parent
76
+ @placeholders = {}
77
+ stringify_conditions
78
+ copy_original_conditions
79
+ deduce_placeholders
80
+ @params.merge! params
81
+ end
82
+
83
+ # Register a new route.
84
+ #
85
+ # ==== Parameters
86
+ # path<String, Regex>:: The url path to match
87
+ # params<Hash>:: The parameters the new routes maps to.
88
+ #
89
+ # ==== Returns
90
+ # Route:: The resulting Route.
91
+ #---
92
+ # @public
93
+ def add(path, params = {})
94
+ match(path).to(params)
95
+ end
96
+
97
+ # Matches a +path+ and any number of optional request methods as
98
+ # conditions of a route. Alternatively, +path+ can be a hash of
99
+ # conditions, in which case +conditions+ ignored.
100
+ #
101
+ # ==== Parameters
102
+ #
103
+ # path<String, Regexp>::
104
+ # When passing a string as +path+ you're defining a literal definition
105
+ # for your route. Using a colon, ex.: ":login", defines both a capture
106
+ # and a named param.
107
+ # When passing a regular expression you can define captures explicitly
108
+ # within the regular expression syntax.
109
+ # +path+ is optional.
110
+ # conditions<Hash>::
111
+ # Addational conditions that the request must meet in order to match.
112
+ # the keys must be methods that the Merb::Request instance will respond
113
+ # to. The value is the string or regexp that matched the returned value.
114
+ # Conditions are inherited by child routes.
115
+ #
116
+ # The Following have special meaning:
117
+ # * :method -- Limit this match based on the request method. (GET,
118
+ # POST, PUT, DELETE)
119
+ # * :path -- Used internally to maintain URL form information
120
+ # * :controller and :action -- These can be used here instead of '#to', and
121
+ # will be inherited in the block.
122
+ # * :params -- Sets other key/value pairs that are placed in the params
123
+ # hash. The value must be a hash.
124
+ # &block::
125
+ # Passes a new instance of a Behavior object into the optional block so
126
+ # that sub-matching and routes nesting may occur.
127
+ #
128
+ # ==== Returns
129
+ # Behavior::
130
+ # A new instance of Behavior with the specified path and conditions.
131
+ #
132
+ # +Tip+: When nesting always make sure the most inner sub-match registers
133
+ # a Route and doesn't just returns new Behaviors.
134
+ #
135
+ # ==== Examples
136
+ #
137
+ # # registers /foo/bar to controller => "foo", :action => "bar"
138
+ # # and /foo/baz to controller => "foo", :action => "baz"
139
+ # r.match "/foo", :controller=>"foo" do |f|
140
+ # f.match("/bar").to(:action => "bar")
141
+ # f.match("/baz").to(:action => "caz")
142
+ # end
143
+ #
144
+ # #match only of the browser string contains MSIE or Gecko
145
+ # r.match ('/foo', :user_agent => /(MSIE|Gecko)/ )
146
+ # .to({:controller=>'foo', :action=>'popular')
147
+ #
148
+ # # Route GET and POST requests to different actions (see also #resources)
149
+ # r.match('/foo', :method=>:get).to(:action=>'show')
150
+ # r.mathc('/foo', :method=>:post).to(:action=>'create')
151
+ #
152
+ # # match also takes regular expressions
153
+ #
154
+ # r.match(%r[/account/([a-z]{4,6})]).to(:controller => "account",
155
+ # :action => "show", :id => "[1]")
156
+ #
157
+ # r.match(/\/?(en|es|fr|be|nl)?/).to(:language => "[1]") do |l|
158
+ # l.match("/guides/:action/:id").to(:controller => "tour_guides")
159
+ # end
160
+ #---
161
+ # @public
162
+ def match(path = '', conditions = {}, &block)
163
+ if path.is_a? Hash
164
+ conditions = path
165
+ else
166
+ conditions[:path] = path
167
+ end
168
+ match_without_path(conditions, &block)
169
+ end
170
+
171
+ # Generates a new child behavior without the path if the path matches
172
+ # an empty string. Yields the new behavior to a block.
173
+ #
174
+ # ==== Parameters
175
+ # conditions<Hash>:: Optional conditions to pass to the new route.
176
+ #
177
+ # ==== Block parameters
178
+ # new_behavior<Behavior>:: The child behavior.
179
+ #
180
+ # ==== Returns
181
+ # Behavior:: The new behavior.
182
+ def match_without_path(conditions = {})
183
+ params = conditions.delete(:params) || {} #parents params will be merged in Route#new
184
+ params[:controller] = conditions.delete(:controller) if conditions[:controller]
185
+ params[:action] = conditions.delete(:action) if conditions[:action]
186
+ new_behavior = self.class.new(conditions, params, self)
187
+ yield new_behavior if block_given?
188
+ new_behavior
189
+ end
190
+
191
+ # ==== Parameters
192
+ # params<Hash>:: Optional additional parameters for generating the route.
193
+ # &conditional_block:: A conditional block to be passed to Route.new.
194
+ #
195
+ # ==== Returns
196
+ # Route:: A new route based on this behavior.
197
+ def to_route(params = {}, &conditional_block)
198
+ @params.merge! params
199
+ Route.new compiled_conditions, compiled_params, self, &conditional_block
200
+ end
201
+
202
+ # Combines common case of match being used with
203
+ # to({}).
204
+ #
205
+ # ==== Returns
206
+ # <Route>:: route that uses params from named path segments.
207
+ #
208
+ # ==== Examples
209
+ # r.match!("/api/:token/:controller/:action/:id")
210
+ #
211
+ # is the same thing as
212
+ #
213
+ # r.match!("/api/:token/:controller/:action/:id").to({})
214
+ def match!(path = '', conditions = {}, &block)
215
+ self.match(path, conditions, &block).to({})
216
+ end
217
+
218
+ # Creates a Route from one or more Behavior objects, unless a +block+ is
219
+ # passed in.
220
+ #
221
+ # ==== Parameters
222
+ # params<Hash>:: The parameters the route maps to.
223
+ # &block::
224
+ # Optional block. A new Behavior object is yielded and further #to
225
+ # operations may be called in the block.
226
+ #
227
+ # ==== Block parameters
228
+ # new_behavior<Behavior>:: The child behavior.
229
+ #
230
+ # ==== Returns
231
+ # Route:: It registers a new route and returns it.
232
+ #
233
+ # ==== Examples
234
+ # r.match('/:controller/:id).to(:action => 'show')
235
+ #
236
+ # r.to :controller => 'simple' do |s|
237
+ # s.match('/test').to(:action => 'index')
238
+ # s.match('/other').to(:action => 'other')
239
+ # end
240
+ #---
241
+ # @public
242
+ def to(params = {}, &block)
243
+ if block_given?
244
+ new_behavior = self.class.new({}, params, self)
245
+ yield new_behavior if block_given?
246
+ new_behavior
247
+ else
248
+ to_route(params).register
249
+ end
250
+ end
251
+
252
+ # Takes a block and stores it for deferred conditional routes. The block
253
+ # takes the +request+ object and the +params+ hash as parameters.
254
+ #
255
+ # ==== Parameters
256
+ # params<Hash>:: Parameters and conditions associated with this behavior.
257
+ # &conditional_block::
258
+ # A block with the conditions to be met for the behavior to take
259
+ # effect.
260
+ #
261
+ # ==== Returns
262
+ # Route :: The default route.
263
+ #
264
+ # ==== Examples
265
+ # r.defer_to do |request, params|
266
+ # params.merge :controller => 'here',
267
+ # :action => 'there' if request.xhr?
268
+ # end
269
+ #---
270
+ # @public
271
+ def defer_to(params = {}, &conditional_block)
272
+ to_route(params, &conditional_block).register
273
+ end
274
+
275
+ # Creates the most common routes /:controller/:action/:id.format when
276
+ # called with no arguments.
277
+ # You can pass a hash or a block to add parameters or override the default
278
+ # behavior.
279
+ #
280
+ # ==== Parameters
281
+ # params<Hash>::
282
+ # This optional hash can be used to augment the default settings
283
+ # &block::
284
+ # When passing a block a new behavior is yielded and more refinement is
285
+ # possible.
286
+ #
287
+ # ==== Returns
288
+ # Route:: the default route
289
+ #
290
+ # ==== Examples
291
+ #
292
+ # # Passing an extra parameter "mode" to all matches
293
+ # r.default_routes :mode => "default"
294
+ #
295
+ # # specifying exceptions within a block
296
+ # r.default_routes do |nr|
297
+ # nr.defer_to do |request, params|
298
+ # nr.match(:protocol => "http://").to(:controller => "login",
299
+ # :action => "new") if request.env["REQUEST_URI"] =~ /\/private\//
300
+ # end
301
+ # end
302
+ #---
303
+ # @public
304
+ def default_routes(params = {}, &block)
305
+ match(%r{/:controller(/:action(/:id)?)?(\.:format)?}).to(params, &block)
306
+ end
307
+
308
+ # Creates a namespace for a route. This way you can have logical
309
+ # separation to your routes.
310
+ #
311
+ # ==== Parameters
312
+ # name_or_path<String, Symbol>:: The name or path of the namespace.
313
+ # options<Hash>:: Optional hash, set :path if you want to override what appears on the url
314
+ # &block::
315
+ # A new Behavior instance is yielded in the block for nested resources.
316
+ #
317
+ # ==== Block parameters
318
+ # r<Behavior>:: The namespace behavior object.
319
+ #
320
+ # ==== Examples
321
+ # r.namespace :admin do |admin|
322
+ # admin.resources :accounts
323
+ # admin.resource :email
324
+ # end
325
+ #
326
+ # # /super_admin/accounts
327
+ # r.namespace(:admin, :path=>"super_admin") do |admin|
328
+ # admin.resources :accounts
329
+ # end
330
+ #---
331
+ # @public
332
+ def namespace(name_or_path, options={}, &block)
333
+ path = options[:path] || name_or_path.to_s
334
+ (path.empty? ? self : match("/#{path}")).to(:namespace => name_or_path.to_s) do |r|
335
+ yield r
336
+ end
337
+ end
338
+
339
+ # Behavior#+resources+ is a route helper for defining a collection of
340
+ # RESTful resources. It yields to a block for child routes.
341
+ #
342
+ # ==== Parameters
343
+ # name<String, Symbol>:: The name of the resources
344
+ # options<Hash>::
345
+ # Ovverides and parameters to be associated with the route
346
+ #
347
+ # ==== Options (options)
348
+ # :namespace<~to_s>: The namespace for this route.
349
+ # :name_prefix<~to_s>:
350
+ # A prefix for the named routes. If a namespace is passed and there
351
+ # isn't a name prefix, the namespace will become the prefix.
352
+ # :controller<~to_s>: The controller for this route
353
+ # :collection<~to_s>: Special settings for the collections routes
354
+ # :member<Hash>:
355
+ # Special settings and resources related to a specific member of this
356
+ # resource.
357
+ # :keys<Array>:
358
+ # A list of the keys to be used instead of :id with the resource in the order of the url.
359
+ #
360
+ # ==== Block parameters
361
+ # next_level<Behavior>:: The child behavior.
362
+ #
363
+ # ==== Returns
364
+ # Array::
365
+ # Routes which will define the specified RESTful collection of resources
366
+ #
367
+ # ==== Examples
368
+ #
369
+ # r.resources :posts # will result in the typical RESTful CRUD
370
+ # # lists resources
371
+ # # GET /posts/?(\.:format)? :action => "index"
372
+ # # GET /posts/index(\.:format)? :action => "index"
373
+ #
374
+ # # shows new resource form
375
+ # # GET /posts/new :action => "new"
376
+ #
377
+ # # creates resource
378
+ # # POST /posts/?(\.:format)?, :action => "create"
379
+ #
380
+ # # shows resource
381
+ # # GET /posts/:id(\.:format)? :action => "show"
382
+ #
383
+ # # shows edit form
384
+ # # GET /posts/:id/edit :action => "edit"
385
+ #
386
+ # # updates resource
387
+ # # PUT /posts/:id(\.:format)? :action => "update"
388
+ #
389
+ # # shows deletion confirmation page
390
+ # # GET /posts/:id/delete :action => "delete"
391
+ #
392
+ # # destroys resources
393
+ # # DELETE /posts/:id(\.:format)? :action => "destroy"
394
+ #
395
+ # # Nesting resources
396
+ # r.resources :posts do |posts|
397
+ # posts.resources :comments
398
+ # end
399
+ #---
400
+ # @public
401
+ def resources(name, options = {})
402
+ namespace = options[:namespace] || merged_params[:namespace]
403
+
404
+ next_level = match "/#{name}"
405
+
406
+ name_prefix = options.delete :name_prefix
407
+ matched_keys = options[:keys] ? options.delete(:keys).map{|k| ":#{k}"}.join("/") : ":id"
408
+
409
+ if name_prefix.nil? && !namespace.nil?
410
+ name_prefix = namespace_to_name_prefix namespace
411
+ end
412
+
413
+ unless @@parent_resources.empty?
414
+ parent_resource = namespace_to_name_prefix @@parent_resources.join('_')
415
+ end
416
+
417
+ options[:controller] ||= merged_params[:controller] || name.to_s
418
+
419
+ singular = name.to_s.singularize
420
+
421
+ route_plural_name = "#{name_prefix}#{parent_resource}#{name}"
422
+ route_singular_name = "#{name_prefix}#{parent_resource}#{singular}"
423
+
424
+ behaviors = []
425
+
426
+ if member = options.delete(:member)
427
+ member.each_pair do |action, methods|
428
+ behaviors << Behavior.new(
429
+ { :path => %r{^/#{matched_keys}/#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
430
+ { :action => action.to_s }, next_level
431
+ )
432
+ next_level.match("/#{matched_keys}/#{action}").to_route.name(:"#{action}_#{route_singular_name}")
433
+ end
434
+ end
435
+
436
+ if collection = options.delete(:collection)
437
+ collection.each_pair do |action, methods|
438
+ behaviors << Behavior.new(
439
+ { :path => %r{^/#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
440
+ { :action => action.to_s }, next_level
441
+ )
442
+ next_level.match("/#{action}").to_route.name(:"#{action}_#{route_plural_name}")
443
+ end
444
+ end
445
+
446
+ routes = many_behaviors_to(behaviors + next_level.send(:resources_behaviors, matched_keys), options)
447
+
448
+
449
+
450
+ # Add names to some routes
451
+ [['', :"#{route_plural_name}"],
452
+ ["/#{matched_keys}", :"#{route_singular_name}"],
453
+ ['/new', :"new_#{route_singular_name}"],
454
+ ["/#{matched_keys}/edit", :"edit_#{route_singular_name}"],
455
+ ["/#{matched_keys}/delete", :"delete_#{route_singular_name}"]
456
+ ].each do |path,name|
457
+ next_level.match(path).to_route.name(name)
458
+ end
459
+
460
+
461
+ parent_keys = (matched_keys == ":id") ? ":#{singular}_id" : matched_keys
462
+ if block_given?
463
+ @@parent_resources.push(singular)
464
+ yield next_level.match("/#{parent_keys}")
465
+ @@parent_resources.pop
466
+ end
467
+
468
+ routes
469
+ end
470
+
471
+ # Behavior#+resource+ is a route helper for defining a singular RESTful
472
+ # resource. It yields to a block for child routes.
473
+ #
474
+ # ==== Parameters
475
+ # name<String, Symbol>:: The name of the resource.
476
+ # options<Hash>::
477
+ # Overides and parameters to be associated with the route.
478
+ #
479
+ # ==== Options (options)
480
+ # :namespace<~to_s>: The namespace for this route.
481
+ # :name_prefix<~to_s>:
482
+ # A prefix for the named routes. If a namespace is passed and there
483
+ # isn't a name prefix, the namespace will become the prefix.
484
+ # :controller<~to_s>: The controller for this route
485
+ #
486
+ # ==== Block parameters
487
+ # next_level<Behavior>:: The child behavior.
488
+ #
489
+ # ==== Returns
490
+ # Array:: Routes which define a RESTful single resource.
491
+ #
492
+ # ==== Examples
493
+ #
494
+ # r.resource :account # will result in the typical RESTful CRUD
495
+ # # shows new resource form
496
+ # # GET /account/new :action => "new"
497
+ #
498
+ # # creates resource
499
+ # # POST /account/?(\.:format)?, :action => "create"
500
+ #
501
+ # # shows resource
502
+ # # GET /account/(\.:format)? :action => "show"
503
+ #
504
+ # # shows edit form
505
+ # # GET /account//edit :action => "edit"
506
+ #
507
+ # # updates resource
508
+ # # PUT /account/(\.:format)? :action => "update"
509
+ #
510
+ # # shows deletion confirmation page
511
+ # # GET /account//delete :action => "delete"
512
+ #
513
+ # # destroys resources
514
+ # # DELETE /account/(\.:format)? :action => "destroy"
515
+ #
516
+ # You can optionally pass :namespace and :controller to refine the routing
517
+ # or pass a block to nest resources.
518
+ #
519
+ # r.resource :account, :namespace => "admin" do |account|
520
+ # account.resources :preferences, :controller => "settings"
521
+ # end
522
+ # ---
523
+ # @public
524
+ def resource(name, options = {})
525
+ namespace = options[:namespace] || merged_params[:namespace]
526
+
527
+ next_level = match "/#{name}"
528
+
529
+ options[:controller] ||= merged_params[:controller] || name.to_s
530
+
531
+ # Do not pass :name_prefix option on to to_resource
532
+ name_prefix = options.delete :name_prefix
533
+
534
+ if name_prefix.nil? && !namespace.nil?
535
+ name_prefix = namespace_to_name_prefix namespace
536
+ end
537
+
538
+ unless @@parent_resources.empty?
539
+ parent_resource = namespace_to_name_prefix @@parent_resources.join('_')
540
+ end
541
+
542
+ routes = next_level.to_resource options
543
+
544
+ route_name = "#{name_prefix}#{name}"
545
+
546
+ next_level.match('').to_route.name(:"#{route_name}")
547
+ next_level.match('/new').to_route.name(:"new_#{route_name}")
548
+ next_level.match('/edit').to_route.name(:"edit_#{route_name}")
549
+ next_level.match('/delete').to_route.name(:"delete_#{route_name}")
550
+
551
+ if block_given?
552
+ @@parent_resources.push(route_name)
553
+ yield next_level
554
+ @@parent_resources.pop
555
+ end
556
+
557
+ routes
558
+ end
559
+
560
+ # ==== Parameters
561
+ # params<Hash>:: Optional params for generating the RESTful routes.
562
+ # &block:: Optional block for the route generation.
563
+ #
564
+ # ==== Returns
565
+ # Array:: Routes matching the RESTful resource.
566
+ def to_resources(params = {}, &block)
567
+ many_behaviors_to resources_behaviors, params, &block
568
+ end
569
+
570
+ # ==== Parameters
571
+ # params<Hash>:: Optional params for generating the RESTful routes.
572
+ # &block:: Optional block for the route generation.
573
+ #
574
+ # ==== Returns
575
+ # Array:: Routes matching the RESTful singular resource.
576
+ def to_resource(params = {}, &block)
577
+ many_behaviors_to resource_behaviors, params, &block
578
+ end
579
+
580
+ # ==== Returns
581
+ # Hash::
582
+ # The original conditions of this behavior merged with the original
583
+ # conditions of all its ancestors.
584
+ def merged_original_conditions
585
+ if parent.nil?
586
+ @original_conditions
587
+ else
588
+ merged_so_far = parent.merged_original_conditions
589
+ if path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path])
590
+ merged_so_far.merge(@original_conditions).merge(:path => path)
591
+ else
592
+ merged_so_far.merge(@original_conditions)
593
+ end
594
+ end
595
+ end
596
+
597
+ # ==== Returns
598
+ # Hash::
599
+ # The conditions of this behavior merged with the conditions of all its
600
+ # ancestors.
601
+ def merged_conditions
602
+ if parent.nil?
603
+ @conditions
604
+ else
605
+ merged_so_far = parent.merged_conditions
606
+ if path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path])
607
+ merged_so_far.merge(@conditions).merge(:path => path)
608
+ else
609
+ merged_so_far.merge(@conditions)
610
+ end
611
+ end
612
+ end
613
+
614
+ # ==== Returns
615
+ # Hash::
616
+ # The params of this behavior merged with the params of all its
617
+ # ancestors.
618
+ def merged_params
619
+ if parent.nil?
620
+ @params
621
+ else
622
+ parent.merged_params.merge(@params)
623
+ end
624
+ end
625
+
626
+ # ==== Returns
627
+ # Hash::
628
+ # The route placeholders, e.g. :controllers, of this behavior merged
629
+ # with the placeholders of all its ancestors.
630
+ def merged_placeholders
631
+ placeholders = {}
632
+ (ancestors.reverse + [self]).each do |a|
633
+ a.placeholders.each_pair do |k, pair|
634
+ param, place = pair
635
+ placeholders[k] = [param, place + (param == :path ? a.total_previous_captures : 0)]
636
+ end
637
+ end
638
+ placeholders
639
+ end
640
+
641
+ # ==== Returns
642
+ # String:: A human readable form of the behavior.
643
+ def inspect
644
+ "[captures: #{path_captures.inspect}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]"
645
+ end
646
+
647
+ # ==== Returns
648
+ # Boolean:: True if this behavior has a regexp.
649
+ def regexp?
650
+ @conditions_have_regexp
651
+ end
652
+
653
+ def redirect(url, permanent = true)
654
+ @redirects = true
655
+ @redirect_url = url
656
+ @redirect_status = permanent ? 301 : 302
657
+
658
+ # satisfy route compilation
659
+ self.to({})
660
+ end
661
+
662
+ def redirects?
663
+ @redirects
664
+ end
665
+
666
+ def ancestors
667
+ @ancestors ||= find_ancestors
668
+ end
669
+
670
+ protected
671
+
672
+ # ==== Parameters
673
+ # name_or_path<~to_s>::
674
+ # The name or path to convert to a form suitable for a prefix.
675
+ #
676
+ # ==== Returns
677
+ # String:: The prefix.
678
+ def namespace_to_name_prefix(name_or_path)
679
+ name_or_path.to_s.tr('/', '_') + '_'
680
+ end
681
+
682
+ # ==== Parameters
683
+ # matched_keys<String>::
684
+ # The keys to match
685
+ #
686
+ # ==== Returns
687
+ # Array:: Behaviors for a RESTful resource.
688
+ def resources_behaviors(matched_keys = ":id")
689
+ [
690
+ Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :get }, { :action => "index" }, self),
691
+ Behavior.new({ :path => %r[^/index(\.:format)?$], :method => :get }, { :action => "index" }, self),
692
+ Behavior.new({ :path => %r[^/new$], :method => :get }, { :action => "new" }, self),
693
+ Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :post }, { :action => "create" }, self),
694
+ Behavior.new({ :path => %r[^/#{matched_keys}(\.:format)?$], :method => :get }, { :action => "show" }, self),
695
+ Behavior.new({ :path => %r[^/#{matched_keys}/edit$], :method => :get }, { :action => "edit" }, self),
696
+ Behavior.new({ :path => %r[^/#{matched_keys}/delete$], :method => :get }, { :action => "delete" }, self),
697
+ Behavior.new({ :path => %r[^/#{matched_keys}(\.:format)?$], :method => :put }, { :action => "update" }, self),
698
+ Behavior.new({ :path => %r[^/#{matched_keys}(\.:format)?$], :method => :delete }, { :action => "destroy" }, self)
699
+ ]
700
+ end
701
+
702
+ # ==== Parameters
703
+ # parent<Merb::Router::Behavior>::
704
+ # The parent behavior for the generated resource behaviors.
705
+ #
706
+ # ==== Returns
707
+ # Array:: Behaviors for a singular RESTful resource.
708
+ def resource_behaviors(parent = self)
709
+ [
710
+ Behavior.new({ :path => %r{^/new$}, :method => :get }, { :action => "new" }, parent),
711
+ Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :post }, { :action => "create" }, parent),
712
+ Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :get }, { :action => "show" }, parent),
713
+ Behavior.new({ :path => %r{^/edit$}, :method => :get }, { :action => "edit" }, parent),
714
+ Behavior.new({ :path => %r{^/delete$}, :method => :get }, { :action => "delete" }, parent),
715
+ Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :put }, { :action => "update" }, parent),
716
+ Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :delete }, { :action => "destroy" }, parent)
717
+ ]
718
+ end
719
+
720
+ # ==== Parameters
721
+ # behaviors<Array>:: The behaviors to create routes from.
722
+ # params<Hash>:: Optional params for the route generation.
723
+ # &conditional_block:: Optional block for the route generation.
724
+ #
725
+ # ==== Returns
726
+ # Array:: The routes matching the behaviors.
727
+ def many_behaviors_to(behaviors, params = {}, &conditional_block)
728
+ behaviors.map { |b| b.to params, &conditional_block }
729
+ end
730
+
731
+ # Convert conditions to regular expression string sources for consistency.
732
+ def stringify_conditions
733
+ @conditions_have_regexp = false
734
+ @conditions.each_pair do |k,v|
735
+ # TODO: Other Regexp special chars
736
+
737
+ @conditions[k] = case v
738
+ when String,Symbol
739
+ "^#{v.to_s.escape_regexp}$"
740
+ when Regexp
741
+ @conditions_have_regexp = true
742
+ v.source
743
+ end
744
+ end
745
+ end
746
+
747
+ # Store the conditions as original conditions.
748
+ def copy_original_conditions
749
+ @original_conditions = {}
750
+ @conditions.each_pair do |key, value|
751
+ @original_conditions[key] = value.dup
752
+ end
753
+ @original_conditions
754
+ end
755
+
756
+ # Calculate the behaviors from the conditions and store them.
757
+ def deduce_placeholders
758
+ @conditions.each_pair do |match_key, source|
759
+ while match = SEGMENT_REGEXP.match(source)
760
+ source.sub! SEGMENT_REGEXP, PARENTHETICAL_SEGMENT_STRING
761
+ unless match[2] == ':' # No need to store anonymous place holders
762
+ placeholder_key = match[2].intern
763
+ @params[placeholder_key] = "#{match[1]}"
764
+ @placeholders[placeholder_key] = [
765
+ match_key, Behavior.count_parens_up_to(source, match.offset(1)[0])
766
+ ]
767
+ end
768
+ end
769
+ end
770
+ end
771
+
772
+ # ==== Parameters
773
+ # list<Array>:: A list to which the ancestors should be added.
774
+ #
775
+ # ==== Returns
776
+ # Array:: All the ancestor behaviors of this behavior.
777
+ def find_ancestors(list = [])
778
+ if parent.nil?
779
+ list
780
+ else
781
+ list.push parent
782
+ parent.find_ancestors list
783
+ list
784
+ end
785
+ end
786
+
787
+ # ==== Returns
788
+ # Fixnum:: Number of regexp captures in the :path condition.
789
+ def path_captures
790
+ return 0 unless conditions[:path]
791
+ Behavior.count_parens_up_to(conditions[:path], conditions[:path].size)
792
+ end
793
+
794
+ # ==== Returns
795
+ # Fixnum:: Total number of previous path captures.
796
+ def total_previous_captures
797
+ ancestors.map{|a| a.path_captures}.inject(0){|sum, n| sum + n}
798
+ end
799
+
800
+ # def merge_with_ancestors
801
+ # self.class.new(merged_conditions, merged_params)
802
+ # end
803
+
804
+ # ==== Parameters
805
+ # conditions<Hash>::
806
+ # The conditions to compile. Defaults to merged_conditions.
807
+ #
808
+ # ==== Returns
809
+ # Hash:: The compiled conditions, with each value as a Regexp object.
810
+ def compiled_conditions(conditions = merged_conditions)
811
+ conditions.inject({}) do |compiled,(k,v)|
812
+ compiled.merge k => Regexp.new(v)
813
+ end
814
+ end
815
+
816
+ # ==== Parameters
817
+ # params<Hash>:: The params to compile. Defaults to merged_params.
818
+ # placeholders<Hash>::
819
+ # The route placeholders for this behavior. Defaults to
820
+ # merged_placeholders.
821
+ #
822
+ # ==== Returns
823
+ # String:: The params hash in an eval'able form.
824
+ #
825
+ # ==== Examples
826
+ # compiled_params({ :controller => "admin/:controller" })
827
+ # # => { :controller => "'admin/' + matches[:path][1]" }
828
+ #
829
+ def compiled_params(params = merged_params, placeholders = merged_placeholders)
830
+ compiled = {}
831
+ params.each_pair do |key, value|
832
+ unless value.is_a? String
833
+ raise ArgumentError, "param value for #{key.to_s} must be string (#{value.inspect})"
834
+ end
835
+ result = []
836
+ value = value.dup
837
+ match = true
838
+ while match
839
+ if match = SEGMENT_REGEXP_WITH_BRACKETS.match(value)
840
+ result << match.pre_match unless match.pre_match.empty?
841
+ ph_key = match[1][1..-1].intern
842
+ if match[2] # has brackets, e.g. :path[2]
843
+ result << :"#{ph_key}#{match[3]}"
844
+ else # no brackets, e.g. a named placeholder such as :controller
845
+ if place = placeholders[ph_key]
846
+ result << :"#{place[0]}#{place[1]}"
847
+ else
848
+ raise "Placeholder not found while compiling routes: :#{ph_key}"
849
+ end
850
+ end
851
+ value = match.post_match
852
+ elsif match = JUST_BRACKETS.match(value)
853
+ # This is a reference to :path
854
+ result << match.pre_match unless match.pre_match.empty?
855
+ result << :"path#{match[1]}"
856
+ value = match.post_match
857
+ else
858
+ result << value unless value.empty?
859
+ end
860
+ end
861
+ compiled[key] = Behavior.array_to_code(result).gsub("\\_", "_")
862
+ end
863
+ compiled
864
+ end
865
+ end # Behavior
866
+ end
867
+ end