joe-merb-core 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +992 -0
  2. data/CONTRIBUTORS +94 -0
  3. data/LICENSE +20 -0
  4. data/PUBLIC_CHANGELOG +142 -0
  5. data/README +21 -0
  6. data/Rakefile +456 -0
  7. data/TODO +0 -0
  8. data/bin/merb +11 -0
  9. data/bin/merb-specs +5 -0
  10. data/lib/merb-core.rb +648 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +889 -0
  13. data/lib/merb-core/config.rb +380 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +620 -0
  16. data/lib/merb-core/controller/exceptions.rb +302 -0
  17. data/lib/merb-core/controller/merb_controller.rb +283 -0
  18. data/lib/merb-core/controller/mime.rb +111 -0
  19. data/lib/merb-core/controller/mixins/authentication.rb +123 -0
  20. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  21. data/lib/merb-core/controller/mixins/controller.rb +316 -0
  22. data/lib/merb-core/controller/mixins/render.rb +513 -0
  23. data/lib/merb-core/controller/mixins/responder.rb +469 -0
  24. data/lib/merb-core/controller/template.rb +254 -0
  25. data/lib/merb-core/core_ext.rb +9 -0
  26. data/lib/merb-core/core_ext/hash.rb +7 -0
  27. data/lib/merb-core/core_ext/kernel.rb +345 -0
  28. data/lib/merb-core/dispatch/cookies.rb +130 -0
  29. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  30. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +200 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +77 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +98 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +172 -0
  34. data/lib/merb-core/dispatch/request.rb +718 -0
  35. data/lib/merb-core/dispatch/router.rb +228 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +610 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +220 -0
  39. data/lib/merb-core/dispatch/router/route.rb +560 -0
  40. data/lib/merb-core/dispatch/session.rb +222 -0
  41. data/lib/merb-core/dispatch/session/container.rb +74 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +173 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +68 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +99 -0
  45. data/lib/merb-core/dispatch/session/store_container.rb +150 -0
  46. data/lib/merb-core/dispatch/worker.rb +28 -0
  47. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  48. data/lib/merb-core/logger.rb +215 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +27 -0
  51. data/lib/merb-core/rack/adapter.rb +47 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +24 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +13 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +119 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +33 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +14 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +40 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +17 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +72 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +96 -0
  64. data/lib/merb-core/rack/middleware.rb +20 -0
  65. data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
  66. data/lib/merb-core/rack/middleware/content_length.rb +18 -0
  67. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  68. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  69. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  70. data/lib/merb-core/rack/middleware/static.rb +45 -0
  71. data/lib/merb-core/rack/middleware/tracer.rb +20 -0
  72. data/lib/merb-core/server.rb +321 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +252 -0
  75. data/lib/merb-core/tasks/merb.rb +2 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +51 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +17 -0
  79. data/lib/merb-core/test/helpers.rb +10 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +176 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +61 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +47 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +10 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +108 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +393 -0
  89. data/lib/merb-core/test/run_specs.rb +141 -0
  90. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  91. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  92. data/lib/merb-core/test/test_ext/object.rb +14 -0
  93. data/lib/merb-core/test/test_ext/string.rb +14 -0
  94. data/lib/merb-core/vendor/facets.rb +2 -0
  95. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  96. data/lib/merb-core/vendor/facets/inflect.rb +342 -0
  97. data/lib/merb-core/version.rb +3 -0
  98. metadata +253 -0
@@ -0,0 +1,20 @@
1
+ module Merb
2
+ module Rack
3
+ class Tracer < Merb::Rack::Middleware
4
+
5
+ def call(env)
6
+
7
+ Merb.logger.debug!("Rack environment:\n" + env.inspect + "\n\n")
8
+
9
+ status, headers, body = @app.call(env)
10
+
11
+ Merb.logger.debug!("Status: #{status.inspect}")
12
+ Merb.logger.debug!("Headers: #{headers.inspect}")
13
+ Merb.logger.debug!("Body: #{body.inspect}")
14
+
15
+ [status, headers, body]
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,321 @@
1
+ require 'etc'
2
+
3
+ module Merb
4
+
5
+ # Server encapsulates the management of Merb daemons.
6
+ class Server
7
+ class << self
8
+
9
+ # Start a Merb server, in either foreground, daemonized or cluster mode.
10
+ #
11
+ # ==== Parameters
12
+ # port<~to_i>::
13
+ # The port to which the first server instance should bind to.
14
+ # Subsequent server instances bind to the immediately following ports.
15
+ # cluster<~to_i>::
16
+ # Number of servers to run in a cluster.
17
+ #
18
+ # ==== Alternatives
19
+ # If cluster is left out, then one process will be started. This process
20
+ # will be daemonized if Merb::Config[:daemonize] is true.
21
+ def start(port, cluster=nil)
22
+
23
+ @port = port
24
+ @cluster = cluster
25
+
26
+ if Merb::Config[:daemonize]
27
+ pidfile = pid_file(port)
28
+ pid = File.read(pidfile).chomp.to_i if File.exist?(pidfile)
29
+
30
+ unless alive?(@port)
31
+ remove_pid_file(@port)
32
+ puts "Daemonizing..." if Merb::Config[:verbose]
33
+ daemonize(@port)
34
+ else
35
+ Merb.fatal! "Merb is already running on port #{port}.\n" \
36
+ "\e[0m \e[1;31;47mpid file: \e[34;47m#{pidfile}" \
37
+ "\e[1;31;47m, process id is \e[34;47m#{pid}."
38
+ end
39
+ else
40
+ bootup
41
+ end
42
+ end
43
+
44
+ # ==== Parameters
45
+ # port<~to_s>:: The port to check for Merb instances on.
46
+ #
47
+ # ==== Returns
48
+ # Boolean::
49
+ # True if Merb is running on the specified port.
50
+ def alive?(port)
51
+ puts "About to check if port #{port} is alive..." if Merb::Config[:verbose]
52
+ 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
+ Process.kill(0, pid)
57
+ true
58
+ rescue Errno::ESRCH, Errno::ENOENT
59
+ false
60
+ rescue Errno::EACCES => e
61
+ Merb.fatal!("You don't have access to the PID file at #{pidfile}.", e)
62
+ end
63
+
64
+ # ==== Parameters
65
+ # 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.
67
+ #
68
+ # ==== 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
+
82
+ if sig.is_a?(Integer)
83
+ sig = Signal.list.invert[sig]
84
+ end
85
+
86
+ if sig == "KILL" && port == "main"
87
+ Merb.fatal! "Killed all PIDs with signal KILL"
88
+ else
89
+ Merb.fatal! "Killed #{port} with signal #{sig}"
90
+ end
91
+ end
92
+
93
+ def kill_pid(sig, file)
94
+ begin
95
+ pid = File.read(file).chomp.to_i
96
+ Merb.logger.warn! "Killing pid #{pid}"
97
+ Process.kill(sig, pid)
98
+ FileUtils.rm(file) if File.exist?(file)
99
+ rescue Errno::EINVAL
100
+ Merb.fatal! "Failed to kill PID #{pid}: '#{sig}' is an invalid " \
101
+ "or unsupported signal number."
102
+ rescue Errno::EPERM
103
+ Merb.fatal! "Failed to kill PID #{pid}: Insufficient permissions."
104
+ rescue Errno::ESRCH
105
+ FileUtils.rm file
106
+ Merb.fatal! "Failed to kill PID #{pid}: Process is " \
107
+ "deceased or zombie."
108
+ rescue Errno::EACCES => e
109
+ Merb.fatal! e.message, e
110
+ rescue Errno::ENOENT => e
111
+ Merb.fatal! "Could not find a PID file at #{file}", e
112
+ rescue Exception => e
113
+ if !e.is_a?(SystemExit)
114
+ Merb.fatal! "Failed to kill PID #{pid}", e
115
+ end
116
+ end
117
+ end
118
+
119
+ # ==== Parameters
120
+ # port<~to_s>:: The port of the Merb process to daemonize.
121
+ def daemonize(port)
122
+ puts "About to fork..." if Merb::Config[:verbose]
123
+ fork do
124
+ Process.setsid
125
+ exit if fork
126
+ Merb.logger.warn! "In #{Process.pid}" if Merb.logger
127
+ File.umask 0000
128
+ STDIN.reopen "/dev/null"
129
+ STDOUT.reopen "/dev/null", "a"
130
+ STDERR.reopen STDOUT
131
+ begin
132
+ Dir.chdir Merb::Config[:merb_root]
133
+ 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
136
+ end
137
+ at_exit { remove_pid_file(port) }
138
+ Merb::Config[:port] = port
139
+ bootup
140
+ end
141
+ rescue NotImplementedError => e
142
+ Merb.fatal! "Daemonized mode is not supported on your platform", e
143
+ end
144
+
145
+ def bootup
146
+ trap('TERM') { exit }
147
+
148
+ puts "Running bootloaders..." if Merb::Config[:verbose]
149
+ BootLoader.run
150
+ puts "Starting Rack adapter..." if Merb::Config[:verbose]
151
+ Merb.adapter.start(Merb::Config.to_hash)
152
+ end
153
+
154
+ def change_privilege
155
+ if Merb::Config[:user] && Merb::Config[:group]
156
+ Merb.logger.verbose! "About to change privilege to group " \
157
+ "#{Merb::Config[:group]} and user #{Merb::Config[:user]}"
158
+ _change_privilege(Merb::Config[:user], Merb::Config[:group])
159
+ elsif Merb::Config[:user]
160
+ Merb.logger.verbose! "About to change privilege to user " \
161
+ "#{Merb::Config[:user]}"
162
+ _change_privilege(Merb::Config[:user])
163
+ else
164
+ return true
165
+ end
166
+ end
167
+
168
+ # Removes a PID file used by the server from the filesystem.
169
+ # This uses :pid_file options from configuration when provided
170
+ # or merb.<port>.pid in log directory by default.
171
+ #
172
+ # ==== Parameters
173
+ # port<~to_s>::
174
+ # The port of the Merb process to whom the the PID file belongs to.
175
+ #
176
+ # ==== Alternatives
177
+ # If Merb::Config[:pid_file] has been specified, that will be used
178
+ # instead of the port based PID file.
179
+ def remove_pid_file(port)
180
+ pidfile = pid_file(port)
181
+ if File.exist?(pidfile)
182
+ puts "Removing pid file #{pidfile} (port is #{port})..."
183
+ FileUtils.rm(pidfile)
184
+ end
185
+ end
186
+
187
+ # Stores a PID file on the filesystem.
188
+ # This uses :pid_file options from configuration when provided
189
+ # or merb.<port>.pid in log directory by default.
190
+ #
191
+ # ==== Parameters
192
+ # port<~to_s>::
193
+ # The port of the Merb process to whom the the PID file belongs to.
194
+ #
195
+ # ==== Alternatives
196
+ # If Merb::Config[:pid_file] has been specified, that will be used
197
+ # instead of the port based PID file.
198
+ def store_pid(port)
199
+ store_details(port)
200
+ end
201
+
202
+ def remove_pid(port)
203
+ FileUtils.rm(pid_file(port)) if File.file?(pid_file(port))
204
+ end
205
+
206
+ def store_details(port = nil)
207
+ file = pid_file(port)
208
+ begin
209
+ FileUtils.mkdir_p(File.dirname(file))
210
+ 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
213
+ end
214
+ Merb.logger.warn! "Storing #{type} file to #{file}..." if Merb::Config[:verbose]
215
+ File.open(file, 'w'){ |f| f.write(Process.pid.to_s) }
216
+ end
217
+
218
+ # Gets the pid file for the specified port.
219
+ #
220
+ # ==== Parameters
221
+ # port<~to_s>::
222
+ # The port of the Merb process to whom the the PID file belongs to.
223
+ #
224
+ # ==== Returns
225
+ # String::
226
+ # Location of pid file for specified port. If clustered and pid_file option
227
+ # is specified, it adds the port value to the path.
228
+ def pid_file(port)
229
+ pidfile = Merb::Config[:pid_file] || (Merb.log_path / "merb.%s.pid")
230
+ pidfile % port
231
+ end
232
+
233
+ # Get a list of the pid files.
234
+ #
235
+ # ==== Returns
236
+ # Array::
237
+ # List of pid file paths. If not clustered, array contains a single path.
238
+ def pid_files
239
+ if Merb::Config[:pid_file]
240
+ if Merb::Config[:cluster]
241
+ Dir[Merb::Config[:pid_file] % "*"]
242
+ else
243
+ [ Merb::Config[:pid_file] ]
244
+ end
245
+ else
246
+ Dir[Merb.log_path / "merb.*.pid"]
247
+ end
248
+ end
249
+
250
+ # Change privileges of the process to the specified user and group.
251
+ #
252
+ # ==== Parameters
253
+ # user<String>:: The user who should own the server process.
254
+ # group<String>:: The group who should own the server process.
255
+ #
256
+ # ==== Alternatives
257
+ # If group is left out, the user will be used as the group.
258
+ def _change_privilege(user, group=user)
259
+
260
+ Merb.logger.warn! "Changing privileges to #{user}:#{group}"
261
+
262
+ uid, gid = Process.euid, Process.egid
263
+
264
+ begin
265
+ target_uid = Etc.getpwnam(user).uid
266
+ rescue ArgumentError => e
267
+ Merb.fatal!(
268
+ "You tried to use user #{user}, but no such user was found", e)
269
+ return false
270
+ end
271
+
272
+ begin
273
+ target_gid = Etc.getgrnam(group).gid
274
+ rescue ArgumentError => e
275
+ Merb.fatal!(
276
+ "You tried to use group #{group}, but no such group was found", e)
277
+ return false
278
+ end
279
+
280
+ if uid != target_uid || gid != target_gid
281
+ # Change process ownership
282
+ Process.initgroups(user, target_gid)
283
+ Process::GID.change_privilege(target_gid)
284
+ Process::UID.change_privilege(target_uid)
285
+ end
286
+ true
287
+ rescue Errno::EPERM => e
288
+ Merb.fatal! "Couldn't change user and group to #{user}:#{group}", e
289
+ false
290
+ end
291
+
292
+ def add_irb_trap
293
+ trap('INT') do
294
+ if @interrupted
295
+ puts "Exiting\n"
296
+ exit
297
+ end
298
+
299
+ @interrupted = true
300
+ puts "Interrupt a second time to quit"
301
+ Kernel.sleep 1.5
302
+ ARGV.clear # Avoid passing args to IRB
303
+
304
+ if @irb.nil?
305
+ require 'irb'
306
+ IRB.setup(nil)
307
+ @irb = IRB::Irb.new(nil)
308
+ IRB.conf[:MAIN_CONTEXT] = @irb.context
309
+ end
310
+
311
+ trap(:INT) { @irb.signal_handle }
312
+ catch(:IRB_EXIT) { @irb.eval_input }
313
+
314
+ puts "Exiting IRB mode, back in server mode"
315
+ @interrupted = false
316
+ add_irb_trap
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,68 @@
1
+ namespace :audit do
2
+
3
+ desc "Print out the named and anonymous routes"
4
+ task :routes => :merb_env do
5
+ seen = []
6
+ unless Merb::Router.named_routes.empty?
7
+ puts "Named Routes"
8
+ Merb::Router.named_routes.each do |name,route|
9
+ puts " #{name}: #{route}"
10
+ seen << route
11
+ end
12
+ end
13
+ puts "Anonymous Routes"
14
+ (Merb::Router.routes - seen).each do |route|
15
+ puts " #{route}"
16
+ end
17
+ nil
18
+ end
19
+
20
+ desc "Print out all controllers"
21
+ task :controllers => :merb_env do
22
+ puts "\nControllers:\n\n"
23
+ abstract_controller_classes.each do |klass|
24
+ if klass.respond_to?(:subclasses_list)
25
+ puts "#{klass} < #{klass.superclass}"
26
+ subklasses = klass.subclasses_list.sort.map { |x| Object.full_const_get(x) }
27
+ unless subklasses.empty?
28
+ subklasses.each { |subklass| puts "- #{subklass}" }
29
+ else
30
+ puts "~ no subclasses"
31
+ end
32
+ puts
33
+ end
34
+ end
35
+ end
36
+
37
+ desc "Print out controllers and their actions (use CONTROLLER=Foo,Bar to be selective)"
38
+ task :actions => :merb_env do
39
+ puts "\nControllers and their actions:\n\n"
40
+ filter_controllers = ENV['CONTROLLER'] ? ENV['CONTROLLER'].split(',') : nil
41
+ abstract_controllers = abstract_controller_classes
42
+ classes = Merb::AbstractController.subclasses_list.sort.map { |x| Object.full_const_get(x) }
43
+ classes = classes.select { |k| k.name.in?(filter_controllers) } if filter_controllers
44
+ classes.each do |subklass|
45
+ next if subklass.in?(abstract_controllers) || !subklass.respond_to?(:callable_actions)
46
+ puts "#{subklass} < #{subklass.superclass}"
47
+ unless subklass.callable_actions.empty?
48
+ subklass.callable_actions.sort.each do |action, null|
49
+ if subklass.respond_to?(:action_argument_list)
50
+ arguments, defaults = subklass.action_argument_list[action]
51
+ args = arguments.map { |name, value| value ? "#{name} = #{value.inspect}" : name.to_s }.join(', ')
52
+ puts args.empty? ? "- #{action}" : "- #{action}(#{args})"
53
+ else
54
+ puts "- #{action}"
55
+ end
56
+ end
57
+ else
58
+ puts "~ no callable actions"
59
+ end
60
+ puts
61
+ end
62
+ end
63
+
64
+ def abstract_controller_classes
65
+ ObjectSpace.classes.select { |x| x.superclass == Merb::AbstractController }.sort_by { |x| x.name }
66
+ end
67
+
68
+ end
@@ -0,0 +1,252 @@
1
+ require 'rubygems'
2
+ require 'rubygems/dependency_installer'
3
+ require 'rubygems/uninstaller'
4
+ require 'rubygems/dependency'
5
+
6
+ module ColorfulMessages
7
+
8
+ # red
9
+ def error(*messages)
10
+ puts messages.map { |msg| "\033[1;31m#{msg}\033[0m" }
11
+ end
12
+
13
+ # yellow
14
+ def warning(*messages)
15
+ puts messages.map { |msg| "\033[1;33m#{msg}\033[0m" }
16
+ end
17
+
18
+ # green
19
+ def success(*messages)
20
+ puts messages.map { |msg| "\033[1;32m#{msg}\033[0m" }
21
+ end
22
+
23
+ alias_method :message, :success
24
+
25
+ end
26
+
27
+ module GemManagement
28
+
29
+ include ColorfulMessages
30
+
31
+ # Install a gem - looks remotely and local gem cache;
32
+ # won't process rdoc or ri options.
33
+ def install_gem(gem, options = {})
34
+ from_cache = (options.key?(:cache) && options.delete(:cache))
35
+ if from_cache
36
+ install_gem_from_cache(gem, options)
37
+ else
38
+ version = options.delete(:version)
39
+ Gem.configuration.update_sources = false
40
+
41
+ update_source_index(options[:install_dir]) if options[:install_dir]
42
+
43
+ installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
44
+ exception = nil
45
+ begin
46
+ installer.install gem, version
47
+ rescue Gem::InstallError => e
48
+ exception = e
49
+ rescue Gem::GemNotFoundException => e
50
+ if from_cache && gem_file = find_gem_in_cache(gem, version)
51
+ puts "Located #{gem} in gem cache..."
52
+ installer.install gem_file
53
+ else
54
+ exception = e
55
+ end
56
+ rescue => e
57
+ exception = e
58
+ end
59
+ if installer.installed_gems.empty? && exception
60
+ error "Failed to install gem '#{gem} (#{version})' (#{exception.message})"
61
+ end
62
+ installer.installed_gems.each do |spec|
63
+ success "Successfully installed #{spec.full_name}"
64
+ end
65
+ return !installer.installed_gems.empty?
66
+ end
67
+ end
68
+
69
+ # Install a gem - looks in the system's gem cache instead of remotely;
70
+ # won't process rdoc or ri options.
71
+ def install_gem_from_cache(gem, options = {})
72
+ version = options.delete(:version)
73
+ Gem.configuration.update_sources = false
74
+ installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
75
+ exception = nil
76
+ begin
77
+ if gem_file = find_gem_in_cache(gem, version)
78
+ puts "Located #{gem} in gem cache..."
79
+ installer.install gem_file
80
+ else
81
+ raise Gem::InstallError, "Unknown gem #{gem}"
82
+ end
83
+ rescue Gem::InstallError => e
84
+ exception = e
85
+ end
86
+ if installer.installed_gems.empty? && exception
87
+ error "Failed to install gem '#{gem}' (#{e.message})"
88
+ end
89
+ installer.installed_gems.each do |spec|
90
+ success "Successfully installed #{spec.full_name}"
91
+ end
92
+ end
93
+
94
+ # Install a gem from source - builds and packages it first then installs.
95
+ def install_gem_from_src(gem_src_dir, options = {})
96
+ if !File.directory?(gem_src_dir)
97
+ raise "Missing rubygem source path: #{gem_src_dir}"
98
+ end
99
+ if options[:install_dir] && !File.directory?(options[:install_dir])
100
+ raise "Missing rubygems path: #{options[:install_dir]}"
101
+ end
102
+
103
+ gem_name = File.basename(gem_src_dir)
104
+ gem_pkg_dir = File.expand_path(File.join(gem_src_dir, 'pkg'))
105
+
106
+ # We need to use local bin executables if available.
107
+ thor = "#{Gem.ruby} -S #{which('thor')}"
108
+ rake = "#{Gem.ruby} -S #{which('rake')}"
109
+
110
+ # Handle pure Thor installation instead of Rake
111
+ if File.exists?(File.join(gem_src_dir, 'Thorfile'))
112
+ # Remove any existing packages.
113
+ FileUtils.rm_rf(gem_pkg_dir) if File.directory?(gem_pkg_dir)
114
+ # Create the package.
115
+ FileUtils.cd(gem_src_dir) { system("#{thor} :package") }
116
+ # Install the package using rubygems.
117
+ if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
118
+ FileUtils.cd(File.dirname(package)) do
119
+ install_gem(File.basename(package), options.dup)
120
+ return true
121
+ end
122
+ else
123
+ raise Gem::InstallError, "No package found for #{gem_name}"
124
+ end
125
+ # Handle elaborate installation through Rake
126
+ else
127
+ # Clean and regenerate any subgems for meta gems.
128
+ Dir[File.join(gem_src_dir, '*', 'Rakefile')].each do |rakefile|
129
+ FileUtils.cd(File.dirname(rakefile)) do
130
+ system("#{rake} clobber_package; #{rake} package")
131
+ end
132
+ end
133
+
134
+ # Handle the main gem install.
135
+ if File.exists?(File.join(gem_src_dir, 'Rakefile'))
136
+ # Remove any existing packages.
137
+ FileUtils.cd(gem_src_dir) { system("#{rake} clobber_package") }
138
+ # Create the main gem pkg dir if it doesn't exist.
139
+ FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
140
+ # Copy any subgems to the main gem pkg dir.
141
+ Dir[File.join(gem_src_dir, '*', 'pkg', '*.gem')].each do |subgem_pkg|
142
+ dest = File.join(gem_pkg_dir, File.basename(subgem_pkg))
143
+ FileUtils.copy_entry(subgem_pkg, dest, true, false, true)
144
+ end
145
+
146
+ # Finally generate the main package and install it; subgems
147
+ # (dependencies) are local to the main package.
148
+ FileUtils.cd(gem_src_dir) do
149
+ system("#{rake} package")
150
+ FileUtils.cd(gem_pkg_dir) do
151
+ if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
152
+ # If the (meta) gem has it's own package, install it.
153
+ install_gem(File.basename(package), options.dup)
154
+ else
155
+ # Otherwise install each package seperately.
156
+ Dir["*.gem"].each { |gem| install_gem(gem, options.dup) }
157
+ end
158
+ end
159
+ return true
160
+ end
161
+ end
162
+ end
163
+ raise Gem::InstallError, "No Rakefile found for #{gem_name}"
164
+ end
165
+
166
+ # Uninstall a gem.
167
+ def uninstall_gem(gem, options = {})
168
+ if options[:version] && !options[:version].is_a?(Gem::Requirement)
169
+ options[:version] = Gem::Requirement.new ["= #{options[:version]}"]
170
+ end
171
+ update_source_index(options[:install_dir]) if options[:install_dir]
172
+ Gem::Uninstaller.new(gem, options).uninstall
173
+ end
174
+
175
+ # Use the local bin/* executables if available.
176
+ def which(executable)
177
+ if File.executable?(exec = File.join(Dir.pwd, 'bin', executable))
178
+ exec
179
+ else
180
+ executable
181
+ end
182
+ end
183
+
184
+ # Create a modified executable wrapper in the specified bin directory.
185
+ def ensure_bin_wrapper_for(gem_dir, bin_dir, *gems)
186
+ if bin_dir && File.directory?(bin_dir)
187
+ gems.each do |gem|
188
+ if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last
189
+ spec = Gem::Specification.load(gemspec_path)
190
+ spec.executables.each do |exec|
191
+ executable = File.join(bin_dir, exec)
192
+ message "Writing executable wrapper #{executable}"
193
+ File.open(executable, 'w', 0755) do |f|
194
+ f.write(executable_wrapper(spec, exec))
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ private
203
+
204
+ def executable_wrapper(spec, bin_file_name)
205
+ <<-TEXT
206
+ #!/usr/bin/env ruby
207
+ #
208
+ # This file was generated by Merb's GemManagement
209
+ #
210
+ # The application '#{spec.name}' is installed as part of a gem, and
211
+ # this file is here to facilitate running it.
212
+
213
+ begin
214
+ require 'minigems'
215
+ rescue LoadError
216
+ require 'rubygems'
217
+ end
218
+
219
+ if File.directory?(gems_dir = File.join(Dir.pwd, 'gems')) ||
220
+ File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
221
+ $BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir)
222
+ end
223
+
224
+ version = "#{Gem::Requirement.default}"
225
+
226
+ if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
227
+ version = $1
228
+ ARGV.shift
229
+ end
230
+
231
+ gem '#{spec.name}', version
232
+ load '#{bin_file_name}'
233
+ TEXT
234
+ end
235
+
236
+ def find_gem_in_cache(gem, version)
237
+ spec = if version
238
+ version = Gem::Requirement.new ["= #{version}"] unless version.is_a?(Gem::Requirement)
239
+ Gem.source_index.find_name(gem, version).first
240
+ else
241
+ Gem.source_index.find_name(gem).sort_by { |g| g.version }.last
242
+ end
243
+ if spec && File.exists?(gem_file = "#{spec.installation_path}/cache/#{spec.full_name}.gem")
244
+ gem_file
245
+ end
246
+ end
247
+
248
+ def update_source_index(dir)
249
+ Gem.source_index.load_gems_in(File.join(dir, 'specifications'))
250
+ end
251
+
252
+ end