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.
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 +458 -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 +598 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +717 -0
  13. data/lib/merb-core/config.rb +305 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +568 -0
  16. data/lib/merb-core/controller/exceptions.rb +315 -0
  17. data/lib/merb-core/controller/merb_controller.rb +256 -0
  18. data/lib/merb-core/controller/mime.rb +107 -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 +319 -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 +340 -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 +198 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +176 -0
  34. data/lib/merb-core/dispatch/request.rb +729 -0
  35. data/lib/merb-core/dispatch/router.rb +151 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +566 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +191 -0
  39. data/lib/merb-core/dispatch/router/route.rb +511 -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 +203 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +25 -0
  51. data/lib/merb-core/rack/adapter.rb +44 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +25 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +118 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +39 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +36 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +97 -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 +284 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +229 -0
  75. data/lib/merb-core/tasks/merb.rb +1 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +11 -0
  79. data/lib/merb-core/test/helpers.rb +9 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +393 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +39 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +9 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
  89. data/lib/merb-core/test/run_specs.rb +49 -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,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