merb-core 0.9.2

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