thorero 0.9.4.4 → 0.9.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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