wycats-merb-core 0.9.8
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.
- data/CHANGELOG +992 -0
- data/CONTRIBUTORS +94 -0
- data/LICENSE +20 -0
- data/PUBLIC_CHANGELOG +142 -0
- data/README +21 -0
- data/Rakefile +458 -0
- data/TODO +0 -0
- data/bin/merb +11 -0
- data/bin/merb-specs +5 -0
- data/lib/merb-core.rb +598 -0
- data/lib/merb-core/autoload.rb +31 -0
- data/lib/merb-core/bootloader.rb +717 -0
- data/lib/merb-core/config.rb +305 -0
- data/lib/merb-core/constants.rb +45 -0
- data/lib/merb-core/controller/abstract_controller.rb +568 -0
- data/lib/merb-core/controller/exceptions.rb +315 -0
- data/lib/merb-core/controller/merb_controller.rb +256 -0
- data/lib/merb-core/controller/mime.rb +107 -0
- data/lib/merb-core/controller/mixins/authentication.rb +123 -0
- data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
- data/lib/merb-core/controller/mixins/controller.rb +319 -0
- data/lib/merb-core/controller/mixins/render.rb +513 -0
- data/lib/merb-core/controller/mixins/responder.rb +469 -0
- data/lib/merb-core/controller/template.rb +254 -0
- data/lib/merb-core/core_ext.rb +9 -0
- data/lib/merb-core/core_ext/hash.rb +7 -0
- data/lib/merb-core/core_ext/kernel.rb +340 -0
- data/lib/merb-core/dispatch/cookies.rb +130 -0
- data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
- data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
- data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
- data/lib/merb-core/dispatch/dispatcher.rb +176 -0
- data/lib/merb-core/dispatch/request.rb +729 -0
- data/lib/merb-core/dispatch/router.rb +151 -0
- data/lib/merb-core/dispatch/router/behavior.rb +566 -0
- data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
- data/lib/merb-core/dispatch/router/resources.rb +191 -0
- data/lib/merb-core/dispatch/router/route.rb +511 -0
- data/lib/merb-core/dispatch/session.rb +222 -0
- data/lib/merb-core/dispatch/session/container.rb +74 -0
- data/lib/merb-core/dispatch/session/cookie.rb +173 -0
- data/lib/merb-core/dispatch/session/memcached.rb +68 -0
- data/lib/merb-core/dispatch/session/memory.rb +99 -0
- data/lib/merb-core/dispatch/session/store_container.rb +150 -0
- data/lib/merb-core/dispatch/worker.rb +28 -0
- data/lib/merb-core/gem_ext/erubis.rb +77 -0
- data/lib/merb-core/logger.rb +203 -0
- data/lib/merb-core/plugins.rb +67 -0
- data/lib/merb-core/rack.rb +25 -0
- data/lib/merb-core/rack/adapter.rb +44 -0
- data/lib/merb-core/rack/adapter/ebb.rb +25 -0
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
- data/lib/merb-core/rack/adapter/irb.rb +118 -0
- data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/runner.rb +28 -0
- data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/thin.rb +39 -0
- data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
- data/lib/merb-core/rack/adapter/webrick.rb +36 -0
- data/lib/merb-core/rack/application.rb +32 -0
- data/lib/merb-core/rack/handler/mongrel.rb +97 -0
- data/lib/merb-core/rack/middleware.rb +20 -0
- data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
- data/lib/merb-core/rack/middleware/content_length.rb +18 -0
- data/lib/merb-core/rack/middleware/csrf.rb +73 -0
- data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
- data/lib/merb-core/rack/middleware/profiler.rb +19 -0
- data/lib/merb-core/rack/middleware/static.rb +45 -0
- data/lib/merb-core/rack/middleware/tracer.rb +20 -0
- data/lib/merb-core/server.rb +284 -0
- data/lib/merb-core/tasks/audit.rake +68 -0
- data/lib/merb-core/tasks/gem_management.rb +229 -0
- data/lib/merb-core/tasks/merb.rb +1 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
- data/lib/merb-core/tasks/stats.rake +71 -0
- data/lib/merb-core/test.rb +11 -0
- data/lib/merb-core/test/helpers.rb +9 -0
- data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
- data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
- data/lib/merb-core/test/helpers/request_helper.rb +393 -0
- data/lib/merb-core/test/helpers/route_helper.rb +39 -0
- data/lib/merb-core/test/helpers/view_helper.rb +121 -0
- data/lib/merb-core/test/matchers.rb +9 -0
- data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
- data/lib/merb-core/test/run_specs.rb +49 -0
- data/lib/merb-core/test/tasks/spectasks.rb +68 -0
- data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
- data/lib/merb-core/test/test_ext/object.rb +14 -0
- data/lib/merb-core/test/test_ext/string.rb +14 -0
- data/lib/merb-core/vendor/facets.rb +2 -0
- data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
- data/lib/merb-core/vendor/facets/inflect.rb +342 -0
- data/lib/merb-core/version.rb +3 -0
- metadata +253 -0
@@ -0,0 +1,284 @@
|
|
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
|
+
pidfile = pid_file(port)
|
26
|
+
pid = IO.read(pidfile).chomp.to_i if File.exist?(pidfile)
|
27
|
+
|
28
|
+
unless alive?(port)
|
29
|
+
remove_pid_file(port)
|
30
|
+
puts "Starting merb server on port #{port}, pid file: #{pidfile} and process id is #{pid}" if Merb::Config[:verbose]
|
31
|
+
daemonize(port)
|
32
|
+
else
|
33
|
+
raise "Merb is already running: port is #{port}, pid file: #{pidfile}, process id is #{pid}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
elsif Merb::Config[:daemonize]
|
37
|
+
pidfile = pid_file(port)
|
38
|
+
pid = IO.read(pidfile).chomp.to_i if File.exist?(pidfile)
|
39
|
+
|
40
|
+
unless alive?(@port)
|
41
|
+
remove_pid_file(@port)
|
42
|
+
puts "Daemonizing..." if Merb::Config[:verbose]
|
43
|
+
daemonize(@port)
|
44
|
+
else
|
45
|
+
raise "Merb is already running: port is #{port}, pid file: #{pidfile}, process id is #{pid}"
|
46
|
+
end
|
47
|
+
else
|
48
|
+
trap('TERM') { exit }
|
49
|
+
|
50
|
+
if Merb::Config[:console_trap]
|
51
|
+
add_irb_trap
|
52
|
+
else
|
53
|
+
trap('INT') { puts "\nExiting"; exit }
|
54
|
+
end
|
55
|
+
|
56
|
+
puts "Running bootloaders..." if Merb::Config[:verbose]
|
57
|
+
BootLoader.run
|
58
|
+
puts "Starting Rack adapter..." if Merb::Config[:verbose]
|
59
|
+
Merb.logger.info! "Starting Merb server listening at #{Merb::Config[:host]}:#{port}"
|
60
|
+
Merb.adapter.start(Merb::Config.to_hash)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# ==== Parameters
|
65
|
+
# port<~to_s>:: The port to check for Merb instances on.
|
66
|
+
#
|
67
|
+
# ==== Returns
|
68
|
+
# Boolean::
|
69
|
+
# True if Merb is running on the specified port.
|
70
|
+
def alive?(port)
|
71
|
+
puts "About to check if port #{port} is alive..." if Merb::Config[:verbose]
|
72
|
+
pidfile = pid_file(port)
|
73
|
+
puts "Pidfile is #{pidfile}..." if Merb::Config[:verbose]
|
74
|
+
pid = IO.read(pidfile).chomp.to_i
|
75
|
+
puts "Process id is #{pid}" if Merb::Config[:verbose]
|
76
|
+
Process.kill(0, pid)
|
77
|
+
true
|
78
|
+
rescue
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
# ==== Parameters
|
83
|
+
# port<~to_s>:: The port of the Merb process to kill.
|
84
|
+
# sig<~to_s>:: The signal to send to the process. Defaults to 9.
|
85
|
+
#
|
86
|
+
# ==== Alternatives
|
87
|
+
# If you pass "all" as the port, the signal will be sent to all Merb
|
88
|
+
# processes.
|
89
|
+
def kill(port, sig=9)
|
90
|
+
Merb::BootLoader::BuildFramework.run
|
91
|
+
begin
|
92
|
+
pidfiles = port == "all" ?
|
93
|
+
pid_files : [ pid_file(port) ]
|
94
|
+
|
95
|
+
pidfiles.each do |f|
|
96
|
+
pid = IO.read(f).chomp.to_i
|
97
|
+
begin
|
98
|
+
Process.kill(sig, pid)
|
99
|
+
FileUtils.rm(f) if File.exist?(f)
|
100
|
+
puts "killed PID #{pid} with signal #{sig}"
|
101
|
+
rescue Errno::EINVAL
|
102
|
+
puts "Failed to kill PID #{pid}: '#{sig}' is an invalid or unsupported signal number."
|
103
|
+
rescue Errno::EPERM
|
104
|
+
puts "Failed to kill PID #{pid}: Insufficient permissions."
|
105
|
+
rescue Errno::ESRCH
|
106
|
+
puts "Failed to kill PID #{pid}: Process is deceased or zombie."
|
107
|
+
FileUtils.rm f
|
108
|
+
rescue Exception => e
|
109
|
+
puts "Failed to kill PID #{pid}: #{e.message}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
ensure
|
113
|
+
Merb.started = false
|
114
|
+
exit
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# ==== Parameters
|
119
|
+
# port<~to_s>:: The port of the Merb process to daemonize.
|
120
|
+
def daemonize(port)
|
121
|
+
puts "About to fork..." if Merb::Config[:verbose]
|
122
|
+
fork do
|
123
|
+
Process.setsid
|
124
|
+
exit if fork
|
125
|
+
File.umask 0000
|
126
|
+
STDIN.reopen "/dev/null"
|
127
|
+
STDOUT.reopen "/dev/null", "a"
|
128
|
+
STDERR.reopen STDOUT
|
129
|
+
trap("TERM") { exit }
|
130
|
+
Dir.chdir Merb::Config[:merb_root]
|
131
|
+
at_exit { remove_pid_file(port) }
|
132
|
+
Merb::Config[:port] = port
|
133
|
+
BootLoader.run
|
134
|
+
Merb.adapter.start(Merb::Config.to_hash)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def change_privilege
|
139
|
+
if Merb::Config[:user]
|
140
|
+
if Merb::Config[:group]
|
141
|
+
puts "About to change privilege to group #{Merb::Config[:group]} and user #{Merb::Config[:user]}" if Merb::Config[:verbose]
|
142
|
+
_change_privilege(Merb::Config[:user], Merb::Config[:group])
|
143
|
+
else
|
144
|
+
puts "About to change privilege to user #{Merb::Config[:user]}" if Merb::Config[:verbose]
|
145
|
+
_change_privilege(Merb::Config[:user])
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Removes a PID file used by the server from the filesystem.
|
151
|
+
# This uses :pid_file options from configuration when provided
|
152
|
+
# or merb.<port>.pid in log directory by default.
|
153
|
+
#
|
154
|
+
# ==== Parameters
|
155
|
+
# port<~to_s>::
|
156
|
+
# The port of the Merb process to whom the the PID file belongs to.
|
157
|
+
#
|
158
|
+
# ==== Alternatives
|
159
|
+
# If Merb::Config[:pid_file] has been specified, that will be used
|
160
|
+
# instead of the port based PID file.
|
161
|
+
def remove_pid_file(port)
|
162
|
+
pidfile = pid_file(port)
|
163
|
+
puts "Removing pid file #{pidfile} (port is #{port})..."
|
164
|
+
FileUtils.rm(pidfile) if File.exist?(pidfile)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Stores a PID file on the filesystem.
|
168
|
+
# This uses :pid_file options from configuration when provided
|
169
|
+
# or merb.<port>.pid in log directory by default.
|
170
|
+
#
|
171
|
+
# ==== Parameters
|
172
|
+
# port<~to_s>::
|
173
|
+
# The port of the Merb process to whom the the PID file belongs to.
|
174
|
+
#
|
175
|
+
# ==== Alternatives
|
176
|
+
# If Merb::Config[:pid_file] has been specified, that will be used
|
177
|
+
# instead of the port based PID file.
|
178
|
+
def store_pid(port)
|
179
|
+
pidfile = pid_file(port)
|
180
|
+
puts "Storing pid file to #{pidfile}..."
|
181
|
+
FileUtils.mkdir_p(File.dirname(pidfile)) unless File.directory?(File.dirname(pidfile))
|
182
|
+
puts "Created directory, writing process id..." if Merb::Config[:verbose]
|
183
|
+
File.open(pidfile, 'w'){ |f| f.write("#{Process.pid}") }
|
184
|
+
end
|
185
|
+
|
186
|
+
# Gets the pid file for the specified port.
|
187
|
+
#
|
188
|
+
# ==== Parameters
|
189
|
+
# port<~to_s>::
|
190
|
+
# The port of the Merb process to whom the the PID file belongs to.
|
191
|
+
#
|
192
|
+
# ==== Returns
|
193
|
+
# String::
|
194
|
+
# Location of pid file for specified port. If clustered and pid_file option
|
195
|
+
# is specified, it adds the port value to the path.
|
196
|
+
def pid_file(port)
|
197
|
+
if Merb::Config[:pid_file]
|
198
|
+
pidfile = Merb::Config[:pid_file]
|
199
|
+
if Merb::Config[:cluster]
|
200
|
+
ext = File.extname(Merb::Config[:pid_file])
|
201
|
+
base = File.basename(Merb::Config[:pid_file], ext)
|
202
|
+
dir = File.dirname(Merb::Config[:pid_file])
|
203
|
+
File.join(dir, "#{base}.#{port}#{ext}")
|
204
|
+
else
|
205
|
+
Merb::Config[:pid_file]
|
206
|
+
end
|
207
|
+
else
|
208
|
+
pidfile = Merb.log_path / "merb.#{port}.pid"
|
209
|
+
Merb.log_path / "merb.#{port}.pid"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Get a list of the pid files.
|
214
|
+
#
|
215
|
+
# ==== Returns
|
216
|
+
# Array::
|
217
|
+
# List of pid file paths. If not clustered, array contains a single path.
|
218
|
+
def pid_files
|
219
|
+
if Merb::Config[:pid_file]
|
220
|
+
if Merb::Config[:cluster]
|
221
|
+
ext = File.extname(Merb::Config[:pid_file])
|
222
|
+
base = File.basename(Merb::Config[:pid_file], ext)
|
223
|
+
dir = File.dirname(Merb::Config[:pid_file])
|
224
|
+
Dir[dir / "#{base}.*#{ext}"]
|
225
|
+
else
|
226
|
+
[ Merb::Config[:pid_file] ]
|
227
|
+
end
|
228
|
+
else
|
229
|
+
Dir[Merb.log_path / "merb.*.pid"]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Change privileges of the process to the specified user and group.
|
234
|
+
#
|
235
|
+
# ==== Parameters
|
236
|
+
# user<String>:: The user who should own the server process.
|
237
|
+
# group<String>:: The group who should own the server process.
|
238
|
+
#
|
239
|
+
# ==== Alternatives
|
240
|
+
# If group is left out, the user will be used as the group.
|
241
|
+
def _change_privilege(user, group=user)
|
242
|
+
|
243
|
+
puts "Changing privileges to #{user}:#{group}"
|
244
|
+
|
245
|
+
uid, gid = Process.euid, Process.egid
|
246
|
+
target_uid = Etc.getpwnam(user).uid
|
247
|
+
target_gid = Etc.getgrnam(group).gid
|
248
|
+
|
249
|
+
if uid != target_uid || gid != target_gid
|
250
|
+
# Change process ownership
|
251
|
+
Process.initgroups(user, target_gid)
|
252
|
+
Process::GID.change_privilege(target_gid)
|
253
|
+
Process::UID.change_privilege(target_uid)
|
254
|
+
end
|
255
|
+
rescue Errno::EPERM => e
|
256
|
+
puts "Couldn't change user and group to #{user}:#{group}: #{e}"
|
257
|
+
end
|
258
|
+
|
259
|
+
def add_irb_trap
|
260
|
+
trap('INT') do
|
261
|
+
exit if @interrupted
|
262
|
+
@interrupted = true
|
263
|
+
puts "Interrupt a second time to quit"
|
264
|
+
Kernel.sleep 1.5
|
265
|
+
ARGV.clear # Avoid passing args to IRB
|
266
|
+
|
267
|
+
if @irb.nil?
|
268
|
+
require 'irb'
|
269
|
+
IRB.setup(nil)
|
270
|
+
@irb = IRB::Irb.new(nil)
|
271
|
+
IRB.conf[:MAIN_CONTEXT] = @irb.context
|
272
|
+
end
|
273
|
+
|
274
|
+
trap(:INT) { @irb.signal_handle }
|
275
|
+
catch(:IRB_EXIT) { @irb.eval_input }
|
276
|
+
|
277
|
+
puts "Exiting IRB mode, back in server mode"
|
278
|
+
@interrupted = false
|
279
|
+
add_irb_trap
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
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,229 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/dependency_installer'
|
3
|
+
require 'rubygems/uninstaller'
|
4
|
+
require 'rubygems/dependency'
|
5
|
+
|
6
|
+
module GemManagement
|
7
|
+
|
8
|
+
# Install a gem - looks remotely and local gem cache;
|
9
|
+
# won't process rdoc or ri options.
|
10
|
+
def install_gem(gem, options = {})
|
11
|
+
from_cache = (options.key?(:cache) && options.delete(:cache))
|
12
|
+
if from_cache
|
13
|
+
install_gem_from_cache(gem, options)
|
14
|
+
else
|
15
|
+
version = options.delete(:version)
|
16
|
+
Gem.configuration.update_sources = false
|
17
|
+
|
18
|
+
update_source_index(options[:install_dir]) if options[:install_dir]
|
19
|
+
|
20
|
+
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
|
21
|
+
exception = nil
|
22
|
+
begin
|
23
|
+
installer.install gem, version
|
24
|
+
rescue Gem::InstallError => e
|
25
|
+
exception = e
|
26
|
+
rescue Gem::GemNotFoundException => e
|
27
|
+
if from_cache && gem_file = find_gem_in_cache(gem, version)
|
28
|
+
puts "Located #{gem} in gem cache..."
|
29
|
+
installer.install gem_file
|
30
|
+
else
|
31
|
+
exception = e
|
32
|
+
end
|
33
|
+
rescue => e
|
34
|
+
exception = e
|
35
|
+
end
|
36
|
+
if installer.installed_gems.empty? && exception
|
37
|
+
puts "Failed to install gem '#{gem} (#{version})' (#{exception.message})"
|
38
|
+
end
|
39
|
+
installer.installed_gems.each do |spec|
|
40
|
+
puts "Successfully installed #{spec.full_name}"
|
41
|
+
end
|
42
|
+
return !installer.installed_gems.empty?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Install a gem - looks in the system's gem cache instead of remotely;
|
47
|
+
# won't process rdoc or ri options.
|
48
|
+
def install_gem_from_cache(gem, options = {})
|
49
|
+
version = options.delete(:version)
|
50
|
+
Gem.configuration.update_sources = false
|
51
|
+
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
|
52
|
+
exception = nil
|
53
|
+
begin
|
54
|
+
if gem_file = find_gem_in_cache(gem, version)
|
55
|
+
puts "Located #{gem} in gem cache..."
|
56
|
+
installer.install gem_file
|
57
|
+
else
|
58
|
+
raise Gem::InstallError, "Unknown gem #{gem}"
|
59
|
+
end
|
60
|
+
rescue Gem::InstallError => e
|
61
|
+
exception = e
|
62
|
+
end
|
63
|
+
if installer.installed_gems.empty? && exception
|
64
|
+
puts "Failed to install gem '#{gem}' (#{e.message})"
|
65
|
+
end
|
66
|
+
installer.installed_gems.each do |spec|
|
67
|
+
puts "Successfully installed #{spec.full_name}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Install a gem from source - builds and packages it first then installs.
|
72
|
+
def install_gem_from_src(gem_src_dir, options = {})
|
73
|
+
if !File.directory?(gem_src_dir)
|
74
|
+
raise "Missing rubygem source path: #{gem_src_dir}"
|
75
|
+
end
|
76
|
+
if options[:install_dir] && !File.directory?(options[:install_dir])
|
77
|
+
raise "Missing rubygems path: #{options[:install_dir]}"
|
78
|
+
end
|
79
|
+
|
80
|
+
gem_name = File.basename(gem_src_dir)
|
81
|
+
gem_pkg_dir = File.expand_path(File.join(gem_src_dir, 'pkg'))
|
82
|
+
|
83
|
+
# We need to use local bin executables if available.
|
84
|
+
thor = "#{Gem.ruby} -S #{which('thor')}"
|
85
|
+
rake = "#{Gem.ruby} -S #{which('rake')}"
|
86
|
+
|
87
|
+
# Handle pure Thor installation instead of Rake
|
88
|
+
if File.exists?(File.join(gem_src_dir, 'Thorfile'))
|
89
|
+
# Remove any existing packages.
|
90
|
+
FileUtils.rm_rf(gem_pkg_dir) if File.directory?(gem_pkg_dir)
|
91
|
+
# Create the package.
|
92
|
+
FileUtils.cd(gem_src_dir) { system("#{thor} :package") }
|
93
|
+
# Install the package using rubygems.
|
94
|
+
if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
|
95
|
+
FileUtils.cd(File.dirname(package)) do
|
96
|
+
install_gem(File.basename(package), options.dup)
|
97
|
+
return
|
98
|
+
end
|
99
|
+
else
|
100
|
+
raise Gem::InstallError, "No package found for #{gem_name}"
|
101
|
+
end
|
102
|
+
# Handle standard installation through Rake
|
103
|
+
else
|
104
|
+
# Clean and regenerate any subgems for meta gems.
|
105
|
+
Dir[File.join(gem_src_dir, '*', 'Rakefile')].each do |rakefile|
|
106
|
+
FileUtils.cd(File.dirname(rakefile)) do
|
107
|
+
system("#{rake} clobber_package; #{rake} package")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Handle the main gem install.
|
112
|
+
if File.exists?(File.join(gem_src_dir, 'Rakefile'))
|
113
|
+
# Remove any existing packages.
|
114
|
+
FileUtils.cd(gem_src_dir) { system("#{rake} clobber_package") }
|
115
|
+
# Create the main gem pkg dir if it doesn't exist.
|
116
|
+
FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
|
117
|
+
# Copy any subgems to the main gem pkg dir.
|
118
|
+
Dir[File.join(gem_src_dir, '*', 'pkg', '*.gem')].each do |subgem_pkg|
|
119
|
+
dest = File.join(gem_pkg_dir, File.basename(subgem_pkg))
|
120
|
+
FileUtils.copy_entry(subgem_pkg, dest, true, false, true)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Finally generate the main package and install it; subgems
|
124
|
+
# (dependencies) are local to the main package.
|
125
|
+
FileUtils.cd(gem_src_dir) do
|
126
|
+
system("#{rake} package")
|
127
|
+
FileUtils.cd(gem_pkg_dir) do
|
128
|
+
if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
|
129
|
+
# If the (meta) gem has it's own package, install it.
|
130
|
+
install_gem(File.basename(package), options.dup)
|
131
|
+
else
|
132
|
+
# Otherwise install each package seperately.
|
133
|
+
Dir["*.gem"].each { |gem| install_gem(gem, options.dup) }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
return
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
raise Gem::InstallError, "No Rakefile found for #{gem_name}"
|
141
|
+
end
|
142
|
+
|
143
|
+
# Uninstall a gem.
|
144
|
+
def uninstall_gem(gem, options = {})
|
145
|
+
if options[:version] && !options[:version].is_a?(Gem::Requirement)
|
146
|
+
options[:version] = Gem::Requirement.new ["= #{options[:version]}"]
|
147
|
+
end
|
148
|
+
update_source_index(options[:install_dir]) if options[:install_dir]
|
149
|
+
Gem::Uninstaller.new(gem, options).uninstall
|
150
|
+
end
|
151
|
+
|
152
|
+
# Use the local bin/* executables if available.
|
153
|
+
def which(executable)
|
154
|
+
if File.executable?(exec = File.join(Dir.pwd, 'bin', executable))
|
155
|
+
exec
|
156
|
+
else
|
157
|
+
executable
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Create a modified executable wrapper in the specified bin directory.
|
162
|
+
def ensure_bin_wrapper_for(gem_dir, bin_dir, *gems)
|
163
|
+
if bin_dir && File.directory?(bin_dir)
|
164
|
+
gems.each do |gem|
|
165
|
+
if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last
|
166
|
+
spec = Gem::Specification.load(gemspec_path)
|
167
|
+
spec.executables.each do |exec|
|
168
|
+
executable = File.join(bin_dir, exec)
|
169
|
+
puts "Writing executable wrapper #{executable}"
|
170
|
+
File.open(executable, 'w', 0755) do |f|
|
171
|
+
f.write(executable_wrapper(spec, exec))
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def executable_wrapper(spec, bin_file_name)
|
182
|
+
<<-TEXT
|
183
|
+
#!#{Gem.ruby}
|
184
|
+
#
|
185
|
+
# This file was generated by Merb's GemManagement
|
186
|
+
#
|
187
|
+
# The application '#{spec.name}' is installed as part of a gem, and
|
188
|
+
# this file is here to facilitate running it.
|
189
|
+
|
190
|
+
begin
|
191
|
+
require 'minigems'
|
192
|
+
rescue LoadError
|
193
|
+
require 'rubygems'
|
194
|
+
end
|
195
|
+
|
196
|
+
if File.directory?(gems_dir = File.join(Dir.pwd, 'gems')) ||
|
197
|
+
File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
|
198
|
+
$BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir)
|
199
|
+
end
|
200
|
+
|
201
|
+
version = "#{Gem::Requirement.default}"
|
202
|
+
|
203
|
+
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
|
204
|
+
version = $1
|
205
|
+
ARGV.shift
|
206
|
+
end
|
207
|
+
|
208
|
+
gem '#{spec.name}', version
|
209
|
+
load '#{bin_file_name}'
|
210
|
+
TEXT
|
211
|
+
end
|
212
|
+
|
213
|
+
def find_gem_in_cache(gem, version)
|
214
|
+
spec = if version
|
215
|
+
version = Gem::Requirement.new ["= #{version}"] unless version.is_a?(Gem::Requirement)
|
216
|
+
Gem.source_index.find_name(gem, version).first
|
217
|
+
else
|
218
|
+
Gem.source_index.find_name(gem).sort_by { |g| g.version }.last
|
219
|
+
end
|
220
|
+
if spec && File.exists?(gem_file = "#{spec.installation_path}/cache/#{spec.full_name}.gem")
|
221
|
+
gem_file
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def update_source_index(dir)
|
226
|
+
Gem.source_index.load_gems_in(File.join(dir, 'specifications'))
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|