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,97 @@
1
+ require 'stringio'
2
+ class Mongrel::HttpResponse
3
+ NO_CLOSE_STATUS_FORMAT = "HTTP/1.1 %d %s\r\n".freeze
4
+
5
+ # Sends the status to the client without closing the connection.
6
+ #
7
+ # ==== Parameters
8
+ # content_length<Fixnum>:: The length of the content. Defaults to body length.
9
+ def send_status_no_connection_close(content_length=@body.length)
10
+ unless @status_sent
11
+ write(NO_CLOSE_STATUS_FORMAT % [@status, Mongrel::HTTP_STATUS_CODES[@status]])
12
+ @status_sent = true
13
+ end
14
+ end
15
+ end
16
+
17
+ module Merb
18
+ module Rack
19
+ module Handler
20
+ class Mongrel < ::Mongrel::HttpHandler
21
+ # Runs the server and yields it to a block.
22
+ #
23
+ # ==== Parameters
24
+ # app<Merb::Rack::Application>:: The app that Mongrel should handle.
25
+ # options<Hash>:: Options to pass to Mongrel (see below).
26
+ #
27
+ # ==== Block parameters
28
+ # server<Mongrel::HttpServer>:: The server to run.
29
+ #
30
+ # ==== Options (options)
31
+ # :Host<String>::
32
+ # The hostname on which the app should run. Defaults to "0.0.0.0"
33
+ # :Port<Fixnum>:: The port for the app. Defaults to 8080.
34
+ def self.run(app, options={})
35
+ server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0',
36
+ options[:Port] || 8080)
37
+ server.register('/', ::Merb::Rack::Handler::Mongrel.new(app))
38
+ yield server if block_given?
39
+ server.run.join
40
+ end
41
+
42
+ # ==== Parameters
43
+ # app<Merb::Rack::Application>:: The app that Mongrel should handle.
44
+ def initialize(app)
45
+ @app = app
46
+ end
47
+
48
+ # ==== Parameters
49
+ # request<Merb::Request>:: The HTTP request to handle.
50
+ # response<HTTPResponse>:: The response object to write response to.
51
+ def process(request, response)
52
+ env = {}.replace(request.params)
53
+ env.delete "HTTP_CONTENT_TYPE"
54
+ env.delete "HTTP_CONTENT_LENGTH"
55
+
56
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
57
+
58
+ env.update({"rack.version" => [0,1],
59
+ "rack.input" => request.body || StringIO.new(""),
60
+ "rack.errors" => STDERR,
61
+
62
+ "rack.multithread" => true,
63
+ "rack.multiprocess" => false, # ???
64
+ "rack.run_once" => false,
65
+
66
+ "rack.url_scheme" => "http",
67
+ "rack.streaming" => true
68
+ })
69
+ env["QUERY_STRING"] ||= ""
70
+ env.delete "PATH_INFO" if env["PATH_INFO"] == ""
71
+
72
+ status, headers, body = @app.call(env)
73
+
74
+ begin
75
+ response.status = status.to_i
76
+ headers.each { |k, vs|
77
+ vs.each { |v|
78
+ response.header[k] = v
79
+ }
80
+ }
81
+
82
+ if Proc === body
83
+ body.call(response)
84
+ else
85
+ body.each { |part|
86
+ response.body << part
87
+ }
88
+ end
89
+ response.finished
90
+ ensure
91
+ body.close if body.respond_to? :close
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,184 @@
1
+ require 'etc'
2
+ module Merb
3
+
4
+ # Server encapsulates the management of Merb daemons.
5
+ class Server
6
+ class << self
7
+
8
+ # Start a Merb server, in either foreground, daemonized or cluster mode.
9
+ #
10
+ # ==== Parameters
11
+ # port<~to_i>::
12
+ # The port to which the first server instance should bind to.
13
+ # Subsequent server instances bind to the immediately following ports.
14
+ # cluster<~to_i>::
15
+ # Number of servers to run in a cluster.
16
+ #
17
+ # ==== Alternatives
18
+ # If cluster is left out, then one process will be started. This process
19
+ # will be daemonized if Merb::Config[:daemonize] is true.
20
+ def start(port, cluster=nil)
21
+ @port = port
22
+ @cluster = cluster
23
+ if @cluster
24
+ @port.to_i.upto(@port.to_i + @cluster.to_i-1) do |port|
25
+ unless alive?(port)
26
+ remove_pid_file(port)
27
+ puts "Starting merb server on port: #{port}"
28
+ daemonize(port)
29
+ else
30
+ raise "Merb is already running on port: #{port}"
31
+ end
32
+ end
33
+ elsif Merb::Config[:daemonize]
34
+ unless alive?(@port)
35
+ remove_pid_file(@port)
36
+ daemonize(@port)
37
+ else
38
+ raise "Merb is already running on port: #{port}"
39
+ end
40
+ else
41
+ trap('TERM') { exit }
42
+ BootLoader.run
43
+ Merb.adapter.start(Merb::Config.to_hash)
44
+ end
45
+ end
46
+
47
+ # ==== Parameters
48
+ # port<~to_s>:: The port to check for Merb instances on.
49
+ #
50
+ # ==== Returns
51
+ # Boolean::
52
+ # True if Merb is running on the specified port.
53
+ def alive?(port)
54
+ f = "#{Merb.log_path}" / "merb.#{port}.pid"
55
+ pid = IO.read(f).chomp.to_i
56
+ Process.kill(0, pid)
57
+ true
58
+ rescue
59
+ false
60
+ end
61
+
62
+ # ==== Parameters
63
+ # port<~to_s>:: The port of the Merb process to kill.
64
+ # sig<~to_s>:: The signal to send to the process. Defaults to 9.
65
+ #
66
+ # ==== Alternatives
67
+ # If you pass "all" as the port, the signal will be sent to all Merb
68
+ # processes.
69
+ def kill(port, sig=9)
70
+ Merb::BootLoader::BuildFramework.run
71
+ begin
72
+ Dir[Merb.log_path/ "merb.#{port == 'all' ? '*' : port }.pid"].each do |f|
73
+ pid = IO.read(f).chomp.to_i
74
+ begin
75
+ Process.kill(sig, pid)
76
+ FileUtils.rm(f) if File.exist?(f)
77
+ puts "killed PID #{pid} with signal #{sig}"
78
+ rescue Errno::EINVAL
79
+ puts "Failed to kill PID #{pid}: '#{sig}' is an invalid or unsupported signal number."
80
+ rescue Errno::EPERM
81
+ puts "Failed to kill PID #{pid}: Insufficient permissions."
82
+ rescue Errno::ESRCH
83
+ puts "Failed to kill PID #{pid}: Process is deceased or zombie."
84
+ FileUtils.rm f
85
+ rescue Exception => e
86
+ puts "Failed to kill PID #{pid}: #{e.message}"
87
+ end
88
+ end
89
+ ensure
90
+ exit
91
+ end
92
+ end
93
+
94
+ # ==== Parameters
95
+ # port<~to_s>:: The port of the Merb process to daemonize.
96
+ def daemonize(port)
97
+ fork do
98
+ Process.setsid
99
+ exit if fork
100
+ File.umask 0000
101
+ STDIN.reopen "/dev/null"
102
+ STDOUT.reopen "/dev/null", "a"
103
+ STDERR.reopen STDOUT
104
+ trap("TERM") { exit }
105
+ Dir.chdir Merb::Config[:merb_root]
106
+ at_exit { remove_pid_file(port) }
107
+ Merb::Config[:port] = port
108
+ if Merb::Config[:user]
109
+ if Merb::Config[:group]
110
+ change_privilege(Merb::Config[:user], Merb::Config[:group])
111
+ else
112
+ change_privilege(Merb::Config[:user])
113
+ end
114
+ end
115
+ BootLoader.run
116
+ Merb.adapter.start(Merb::Config.to_hash)
117
+ end
118
+ end
119
+
120
+ # Removes a PID file from the filesystem.
121
+ #
122
+ # ==== Parameters
123
+ # port<~to_s>::
124
+ # The port of the Merb process to whom the the PID file belongs to.
125
+ #
126
+ # ==== Alternatives
127
+ # If Merb::Config[:pid_file] has been specified, that will be used
128
+ # instead of the port based PID file.
129
+ def remove_pid_file(port)
130
+ if Merb::Config[:pid_file]
131
+ pidfile = Merb::Config[:pid_file]
132
+ else
133
+ pidfile = Merb.log_path / "merb.#{port}.pid"
134
+ end
135
+ FileUtils.rm(pidfile) if File.exist?(pidfile)
136
+ end
137
+
138
+ # Stores a PID file on the filesystem.
139
+ #
140
+ # ==== Parameters
141
+ # port<~to_s>::
142
+ # The port of the Merb process to whom the the PID file belongs to.
143
+ #
144
+ # ==== Alternatives
145
+ # If Merb::Config[:pid_file] has been specified, that will be used
146
+ # instead of the port based PID file.
147
+ def store_pid(port)
148
+ FileUtils.mkdir_p(Merb.log_path) unless File.directory?(Merb.log_path)
149
+ if Merb::Config[:pid_file]
150
+ pidfile = Merb::Config[:pid_file]
151
+ else
152
+ pidfile = Merb.log_path / "merb.#{port}.pid"
153
+ end
154
+ File.open(pidfile, 'w'){ |f| f.write("#{Process.pid}") }
155
+ end
156
+
157
+ # Change privileges of the process to the specified user and group.
158
+ #
159
+ # ==== Parameters
160
+ # user<String>:: The user who should own the server process.
161
+ # group<String>:: The group who should own the server process.
162
+ #
163
+ # ==== Alternatives
164
+ # If group is left out, the user will be used as the group.
165
+ def change_privilege(user, group=user)
166
+
167
+ puts "Changing privileges to #{user}:#{group}"
168
+
169
+ uid, gid = Process.euid, Process.egid
170
+ target_uid = Etc.getpwnam(user).uid
171
+ target_gid = Etc.getgrnam(group).gid
172
+
173
+ if uid != target_uid || gid != target_gid
174
+ # Change process ownership
175
+ Process.initgroups(user, target_gid)
176
+ Process::GID.change_privilege(target_gid)
177
+ Process::UID.change_privilege(target_uid)
178
+ end
179
+ rescue Errno::EPERM => e
180
+ puts "Couldn't change user and group to #{user}:#{group}: #{e}"
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,10 @@
1
+ require "hpricot"
2
+
3
+ require 'merb-core/test/test_ext/hpricot'
4
+ require 'merb-core/test/test_ext/object'
5
+
6
+ module Merb; module Test; end; end
7
+
8
+ require 'merb-core/test/helpers'
9
+
10
+ require 'merb-core/test/matchers'
@@ -0,0 +1,9 @@
1
+ # This is a place holder to allow plugins etc a place to include
2
+ # testing helpers
3
+ module Merb::Test::Helpers; end
4
+
5
+ require "merb-core/test/helpers/request_helper"
6
+ require "merb-core/test/helpers/multipart_request_helper"
7
+ require "merb-core/test/helpers/controller_helper"
8
+ require "merb-core/test/helpers/route_helper"
9
+ require "merb-core/test/helpers/view_helper"
@@ -0,0 +1,8 @@
1
+ module Merb
2
+ module Test
3
+ module ControllerHelper
4
+ include RequestHelper
5
+ include MultipartRequestHelper
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,175 @@
1
+ module Merb::Test::MultipartRequestHelper
2
+ require 'rubygems'
3
+ require 'mime/types'
4
+
5
+ class Param
6
+ attr_accessor :key, :value
7
+
8
+ # ==== Parameters
9
+ # key<~to_s>:: The parameter key.
10
+ # value<~to_s>:: The parameter value.
11
+ def initialize(key, value)
12
+ @key = key
13
+ @value = value
14
+ end
15
+
16
+ # ==== Returns
17
+ # String:: The parameter in a form suitable for a multipart request.
18
+ def to_multipart
19
+ return %(Content-Disposition: form-data; name="#{key}"\r\n\r\n#{value}\r\n)
20
+ end
21
+ end
22
+
23
+ class FileParam
24
+ attr_accessor :key, :filename, :content
25
+
26
+ # ==== Parameters
27
+ # key<~to_s>:: The parameter key.
28
+ # filename<~to_s>:: Name of the file for this parameter.
29
+ # content<~to_s>:: Content of the file for this parameter.
30
+ def initialize(key, filename, content)
31
+ @key = key
32
+ @filename = filename
33
+ @content = content
34
+ end
35
+
36
+ # ==== Returns
37
+ # String::
38
+ # The file parameter in a form suitable for a multipart request.
39
+ def to_multipart
40
+ return %(Content-Disposition: form-data; name="#{key}"; filename="#{filename}"\r\n) + "Content-Type: #{MIME::Types.type_for(@filename)}\r\n\r\n" + content + "\r\n"
41
+ end
42
+ end
43
+
44
+ class Post
45
+ BOUNDARY = '----------0xKhTmLbOuNdArY'
46
+ CONTENT_TYPE = "multipart/form-data, boundary=" + BOUNDARY
47
+
48
+ # ==== Parameters
49
+ # params<Hash>:: Optional params for the controller.
50
+ def initialize(params = {})
51
+ @multipart_params = []
52
+ push_params(params)
53
+ end
54
+
55
+ # Saves the params in an array of multipart params as Param and
56
+ # FileParam objects.
57
+ #
58
+ # ==== Parameters
59
+ # params<Hash>:: The params to add to the multipart params.
60
+ # prefix<~to_s>:: An optional prefix for the request string keys.
61
+ def push_params(params, prefix = nil)
62
+ params.sort_by {|k| k.to_s}.each do |key, value|
63
+ param_key = prefix.nil? ? key : "#{prefix}[#{key}]"
64
+ if value.respond_to?(:read)
65
+ @multipart_params << FileParam.new(param_key, value.path, value.read)
66
+ else
67
+ if value.is_a?(Hash) || value.is_a?(Mash)
68
+ value.keys.each do |k|
69
+ push_params(value, param_key)
70
+ end
71
+ else
72
+ @multipart_params << Param.new(param_key, value)
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ # ==== Returns
79
+ # Array[String, String]:: The query and the content type.
80
+ def to_multipart
81
+ query = @multipart_params.collect { |param| "--" + BOUNDARY + "\r\n" + param.to_multipart }.join("") + "--" + BOUNDARY + "--"
82
+ return query, CONTENT_TYPE
83
+ end
84
+ end
85
+
86
+ # Similar to dispatch_to but allows for sending files inside params.
87
+ #
88
+ # ==== Paramters
89
+ # controller_klass<Controller>::
90
+ # The controller class object that the action should be dispatched to.
91
+ # action<Symbol>:: The action name, as a symbol.
92
+ # params<Hash>::
93
+ # An optional hash that will end up as params in the controller instance.
94
+ # env<Hash>::
95
+ # An optional hash that is passed to the fake request. Any request options
96
+ # should go here (see +fake_request+).
97
+ # &blk:: The block is executed in the context of the controller.
98
+ #
99
+ # ==== Example
100
+ # dispatch_multipart_to(MyController, :create, :my_file => @a_file ) do
101
+ # self.stub!(:current_user).and_return(@user)
102
+ # end
103
+ #
104
+ # ==== Note
105
+ # Set your option to contain a file object to simulate file uploads.
106
+ #
107
+ # Does not use routes.
108
+ #---
109
+ # @public
110
+ def dispatch_multipart_to(controller_klass, action, params = {}, env = {}, &blk)
111
+ request = multipart_fake_request(env, params)
112
+ dispatch_request(request, controller_klass, action, &blk)
113
+ end
114
+
115
+ # An HTTP POST request that operates through the router and uses multipart
116
+ # parameters.
117
+ #
118
+ # ==== Parameters
119
+ # path<String>:: The path that should go to the router as the request uri.
120
+ # params<Hash>::
121
+ # An optional hash that will end up as params in the controller instance.
122
+ # env<Hash>::
123
+ # An optional hash that is passed to the fake request. Any request options
124
+ # should go here (see +fake_request+).
125
+ # block<Proc>:: The block is executed in the context of the controller.
126
+ #
127
+ # ==== Note
128
+ # To include an uploaded file, put a file object as a value in params.
129
+ def multipart_post(path, params = {}, env = {}, &block)
130
+ env[:request_method] = "POST"
131
+ env[:test_with_multipart] = true
132
+ request(path, params, env, &block)
133
+ end
134
+
135
+ # An HTTP PUT request that operates through the router and uses multipart
136
+ # parameters.
137
+ #
138
+ # ==== Parameters
139
+ # path<String>:: The path that should go to the router as the request uri.
140
+ # params<Hash>::
141
+ # An optional hash that will end up as params in the controller instance.
142
+ # env<Hash>::
143
+ # An optional hash that is passed to the fake request. Any request options
144
+ # should go here (see +fake_request+).
145
+ # block<Proc>:: The block is executed in the context of the controller.
146
+ #
147
+ # ==== Note
148
+ # To include an uplaoded file, put a file object as a value in params.
149
+ def multipart_put(path, params = {}, env = {}, &block)
150
+ env[:request_method] = "PUT"
151
+ env[:test_with_multipart] = true
152
+ request(path, params, env, &block)
153
+ end
154
+
155
+ # ==== Parameters
156
+ # env<Hash>::
157
+ # An optional hash that is passed to the fake request. Any request options
158
+ # should go here (see +fake_request+).
159
+ # params<Hash>::
160
+ # An optional hash that will end up as params in the controller instance.
161
+ #
162
+ # ==== Returns
163
+ # FakeRequest::
164
+ # A multipart Request object that is built based on the parameters.
165
+ def multipart_fake_request(env = {}, params = {})
166
+ if params.empty?
167
+ fake_request(env)
168
+ else
169
+ m = Post.new(params)
170
+ body, head = m.to_multipart
171
+ fake_request(env.merge( :content_type => head,
172
+ :content_length => body.length), :post_body => body)
173
+ end
174
+ end
175
+ end