merb-core 0.9.2

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