merb-core 0.9.8 → 0.9.9

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 (57) hide show
  1. data/CONTRIBUTORS +33 -0
  2. data/README +7 -3
  3. data/Rakefile +3 -3
  4. data/lib/merb-core.rb +165 -94
  5. data/lib/merb-core/bootloader.rb +469 -100
  6. data/lib/merb-core/config.rb +79 -3
  7. data/lib/merb-core/constants.rb +24 -2
  8. data/lib/merb-core/controller/abstract_controller.rb +172 -67
  9. data/lib/merb-core/controller/exceptions.rb +50 -6
  10. data/lib/merb-core/controller/merb_controller.rb +215 -108
  11. data/lib/merb-core/controller/mime.rb +36 -12
  12. data/lib/merb-core/controller/mixins/authentication.rb +52 -7
  13. data/lib/merb-core/controller/mixins/conditional_get.rb +14 -0
  14. data/lib/merb-core/controller/mixins/controller.rb +90 -58
  15. data/lib/merb-core/controller/mixins/render.rb +34 -10
  16. data/lib/merb-core/controller/mixins/responder.rb +40 -16
  17. data/lib/merb-core/controller/template.rb +37 -16
  18. data/lib/merb-core/core_ext/hash.rb +9 -0
  19. data/lib/merb-core/core_ext/kernel.rb +92 -41
  20. data/lib/merb-core/dispatch/dispatcher.rb +29 -45
  21. data/lib/merb-core/dispatch/request.rb +186 -82
  22. data/lib/merb-core/dispatch/router.rb +141 -53
  23. data/lib/merb-core/dispatch/router/behavior.rb +296 -139
  24. data/lib/merb-core/dispatch/router/resources.rb +51 -19
  25. data/lib/merb-core/dispatch/router/route.rb +76 -23
  26. data/lib/merb-core/dispatch/session.rb +80 -36
  27. data/lib/merb-core/dispatch/session/container.rb +31 -15
  28. data/lib/merb-core/dispatch/session/cookie.rb +51 -22
  29. data/lib/merb-core/dispatch/session/memcached.rb +10 -6
  30. data/lib/merb-core/dispatch/session/memory.rb +17 -5
  31. data/lib/merb-core/dispatch/session/store_container.rb +21 -9
  32. data/lib/merb-core/dispatch/worker.rb +16 -2
  33. data/lib/merb-core/gem_ext/erubis.rb +4 -0
  34. data/lib/merb-core/plugins.rb +13 -0
  35. data/lib/merb-core/rack.rb +1 -0
  36. data/lib/merb-core/rack/adapter.rb +1 -0
  37. data/lib/merb-core/rack/adapter/abstract.rb +95 -17
  38. data/lib/merb-core/rack/adapter/irb.rb +50 -5
  39. data/lib/merb-core/rack/application.rb +27 -5
  40. data/lib/merb-core/rack/handler/mongrel.rb +6 -6
  41. data/lib/merb-core/rack/helpers.rb +33 -0
  42. data/lib/merb-core/rack/middleware/conditional_get.rb +1 -1
  43. data/lib/merb-core/rack/middleware/path_prefix.rb +3 -3
  44. data/lib/merb-core/rack/middleware/static.rb +11 -7
  45. data/lib/merb-core/server.rb +134 -69
  46. data/lib/merb-core/tasks/gem_management.rb +153 -80
  47. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -4
  48. data/lib/merb-core/tasks/stats.rake +1 -1
  49. data/lib/merb-core/test/helpers/mock_request_helper.rb +29 -22
  50. data/lib/merb-core/test/helpers/request_helper.rb +1 -1
  51. data/lib/merb-core/test/helpers/route_helper.rb +50 -4
  52. data/lib/merb-core/test/matchers/request_matchers.rb +2 -36
  53. data/lib/merb-core/test/matchers/view_matchers.rb +32 -22
  54. data/lib/merb-core/test/run_specs.rb +6 -5
  55. data/lib/merb-core/test/test_ext/rspec.rb +6 -19
  56. data/lib/merb-core/version.rb +1 -1
  57. metadata +5 -4
@@ -0,0 +1,33 @@
1
+ module Merb
2
+ module Rack
3
+ module Helpers
4
+
5
+ # A helper to build a rack response which implements a redirect. The status will be set to
6
+ # the passed in status if passed. If you pass in permanent it will be a 301, permanent redirect,
7
+ # otherwise it defaults to a temporary 302 redirect.
8
+ #
9
+ # ==== Parameters
10
+ # url<~to_s>:: The url to redirect to.
11
+ # options<Hash>:: A hash of options for the redirect
12
+ # status: The status code to use for the redirect
13
+ # permanent: True if this is a permanent redirect (301)
14
+ #
15
+ # ==== Returns
16
+ # <Array>:: A rack response to redirect to the specified url.
17
+ #
18
+ # @api plugin
19
+ def self.redirect(url, options = {})
20
+ # Build the rack array
21
+ status = options.delete(:status)
22
+ status ||= options[:permanent] ? 301 : 302
23
+
24
+ Merb.logger.info("Dispatcher redirecting to: #{url} (#{status})")
25
+ Merb.logger.flush
26
+
27
+ [status, { Merb::Const::LOCATION => url },
28
+ Merb::Rack::StreamWrapper.new("<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>")]
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -7,7 +7,7 @@ module Merb
7
7
 
8
8
  if document_not_modified?(env, headers)
9
9
  status = 304
10
- body = ""
10
+ body = Merb::Const::EMPTY_STRING
11
11
  # set Date header using RFC1123 date format as specified by HTTP
12
12
  # RFC2616 section 3.3.1.
13
13
  end
@@ -20,12 +20,12 @@ module Merb
20
20
  def strip_path_prefix(env)
21
21
  ['PATH_INFO', 'REQUEST_URI'].each do |path_key|
22
22
  if env[path_key] =~ @path_prefix
23
- env[path_key].sub!(@path_prefix, '')
24
- env[path_key] = '/' if env[path_key].empty?
23
+ env[path_key].sub!(@path_prefix, Merb::Const::EMPTY_STRING)
24
+ env[path_key] = Merb::Const::SLASH if env[path_key].empty?
25
25
  end
26
26
  end
27
27
  end
28
28
 
29
29
  end
30
30
  end
31
- end
31
+ end
@@ -8,16 +8,20 @@ module Merb
8
8
  end
9
9
 
10
10
  def call(env)
11
- path = env['PATH_INFO'] ? env['PATH_INFO'].chomp('/') : ""
11
+ path = if env[Merb::Const::PATH_INFO]
12
+ env[Merb::Const::PATH_INFO].chomp(Merb::Const::SLASH)
13
+ else
14
+ Merb::Const::EMPTY_STRING
15
+ end
12
16
  cached_path = (path.empty? ? 'index' : path) + '.html'
13
17
 
14
- if file_exist?(path) && env['REQUEST_METHOD'] =~ /GET|HEAD/ # Serve the file if it's there and the request method is GET or HEAD
18
+ if file_exist?(path) && env[Merb::Const::REQUEST_METHOD] =~ /GET|HEAD/ # Serve the file if it's there and the request method is GET or HEAD
15
19
  serve_static(env)
16
- elsif file_exist?(cached_path) && env['REQUEST_METHOD'] =~ /GET|HEAD/ # Serve the page cache if it's there and the request method is GET or HEAD
17
- env['PATH_INFO'] = cached_path
20
+ elsif file_exist?(cached_path) && env[Merb::Const::REQUEST_METHOD] =~ /GET|HEAD/ # Serve the page cache if it's there and the request method is GET or HEAD
21
+ env[Merb::Const::PATH_INFO] = cached_path
18
22
  serve_static(env)
19
23
  elsif path =~ /favicon\.ico/
20
- return [404, {"Content-Type"=>"text/html"}, "404 Not Found."]
24
+ return [404, { Merb::Const::CONTENT_TYPE => Merb::Const::TEXT_SLASH_HTML }, "404 Not Found."]
21
25
  else
22
26
  @app.call(env)
23
27
  end
@@ -36,10 +40,10 @@ module Merb
36
40
  # ==== Parameters
37
41
  # env<Hash>:: Environment variables to pass on to the server.
38
42
  def serve_static(env)
39
- env["PATH_INFO"] = ::Merb::Request.unescape(env["PATH_INFO"])
43
+ env[Merb::Const::PATH_INFO] = ::Merb::Request.unescape(env[Merb::Const::PATH_INFO])
40
44
  @static_server.call(env)
41
45
  end
42
46
 
43
47
  end
44
48
  end
45
- end
49
+ end
@@ -18,10 +18,12 @@ module Merb
18
18
  # ==== Alternatives
19
19
  # If cluster is left out, then one process will be started. This process
20
20
  # will be daemonized if Merb::Config[:daemonize] is true.
21
+ #
22
+ # @api private
21
23
  def start(port, cluster=nil)
22
-
24
+
23
25
  @port = port
24
- @cluster = cluster
26
+ @cluster = cluster
25
27
 
26
28
  if Merb::Config[:daemonize]
27
29
  pidfile = pid_file(port)
@@ -29,7 +31,7 @@ module Merb
29
31
 
30
32
  unless alive?(@port)
31
33
  remove_pid_file(@port)
32
- puts "Daemonizing..." if Merb::Config[:verbose]
34
+ Merb.logger.warn! "Daemonizing..." if Merb::Config[:verbose]
33
35
  daemonize(@port)
34
36
  else
35
37
  Merb.fatal! "Merb is already running on port #{port}.\n" \
@@ -47,79 +49,96 @@ module Merb
47
49
  # ==== Returns
48
50
  # Boolean::
49
51
  # True if Merb is running on the specified port.
52
+ #
53
+ # @api private
50
54
  def alive?(port)
51
- puts "About to check if port #{port} is alive..." if Merb::Config[:verbose]
52
55
  pidfile = pid_file(port)
53
- puts "Pidfile is #{pidfile}..." if Merb::Config[:verbose]
54
- pid = File.read(pidfile).chomp.to_i
55
- puts "Process id is #{pid}" if Merb::Config[:verbose]
56
+ pid = pid_in_file(pidfile)
56
57
  Process.kill(0, pid)
57
58
  true
58
59
  rescue Errno::ESRCH, Errno::ENOENT
59
60
  false
60
61
  rescue Errno::EACCES => e
61
- Merb.fatal!("You don't have access to the PID file at #{pidfile}.", e)
62
+ Merb.fatal!("You don't have access to the PID file at #{pidfile}: #{e.message}")
63
+ end
64
+
65
+ def pid_in_file(pidfile)
66
+ File.read(pidfile).chomp.to_i
62
67
  end
63
68
 
64
69
  # ==== Parameters
65
70
  # port<~to_s>:: The port of the Merb process to kill.
66
- # sig<~to_s>:: The signal to send to the process. Defaults to 9.
71
+ # sig<~to_s>:: The signal to send to the process, the default is 9 - SIGKILL.
72
+ #
73
+ # No Name Default Action Description
74
+ # 1 SIGHUP terminate process terminal line hangup
75
+ # 2 SIGINT terminate process interrupt program
76
+ # 3 SIGQUIT create core image quit program
77
+ # 4 SIGILL create core image illegal instruction
78
+ # 9 SIGKILL terminate process kill program
79
+ # 15 SIGTERM terminate process software termination signal
80
+ # 30 SIGUSR1 terminate process User defined signal 1
81
+ # 31 SIGUSR2 terminate process User defined signal 2
67
82
  #
68
83
  # ==== Alternatives
69
- # If you pass "all" as the port, the signal will be sent to all Merb
70
- # processes.
71
- def kill(port, sig="INT")
72
- Merb::BootLoader::BuildFramework.run
73
- if sig == 9 && port == "main"
74
- kill_pid("INT", pid_file("main"))
75
- Dir["#{Merb.log_path}" / "*.pid"].each do |file|
76
- kill_pid(9, file)
77
- end
78
- else
79
- kill_pid(sig, pid_file(port))
80
- end
81
-
84
+ # If you pass "all" as the port, the signal will be sent to all Merb processes.
85
+ #
86
+ # @api private
87
+ def kill(port, sig = "INT")
82
88
  if sig.is_a?(Integer)
83
89
  sig = Signal.list.invert[sig]
84
90
  end
91
+
92
+ Merb::BootLoader::BuildFramework.run
85
93
 
86
- if sig == "KILL" && port == "main"
87
- Merb.fatal! "Killed all PIDs with signal KILL"
94
+ # If we kill the master, then the workers should be reaped also.
95
+ if %w(main master all).include?(port)
96
+ # If a graceful exit is requested then send INT to the master process.
97
+ #
98
+ # Otherwise read pids from pid files and try to kill each process in turn.
99
+ kill_pid(sig, pid_file("main")) if sig == "INT"
88
100
  else
89
- Merb.fatal! "Killed #{port} with signal #{sig}"
101
+ kill_pid(sig, pid_file(port))
90
102
  end
91
103
  end
92
-
104
+
105
+ # Sends the provided signal to the process pointed at by the provided pid file.
106
+ # @api private
93
107
  def kill_pid(sig, file)
94
108
  begin
95
- pid = File.read(file).chomp.to_i
96
- Merb.logger.warn! "Killing pid #{pid}"
109
+ pid = pid_in_file(file)
110
+ Merb.logger.fatal! "Killing pid #{pid} with #{sig}"
97
111
  Process.kill(sig, pid)
98
112
  FileUtils.rm(file) if File.exist?(file)
99
113
  rescue Errno::EINVAL
100
- Merb.fatal! "Failed to kill PID #{pid}: '#{sig}' is an invalid " \
114
+ Merb.logger.fatal! "Failed to kill PID #{pid} with #{sig}: '#{sig}' is an invalid " \
101
115
  "or unsupported signal number."
102
116
  rescue Errno::EPERM
103
- Merb.fatal! "Failed to kill PID #{pid}: Insufficient permissions."
117
+ Merb.logger.fatal! "Failed to kill PID #{pid} with #{sig}: Insufficient permissions."
104
118
  rescue Errno::ESRCH
105
119
  FileUtils.rm file
106
- Merb.fatal! "Failed to kill PID #{pid}: Process is " \
120
+ Merb.logger.fatal! "Failed to kill PID #{pid} with #{sig}: Process is " \
107
121
  "deceased or zombie."
108
122
  rescue Errno::EACCES => e
109
- Merb.fatal! e.message, e
123
+ Merb.logger.fatal! e.message
110
124
  rescue Errno::ENOENT => e
111
- Merb.fatal! "Could not find a PID file at #{file}", e
125
+ # This should not cause abnormal exit, which is why
126
+ # we do not use Merb.fatal but instead just log with max level.
127
+ Merb.logger.fatal! "Could not find a PID file at #{file}. " \
128
+ "Most likely the process is no longer running and the pid file was not cleaned up."
112
129
  rescue Exception => e
113
130
  if !e.is_a?(SystemExit)
114
- Merb.fatal! "Failed to kill PID #{pid}", e
131
+ Merb.logger.fatal! "Failed to kill PID #{pid.inspect} with #{sig.inspect}: #{e.message}"
115
132
  end
116
133
  end
117
134
  end
118
135
 
119
136
  # ==== Parameters
120
137
  # port<~to_s>:: The port of the Merb process to daemonize.
138
+ #
139
+ # @api private
121
140
  def daemonize(port)
122
- puts "About to fork..." if Merb::Config[:verbose]
141
+ Merb.logger.warn! "About to fork..." if Merb::Config[:verbose]
123
142
  fork do
124
143
  Process.setsid
125
144
  exit if fork
@@ -131,26 +150,42 @@ module Merb
131
150
  begin
132
151
  Dir.chdir Merb::Config[:merb_root]
133
152
  rescue Errno::EACCES => e
134
- Merb.fatal! "You specified #{Merb::Config[:merb_root]} " \
135
- "as the Merb root, but you did not have access to it.", e
153
+ Merb.fatal! "You specified Merb root as #{Merb::Config[:merb_root]}, " \
154
+ "yet the current user does not have access to it. ", e
136
155
  end
137
156
  at_exit { remove_pid_file(port) }
138
157
  Merb::Config[:port] = port
139
158
  bootup
140
159
  end
141
160
  rescue NotImplementedError => e
142
- Merb.fatal! "Daemonized mode is not supported on your platform", e
161
+ Merb.fatal! "Daemonized mode is not supported on your platform. ", e
143
162
  end
144
163
 
164
+ # Starts up Merb by running the bootloader and starting the adapter.
165
+ #
166
+ # @api private
145
167
  def bootup
146
- Merb.trap('TERM') { exit }
168
+ Merb.trap("TERM") { shutdown }
147
169
 
148
- puts "Running bootloaders..." if Merb::Config[:verbose]
170
+ Merb.logger.warn! "Running bootloaders..." if Merb::Config[:verbose]
149
171
  BootLoader.run
150
- puts "Starting Rack adapter..." if Merb::Config[:verbose]
172
+ Merb.logger.warn! "Starting Rack adapter..." if Merb::Config[:verbose]
151
173
  Merb.adapter.start(Merb::Config.to_hash)
152
174
  end
153
175
 
176
+ # Shut down Merb, reap any workers if necessary.
177
+ #
178
+ # @api private
179
+ def shutdown(status = 0)
180
+ # reap_workers does exit but may not be called...
181
+ Merb::BootLoader::LoadClasses.reap_workers(status) if Merb::Config[:fork_for_class_load]
182
+ # which is why we exit explicitly here
183
+ exit(status)
184
+ end
185
+
186
+ # Change process user/group to those specified in Merb::Config.
187
+ #
188
+ # @api private
154
189
  def change_privilege
155
190
  if Merb::Config[:user] && Merb::Config[:group]
156
191
  Merb.logger.verbose! "About to change privilege to group " \
@@ -167,7 +202,7 @@ module Merb
167
202
 
168
203
  # Removes a PID file used by the server from the filesystem.
169
204
  # This uses :pid_file options from configuration when provided
170
- # or merb.<port>.pid in log directory by default.
205
+ # or merb.<port/socket>.pid in log directory by default.
171
206
  #
172
207
  # ==== Parameters
173
208
  # port<~to_s>::
@@ -175,11 +210,13 @@ module Merb
175
210
  #
176
211
  # ==== Alternatives
177
212
  # If Merb::Config[:pid_file] has been specified, that will be used
178
- # instead of the port based PID file.
213
+ # instead of the port/socket based PID file.
214
+ #
215
+ # @api private
179
216
  def remove_pid_file(port)
180
217
  pidfile = pid_file(port)
181
218
  if File.exist?(pidfile)
182
- puts "Removing pid file #{pidfile} (port is #{port})..."
219
+ Merb.logger.warn! "Removing pid file #{pidfile} (port/socket: #{port})..."
183
220
  FileUtils.rm(pidfile)
184
221
  end
185
222
  end
@@ -194,37 +231,61 @@ module Merb
194
231
  #
195
232
  # ==== Alternatives
196
233
  # If Merb::Config[:pid_file] has been specified, that will be used
197
- # instead of the port based PID file.
234
+ # instead of the port/socket based PID file.
235
+ #
236
+ # @api private
198
237
  def store_pid(port)
199
238
  store_details(port)
200
239
  end
201
240
 
241
+ # Delete the pidfile for the specified port/socket.
242
+ #
243
+ # @api private
202
244
  def remove_pid(port)
203
245
  FileUtils.rm(pid_file(port)) if File.file?(pid_file(port))
204
246
  end
205
247
 
248
+ # Stores a PID file on the filesystem.
249
+ # This uses :pid_file options from configuration when provided
250
+ # or merb.<port/socket>.pid in log directory by default.
251
+ #
252
+ # ==== Parameters
253
+ # port<~to_s>::
254
+ # The port of the Merb process to whom the the PID file belongs to.
255
+ #
256
+ # ==== Alternatives
257
+ # If Merb::Config[:pid_file] has been specified, that will be used
258
+ # instead of the port/socket based PID file.
259
+ #
260
+ # @api private
206
261
  def store_details(port = nil)
207
262
  file = pid_file(port)
208
263
  begin
209
264
  FileUtils.mkdir_p(File.dirname(file))
210
265
  rescue Errno::EACCES => e
211
- Merb.fatal! "You tried to store Merb logs in #{File.dirname(file)}, " \
212
- "but you did not have access.", e
266
+ Merb.fatal! "Failed to store Merb logs in #{File.dirname(file)}, " \
267
+ "permission denied. ", e
213
268
  end
214
269
  Merb.logger.warn! "Storing #{type} file to #{file}..." if Merb::Config[:verbose]
215
- File.open(file, 'w'){ |f| f.write(Process.pid.to_s) }
270
+ begin
271
+ File.open(file, 'w'){ |f| f.write(Process.pid.to_s) }
272
+ rescue Errno::EACCES => e
273
+ Merb.fatal! "Failed to access #{file}, permission denied.", e
274
+ end
216
275
  end
217
276
 
218
- # Gets the pid file for the specified port.
277
+ # Gets the pid file for the specified port/socket.
219
278
  #
220
279
  # ==== Parameters
221
280
  # port<~to_s>::
222
- # The port of the Merb process to whom the the PID file belongs to.
281
+ # The port/socket of the Merb process to whom the the PID file belongs to.
223
282
  #
224
283
  # ==== Returns
225
284
  # String::
226
285
  # Location of pid file for specified port. If clustered and pid_file option
227
- # is specified, it adds the port value to the path.
286
+ # is specified, it adds the port/socket value to the path.
287
+ #
288
+ # @api private
228
289
  def pid_file(port)
229
290
  pidfile = Merb::Config[:pid_file] || (Merb.log_path / "merb.%s.pid")
230
291
  pidfile % port
@@ -234,7 +295,9 @@ module Merb
234
295
  #
235
296
  # ==== Returns
236
297
  # Array::
237
- # List of pid file paths. If not clustered, array contains a single path.
298
+ # List of pid file paths. If not running clustered, the array contains a single path.
299
+ #
300
+ # @api private
238
301
  def pid_files
239
302
  if Merb::Config[:pid_file]
240
303
  if Merb::Config[:cluster]
@@ -250,34 +313,33 @@ module Merb
250
313
  # Change privileges of the process to the specified user and group.
251
314
  #
252
315
  # ==== Parameters
253
- # user<String>:: The user who should own the server process.
254
- # group<String>:: The group who should own the server process.
316
+ # user<String>:: The user to change the process to.
317
+ # group<String>:: The group to change the process to.
255
318
  #
256
319
  # ==== Alternatives
257
320
  # If group is left out, the user will be used as the group.
321
+ #
322
+ # @api private
258
323
  def _change_privilege(user, group=user)
259
-
260
324
  Merb.logger.warn! "Changing privileges to #{user}:#{group}"
261
325
 
262
326
  uid, gid = Process.euid, Process.egid
263
-
327
+
264
328
  begin
265
329
  target_uid = Etc.getpwnam(user).uid
266
330
  rescue ArgumentError => e
267
- Merb.fatal!(
268
- "You tried to use user #{user}, but no such user was found", e)
331
+ Merb.fatal!("Failed to change to user #{user}, does the user exist?", e)
269
332
  return false
270
333
  end
271
-
334
+
272
335
  begin
273
336
  target_gid = Etc.getgrnam(group).gid
274
337
  rescue ArgumentError => e
275
- Merb.fatal!(
276
- "You tried to use group #{group}, but no such group was found", e)
338
+ Merb.fatal!("Failed to change to group #{group}, does the group exist?", e)
277
339
  return false
278
340
  end
279
341
 
280
- if uid != target_uid || gid != target_gid
342
+ if (uid != target_uid) || (gid != target_gid)
281
343
  # Change process ownership
282
344
  Process.initgroups(user, target_gid)
283
345
  Process::GID.change_privilege(target_gid)
@@ -285,24 +347,27 @@ module Merb
285
347
  end
286
348
  true
287
349
  rescue Errno::EPERM => e
288
- Merb.fatal! "Couldn't change user and group to #{user}:#{group}", e
350
+ Merb.fatal! "Permission denied for changing user:group to #{user}:#{group}.", e
289
351
  false
290
352
  end
291
-
353
+
354
+ # Add trap to enter IRB on SIGINT. Process exit if second SIGINT is received.
355
+ #
356
+ # @api private
292
357
  def add_irb_trap
293
- Merb.trap('INT') do
358
+ Merb.trap("INT") do
294
359
  if @interrupted
295
- puts "Exiting\n"
360
+ Merb.logger.warn! "Interrupt received a second time, exiting!\n"
296
361
  exit
297
362
  end
298
363
 
299
364
  @interrupted = true
300
- puts "Interrupt a second time to quit"
365
+ Merb.logger.warn! "Interrupt a second time to quit."
301
366
  Kernel.sleep 1.5
302
367
  ARGV.clear # Avoid passing args to IRB
303
368
 
304
369
  if @irb.nil?
305
- require 'irb'
370
+ require "irb"
306
371
  IRB.setup(nil)
307
372
  @irb = IRB::Irb.new(nil)
308
373
  IRB.conf[:MAIN_CONTEXT] = @irb.context
@@ -311,7 +376,7 @@ module Merb
311
376
  Merb.trap(:INT) { @irb.signal_handle }
312
377
  catch(:IRB_EXIT) { @irb.eval_input }
313
378
 
314
- puts "Exiting IRB mode, back in server mode"
379
+ Merb.logger.warn! "Exiting from IRB mode back into server mode."
315
380
  @interrupted = false
316
381
  add_irb_trap
317
382
  end