merb-gen 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/README +6 -3
  2. data/Rakefile +6 -4
  3. data/bin/merb-gen +1 -23
  4. data/lib/generators/controller.rb +4 -2
  5. data/lib/generators/helper.rb +0 -5
  6. data/lib/generators/merb/merb_core.rb +95 -0
  7. data/lib/generators/merb/merb_flat.rb +70 -69
  8. data/lib/generators/merb/merb_stack.rb +110 -0
  9. data/lib/generators/merb/merb_very_flat.rb +61 -64
  10. data/lib/generators/merb_plugin.rb +5 -0
  11. data/lib/generators/model.rb +5 -2
  12. data/lib/generators/resource.rb +7 -2
  13. data/lib/generators/resource_controller.rb +4 -4
  14. data/lib/generators/templates/application/common/Rakefile +2 -6
  15. data/lib/generators/templates/application/common/doc.thor +149 -0
  16. data/lib/generators/templates/application/common/dothtaccess +17 -0
  17. data/lib/generators/templates/application/common/jquery.js +32 -0
  18. data/lib/generators/templates/application/common/merb.thor +1550 -861
  19. data/lib/generators/templates/application/{merb → merb_core}/app/controllers/application.rb +0 -0
  20. data/lib/generators/templates/application/{merb → merb_core}/app/controllers/exceptions.rb +0 -0
  21. data/lib/generators/templates/application/{merb → merb_core}/app/helpers/global_helpers.rb +0 -0
  22. data/lib/generators/templates/application/{merb → merb_core}/app/views/exceptions/internal_server_error.html.erb +0 -0
  23. data/lib/generators/templates/application/{merb → merb_core}/app/views/exceptions/not_acceptable.html.erb +0 -0
  24. data/lib/generators/templates/application/{merb → merb_core}/app/views/exceptions/not_found.html.erb +0 -0
  25. data/lib/generators/templates/application/{merb → merb_core}/autotest/discover.rb +0 -0
  26. data/lib/generators/templates/application/{merb → merb_core}/autotest/merb.rb +0 -0
  27. data/lib/generators/templates/application/{merb → merb_core}/autotest/merb_rspec.rb +0 -0
  28. data/lib/generators/templates/application/{merb → merb_core}/config/environments/development.rb +7 -2
  29. data/lib/generators/templates/application/{merb → merb_core}/config/environments/production.rb +5 -2
  30. data/lib/generators/templates/application/{merb → merb_core}/config/environments/rake.rb +6 -2
  31. data/lib/generators/templates/application/merb_core/config/environments/staging.rb +10 -0
  32. data/lib/generators/templates/application/merb_core/config/environments/test.rb +12 -0
  33. data/lib/generators/templates/application/{merb → merb_core}/config/init.rb +6 -6
  34. data/lib/generators/templates/application/{merb → merb_core}/config/rack.rb +0 -0
  35. data/lib/generators/templates/application/{merb → merb_core}/config/router.rb +0 -0
  36. data/lib/generators/templates/application/merb_core/doc/rdoc/generators/merb_generator.rb +1362 -0
  37. data/lib/generators/templates/application/merb_core/doc/rdoc/generators/template/merb/api_grease.js +640 -0
  38. data/lib/generators/templates/application/merb_core/doc/rdoc/generators/template/merb/index.html.erb +37 -0
  39. data/lib/generators/templates/application/merb_core/doc/rdoc/generators/template/merb/merb.css +252 -0
  40. data/lib/generators/templates/application/merb_core/doc/rdoc/generators/template/merb/merb.rb +351 -0
  41. data/lib/generators/templates/application/merb_core/doc/rdoc/generators/template/merb/merb_doc_styles.css +492 -0
  42. data/lib/generators/templates/application/merb_core/doc/rdoc/generators/template/merb/prototype.js +2515 -0
  43. data/lib/generators/templates/application/{merb/app/models → merb_core/lib}/merb/session.rb +0 -0
  44. data/lib/generators/templates/application/{merb → merb_core}/public/favicon.ico +0 -0
  45. data/lib/generators/templates/application/{merb → merb_core}/public/images/merb.jpg +0 -0
  46. data/lib/generators/templates/application/merb_core/public/javascripts/application.js +1 -0
  47. data/lib/generators/templates/application/{merb → merb_core}/public/merb.fcgi +0 -0
  48. data/lib/generators/templates/application/{merb → merb_core}/public/robots.txt +0 -0
  49. data/lib/generators/templates/application/{merb → merb_core}/public/stylesheets/master.css +0 -0
  50. data/lib/generators/templates/application/{merb → merb_core}/spec/spec.opts +0 -0
  51. data/lib/generators/templates/application/{merb → merb_core}/spec/spec_helper.rb +0 -0
  52. data/lib/generators/templates/application/{merb → merb_core}/test/test_helper.rb +0 -0
  53. data/lib/generators/templates/application/merb_flat/application.rbt +1 -1
  54. data/lib/generators/templates/application/merb_flat/config/init.rb +8 -3
  55. data/lib/generators/templates/application/merb_plugin/LICENSE +1 -1
  56. data/lib/generators/templates/application/merb_stack/app/controllers/application.rb +2 -0
  57. data/lib/generators/templates/application/merb_stack/app/controllers/exceptions.rb +13 -0
  58. data/lib/generators/templates/application/merb_stack/app/helpers/global_helpers.rb +5 -0
  59. data/lib/generators/templates/application/merb_stack/app/models/user.rb +17 -0
  60. data/lib/generators/templates/application/merb_stack/app/views/exceptions/internal_server_error.html.erb +216 -0
  61. data/lib/generators/templates/application/merb_stack/app/views/exceptions/not_acceptable.html.erb +63 -0
  62. data/lib/generators/templates/application/merb_stack/app/views/exceptions/not_found.html.erb +47 -0
  63. data/lib/generators/templates/application/merb_stack/autotest/discover.rb +1 -0
  64. data/lib/generators/templates/application/merb_stack/autotest/merb.rb +149 -0
  65. data/lib/generators/templates/application/merb_stack/autotest/merb_rspec.rb +165 -0
  66. data/lib/generators/templates/application/merb_stack/config/database.yml +33 -0
  67. data/lib/generators/templates/application/merb_stack/config/dependencies.rb +15 -0
  68. data/lib/generators/templates/application/merb_stack/config/environments/development.rb +15 -0
  69. data/lib/generators/templates/application/merb_stack/config/environments/production.rb +10 -0
  70. data/lib/generators/templates/application/merb_stack/config/environments/rake.rb +11 -0
  71. data/lib/generators/templates/application/merb_stack/config/environments/staging.rb +10 -0
  72. data/lib/generators/templates/application/merb_stack/config/environments/test.rb +12 -0
  73. data/lib/generators/templates/application/merb_stack/config/init.rb +24 -0
  74. data/lib/generators/templates/application/merb_stack/config/rack.rb +12 -0
  75. data/lib/generators/templates/application/merb_stack/config/router.rb +44 -0
  76. data/lib/generators/templates/application/merb_stack/doc/rdoc/generators/merb_generator.rb +1362 -0
  77. data/lib/generators/templates/application/merb_stack/doc/rdoc/generators/template/merb/api_grease.js +640 -0
  78. data/lib/generators/templates/application/merb_stack/doc/rdoc/generators/template/merb/index.html.erb +37 -0
  79. data/lib/generators/templates/application/merb_stack/doc/rdoc/generators/template/merb/merb.css +252 -0
  80. data/lib/generators/templates/application/merb_stack/doc/rdoc/generators/template/merb/merb.rb +351 -0
  81. data/lib/generators/templates/application/merb_stack/doc/rdoc/generators/template/merb/merb_doc_styles.css +492 -0
  82. data/lib/generators/templates/application/merb_stack/doc/rdoc/generators/template/merb/prototype.js +2515 -0
  83. data/lib/generators/templates/application/merb_stack/merb/merb-auth/setup.rb +44 -0
  84. data/lib/generators/templates/application/merb_stack/merb/merb-auth/strategies.rb +11 -0
  85. data/lib/generators/templates/application/merb_stack/merb/session/session.rb +9 -0
  86. data/lib/generators/templates/application/merb_stack/public/favicon.ico +0 -0
  87. data/lib/generators/templates/application/merb_stack/public/images/merb.jpg +0 -0
  88. data/lib/generators/templates/application/merb_stack/public/javascripts/application.js +1 -0
  89. data/lib/generators/templates/application/merb_stack/public/merb.fcgi +22 -0
  90. data/lib/generators/templates/application/merb_stack/public/robots.txt +5 -0
  91. data/lib/generators/templates/application/merb_stack/public/stylesheets/master.css +119 -0
  92. data/lib/generators/templates/application/merb_stack/spec/spec.opts +0 -0
  93. data/lib/generators/templates/application/merb_stack/spec/spec_helper.rb +20 -0
  94. data/lib/generators/templates/application/merb_stack/test/test_helper.rb +19 -0
  95. data/lib/generators/templates/application/merb_very_flat/application.rbt +11 -5
  96. data/lib/generators/templates/component/layout/app/views/layout/%file_name%.html.erb +1 -0
  97. data/lib/generators/templates/component/resource_controller/spec/requests/%file_name%_spec.rb +53 -0
  98. data/lib/merb-gen/app_generator.rb +13 -0
  99. data/lib/merb-gen/generator.rb +36 -1
  100. data/lib/merb-gen/named_generator.rb +5 -2
  101. data/lib/merb-gen.rb +6 -8
  102. data/spec/controller_spec.rb +0 -20
  103. data/spec/{merb_full_spec.rb → merb_core_spec.rb} +11 -3
  104. data/spec/merb_stack_spec.rb +47 -0
  105. data/spec/part_controller_spec.rb +0 -39
  106. data/spec/resource_controller_spec.rb +1 -35
  107. data/spec/spec_helper.rb +4 -2
  108. metadata +133 -111
  109. data/lib/generators/merb/merb_full.rb +0 -85
  110. data/lib/generators/merb.rb +0 -24
  111. data/lib/generators/templates/application/merb/config/environments/staging.rb +0 -7
  112. data/lib/generators/templates/application/merb/config/environments/test.rb +0 -7
  113. data/lib/generators/templates/component/helper/spec/helpers/%file_name%_helper_spec.rb +0 -5
  114. data/lib/generators/templates/component/resource_controller/spec/controllers/%file_name%_spec.rb +0 -7
  115. data/spec/helper_spec.rb +0 -43
  116. data/spec/merb_spec.rb +0 -20
@@ -4,29 +4,10 @@ require 'thor'
4
4
  require 'fileutils'
5
5
  require 'yaml'
6
6
 
7
- # TODO
8
- # - pulling a specific UUID/Tag (gitspec hash) with clone/update
9
- # - a 'deploy' task (in addition to 'redeploy' ?)
10
- # - add merb:gems:refresh to refresh all gems (from specifications)
11
- # - merb:gems:uninstall should remove local bin/ entries
7
+ # Important - don't change this line or its position
8
+ MERB_THOR_VERSION = '0.0.5'
12
9
 
13
10
  ##############################################################################
14
- #
15
- # GemManagement
16
- #
17
- # The following code is also used by Merb core, but we can't rely on it as a
18
- # dependency, since merb.thor should be completely selfcontained (except for
19
- # Thor itself). Therefore, the code below is copied here. Should you work on
20
- # this code, be sure to edit the original code to keep them in sync.
21
- #
22
- # You can find the original here: merb-core/tasks/gem_management.rb
23
- #
24
- ##############################################################################
25
-
26
- require 'rubygems'
27
- require 'rubygems/dependency_installer'
28
- require 'rubygems/uninstaller'
29
- require 'rubygems/dependency'
30
11
 
31
12
  module ColorfulMessages
32
13
 
@@ -47,12 +28,28 @@ module ColorfulMessages
47
28
 
48
29
  alias_method :message, :success
49
30
 
31
+ # magenta
32
+ def note(*messages)
33
+ puts messages.map { |msg| "\033[1;35m#{msg}\033[0m" }
34
+ end
35
+
36
+ # blue
37
+ def info(*messages)
38
+ puts messages.map { |msg| "\033[1;34m#{msg}\033[0m" }
39
+ end
40
+
50
41
  end
51
42
 
43
+ ##############################################################################
44
+
45
+ require 'rubygems/dependency_installer'
46
+ require 'rubygems/uninstaller'
47
+ require 'rubygems/dependency'
48
+
52
49
  module GemManagement
53
50
 
54
51
  include ColorfulMessages
55
-
52
+
56
53
  # Install a gem - looks remotely and local gem cache;
57
54
  # won't process rdoc or ri options.
58
55
  def install_gem(gem, options = {})
@@ -64,13 +61,13 @@ module GemManagement
64
61
  version = options.delete(:version)
65
62
  Gem.configuration.update_sources = false
66
63
 
64
+ # Limit source index to install dir
67
65
  update_source_index(options[:install_dir]) if options[:install_dir]
68
66
 
69
67
  installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
70
68
 
71
- # Exclude gems to refresh from index - force (re)install of new version
72
- # def installer.source_index; @source_index; end
73
- unless refresh.empty?
69
+ # Force-refresh certain gems by excluding them from the current index
70
+ if !options[:ignore_dependencies] && refresh.respond_to?(:include?) && !refresh.empty?
74
71
  source_index = installer.instance_variable_get(:@source_index)
75
72
  source_index.gems.each do |name, spec|
76
73
  source_index.gems.delete(name) if refresh.include?(spec.name)
@@ -93,7 +90,7 @@ module GemManagement
93
90
  exception = e
94
91
  end
95
92
  if installer.installed_gems.empty? && exception
96
- error "Failed to install gem '#{gem} (#{version})' (#{exception.message})"
93
+ error "Failed to install gem '#{gem} (#{version || 'any version'})' (#{exception.message})"
97
94
  end
98
95
  installer.installed_gems.each do |spec|
99
96
  success "Successfully installed #{spec.full_name}"
@@ -128,81 +125,73 @@ module GemManagement
128
125
  end
129
126
 
130
127
  # Install a gem from source - builds and packages it first then installs.
131
- def install_gem_from_src(gem_src_dir, options = {})
132
- if !File.directory?(gem_src_dir)
133
- raise "Missing rubygem source path: #{gem_src_dir}"
134
- end
135
- if options[:install_dir] && !File.directory?(options[:install_dir])
136
- raise "Missing rubygems path: #{options[:install_dir]}"
137
- end
138
-
139
- gem_name = File.basename(gem_src_dir)
140
- gem_pkg_dir = File.expand_path(File.join(gem_src_dir, 'pkg'))
141
-
142
- # We need to use local bin executables if available.
143
- thor = "#{Gem.ruby} -S #{which('thor')}"
144
- rake = "#{Gem.ruby} -S #{which('rake')}"
145
-
146
- # Handle pure Thor installation instead of Rake
147
- if File.exists?(File.join(gem_src_dir, 'Thorfile'))
148
- # Remove any existing packages.
149
- FileUtils.rm_rf(gem_pkg_dir) if File.directory?(gem_pkg_dir)
150
- # Create the package.
151
- FileUtils.cd(gem_src_dir) { system("#{thor} :package") }
152
- # Install the package using rubygems.
153
- if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
154
- FileUtils.cd(File.dirname(package)) do
155
- install_gem(File.basename(package), options.dup)
156
- return true
157
- end
128
+ #
129
+ # Examples:
130
+ # install_gem_from_source(source_dir, :install_dir => ...)
131
+ # install_gem_from_source(source_dir, gem_name)
132
+ # install_gem_from_source(source_dir, :skip => [...])
133
+ def install_gem_from_source(source_dir, *args)
134
+ installed_gems = []
135
+ Dir.chdir(source_dir) do
136
+ opts = args.last.is_a?(Hash) ? args.pop : {}
137
+ gem_name = args[0] || File.basename(source_dir)
138
+ gem_pkg_dir = File.join(source_dir, 'pkg')
139
+ gem_pkg_glob = File.join(gem_pkg_dir, "#{gem_name}-*.gem")
140
+ skip_gems = opts.delete(:skip) || []
141
+
142
+ # Cleanup what's already there
143
+ clobber(source_dir)
144
+ FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
145
+
146
+ # Recursively process all gem packages within the source dir
147
+ skip_gems << gem_name
148
+ packages = package_all(source_dir, skip_gems)
149
+
150
+ if packages.length == 1
151
+ # The are no subpackages for the main package
152
+ refresh = [gem_name]
158
153
  else
159
- raise Gem::InstallError, "No package found for #{gem_name}"
160
- end
161
- # Handle elaborate installation through Rake
162
- else
163
- # Clean and regenerate any subgems for meta gems.
164
- Dir[File.join(gem_src_dir, '*', 'Rakefile')].each do |rakefile|
165
- FileUtils.cd(File.dirname(rakefile)) do
166
- system("#{rake} clobber_package; #{rake} package")
167
- end
168
- end
169
-
170
- # Handle the main gem install.
171
- if File.exists?(File.join(gem_src_dir, 'Rakefile'))
172
- subgems = []
173
- # Remove any existing packages.
174
- FileUtils.cd(gem_src_dir) { system("#{rake} clobber_package") }
175
- # Create the main gem pkg dir if it doesn't exist.
176
- FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
177
- # Copy any subgems to the main gem pkg dir.
178
- Dir[File.join(gem_src_dir, '*', 'pkg', '*.gem')].each do |subgem_pkg|
179
- if name = File.basename(subgem_pkg, '.gem')[/^(.*?)-([\d\.]+)$/, 1]
180
- subgems << name
181
- end
182
- dest = File.join(gem_pkg_dir, File.basename(subgem_pkg))
183
- FileUtils.copy_entry(subgem_pkg, dest, true, false, true)
154
+ # Gather all packages into the top-level pkg directory
155
+ packages.each do |pkg|
156
+ FileUtils.copy_entry(pkg, File.join(gem_pkg_dir, File.basename(pkg)))
184
157
  end
185
-
186
- # Finally generate the main package and install it; subgems
187
- # (dependencies) are local to the main package.
188
- FileUtils.cd(gem_src_dir) do
189
- system("#{rake} package")
190
- FileUtils.cd(gem_pkg_dir) do
191
- if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
192
- # If the (meta) gem has it's own package, install it.
193
- install_gem(File.basename(package), options.merge(:refresh => subgems))
194
- else
195
- # Otherwise install each package seperately.
196
- Dir["*.gem"].each { |gem| install_gem(gem, options.dup) }
197
- end
158
+
159
+ # Finally package the main gem - without clobbering the already copied pkgs
160
+ package(source_dir, false)
161
+
162
+ # Gather subgems to refresh during installation of the main gem
163
+ refresh = packages.map do |pkg|
164
+ File.basename(pkg, '.gem')[/^(.*?)-([\d\.]+)$/, 1] rescue nil
165
+ end.compact
166
+
167
+ # Install subgems explicitly even if ignore_dependencies is set
168
+ if opts[:ignore_dependencies]
169
+ refresh.each do |name|
170
+ gem_pkg = Dir[File.join(gem_pkg_dir, "#{name}-*.gem")][0]
171
+ install_pkg(gem_pkg, opts)
198
172
  end
199
- return true
200
173
  end
201
174
  end
175
+
176
+ # Finally install the main gem
177
+ if install_pkg(Dir[gem_pkg_glob][0], opts.merge(:refresh => refresh))
178
+ installed_gems = refresh
179
+ else
180
+ installed_gems = []
181
+ end
202
182
  end
203
- raise Gem::InstallError, "No Rakefile found for #{gem_name}"
183
+ installed_gems
204
184
  end
205
-
185
+
186
+ def install_pkg(gem_pkg, opts = {})
187
+ if (gem_pkg && File.exists?(gem_pkg))
188
+ # Needs to be executed from the directory that contains all packages
189
+ Dir.chdir(File.dirname(gem_pkg)) { install_gem(gem_pkg, opts) }
190
+ else
191
+ false
192
+ end
193
+ end
194
+
206
195
  # Uninstall a gem.
207
196
  def uninstall_gem(gem, options = {})
208
197
  if options[:version] && !options[:version].is_a?(Gem::Requirement)
@@ -212,6 +201,45 @@ module GemManagement
212
201
  Gem::Uninstaller.new(gem, options).uninstall
213
202
  end
214
203
 
204
+ def clobber(source_dir)
205
+ Dir.chdir(source_dir) do
206
+ system "#{Gem.ruby} -S rake -s clobber" unless File.exists?('Thorfile')
207
+ end
208
+ end
209
+
210
+ def package(source_dir, clobber = true)
211
+ Dir.chdir(source_dir) do
212
+ if File.exists?('Thorfile')
213
+ thor ":package"
214
+ elsif File.exists?('Rakefile')
215
+ rake "clobber" if clobber
216
+ rake "package"
217
+ end
218
+ end
219
+ Dir[File.join(source_dir, 'pkg/*.gem')]
220
+ end
221
+
222
+ def package_all(source_dir, skip = [], packages = [])
223
+ if Dir[File.join(source_dir, '{Rakefile,Thorfile}')][0]
224
+ name = File.basename(source_dir)
225
+ Dir[File.join(source_dir, '*', '{Rakefile,Thorfile}')].each do |taskfile|
226
+ package_all(File.dirname(taskfile), skip, packages)
227
+ end
228
+ packages.push(*package(source_dir)) unless skip.include?(name)
229
+ end
230
+ packages.uniq
231
+ end
232
+
233
+ def rake(cmd)
234
+ cmd << " >/dev/null" if $SILENT && !Gem.win_platform?
235
+ system "#{Gem.ruby} -S #{which('rake')} -s #{cmd} >/dev/null"
236
+ end
237
+
238
+ def thor(cmd)
239
+ cmd << " >/dev/null" if $SILENT && !Gem.win_platform?
240
+ system "#{Gem.ruby} -S #{which('thor')} #{cmd}"
241
+ end
242
+
215
243
  # Use the local bin/* executables if available.
216
244
  def which(executable)
217
245
  if File.executable?(exec = File.join(Dir.pwd, 'bin', executable))
@@ -221,17 +249,43 @@ module GemManagement
221
249
  end
222
250
  end
223
251
 
252
+ # Partition gems into system, local and missing gems
253
+ def partition_dependencies(dependencies, gem_dir)
254
+ system_specs, local_specs, missing_deps = [], [], []
255
+ if gem_dir && File.directory?(gem_dir)
256
+ gem_dir = File.expand_path(gem_dir)
257
+ ::Gem.clear_paths; ::Gem.path.unshift(gem_dir)
258
+ ::Gem.source_index.refresh!
259
+ dependencies.each do |dep|
260
+ gemspecs = ::Gem.source_index.search(dep)
261
+ local = gemspecs.reverse.find { |s| s.loaded_from.index(gem_dir) == 0 }
262
+ if local
263
+ local_specs << local
264
+ elsif gemspecs.last
265
+ system_specs << gemspecs.last
266
+ else
267
+ missing_deps << dep
268
+ end
269
+ end
270
+ ::Gem.clear_paths
271
+ end
272
+ [system_specs, local_specs, missing_deps]
273
+ end
274
+
224
275
  # Create a modified executable wrapper in the specified bin directory.
225
276
  def ensure_bin_wrapper_for(gem_dir, bin_dir, *gems)
277
+ options = gems.last.is_a?(Hash) ? gems.last : {}
278
+ options[:no_minigems] ||= []
226
279
  if bin_dir && File.directory?(bin_dir)
227
280
  gems.each do |gem|
228
281
  if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last
229
282
  spec = Gem::Specification.load(gemspec_path)
283
+ enable_minigems = !options[:no_minigems].include?(spec.name)
230
284
  spec.executables.each do |exec|
231
285
  executable = File.join(bin_dir, exec)
232
286
  message "Writing executable wrapper #{executable}"
233
287
  File.open(executable, 'w', 0755) do |f|
234
- f.write(executable_wrapper(spec, exec))
288
+ f.write(executable_wrapper(spec, exec, enable_minigems))
235
289
  end
236
290
  end
237
291
  end
@@ -241,7 +295,10 @@ module GemManagement
241
295
 
242
296
  private
243
297
 
244
- def executable_wrapper(spec, bin_file_name)
298
+ def executable_wrapper(spec, bin_file_name, minigems = true)
299
+ requirements = ['minigems', 'rubygems']
300
+ requirements.reverse! unless minigems
301
+ try_req, then_req = requirements
245
302
  <<-TEXT
246
303
  #!/usr/bin/env ruby
247
304
  #
@@ -251,9 +308,9 @@ module GemManagement
251
308
  # this file is here to facilitate running it.
252
309
 
253
310
  begin
254
- require 'minigems'
311
+ require '#{try_req}'
255
312
  rescue LoadError
256
- require 'rubygems'
313
+ require '#{then_req}'
257
314
  end
258
315
 
259
316
  if File.directory?(gems_dir = File.join(Dir.pwd, 'gems')) ||
@@ -288,44 +345,241 @@ TEXT
288
345
  def update_source_index(dir)
289
346
  Gem.source_index.load_gems_in(File.join(dir, 'specifications'))
290
347
  end
291
-
348
+
292
349
  end
293
350
 
294
-
295
351
  ##############################################################################
296
352
 
297
- module MerbThorHelper
298
-
353
+ class SourceManager
354
+
299
355
  include ColorfulMessages
356
+
357
+ attr_accessor :source_dir
358
+
359
+ def initialize(source_dir)
360
+ self.source_dir = source_dir
361
+ end
362
+
363
+ def clone(name, url)
364
+ FileUtils.cd(source_dir) do
365
+ raise "destination directory already exists" if File.directory?(name)
366
+ system("git clone --depth 1 #{url} #{name}")
367
+ end
368
+ rescue => e
369
+ error "Unable to clone #{name} repository (#{e.message})"
370
+ end
371
+
372
+ def update(name, url)
373
+ if File.directory?(repository_dir = File.join(source_dir, name))
374
+ FileUtils.cd(repository_dir) do
375
+ repos = existing_repos(name)
376
+ fork_name = url[/.com\/+?(.+)\/.+\.git/u, 1]
377
+ if url == repos["origin"]
378
+ # Pull from the original repository - no branching needed
379
+ info "Pulling from origin: #{url}"
380
+ system "git fetch; git checkout master; git rebase origin/master"
381
+ elsif repos.values.include?(url) && fork_name
382
+ # Update and switch to a remote branch for a particular github fork
383
+ info "Switching to remote branch: #{fork_name}"
384
+ system "git checkout -b #{fork_name} #{fork_name}/master"
385
+ system "git rebase #{fork_name}/master"
386
+ elsif fork_name
387
+ # Create a new remote branch for a particular github fork
388
+ info "Adding a new remote branch: #{fork_name}"
389
+ system "git remote add -f #{fork_name} #{url}"
390
+ system "git checkout -b #{fork_name} #{fork_name}/master"
391
+ else
392
+ warning "No valid repository found for: #{name}"
393
+ end
394
+ end
395
+ return true
396
+ else
397
+ warning "No valid repository found at: #{repository_dir}"
398
+ end
399
+ rescue => e
400
+ error "Unable to update #{name} repository (#{e.message})"
401
+ return false
402
+ end
403
+
404
+ def existing_repos(name)
405
+ repos = []
406
+ FileUtils.cd(File.join(source_dir, name)) do
407
+ repos = %x[git remote -v].split("\n").map { |branch| branch.split(/\s+/) }
408
+ end
409
+ Hash[*repos.flatten]
410
+ end
411
+
412
+ end
300
413
 
301
- DO_ADAPTERS = %w[mysql postgres sqlite3]
414
+ ##############################################################################
302
415
 
303
- private
416
+ module MerbThorHelper
417
+
418
+ attr_accessor :include_dependencies
419
+
420
+ def self.included(base)
421
+ base.send(:include, ColorfulMessages)
422
+ base.extend ColorfulMessages
423
+ end
424
+
425
+ def install_dependency(dependency, opts = {})
426
+ opts[:version] ||= dependency.version_requirements.to_s
427
+ Merb::Gem.install(dependency.name, default_install_options.merge(opts))
428
+ end
429
+
430
+ def source_manager
431
+ @_source_manager ||= SourceManager.new(source_dir)
432
+ end
433
+
434
+ def extract_repositories(names)
435
+ repos = []
436
+ names.each do |name|
437
+ if repo_url = Merb::Source.repo(name, options[:sources])
438
+ # A repository entry for this dependency exists
439
+ repo = [name, repo_url]
440
+ repos << repo unless repos.include?(repo)
441
+ elsif (repo_name = Merb::Stack.lookup_repository_name(name)) &&
442
+ (repo_url = Merb::Source.repo(repo_name, options[:sources]))
443
+ # A parent repository entry for this dependency exists
444
+ repo = [repo_name, repo_url]
445
+ unless repos.include?(repo)
446
+ puts "Found #{repo_name}/#{name} at #{repo_url}"
447
+ repos << repo
448
+ end
449
+ end
450
+ end
451
+ repos
452
+ end
453
+
454
+ def update_dependency_repositories(dependencies)
455
+ repos = extract_repositories(dependencies.map { |d| d.name })
456
+ update_repositories(repos)
457
+ end
458
+
459
+ def update_repositories(repos)
460
+ repos.each do |(name, url)|
461
+ if File.directory?(repository_dir = File.join(source_dir, name))
462
+ message "Updating or branching #{name}..."
463
+ source_manager.update(name, url)
464
+ else
465
+ message "Cloning #{name} repository from #{url}..."
466
+ source_manager.clone(name, url)
467
+ end
468
+ end
469
+ end
304
470
 
471
+ def install_dependency_from_source(dependency, opts = {})
472
+ matches = Dir[File.join(source_dir, "**", dependency.name, "{Rakefile,Thorfile}")]
473
+ matches.reject! { |m| File.basename(m) == 'Thorfile' }
474
+ if matches.length == 1 && matches[0]
475
+ if File.directory?(gem_src_dir = File.dirname(matches[0]))
476
+ begin
477
+ Merb::Gem.install_gem_from_source(gem_src_dir, default_install_options.merge(opts))
478
+ puts "Installed #{dependency.name}"
479
+ return true
480
+ rescue => e
481
+ warning "Unable to install #{dependency.name} from source (#{e.message})"
482
+ end
483
+ else
484
+ msg = "Unknown directory: #{gem_src_dir}"
485
+ warning "Unable to install #{dependency.name} from source (#{msg})"
486
+ end
487
+ elsif matches.length > 1
488
+ error "Ambigous source(s) for dependency: #{dependency.name}"
489
+ matches.each { |m| puts "- #{m}" }
490
+ end
491
+ return false
492
+ end
493
+
494
+ def clobber_dependencies!
495
+ if options[:force] && gem_dir && File.directory?(gem_dir)
496
+ # Remove all existing local gems by clearing the gems directory
497
+ if dry_run?
498
+ note 'Clearing existing local gems...'
499
+ else
500
+ message 'Clearing existing local gems...'
501
+ FileUtils.rm_rf(gem_dir) && FileUtils.mkdir_p(default_gem_dir)
502
+ end
503
+ elsif !local.empty?
504
+ # Uninstall all local versions of the gems to install
505
+ if dry_run?
506
+ note 'Uninstalling existing local gems:'
507
+ local.each { |gemspec| note "Uninstalled #{gemspec.name}" }
508
+ else
509
+ message 'Uninstalling existing local gems:'
510
+ local.each do |gemspec|
511
+ Merb::Gem.uninstall(gemspec.name, default_uninstall_options)
512
+ end
513
+ end
514
+ end
515
+ end
516
+
517
+ def display_gemspecs(gemspecs)
518
+ if gemspecs.empty?
519
+ puts "- none"
520
+ else
521
+ gemspecs.each do |spec|
522
+ if hint = Dir[File.join(spec.full_gem_path, '*.strategy')][0]
523
+ strategy = File.basename(hint, '.strategy')
524
+ puts "- #{spec.full_name} (#{strategy})"
525
+ else
526
+ puts "~ #{spec.full_name}" # unknown strategy
527
+ end
528
+ end
529
+ end
530
+ end
531
+
532
+ def display_dependencies(dependencies)
533
+ if dependencies.empty?
534
+ puts "- none"
535
+ else
536
+ dependencies.each { |d| puts "- #{d.name} (#{d.version_requirements})" }
537
+ end
538
+ end
539
+
540
+ def default_install_options
541
+ { :install_dir => gem_dir, :ignore_dependencies => ignore_dependencies? }
542
+ end
543
+
544
+ def default_uninstall_options
545
+ { :install_dir => gem_dir, :ignore => true, :all => true, :executables => true }
546
+ end
547
+
548
+ def dry_run?
549
+ options[:"dry-run"]
550
+ end
551
+
552
+ def ignore_dependencies?
553
+ options[:"ignore-dependencies"] || !include_dependencies?
554
+ end
555
+
556
+ def include_dependencies?
557
+ options[:"include-dependencies"] || self.include_dependencies
558
+ end
559
+
305
560
  # The current working directory, or Merb app root (--merb-root option).
306
561
  def working_dir
307
562
  @_working_dir ||= File.expand_path(options['merb-root'] || Dir.pwd)
308
563
  end
309
-
564
+
310
565
  # We should have a ./src dir for local and system-wide management.
311
566
  def source_dir
312
567
  @_source_dir ||= File.join(working_dir, 'src')
313
568
  create_if_missing(@_source_dir)
314
569
  @_source_dir
315
570
  end
316
-
317
- # If a local ./gems dir is found, it means we are in a Merb app.
318
- def application?
319
- gem_dir
320
- end
321
-
571
+
322
572
  # If a local ./gems dir is found, return it.
323
573
  def gem_dir
324
- if File.directory?(dir = File.join(working_dir, 'gems'))
574
+ if File.directory?(dir = default_gem_dir)
325
575
  dir
326
576
  end
327
577
  end
328
-
578
+
579
+ def default_gem_dir
580
+ File.join(working_dir, 'gems')
581
+ end
582
+
329
583
  # If we're in a Merb app, we can have a ./bin directory;
330
584
  # create it if it's not there.
331
585
  def bin_dir
@@ -338,884 +592,1319 @@ module MerbThorHelper
338
592
  end
339
593
  end
340
594
 
341
- def config_dir
342
- @_config_dir ||= File.join(working_dir, 'config')
343
- end
344
-
345
- def config_file
346
- @_config_file ||= File.join(config_dir, 'dependencies.yml')
347
- end
348
-
349
- # Find the latest merb-core and gather its dependencies.
350
- # We check for 0.9.8 as a minimum release version.
351
- def core_dependencies
352
- @_core_dependencies ||= begin
353
- if gem_dir
354
- Gem.clear_paths; Gem.path.unshift(gem_dir)
355
- end
356
- deps = []
357
- merb_core = Gem::Dependency.new('merb-core', '>= 0.9.8')
358
- if gemspec = Gem.source_index.search(merb_core).last
359
- deps << Gem::Dependency.new('merb-core', gemspec.version)
360
- deps += gemspec.dependencies
361
- end
362
- Gem.clear_paths
363
- deps
364
- end
365
- end
366
-
367
- # Find local gems and return matched version numbers.
368
- def find_dependency_versions(dependency)
369
- versions = []
370
- specs = Dir[File.join(gem_dir, 'specifications', "#{dependency.name}-*.gemspec")]
371
- unless specs.empty?
372
- specs.inject(versions) do |versions, gemspec_path|
373
- versions << gemspec_path[/-([\d\.]+)\.gemspec$/, 1]
374
- end
375
- end
376
- versions.sort.reverse
377
- end
378
-
379
595
  # Helper to create dir unless it exists.
380
596
  def create_if_missing(path)
381
597
  FileUtils.mkdir(path) unless File.exists?(path)
382
598
  end
383
-
384
- def neutralise_sudo_for(dir)
385
- if ENV['SUDO_UID'] && ENV['SUDO_GID']
386
- FileUtils.chown_R(ENV['SUDO_UID'], ENV['SUDO_GID'], dir)
387
- end
599
+
600
+ def ensure_bin_wrapper_for(*gems)
601
+ Merb::Gem.ensure_bin_wrapper_for(gem_dir, bin_dir, *gems)
388
602
  end
389
603
 
390
- def ensure_bin_wrapper_for(*gems)
391
- Merb.ensure_bin_wrapper_for(gem_dir, bin_dir, *gems)
604
+ def sudo
605
+ ENV['THOR_SUDO'] ||= "sudo"
606
+ sudo = Gem.win_platform? ? "" : ENV['THOR_SUDO']
392
607
  end
393
608
 
394
- def ensure_bin_wrapper_for_core_components
395
- ensure_bin_wrapper_for('merb-core', 'rake', 'rspec', 'thor', 'merb-gen')
609
+ def local_gemspecs(directory = gem_dir)
610
+ if File.directory?(specs_dir = File.join(directory, 'specifications'))
611
+ Dir[File.join(specs_dir, '*.gemspec')].map do |gemspec_path|
612
+ gemspec = Gem::Specification.load(gemspec_path)
613
+ gemspec.loaded_from = gemspec_path
614
+ gemspec
615
+ end
616
+ else
617
+ []
618
+ end
396
619
  end
397
620
 
398
621
  end
399
622
 
400
623
  ##############################################################################
401
624
 
402
- class Merb < Thor
403
-
404
- extend GemManagement
405
-
406
- # Default Git repositories
407
- def self.default_repos
408
- @_default_repos ||= {
409
- 'merb-core' => "git://github.com/wycats/merb-core.git",
410
- 'merb-more' => "git://github.com/wycats/merb-more.git",
411
- 'merb-plugins' => "git://github.com/wycats/merb-plugins.git",
412
- 'extlib' => "git://github.com/sam/extlib.git",
413
- 'dm-core' => "git://github.com/sam/dm-core.git",
414
- 'dm-more' => "git://github.com/sam/dm-more.git",
415
- 'do' => "git://github.com/sam/do.git",
416
- 'thor' => "git://github.com/wycats/thor.git",
417
- 'minigems' => "git://github.com/fabien/minigems.git"
418
- }
419
- end
625
+ $SILENT = true # don't output all the mess some rake package tasks spit out
420
626
 
421
- # Git repository sources - pass source_config option to load a yaml
422
- # configuration file - defaults to ./config/git-sources.yml and
423
- # ~/.merb/git-sources.yml - which need to create yourself if desired.
424
- #
425
- # Example of contents:
426
- #
427
- # merb-core: git://github.com/myfork/merb-core.git
428
- # merb-more: git://github.com/myfork/merb-more.git
429
- def self.repos(source_config = nil)
430
- source_config ||= begin
431
- local_config = File.join(Dir.pwd, 'config', 'git-sources.yml')
432
- user_config = File.join(ENV["HOME"] || ENV["APPDATA"], '.merb', 'git-sources.yml')
433
- File.exists?(local_config) ? local_config : user_config
434
- end
435
- if source_config && File.exists?(source_config)
436
- default_repos.merge(YAML.load(File.read(source_config)))
437
- else
438
- default_repos
439
- end
440
- end
441
-
627
+ module Merb
628
+
442
629
  class Dependencies < Thor
443
-
630
+
631
+ # The Dependencies tasks will install dependencies based on actual application
632
+ # dependencies. For this, the application is queried for any dependencies.
633
+ # All operations will be performed within this context.
634
+
635
+ attr_accessor :system, :local, :missing
636
+
444
637
  include MerbThorHelper
445
638
 
446
- # List all dependencies by extracting them from the actual application;
447
- # will differentiate between locally available gems and system gems.
448
- # Local gems will be shown with the installed gem version numbers.
449
-
450
- desc 'list', 'List all application dependencies'
451
- method_options "--merb-root" => :optional,
452
- "--local" => :boolean,
453
- "--system" => :boolean
454
- def list
455
- partitioned = { :local => [], :system => [] }
456
- (core_dependencies + extract_dependencies).each do |dependency|
457
- if gem_dir && !(versions = find_dependency_versions(dependency)).empty?
458
- partitioned[:local] << "#{dependency} [#{versions.join(', ')}]"
459
- else
460
- partitioned[:system] << "#{dependency}"
461
- end
462
- end
463
- none = options[:system].nil? && options[:local].nil?
464
- if (options[:system] || none) && !partitioned[:system].empty?
465
- message "System dependencies:"
466
- partitioned[:system].each { |str| puts "- #{str}" }
467
- end
468
- if (options[:local] || none) && !partitioned[:local].empty?
469
- message "Local dependencies:"
470
- partitioned[:local].each { |str| puts "- #{str}" }
471
- end
472
- end
639
+ global_method_options = {
640
+ "--merb-root" => :optional, # the directory to operate on
641
+ "--include-dependencies" => :boolean, # gather sub-dependencies
642
+ "--stack" => :boolean, # gather only stack dependencies
643
+ "--no-stack" => :boolean, # gather only non-stack dependencies
644
+ "--config" => :boolean, # gather dependencies from yaml config
645
+ "--config-file" => :optional, # gather from the specified yaml config
646
+ "--version" => :optional # gather specific version of framework
647
+ }
473
648
 
474
- # Install the gems listed in dependencies.yml from RubyForge (stable).
475
- # Will also install local bin wrappers for known components.
649
+ method_options global_method_options
650
+ def initialize(*args); super; end
476
651
 
477
- desc 'install', 'Install the gems listed in ./config/dependencies.yml'
478
- method_options "--merb-root" => :optional,
479
- "--cache" => :boolean,
480
- "--binaries" => :boolean
481
- def install
482
- if File.exists?(config_file)
483
- dependencies = parse_dependencies_yaml(File.read(config_file))
484
- gems = Gems.new
485
- gems.options = options
486
- dependencies.each do |dependency|
487
- v = dependency.version_requirements
488
- gems.install_gem(dependency.name, :version => v, :cache => options[:cache])
652
+ # List application dependencies.
653
+ #
654
+ # By default all dependencies are listed, partitioned into system, local and
655
+ # currently missing dependencies. The first argument allows you to filter
656
+ # on any of the partitionings. A second argument can be used to filter on
657
+ # a set of known components, like all merb-more gems for example.
658
+ #
659
+ # Examples:
660
+ #
661
+ # merb:dependencies:list # list all dependencies - the default
662
+ # merb:dependencies:list local # list only local gems
663
+ # merb:dependencies:list all merb-more # list only merb-more related dependencies
664
+ # merb:dependencies:list --stack # list framework dependencies
665
+ # merb:dependencies:list --no-stack # list 3rd party dependencies
666
+ # merb:dependencies:list --config # list dependencies from the default config
667
+ # merb:dependencies:list --config-file file.yml # list from the specified config file
668
+
669
+ desc 'list [all|local|system|missing] [comp]', 'Show application dependencies'
670
+ def list(filter = 'all', comp = nil)
671
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
672
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
673
+ case filter
674
+ when 'all'
675
+ message 'Installed system gem dependencies:'
676
+ display_gemspecs(system)
677
+ message 'Installed local gem dependencies:'
678
+ display_gemspecs(local)
679
+ unless missing.empty?
680
+ error 'Missing gem dependencies:'
681
+ display_dependencies(missing)
489
682
  end
490
- # if options[:binaries] is set this is already taken care of - skip it
491
- ensure_bin_wrapper_for_core_components unless options[:binaries]
683
+ when 'system'
684
+ message 'Installed system gem dependencies:'
685
+ display_gemspecs(system)
686
+ when 'local'
687
+ message 'Installed local gem dependencies:'
688
+ display_gemspecs(local)
689
+ when 'missing'
690
+ error 'Missing gem dependencies:'
691
+ display_dependencies(missing)
492
692
  else
493
- error "No configuration file found at #{config_file}"
494
- error "Please run merb:dependencies:configure first."
693
+ warning "Invalid listing filter '#{filter}'"
694
+ end
695
+ if missing.size > 0
696
+ info "Some dependencies are currently missing!"
697
+ elsif local.size == deps.size
698
+ info "All dependencies have been bundled with the application."
699
+ elsif local.size > system.size
700
+ info "Most dependencies have been bundled with the application."
701
+ elsif system.size > 0 && local.size > 0
702
+ info "Some dependencies have been bundled with the application."
703
+ elsif local.empty? && system.size == deps.size
704
+ info "All dependencies are available on the system."
495
705
  end
496
706
  end
497
707
 
498
- # Retrieve all application dependencies and store them in a local
499
- # configuration file at ./config/dependencies.yml
708
+ # Install application dependencies.
709
+ #
710
+ # By default all required dependencies are installed. The first argument
711
+ # specifies which strategy to use: stable or edge. A second argument can be
712
+ # used to filter on a set of known components.
713
+ #
714
+ # Existing dependencies will be clobbered; when :force => true then all gems
715
+ # will be cleared first, otherwise only existing local dependencies of the
716
+ # gems to be installed will be removed.
500
717
  #
501
- # The format of this YAML file is as follows:
502
- # - merb_helpers (>= 0, runtime)
503
- # - merb-slices (> 0.9.4, runtime)
504
-
505
- desc 'configure', 'Retrieve and store dependencies in ./config/dependencies.yml'
506
- method_options "--merb-root" => :optional,
507
- "--force" => :boolean
508
- def configure
509
- entries = (core_dependencies + extract_dependencies).map { |d| d.to_s }
510
- FileUtils.mkdir_p(config_dir) unless File.directory?(config_dir)
511
- config = YAML.dump(entries)
512
- puts "#{config}\n"
513
- if File.exists?(config_file) && !options[:force]
514
- error "File already exists! Use --force to overwrite."
515
- else
516
- File.open(config_file, 'w') { |f| f.write config }
517
- success "Written #{config_file}:"
518
- end
519
- rescue
520
- error "Failed to write to #{config_file}"
521
- end
718
+ # Examples:
719
+ #
720
+ # merb:dependencies:install # install all dependencies using stable strategy
721
+ # merb:dependencies:install stable --version 0.9.8 # install a specific version of the framework
722
+ # merb:dependencies:install stable missing # install currently missing gems locally
723
+ # merb:dependencies:install stable merb-more # install only merb-more related dependencies
724
+ # merb:dependencies:install stable --stack # install framework dependencies
725
+ # merb:dependencies:install stable --no-stack # install 3rd party dependencies
726
+ # merb:dependencies:install stable --config # read dependencies from the default config
727
+ # merb:dependencies:install stable --config-file file.yml # read from the specified config file
728
+ #
729
+ # In addition to the options above, edge install uses the following:
730
+ #
731
+ # merb:dependencies:install edge # install all dependencies using edge strategy
732
+ # merb:dependencies:install edge --sources file.yml # install edge from the specified git sources config
522
733
 
523
- protected
734
+ desc 'install [stable|edge] [comp]', 'Install application dependencies'
735
+ method_options "--sources" => :optional, # only for edge strategy
736
+ "--local" => :boolean, # force local install
737
+ "--dry-run" => :boolean,
738
+ "--force" => :boolean
739
+ def install(strategy = 'stable', comp = nil)
740
+ if strategy?(strategy)
741
+ # Force local dependencies by creating ./gems before proceeding
742
+ create_if_missing(default_gem_dir) if options[:local]
743
+
744
+ where = gem_dir ? 'locally' : 'system-wide'
524
745
 
525
- # Extract the runtime dependencies by starting the application in runner mode.
526
- def extract_dependencies
527
- FileUtils.cd(working_dir) do
528
- cmd = ["require 'yaml';"]
529
- cmd << "dependencies = Merb::BootLoader::Dependencies.dependencies"
530
- cmd << "entries = dependencies.map { |d| d.to_s }"
531
- cmd << "puts YAML.dump(entries)"
532
- output = `merb -r "#{cmd.join("\n")}"`
533
- if index = (lines = output.split(/\n/)).index('--- ')
534
- yaml = lines.slice(index, lines.length - 1).join("\n")
535
- return parse_dependencies_yaml(yaml)
746
+ # When comp == 'missing' then filter on missing dependencies
747
+ if only_missing = comp == 'missing'
748
+ message "Preparing to install missing gems #{where} using #{strategy} strategy..."
749
+ comp = nil
750
+ else
751
+ message "Preparing to install #{where} using #{strategy} strategy..."
536
752
  end
537
- end
538
- return []
539
- end
540
-
541
- # Parse the basic YAML config data, and process Gem::Dependency output.
542
- # Formatting example: merb_helpers (>= 0.9.8, runtime)
543
- def parse_dependencies_yaml(yaml)
544
- dependencies = []
545
- entries = YAML.load(yaml) rescue []
546
- entries.each do |entry|
547
- if matches = entry.match(/^(\S+) \(([^,]+)?, ([^\)]+)\)/)
548
- name, version_req, type = matches.captures
549
- dependencies << Gem::Dependency.new(name, version_req, type.to_sym)
753
+
754
+ # If comp given, filter on known stack components
755
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
756
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
757
+
758
+ # Only install currently missing gems (for comp == missing)
759
+ if only_missing
760
+ deps.reject! { |dep| not missing.include?(dep) }
761
+ end
762
+
763
+ if deps.empty?
764
+ warning "No dependencies to install..."
550
765
  else
551
- error "Invalid entry: #{entry}"
766
+ puts "#{deps.length} dependencies to install..."
767
+ install_dependencies(strategy, deps)
552
768
  end
553
- end
554
- dependencies
769
+
770
+ # Show current dependency info now that we're done
771
+ puts # Seperate output
772
+ list('local', comp)
773
+ else
774
+ warning "Invalid install strategy '#{strategy}'"
775
+ puts
776
+ message "Please choose one of the following installation strategies: stable or edge:"
777
+ puts "$ thor merb:dependencies:install stable"
778
+ puts "$ thor merb:dependencies:install edge"
779
+ end
555
780
  end
556
781
 
557
- end
558
-
559
- # Install a Merb stack from stable RubyForge gems. Optionally install a
560
- # suitable Rack adapter/server when setting --adapter to one of the
561
- # following: mongrel, emongrel, thin or ebb. Or with --orm, install a
562
- # supported ORM: datamapper, activerecord or sequel
563
-
564
- desc 'stable', 'Install extlib, merb-core and merb-more from rubygems'
565
- method_options "--merb-root" => :optional,
566
- "--adapter" => :optional,
567
- "--orm" => :optional
568
- def stable
569
- adapters = {
570
- 'mongrel' => ['mongrel'],
571
- 'emongrel' => ['emongrel'],
572
- 'thin' => ['thin'],
573
- 'ebb' => ['ebb']
574
- }
575
- orms = {
576
- 'datamapper' => ['dm-core'],
577
- 'activerecord' => ['activerecord'],
578
- 'sequel' => ['sequel']
579
- }
580
- stable = Stable.new
581
- stable.options = options
582
- if stable.core && stable.more
583
- success "Installed extlib, merb-core and merb-more"
584
- if options[:adapter] && adapters[options[:adapter]]
585
- stable.refresh_from_gems(*adapters[options[:adapter]])
586
- success "Installed #{options[:adapter]}"
587
- elsif options[:adapter]
588
- error "Please specify one of the following adapters: #{adapters.keys.join(' ')}"
589
- end
590
- if options[:orm] && orms[options[:orm]]
591
- stable.refresh_from_gems(*orms[options[:orm]])
592
- success "Installed #{options[:orm]}"
593
- elsif options[:orm]
594
- error "Please specify one of the following orms: #{orms.keys.join(' ')}"
595
- end
596
- end
597
- end
598
-
599
- class Stable < Thor
600
-
601
- # The Stable tasks deal with known -stable- gems; available
602
- # as shortcuts to Merb and DataMapper gems.
782
+ # Uninstall application dependencies.
603
783
  #
604
- # These are pulled from rubyforge and installed into into the
605
- # desired gems dir (either system-wide or into the application's
606
- # gems directory).
607
-
608
- include MerbThorHelper
609
-
610
- # Gets latest gem versions from RubyForge and installs them.
784
+ # By default all required dependencies are installed. An optional argument
785
+ # can be used to filter on a set of known components.
786
+ #
787
+ # Existing dependencies will be clobbered; when :force => true then all gems
788
+ # will be cleared, otherwise only existing local dependencies of the
789
+ # matching component set will be removed.
611
790
  #
612
791
  # Examples:
613
792
  #
614
- # thor merb:edge:core
615
- # thor merb:edge:core --merb-root ./path/to/your/app
616
- # thor merb:edge:core --sources ./path/to/sources.yml
617
-
618
- desc 'core', 'Install extlib and merb-core from rubygems'
619
- method_options "--merb-root" => :optional
620
- def core
621
- refresh_from_gems 'extlib', 'merb-core'
622
- ensure_bin_wrapper_for('merb-core', 'rake', 'rspec', 'thor')
623
- end
624
-
625
- desc 'more', 'Install merb-more from rubygems'
626
- method_options "--merb-root" => :optional
627
- def more
628
- refresh_from_gems 'merb-more'
629
- ensure_bin_wrapper_for('merb-gen')
793
+ # merb:dependencies:uninstall # uninstall all dependencies - the default
794
+ # merb:dependencies:uninstall merb-more # uninstall merb-more related gems locally
795
+ # merb:dependencies:uninstall --config # read dependencies from the default config
796
+
797
+ desc 'uninstall [comp]', 'Uninstall application dependencies'
798
+ method_options "--dry-run" => :boolean, "--force" => :boolean
799
+ def uninstall(comp = nil)
800
+ # If comp given, filter on known stack components
801
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
802
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
803
+ # Clobber existing local dependencies - based on self.local
804
+ clobber_dependencies!
630
805
  end
631
-
632
- desc 'plugins', 'Install merb-plugins from rubygems'
633
- method_options "--merb-root" => :optional
634
- def plugins
635
- refresh_from_gems 'merb-plugins'
806
+
807
+ # Recreate binary gems on the current platform.
808
+ #
809
+ # This task should be executed as part of a deployment setup, where the
810
+ # deployment system runs this after the app has been installed.
811
+ # Usually triggered by Capistrano, God...
812
+ #
813
+ # It will regenerate gems from the bundled gems cache for any gem that has
814
+ # C extensions - which need to be recompiled for the target deployment platform.
815
+ #
816
+ # Note: gems/cache should be in your SCM for this to work correctly.
817
+
818
+ desc 'redeploy', 'Recreate any binary gems on the target platform'
819
+ method_options "--dry-run" => :boolean
820
+ def redeploy
821
+ require 'tempfile' # for Dir::tmpdir access
822
+ if gem_dir && File.directory?(cache_dir = File.join(gem_dir, 'cache'))
823
+ local_gemspecs.each do |gemspec|
824
+ unless gemspec.extensions.empty?
825
+ if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem"))
826
+ gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file))
827
+ if dry_run?
828
+ note "Recreating #{gemspec.full_name}"
829
+ else
830
+ message "Recreating #{gemspec.full_name}"
831
+ # Copy the gem to a temporary file, because otherwise RubyGems/FileUtils
832
+ # will complain about copying identical files (same source/destination).
833
+ FileUtils.cp(gem_file, gem_file_copy)
834
+ Merb::Gem.install(gem_file_copy, :install_dir => gem_dir)
835
+ File.delete(gem_file_copy)
836
+ end
837
+ end
838
+ end
839
+ end
840
+ else
841
+ error "No application local gems directory found"
842
+ end
636
843
  end
637
-
638
- desc 'dm_core', 'Install dm-core from rubygems'
639
- method_options "--merb-root" => :optional
640
- def dm_core
641
- refresh_from_gems 'extlib', 'dm-core'
844
+
845
+ # Create a dependencies configuration file.
846
+ #
847
+ # A configuration yaml file will be created from the extracted application
848
+ # dependencies. The format of the configuration is as follows:
849
+ #
850
+ # ---
851
+ # - merb-core (= 0.9.8, runtime)
852
+ # - merb-slices (= 0.9.8, runtime)
853
+ #
854
+ # This format is exactly the same as Gem::Dependency#to_s returns.
855
+ #
856
+ # Examples:
857
+ #
858
+ # merb:dependencies:configure --force # overwrite the default config file
859
+ # merb:dependencies:configure --version 0.9.8 # configure specific framework version
860
+ # merb:dependencies:configure --config-file file.yml # write to the specified config file
861
+
862
+ desc 'configure [comp]', 'Create a dependencies config file'
863
+ method_options "--dry-run" => :boolean, "--force" => :boolean
864
+ def configure(comp = nil)
865
+ # If comp given, filter on known stack components
866
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
867
+ config = YAML.dump(deps.map { |d| d.to_s })
868
+ puts "#{config}\n"
869
+ if File.exists?(config_file) && !options[:force]
870
+ error "File already exists! Use --force to overwrite."
871
+ else
872
+ if dry_run?
873
+ note "Written #{config_file}"
874
+ else
875
+ FileUtils.mkdir_p(config_dir) unless File.directory?(config_dir)
876
+ File.open(config_file, 'w') { |f| f.write config }
877
+ success "Written #{config_file}"
878
+ end
879
+ end
880
+ rescue
881
+ error "Failed to write to #{config_file}"
882
+ end
883
+
884
+ ### Helper Methods
885
+
886
+ def strategy?(strategy)
887
+ if self.respond_to?(method = :"#{strategy}_strategy", true)
888
+ method
889
+ end
642
890
  end
643
-
644
- desc 'dm_more', 'Install dm-more from rubygems'
645
- method_options "--merb-root" => :optional
646
- def dm_more
647
- refresh_from_gems 'extlib', 'dm-core', 'dm-more'
648
- end
649
-
650
- desc 'do [ADAPTER, ...]', 'Install data_objects and optional adapter'
651
- method_options "--merb-root" => :optional
652
- def do(*adapters)
653
- refresh_from_gems 'data_objects'
654
- adapters.each do |adapter|
655
- if adapter && DO_ADAPTERS.include?(adapter)
656
- refresh_from_gems "do_#{adapter}"
657
- elsif adapter
658
- error "Unknown DO adapter '#{adapter}'"
891
+
892
+ def install_dependencies(strategy, deps)
893
+ if method = strategy?(strategy)
894
+ # Clobber existing local dependencies
895
+ clobber_dependencies!
896
+
897
+ # Run the chosen strategy - collect files installed from stable gems
898
+ installed_from_stable = send(method, deps).map { |d| d.name }
899
+
900
+ # Sleep a bit otherwise the following steps won't see the new files
901
+ sleep(deps.length) if deps.length > 0
902
+
903
+ # Leave a file to denote the strategy that has been used for this dependency
904
+ self.local.each do |spec|
905
+ next unless File.directory?(spec.full_gem_path)
906
+ unless installed_from_stable.include?(spec.name)
907
+ FileUtils.touch(File.join(spec.full_gem_path, "#{strategy}.strategy"))
908
+ else
909
+ FileUtils.touch(File.join(spec.full_gem_path, "stable.strategy"))
910
+ end
911
+ end
912
+
913
+ # Ask for system installation of minigem - needs sudo...
914
+ if deps.find { |d| d.name == 'minigems' }
915
+ info "Installing minigems.rb on your system..."
916
+ `#{sudo} minigem install`
659
917
  end
918
+
919
+ # Add local binaries for the installed framework dependencies
920
+ comps = Merb::Stack.all_components & deps.map { |d| d.name }
921
+ comps << { :no_minigems => 'merb-gen' }
922
+ ensure_bin_wrapper_for(*comps)
923
+ return true
660
924
  end
925
+ false
661
926
  end
662
927
 
663
- desc 'minigems', 'Install minigems (system-wide)'
664
- def minigems
665
- if Merb.install_gem('minigems')
666
- system("#{Gem.ruby} -S minigem install")
928
+ def dependencies
929
+ if use_config?
930
+ # Use preconfigured dependencies from yaml file
931
+ deps = config_dependencies
932
+ else
933
+ # Extract dependencies from the current application
934
+ deps = Merb::Stack.core_dependencies(gem_dir, ignore_dependencies?)
935
+ deps += Merb::Dependencies.extract_dependencies(working_dir)
667
936
  end
937
+
938
+ stack_components = Merb::Stack.components
939
+
940
+ if options[:stack]
941
+ # Limit to stack components only
942
+ deps.reject! { |dep| not stack_components.include?(dep.name) }
943
+ elsif options[:"no-stack"]
944
+ # Limit to non-stack components
945
+ deps.reject! { |dep| stack_components.include?(dep.name) }
946
+ end
947
+
948
+ if options[:version]
949
+ version_req = ::Gem::Requirement.create("= #{options[:version]}")
950
+ elsif core = deps.find { |d| d.name == 'merb-core' }
951
+ version_req = core.version_requirements
952
+ end
953
+
954
+ if version_req
955
+ # Handle specific version requirement for framework components
956
+ framework_components = Merb::Stack.framework_components
957
+ deps.each do |dep|
958
+ if framework_components.include?(dep.name)
959
+ dep.version_requirements = version_req
960
+ end
961
+ end
962
+ end
963
+
964
+ deps
668
965
  end
669
966
 
670
- # Pull from RubyForge and install.
671
- def refresh_from_gems(*components)
672
- opts = components.last.is_a?(Hash) ? components.pop : {}
673
- gems = Gems.new
674
- gems.options = options
675
- components.all? { |name| gems.install_gem(name, opts) }
967
+ def config_dependencies
968
+ if File.exists?(config_file)
969
+ self.class.parse_dependencies_yaml(File.read(config_file))
970
+ else
971
+ []
972
+ end
676
973
  end
677
-
678
- end
679
-
680
- # Retrieve latest Merb versions from git and optionally install them.
681
- #
682
- # Note: the --sources option takes a path to a YAML file
683
- # with a regular Hash mapping gem names to git urls.
684
- #
685
- # Examples:
686
- #
687
- # thor merb:edge
688
- # thor merb:edge --install
689
- # thor merb:edge --merb-root ./path/to/your/app
690
- # thor merb:edge --sources ./path/to/sources.yml
691
-
692
- desc 'edge', 'Install extlib, merb-core and merb-more from git HEAD'
693
- method_options "--merb-root" => :optional,
694
- "--sources" => :optional,
695
- "--install" => :boolean
696
- def edge
697
- edge = Edge.new
698
- edge.options = options
699
- edge.core
700
- edge.more
701
- edge.custom
702
- end
703
-
704
- class Edge < Thor
705
-
706
- # The Edge tasks deal with known gems from the bleeding edge; available
707
- # as shortcuts to Merb and DataMapper gems.
708
- #
709
- # These are pulled from git and optionally installed into into the
710
- # desired gems dir (either system-wide or into the application's
711
- # gems directory).
712
-
713
- include MerbThorHelper
714
-
715
- # Gets latest gem versions from git - optionally installs them.
716
- #
717
- # Note: the --sources option takes a path to a YAML file
718
- # with a regular Hash mapping gem names to git urls,
719
- # allowing pulling forks of the official repositories.
720
- #
721
- # Examples:
722
- #
723
- # thor merb:edge:core
724
- # thor merb:edge:core --install
725
- # thor merb:edge:core --merb-root ./path/to/your/app
726
- # thor merb:edge:core --sources ./path/to/sources.yml
727
-
728
- desc 'core', 'Update extlib and merb-core from git HEAD'
729
- method_options "--merb-root" => :optional,
730
- "--sources" => :optional,
731
- "--install" => :boolean
732
- def core
733
- refresh_from_source 'thor'
734
- refresh_from_source 'extlib', 'merb-core'
735
- ensure_bin_wrapper_for('merb-core', 'rake', 'rspec', 'thor')
974
+
975
+ def use_config?
976
+ options[:config] || options[:"config-file"]
736
977
  end
737
-
738
- desc 'more', 'Update merb-more from git HEAD'
739
- method_options "--merb-root" => :optional,
740
- "--sources" => :optional,
741
- "--install" => :boolean
742
- def more
743
- refresh_from_source 'merb-more'
744
- ensure_bin_wrapper_for('merb-gen')
978
+
979
+ def config_file
980
+ @config_file ||= begin
981
+ options[:"config-file"] || File.join(working_dir, 'config', 'dependencies.yml')
982
+ end
745
983
  end
746
-
747
- desc 'plugins', 'Update merb-plugins from git HEAD'
748
- method_options "--merb-root" => :optional,
749
- "--sources" => :optional,
750
- "--install" => :boolean
751
- def plugins
752
- refresh_from_source 'merb-plugins'
984
+
985
+ def config_dir
986
+ File.dirname(config_file)
753
987
  end
754
-
755
- desc 'dm_core', 'Update dm-core from git HEAD'
756
- method_options "--merb-root" => :optional,
757
- "--sources" => :optional,
758
- "--install" => :boolean
759
- def dm_core
760
- refresh_from_source 'extlib', 'dm-core'
988
+
989
+ ### Strategy handlers
990
+
991
+ private
992
+
993
+ def stable_strategy(deps)
994
+ installed_from_rubygems = []
995
+ if core = deps.find { |d| d.name == 'merb-core' }
996
+ if dry_run?
997
+ note "Installing #{core.name}..."
998
+ else
999
+ if install_dependency(core)
1000
+ installed_from_rubygems << core
1001
+ else
1002
+ msg = "Try specifying a lower version of merb-core with --version"
1003
+ if version_no = core.version_requirements.to_s[/([\.\d]+)$/, 1]
1004
+ num = "%03d" % (version_no.gsub('.', '').to_i - 1)
1005
+ puts "The required version (#{version_no}) probably isn't available as a stable rubygem yet."
1006
+ info "#{msg} #{num.split(//).join('.')}"
1007
+ else
1008
+ puts "The required version probably isn't available as a stable rubygem yet."
1009
+ info msg
1010
+ end
1011
+ end
1012
+ end
1013
+ end
1014
+
1015
+ deps.each do |dependency|
1016
+ next if dependency.name == 'merb-core'
1017
+ if dry_run?
1018
+ note "Installing #{dependency.name}..."
1019
+ else
1020
+ install_dependency(dependency)
1021
+ installed_from_rubygems << dependency
1022
+ end
1023
+ end
1024
+ installed_from_rubygems
761
1025
  end
762
-
763
- desc 'dm_more', 'Update dm-more from git HEAD'
764
- method_options "--merb-root" => :optional,
765
- "--sources" => :optional,
766
- "--install" => :boolean
767
- def dm_more
768
- refresh_from_source 'extlib', 'dm-core', 'dm-more'
769
- end
770
-
771
- desc 'do [ADAPTER, ...]', 'Install data_objects and optional adapter'
772
- method_options "--merb-root" => :optional,
773
- "--sources" => :optional,
774
- "--install" => :boolean
775
- def do(*adapters)
776
- source = Source.new
777
- source.options = options
778
- source.clone('do')
779
- source.install('do/data_objects')
780
- adapters.each do |adapter|
781
- if adapter && DO_ADAPTERS.include?(adapter)
782
- source.install("do/do_#{adapter}")
783
- elsif adapter
784
- error "Unknown DO adapter '#{adapter}'"
1026
+
1027
+ def edge_strategy(deps)
1028
+ installed_from_rubygems = []
1029
+
1030
+ # Selectively update repositories for the matching dependencies
1031
+ update_dependency_repositories(deps) unless dry_run?
1032
+
1033
+ # Skip gem dependencies to prevent them from being installed from stable;
1034
+ # however, core dependencies will be retrieved from source when available
1035
+ install_opts = { :ignore_dependencies => true }
1036
+ if core = deps.find { |d| d.name == 'merb-core' }
1037
+ if dry_run?
1038
+ note "Installing #{core.name}..."
1039
+ else
1040
+ if install_dependency_from_source(core, install_opts)
1041
+ elsif install_dependency(core, install_opts)
1042
+ info "Installed #{core.name} from rubygems..."
1043
+ installed_from_rubygems << core
1044
+ end
785
1045
  end
786
1046
  end
1047
+
1048
+ deps.each do |dependency|
1049
+ next if dependency.name == 'merb-core'
1050
+ if dry_run?
1051
+ note "Installing #{dependency.name}..."
1052
+ else
1053
+ if install_dependency_from_source(dependency, install_opts)
1054
+ elsif install_dependency(dependency, install_opts)
1055
+ info "Installed #{dependency.name} from rubygems..."
1056
+ installed_from_rubygems << dependency
1057
+ end
1058
+ end
1059
+ end
1060
+
1061
+ installed_from_rubygems
787
1062
  end
788
-
789
- desc 'custom', 'Update all the custom repos from git HEAD'
790
- method_options "--merb-root" => :optional,
791
- "--sources" => :optional,
792
- "--install" => :boolean
793
- def custom
794
- custom_repos = Merb.repos.keys - Merb.default_repos.keys
795
- refresh_from_source *custom_repos
1063
+
1064
+ ### Class Methods
1065
+
1066
+ public
1067
+
1068
+ def self.list(filter = 'all', comp = nil, options = {})
1069
+ instance = Merb::Dependencies.new
1070
+ instance.options = options
1071
+ instance.list(filter, comp)
796
1072
  end
797
-
798
- desc 'minigems', 'Install minigems from git HEAD (system-wide)'
799
- method_options "--merb-root" => :optional,
800
- "--sources" => :optional
801
- def minigems
802
- source = Source.new
803
- source.options = options
804
- source.clone('minigems')
805
- if Merb.install_gem_from_src(File.join(source_dir, 'minigems'))
806
- system("#{Gem.ruby} -S minigem install")
1073
+
1074
+ # Extract application dependencies by querying the app directly.
1075
+ def self.extract_dependencies(merb_root, env = 'production')
1076
+ require 'merb-core'
1077
+ if !@_merb_loaded || Merb.root != merb_root
1078
+ Merb.start_environment(
1079
+ :testing => true,
1080
+ :adapter => 'runner',
1081
+ :environment => env,
1082
+ :merb_root => merb_root
1083
+ )
1084
+ @_merb_loaded = true
807
1085
  end
1086
+ Merb::BootLoader::Dependencies.dependencies
1087
+ rescue => e
1088
+ error "Couldn't extract dependencies from application!"
1089
+ error e.message
1090
+
1091
+ p e.backtrace
1092
+
1093
+ puts "Make sure you're executing the task from your app (--merb-root), or"
1094
+ puts "specify a config option (--config or --config-file=YAML_FILE)"
1095
+ return []
808
1096
  end
809
-
810
- # Pull from git and optionally install the resulting gems.
811
- def refresh_from_source(*components)
812
- opts = components.last.is_a?(Hash) ? components.pop : {}
813
- source = Source.new
814
- source.options = options
815
- components.each do |name|
816
- source.clone(name)
817
- source.install_from_src(name, opts) if options[:install]
1097
+
1098
+ # Parse the basic YAML config data, and process Gem::Dependency output.
1099
+ # Formatting example: merb_helpers (>= 0.9.8, runtime)
1100
+ def self.parse_dependencies_yaml(yaml)
1101
+ dependencies = []
1102
+ entries = YAML.load(yaml) rescue []
1103
+ entries.each do |entry|
1104
+ if matches = entry.match(/^(\S+) \(([^,]+)?, ([^\)]+)\)/)
1105
+ name, version_req, type = matches.captures
1106
+ dependencies << ::Gem::Dependency.new(name, version_req, type.to_sym)
1107
+ else
1108
+ error "Invalid entry: #{entry}"
1109
+ end
818
1110
  end
1111
+ dependencies
819
1112
  end
820
-
821
- end
822
-
823
- class Source < Thor
824
-
825
- # The Source tasks deal with gem source packages - mainly from github.
826
- # Any directory inside ./src is regarded as a gem that can be packaged
827
- # and installed from there into the desired gems dir (either system-wide
828
- # or into the application's gems directory).
829
-
1113
+
1114
+ end
1115
+
1116
+ class Stack < Thor
1117
+
1118
+ # The Stack tasks will install dependencies based on known sets of gems,
1119
+ # regardless of actual application dependency settings.
1120
+
1121
+ DM_STACK = %w[
1122
+ dm-core
1123
+ dm-aggregates
1124
+ dm-migrations
1125
+ dm-timestamps
1126
+ dm-types
1127
+ dm-validations
1128
+ ]
1129
+
1130
+ MERB_STACK = %w[
1131
+ minigems
1132
+
1133
+ merb-core
1134
+ merb-action-args
1135
+ merb-assets
1136
+ merb-cache
1137
+ merb-helpers
1138
+ merb-mailer
1139
+ merb-slices
1140
+ merb-auth
1141
+ ] + DM_STACK
1142
+
1143
+ MERB_BASICS = %w[
1144
+ minigems
1145
+
1146
+ merb-core
1147
+ merb-action-args
1148
+ merb-assets
1149
+ merb-cache
1150
+ merb-helpers
1151
+ merb-mailer
1152
+ merb-slices
1153
+ ]
1154
+
1155
+ # The following sets are meant for repository lookup; unlike the sets above
1156
+ # these correspond to specific git repository items.
1157
+
1158
+ MERB_MORE = %w[
1159
+ merb-action-args
1160
+ merb-assets
1161
+ merb-auth
1162
+ merb-auth-core
1163
+ merb-auth-more
1164
+ merb-auth-slice-password
1165
+ merb-cache
1166
+ merb-exceptions
1167
+ merb-gen
1168
+ merb-haml
1169
+ merb-helpers
1170
+ merb-mailer
1171
+ merb-param-protection
1172
+ merb-slices
1173
+ merb_datamapper
1174
+ ]
1175
+
1176
+ MERB_PLUGINS = %w[
1177
+ merb_activerecord
1178
+ merb_builder
1179
+ merb_jquery
1180
+ merb_laszlo
1181
+ merb_parts
1182
+ merb_screw_unit
1183
+ merb_sequel
1184
+ merb_stories
1185
+ merb_test_unit
1186
+ ]
1187
+
1188
+ DM_MORE = %w[
1189
+ dm-adjust
1190
+ dm-aggregates
1191
+ dm-ar-finders
1192
+ dm-cli
1193
+ dm-constraints
1194
+ dm-is-example
1195
+ dm-is-list
1196
+ dm-is-nested_set
1197
+ dm-is-remixable
1198
+ dm-is-searchable
1199
+ dm-is-state_machine
1200
+ dm-is-tree
1201
+ dm-is-versioned
1202
+ dm-migrations
1203
+ dm-observer
1204
+ dm-querizer
1205
+ dm-serializer
1206
+ dm-shorthand
1207
+ dm-sweatshop
1208
+ dm-tags
1209
+ dm-timestamps
1210
+ dm-types
1211
+ dm-validations
1212
+
1213
+ dm-couchdb-adapter
1214
+ dm-ferret-adapter
1215
+ dm-rest-adapter
1216
+ ]
1217
+
1218
+ attr_accessor :system, :local, :missing
1219
+
830
1220
  include MerbThorHelper
831
-
832
- # Install a particular gem from source.
833
- #
834
- # If a local ./gems dir is found, or --merb-root is given
835
- # the gems will be installed locally into that directory.
836
- #
837
- # Note that this task doesn't retrieve any (new) source from git;
838
- # To update and install you'd execute the following two tasks:
1221
+
1222
+ global_method_options = {
1223
+ "--merb-root" => :optional, # the directory to operate on
1224
+ "--include-dependencies" => :boolean, # gather sub-dependencies
1225
+ "--version" => :optional # gather specific version of framework
1226
+ }
1227
+
1228
+ method_options global_method_options
1229
+ def initialize(*args); super; end
1230
+
1231
+ # List components and their dependencies.
839
1232
  #
840
- # thor merb:source:update merb-core
841
- # thor merb:source:install merb-core
1233
+ # Examples:
1234
+ #
1235
+ # merb:stack:list # list all standard stack components
1236
+ # merb:stack:list all # list all component sets
1237
+ # merb:stack:list merb-more # list all dependencies of merb-more
1238
+
1239
+ desc 'list [all|comp]', 'List available components (optionally filtered, defaults to merb stack)'
1240
+ def list(comp = 'stack')
1241
+ if comp == 'all'
1242
+ Merb::Stack.component_sets.keys.sort.each do |comp|
1243
+ unless (components = Merb::Stack.component_sets[comp]).empty?
1244
+ message "Dependencies for '#{comp}' set:"
1245
+ components.each { |c| puts "- #{c}" }
1246
+ end
1247
+ end
1248
+ else
1249
+ message "Dependencies for '#{comp}' set:"
1250
+ Merb::Stack.components(comp).each { |c| puts "- #{c}" }
1251
+ end
1252
+ end
1253
+
1254
+ # Install stack components or individual gems - from stable rubygems by default.
842
1255
  #
843
- # Alternatively, look at merb:edge and merb:edge:* with --install.
1256
+ # See also: Merb::Dependencies#install and Merb::Dependencies#install_dependencies
844
1257
  #
845
1258
  # Examples:
846
1259
  #
847
- # thor merb:source:install merb-core
848
- # thor merb:source:install merb-more
849
- # thor merb:source:install merb-more/merb-slices
850
- # thor merb:source:install merb-plugins/merb_helpers
851
- # thor merb:source:install merb-core --merb-root ./path/to/your/app
852
-
853
- desc 'install GEM_NAME', 'Install a rubygem from (git) source'
854
- method_options "--merb-root" => :optional
855
- def install(name)
856
- message "Installing #{name}..."
857
- install_from_src(name)
858
- rescue => e
859
- error "Failed to install #{name} (#{e.message})"
1260
+ # merb:stack:install # install the default merb stack
1261
+ # merb:stack:install basics # install a basic set of dependencies
1262
+ # merb:stack:install merb-core # install merb-core from stable
1263
+ # merb:stack:install merb-more --edge # install merb-core from edge
1264
+ # merb:stack:install merb-core thor merb-slices # install the specified gems
1265
+
1266
+ desc 'install [COMP, ...]', 'Install stack components'
1267
+ method_options "--edge" => :boolean,
1268
+ "--sources" => :optional,
1269
+ "--force" => :boolean,
1270
+ "--dry-run" => :boolean,
1271
+ "--strategy" => :optional
1272
+ def install(*comps)
1273
+ mngr = self.dependency_manager
1274
+ deps = gather_dependencies(comps)
1275
+ mngr.system, mngr.local, mngr.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1276
+ mngr.install_dependencies(strategy, deps)
860
1277
  end
861
-
862
- # Clone a git repository into ./src. The repository can be
863
- # a direct git url or a known -named- repository.
1278
+
1279
+ # Uninstall stack components or individual gems.
864
1280
  #
865
- # Examples:
1281
+ # See also: Merb::Dependencies#uninstall
866
1282
  #
867
- # thor merb:source:clone dm-core
868
- # thor merb:source:clone dm-core --sources ./path/to/sources.yml
869
- # thor merb:source:clone git://github.com/sam/dm-core.git
870
-
871
- desc 'clone REPOSITORY', 'Clone a git repository into ./src'
872
- method_options "--sources" => :optional
873
- def clone(repository)
874
- if repository =~ /^git:\/\//
875
- repository_url = repository
876
- elsif url = Merb.repos(options[:sources])[repository]
877
- repository_url = url
1283
+ # Examples:
1284
+ # merb:stack:uninstall # uninstall the default merb stack
1285
+ # merb:stack:uninstall merb-more # uninstall merb-more
1286
+ # merb:stack:uninstall merb-core thor merb-slices # uninstall the specified gems
1287
+
1288
+ desc 'uninstall [COMP, ...]', 'Uninstall stack components'
1289
+ method_options "--dry-run" => :boolean, "--force" => :boolean
1290
+ def uninstall(*comps)
1291
+ deps = gather_dependencies(comps)
1292
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1293
+ # Clobber existing local dependencies - based on self.local
1294
+ clobber_dependencies!
1295
+ end
1296
+
1297
+ protected
1298
+
1299
+ def gather_dependencies(comps = [])
1300
+ if comps.empty?
1301
+ gems = MERB_STACK
1302
+ else
1303
+ gems = comps.map { |c| Merb::Stack.components(c) }.flatten
878
1304
  end
879
-
880
- if repository_url
881
- repository_name = repository_url[/([\w+|-]+)\.git/u, 1]
882
- fork_name = repository_url[/.com\/+?(.+)\/.+\.git/u, 1]
883
- local_repo_path = "#{source_dir}/#{repository_name}"
884
-
885
- if File.directory?(local_repo_path)
886
- puts "\n#{repository_name} repository exists, updating or branching instead of cloning..."
887
- FileUtils.cd(local_repo_path) do
888
-
889
- # To avoid conflicts we need to set a remote branch for non official repos
890
- existing_repos = `git remote -v`.split("\n").map{|branch| branch.split(/\s+/)}
891
- origin_repo_url = existing_repos.detect{ |r| r.first == "origin" }.last
892
-
893
- # Pull from the original repository - no branching needed
894
- if repository_url == origin_repo_url
895
- puts "Pulling from #{repository_url}"
896
- system %{
897
- git fetch
898
- git checkout master
899
- git rebase origin/master
900
- }
901
- # Update and switch to a branch for a particular github fork
902
- elsif existing_repos.map{ |r| r.last }.include?(repository_url)
903
- puts "Switching to remote branch: #{fork_name}"
904
- `git checkout -b #{fork_name} #{fork_name}/master`
905
- `git rebase #{fork_name}/master`
906
- # Create a new remote branch for a particular github fork
907
- else
908
- puts "Add a new remote branch: #{fork_name}"
909
- `git remote add -f #{fork_name} #{repository_url}`
910
- `git checkout -b#{fork_name} #{fork_name}/master`
911
- end
912
- end
1305
+
1306
+ version_req = if options[:version]
1307
+ ::Gem::Requirement.create(options[:version])
1308
+ end
1309
+
1310
+ framework_components = Merb::Stack.framework_components
1311
+
1312
+ gems.map do |gem|
1313
+ if version_req && framework_components.include?(gem)
1314
+ ::Gem::Dependency.new(gem, version_req)
913
1315
  else
914
- FileUtils.cd(source_dir) do
915
- puts "\nCloning #{repository_name} repository from #{repository_url}..."
916
- system("git clone --depth 1 #{repository_url} ")
917
- end
1316
+ ::Gem::Dependency.new(gem, ::Gem::Requirement.default)
918
1317
  end
1318
+ end
1319
+ end
1320
+
1321
+ def strategy
1322
+ options[:strategy] || (options[:edge] ? 'edge' : 'stable')
1323
+ end
1324
+
1325
+ def dependency_manager
1326
+ @_dependency_manager ||= begin
1327
+ instance = Merb::Dependencies.new
1328
+ instance.options = options
1329
+ instance
1330
+ end
1331
+ end
1332
+
1333
+ public
1334
+
1335
+ def self.repository_sets
1336
+ @_repository_sets ||= begin
1337
+ # the component itself as a fallback
1338
+ comps = Hash.new { |(hsh,c)| [c] }
1339
+
1340
+ # git repository based component sets
1341
+ comps["merb"] = ["merb-core"] + MERB_MORE
1342
+ comps["merb-more"] = MERB_MORE.sort
1343
+ comps["merb-plugins"] = MERB_PLUGINS.sort
1344
+ comps["dm-more"] = DM_MORE.sort
919
1345
 
920
- # We don't want to have our source directory under sudo permissions
921
- # even if clone (with --install) is called with sudo. Maybe there's
922
- # a better way, but this works.
923
- neutralise_sudo_for(local_repo_path)
1346
+ comps
1347
+ end
1348
+ end
1349
+
1350
+ def self.component_sets
1351
+ @_component_sets ||= begin
1352
+ # the component itself as a fallback
1353
+ comps = Hash.new { |(hsh,c)| [c] }
1354
+ comps.update(repository_sets)
1355
+
1356
+ # specific set of dependencies
1357
+ comps["stack"] = MERB_STACK.sort
1358
+ comps["basics"] = MERB_BASICS.sort
1359
+
1360
+ # orm dependencies
1361
+ comps["datamapper"] = DM_STACK.sort
1362
+ comps["sequel"] = ["merb_sequel", "sequel"]
1363
+ comps["activerecord"] = ["merb_activerecord", "activerecord"]
1364
+
1365
+ comps
1366
+ end
1367
+ end
1368
+
1369
+ def self.framework_components
1370
+ %w[merb-core merb-more merb-plugins].inject([]) do |all, comp|
1371
+ all + components(comp)
1372
+ end
1373
+ end
1374
+
1375
+ def self.components(comp = nil)
1376
+ if comp
1377
+ component_sets[comp]
924
1378
  else
925
- error "No valid repository url given"
1379
+ comps = %w[merb-core merb-more merb-plugins dm-core dm-more]
1380
+ comps.inject([]) do |all, grp|
1381
+ all + (component_sets[grp] || [])
1382
+ end
926
1383
  end
927
1384
  end
928
-
929
- # Update a specific gem source directory from git. See #clone.
930
-
931
- desc 'update REPOSITORY', 'Update a git repository in ./src'
932
- alias :update :clone
933
-
934
- # Update all gem sources from git - based on the current branch.
935
- # Optionally install the gems when --install is specified.
936
-
937
- desc 'refresh', 'Pull fresh copies of all source gems'
938
- method_options "--install" => :boolean
939
- def refresh
940
- repos = Dir["#{source_dir}/*"]
941
- repos.each do |repo|
942
- next unless File.directory?(repo) && File.exists?(File.join(repo, '.git'))
943
- FileUtils.cd(repo) do
944
- gem_name = File.basename(repo)
945
- message "Refreshing #{gem_name}"
946
- system %{git fetch}
947
- branch = `git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1) /'`[/\* (.+)/, 1]
948
- system %{git rebase #{branch}}
949
- install(gem_name) if options[:install]
1385
+
1386
+ def self.select_component_dependencies(dependencies, comp = nil)
1387
+ comps = components(comp) || []
1388
+ dependencies.select { |dep| comps.include?(dep.name) }
1389
+ end
1390
+
1391
+ def self.base_components
1392
+ %w[thor rake]
1393
+ end
1394
+
1395
+ def self.all_components
1396
+ base_components + framework_components
1397
+ end
1398
+
1399
+ # Find the latest merb-core and gather its dependencies.
1400
+ # We check for 0.9.8 as a minimum release version.
1401
+ def self.core_dependencies(gem_dir = nil, ignore_deps = false)
1402
+ @_core_dependencies ||= begin
1403
+ if gem_dir # add local gems to index
1404
+ ::Gem.clear_paths; ::Gem.path.unshift(gem_dir)
1405
+ end
1406
+ deps = []
1407
+ merb_core = ::Gem::Dependency.new('merb-core', '>= 0.9.8')
1408
+ if gemspec = ::Gem.source_index.search(merb_core).last
1409
+ deps << ::Gem::Dependency.new('merb-core', gemspec.version)
1410
+ if ignore_deps
1411
+ deps += gemspec.dependencies.select do |d|
1412
+ base_components.include?(d.name)
1413
+ end
1414
+ else
1415
+ deps += gemspec.dependencies
1416
+ end
950
1417
  end
951
- neutralise_sudo_for(repo)
1418
+ ::Gem.clear_paths if gem_dir # reset
1419
+ deps
952
1420
  end
953
1421
  end
954
1422
 
955
- def install_from_src(name, opts = {})
956
- gem_src_dir = File.join(source_dir, name)
957
- defaults = {}
958
- defaults[:install_dir] = gem_dir if gem_dir
959
- Merb.install_gem_from_src(gem_src_dir, defaults.merge(opts))
1423
+ def self.lookup_repository_name(item)
1424
+ set_name = nil
1425
+ # The merb repo contains -more as well, so it needs special attention
1426
+ return 'merb' if self.repository_sets['merb'].include?(item)
1427
+
1428
+ # Proceed with finding the item in a known component set
1429
+ self.repository_sets.find do |set, items|
1430
+ next if set == 'merb'
1431
+ items.include?(item) ? (set_name = set) : nil
1432
+ end
1433
+ set_name
960
1434
  end
961
-
1435
+
962
1436
  end
963
-
964
- class Gems < Thor
965
-
966
- # The Gems tasks deal directly with rubygems, either through remotely
967
- # available sources (rubyforge for example) or by searching the
968
- # system-wide gem cache for matching gems. The gems are installed from
969
- # there into the desired gems dir (either system-wide or into the
970
- # application's gems directory).
971
-
1437
+
1438
+ class Tasks < Thor
1439
+
972
1440
  include MerbThorHelper
973
-
974
- # Install a gem and its dependencies.
1441
+
1442
+ # Show merb.thor version information
975
1443
  #
976
- # If a local ./gems dir is found, or --merb-root is given
977
- # the gems will be installed locally into that directory.
1444
+ # merb:tasks:version # show the current version info
1445
+ # merb:tasks:version --info # show extended version info
1446
+
1447
+ desc 'version', 'Show verion info'
1448
+ method_options "--info" => :boolean
1449
+ def version
1450
+ message "Currently installed merb.thor version: #{MERB_THOR_VERSION}"
1451
+ if options[:version]
1452
+ self.options = { :"dry-run" => true }
1453
+ self.update # run update task with dry-run enabled
1454
+ end
1455
+ end
1456
+
1457
+ # Update merb.thor tasks from remotely available version
978
1458
  #
979
- # The option --cache will look in the system's gem cache
980
- # for the latest version and install it in the apps' gems.
981
- # This is particularly handy for gems that aren't available
982
- # through rubyforge.org - like in-house merb slices etc.
1459
+ # merb:tasks:update # update merb.thor
1460
+ # merb:tasks:update --force # force-update merb.thor
1461
+ # merb:tasks:update --dry-run # show version info only
1462
+
1463
+ desc 'update [URL]', 'Fetch the latest merb.thor and install it locally'
1464
+ method_options "--dry-run" => :boolean, "--force" => :boolean
1465
+ def update(url = 'http://merbivore.com/merb.thor')
1466
+ require 'open-uri'
1467
+ require 'rubygems/version'
1468
+ remote_file = open(url)
1469
+ code = remote_file.read
1470
+
1471
+ # Extract version information from the source code
1472
+ if version = code[/^MERB_THOR_VERSION\s?=\s?('|")([\.\d]+)('|")/,2]
1473
+ # borrow version comparison from rubygems' Version class
1474
+ current_version = ::Gem::Version.new(MERB_THOR_VERSION)
1475
+ remote_version = ::Gem::Version.new(version)
1476
+
1477
+ if current_version >= remote_version
1478
+ puts "currently installed: #{current_version}"
1479
+ if current_version != remote_version
1480
+ puts "available version: #{remote_version}"
1481
+ end
1482
+ info "No update of merb.thor necessary#{options[:force] ? ' (forced)' : ''}"
1483
+ proceed = options[:force]
1484
+ elsif current_version < remote_version
1485
+ puts "currently installed: #{current_version}"
1486
+ puts "available version: #{remote_version}"
1487
+ proceed = true
1488
+ end
1489
+
1490
+ if proceed && !dry_run?
1491
+ File.open(File.join(__FILE__), 'w') do |f|
1492
+ f.write(code)
1493
+ end
1494
+ success "Installed the latest merb.thor (v#{version})"
1495
+ end
1496
+ else
1497
+ raise "invalid source-code data"
1498
+ end
1499
+ rescue OpenURI::HTTPError
1500
+ error "Error opening #{url}"
1501
+ rescue => e
1502
+ error "An error occurred (#{e.message})"
1503
+ end
1504
+
1505
+ end
1506
+
1507
+ #### MORE LOW-LEVEL TASKS ####
1508
+
1509
+ class Gem < Thor
1510
+
1511
+ group 'core'
1512
+
1513
+ include MerbThorHelper
1514
+ extend GemManagement
1515
+
1516
+ attr_accessor :system, :local, :missing
1517
+
1518
+ global_method_options = {
1519
+ "--merb-root" => :optional, # the directory to operate on
1520
+ "--version" => :optional, # gather specific version of gem
1521
+ "--ignore-dependencies" => :boolean # don't install sub-dependencies
1522
+ }
1523
+
1524
+ method_options global_method_options
1525
+ def initialize(*args); super; end
1526
+
1527
+ # List gems that match the specified criteria.
983
1528
  #
1529
+ # By default all local gems are listed. When the first argument is 'all' the
1530
+ # list is partitioned into system an local gems; specify 'system' to show
1531
+ # only system gems. A second argument can be used to filter on a set of known
1532
+ # components, like all merb-more gems for example.
1533
+ #
984
1534
  # Examples:
985
1535
  #
986
- # thor merb:gems:install merb-core
987
- # thor merb:gems:install merb-core --cache
988
- # thor merb:gems:install merb-core --version 0.9.7
989
- # thor merb:gems:install merb-core --merb-root ./path/to/your/app
990
-
991
- desc 'install GEM_NAME', 'Install a gem from rubygems'
992
- method_options "--version" => :optional,
993
- "--merb-root" => :optional,
994
- "--cache" => :boolean,
995
- "--binaries" => :boolean
996
- def install(name)
997
- message "Installing #{name}..."
998
- install_gem(name, :version => options[:version], :cache => options[:cache])
999
- ensure_bin_wrapper_for(name) if options[:binaries]
1000
- rescue => e
1001
- error "Failed to install #{name} (#{e.message})"
1536
+ # merb:gem:list # list all local gems - the default
1537
+ # merb:gem:list all # list system and local gems
1538
+ # merb:gem:list system # list only system gems
1539
+ # merb:gem:list all merb-more # list only merb-more related gems
1540
+ # merb:gem:list --version 0.9.8 # list gems that match the version
1541
+
1542
+ desc 'list [all|local|system] [comp]', 'Show installed gems'
1543
+ def list(filter = 'local', comp = nil)
1544
+ deps = comp ? Merb::Stack.select_component_dependencies(dependencies, comp) : dependencies
1545
+ self.system, self.local, self.missing = Merb::Gem.partition_dependencies(deps, gem_dir)
1546
+ case filter
1547
+ when 'all'
1548
+ message 'Installed system gems:'
1549
+ display_gemspecs(system)
1550
+ message 'Installed local gems:'
1551
+ display_gemspecs(local)
1552
+ when 'system'
1553
+ message 'Installed system gems:'
1554
+ display_gemspecs(system)
1555
+ when 'local'
1556
+ message 'Installed local gems:'
1557
+ display_gemspecs(local)
1558
+ else
1559
+ warning "Invalid listing filter '#{filter}'"
1560
+ end
1002
1561
  end
1003
-
1004
- # Update a gem and its dependencies.
1005
- #
1006
- # If a local ./gems dir is found, or --merb-root is given
1007
- # the gems will be installed locally into that directory.
1562
+
1563
+ # Install the specified gems.
1008
1564
  #
1009
- # The option --cache will look in the system's gem cache
1010
- # for the latest version and install it in the apps' gems.
1011
- # This is particularly handy for gems that aren't available
1012
- # through rubyforge.org - like in-house merb slices etc.
1565
+ # All arguments should be names of gems to install.
1013
1566
  #
1567
+ # When :force => true then any existing versions of the gems to be installed
1568
+ # will be uninstalled first. It's important to note that so-called meta-gems
1569
+ # or gems that exactly match a set of Merb::Stack.components will have their
1570
+ # sub-gems uninstalled too. For example, uninstalling merb-more will install
1571
+ # all contained gems: merb-action-args, merb-assets, merb-gen, ...
1572
+ #
1014
1573
  # Examples:
1015
1574
  #
1016
- # thor merb:gems:update merb-core
1017
- # thor merb:gems:update merb-core --cache
1018
- # thor merb:gems:update merb-core --merb-root ./path/to/your/app
1019
-
1020
- desc 'update GEM_NAME', 'Update a gem from rubygems'
1021
- method_options "--merb-root" => :optional,
1022
- "--cache" => :boolean,
1023
- "--binaries" => :boolean
1024
- def update(name)
1025
- message "Updating #{name}..."
1026
- version = nil
1027
- if gem_dir && (gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{name}-*.gemspec")].last)
1028
- gemspec = Gem::Specification.load(gemspec_path)
1029
- version = Gem::Requirement.new [">#{gemspec.version}"]
1030
- end
1031
- if install_gem(name, :version => version, :cache => options[:cache])
1032
- ensure_bin_wrapper_for(name) if options[:binaries]
1033
- elsif gemspec
1034
- message "current version is: #{gemspec.version}"
1575
+ # merb:gem:install merb-core merb-slices # install all specified gems
1576
+ # merb:gem:install merb-core --version 0.9.8 # install a specific version of a gem
1577
+ # merb:gem:install merb-core --force # uninstall then subsequently install the gem
1578
+ # merb:gem:install merb-core --cache # try to install locally from system gems
1579
+ # merb:gem:install merb-core --binaries # also install adapted bin wrapper
1580
+
1581
+ desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from rubygems'
1582
+ method_options "--cache" => :boolean,
1583
+ "--binaries" => :boolean,
1584
+ "--dry-run" => :boolean,
1585
+ "--force" => :boolean
1586
+ def install(*names)
1587
+ self.include_dependencies = true # deal with dependencies by default
1588
+ opts = { :version => options[:version], :cache => options[:cache] }
1589
+ current_gem = nil
1590
+
1591
+ # uninstall existing gems of the ones we're going to install
1592
+ uninstall(*names) if options[:force]
1593
+
1594
+ names.each do |gem_name|
1595
+ current_gem = gem_name
1596
+ if dry_run?
1597
+ note "Installing #{current_gem}..."
1598
+ else
1599
+ message "Installing #{current_gem}..."
1600
+ self.class.install(gem_name, default_install_options.merge(opts))
1601
+ ensure_bin_wrapper_for(gem_name) if options[:binaries]
1602
+ end
1035
1603
  end
1604
+ rescue => e
1605
+ error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})"
1036
1606
  end
1037
-
1038
- # Uninstall a gem - ignores dependencies.
1607
+
1608
+ # Uninstall the specified gems.
1039
1609
  #
1040
- # If a local ./gems dir is found, or --merb-root is given
1041
- # the gems will be uninstalled locally from that directory.
1610
+ # By default all specified gems are uninstalled. It's important to note that
1611
+ # so-called meta-gems or gems that match a set of Merb::Stack.components will
1612
+ # have their sub-gems uninstalled too. For example, uninstalling merb-more
1613
+ # will install all contained gems: merb-action-args, merb-assets, ...
1042
1614
  #
1043
- # The --all option indicates that all versions of the gem should be
1044
- # uninstalled. If --version isn't set, and multiple versions are
1045
- # available, you will be prompted to pick one - or all.
1615
+ # Existing dependencies will be clobbered; when :force => true then all gems
1616
+ # will be cleared, otherwise only existing local dependencies of the
1617
+ # matching component set will be removed.
1046
1618
  #
1047
1619
  # Examples:
1048
1620
  #
1049
- # thor merb:gems:uninstall merb-core
1050
- # thor merb:gems:uninstall merb-core --all
1051
- # thor merb:gems:uninstall merb-core --version 0.9.7
1052
- # thor merb:gems:uninstall merb-core --merb-root ./path/to/your/app
1053
-
1054
- desc 'uninstall GEM_NAME', 'Uninstall a gem'
1055
- method_options "--version" => :optional,
1056
- "--merb-root" => :optional,
1057
- "--all" => :boolean
1058
- def uninstall(name)
1059
- message "Uninstalling #{name}..."
1060
- uninstall_gem(name, :version => options[:version], :all => options[:all])
1621
+ # merb:gem:uninstall merb-core merb-slices # uninstall all specified gems
1622
+ # merb:gem:uninstall merb-core --version 0.9.8 # uninstall a specific version of a gem
1623
+
1624
+ desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem'
1625
+ method_options "--dry-run" => :boolean
1626
+ def uninstall(*names)
1627
+ self.include_dependencies = true # deal with dependencies by default
1628
+ opts = { :version => options[:version] }
1629
+ current_gem = nil
1630
+ if dry_run?
1631
+ note "Uninstalling any existing gems of: #{names.join(', ')}"
1632
+ else
1633
+ message "Uninstalling any existing gems of: #{names.join(', ')}"
1634
+ names.each do |gem_name|
1635
+ current_gem = gem_name
1636
+ Merb::Gem.uninstall(gem_name, default_uninstall_options) rescue nil
1637
+ # if this gem is a meta-gem or a component set name, remove sub-gems
1638
+ (Merb::Stack.components(gem_name) || []).each do |comp|
1639
+ Merb::Gem.uninstall(comp, default_uninstall_options) rescue nil
1640
+ end
1641
+ end
1642
+ end
1061
1643
  rescue => e
1062
- error "Failed to uninstall #{name} (#{e.message})"
1644
+ error "Failed to uninstall #{current_gem ? current_gem : 'gem'} (#{e.message})"
1645
+ end
1646
+
1647
+ private
1648
+
1649
+ # Return dependencies for all installed gems; both system-wide and locally;
1650
+ # optionally filters on :version requirement.
1651
+ def dependencies
1652
+ version_req = if options[:version]
1653
+ ::Gem::Requirement.create(options[:version])
1654
+ else
1655
+ ::Gem::Requirement.default
1656
+ end
1657
+ if gem_dir
1658
+ ::Gem.clear_paths; ::Gem.path.unshift(gem_dir)
1659
+ ::Gem.source_index.refresh!
1660
+ end
1661
+ deps = []
1662
+ ::Gem.source_index.each do |fullname, gemspec|
1663
+ if version_req.satisfied_by?(gemspec.version)
1664
+ deps << ::Gem::Dependency.new(gemspec.name, "= #{gemspec.version}")
1665
+ end
1666
+ end
1667
+ ::Gem.clear_paths if gem_dir
1668
+ deps.sort
1669
+ end
1670
+
1671
+ public
1672
+
1673
+ # Install gem with some default options.
1674
+ def self.install(name, options = {})
1675
+ defaults = {}
1676
+ defaults[:cache] = false unless opts[:install_dir]
1677
+ install_gem(name, defaults.merge(options))
1678
+ end
1679
+
1680
+ # Uninstall gem with some default options.
1681
+ def self.uninstall(name, options = {})
1682
+ defaults = { :ignore => true, :executables => true }
1683
+ uninstall_gem(name, defaults.merge(options))
1684
+ end
1685
+
1686
+ end
1687
+
1688
+ class Source < Thor
1689
+
1690
+ group 'core'
1691
+
1692
+ include MerbThorHelper
1693
+ extend GemManagement
1694
+
1695
+ attr_accessor :system, :local, :missing
1696
+
1697
+ global_method_options = {
1698
+ "--merb-root" => :optional, # the directory to operate on
1699
+ "--include-dependencies" => :boolean, # gather sub-dependencies
1700
+ "--sources" => :optional # a yml config to grab sources from
1701
+ }
1702
+
1703
+ method_options global_method_options
1704
+ def initialize(*args); super; end
1705
+
1706
+ # List source repositories, of either local or known sources.
1707
+ #
1708
+ # Examples:
1709
+ #
1710
+ # merb:source:list # list all local sources
1711
+ # merb:source:list available # list all known sources
1712
+
1713
+ desc 'list [local|available]', 'Show git source repositories'
1714
+ def list(mode = 'local')
1715
+ if mode == 'available'
1716
+ message 'Available source repositories:'
1717
+ repos = self.class.repos(options[:sources])
1718
+ repos.keys.sort.each { |name| puts "- #{name}: #{repos[name]}" }
1719
+ elsif mode == 'local'
1720
+ message 'Current source repositories:'
1721
+ Dir[File.join(source_dir, '*')].each do |src|
1722
+ next unless File.directory?(src)
1723
+ src_name = File.basename(src)
1724
+ unless (repos = source_manager.existing_repos(src_name)).empty?
1725
+ puts "#{src_name}"
1726
+ repos.keys.sort.each { |b| puts "- #{b}: #{repos[b]}" }
1727
+ end
1728
+ end
1729
+ else
1730
+ error "Unknown listing: #{mode}"
1731
+ end
1063
1732
  end
1064
1733
 
1065
- # Completely remove a gem and all its versions - ignores dependencies.
1734
+ # Install the specified gems.
1066
1735
  #
1067
- # If a local ./gems dir is found, or --merb-root is given
1068
- # the gems will be uninstalled locally from that directory.
1736
+ # All arguments should be names of gems to install.
1069
1737
  #
1738
+ # When :force => true then any existing versions of the gems to be installed
1739
+ # will be uninstalled first. It's important to note that so-called meta-gems
1740
+ # or gems that exactly match a set of Merb::Stack.components will have their
1741
+ # sub-gems uninstalled too. For example, uninstalling merb-more will install
1742
+ # all contained gems: merb-action-args, merb-assets, merb-gen, ...
1743
+ #
1070
1744
  # Examples:
1071
1745
  #
1072
- # thor merb:gems:wipe merb-core
1073
- # thor merb:gems:wipe merb-core --merb-root ./path/to/your/app
1074
-
1075
- desc 'wipe GEM_NAME', 'Remove a gem completely'
1076
- method_options "--merb-root" => :optional
1077
- def wipe(name)
1078
- message "Wiping #{name}..."
1079
- uninstall_gem(name, :all => true, :executables => true)
1746
+ # merb:source:install merb-core merb-slices # install all specified gems
1747
+ # merb:source:install merb-core --force # uninstall then subsequently install the gem
1748
+ # merb:source:install merb-core --wipe # clear repo then install the gem
1749
+ # merb:source:install merb-core --binaries # also install adapted bin wrapper
1750
+
1751
+ desc 'install GEM_NAME [GEM_NAME, ...]', 'Install a gem from git source/edge'
1752
+ method_options "--binaries" => :boolean,
1753
+ "--dry-run" => :boolean,
1754
+ "--force" => :boolean,
1755
+ "--wipe" => :boolean
1756
+ def install(*names)
1757
+ # uninstall existing gems of the ones we're going to install
1758
+ uninstall(*names) if options[:force] || options[:wipe]
1759
+
1760
+ # We want dependencies instead of just names
1761
+ deps = names.map { |n| ::Gem::Dependency.new(n, ::Gem::Requirement.default) }
1762
+
1763
+ # Selectively update repositories for the matching dependencies
1764
+ update_dependency_repositories(deps) unless dry_run?
1765
+
1766
+ current_gem = nil
1767
+ deps.each do |dependency|
1768
+ current_gem = dependency.name
1769
+ if dry_run?
1770
+ note "Installing #{current_gem} from source..."
1771
+ else
1772
+ message "Installing #{current_gem} from source..."
1773
+ if install_dependency_from_source(dependency)
1774
+ ensure_bin_wrapper_for(dependency.name) if options[:binaries]
1775
+ end
1776
+ end
1777
+ end
1080
1778
  rescue => e
1081
- error "Failed to wipe #{name} (#{e.message})"
1779
+ error "Failed to install #{current_gem ? current_gem : 'gem'} (#{e.message})"
1082
1780
  end
1083
1781
 
1084
- # Refresh all local gems by uninstalling them and subsequently reinstall
1085
- # the latest versions from stable sources.
1782
+ # Uninstall the specified gems.
1783
+ #
1784
+ # By default all specified gems are uninstalled. It's important to note that
1785
+ # so-called meta-gems or gems that match a set of Merb::Stack.components will
1786
+ # have their sub-gems uninstalled too. For example, uninstalling merb-more
1787
+ # will install all contained gems: merb-action-args, merb-assets, ...
1086
1788
  #
1087
- # A local ./gems dir is required, or --merb-root is given
1088
- # the gems will be uninstalled locally from that directory.
1789
+ # Existing dependencies will be clobbered; when :force => true then all gems
1790
+ # will be cleared, otherwise only existing local dependencies of the
1791
+ # matching component set will be removed. Additionally when :wipe => true,
1792
+ # the matching git repositories will be removed from the source directory.
1089
1793
  #
1090
1794
  # Examples:
1091
1795
  #
1092
- # thor merb:gems:refresh
1093
- # thor merb:gems:refresh --merb-root ./path/to/your/app
1094
-
1095
- desc 'refresh', 'Refresh all local gems by installing only the most recent versions'
1096
- method_options "--merb-root" => :optional,
1097
- "--cache" => :boolean,
1098
- "--binaries" => :boolean
1099
- def refresh
1100
- if gem_dir
1101
- gem_names = []
1102
- local_gemspecs.each do |spec|
1103
- gem_names << spec.name unless gem_names.include?(spec.name)
1104
- uninstall_gem(spec.name, :version => spec.version)
1105
- end
1106
- gem_names.each { |name| install_gem(name) }
1107
- # if options[:binaries] is set this is already taken care of - skip it
1108
- ensure_bin_wrapper_for_core_components unless options[:binaries]
1109
- else
1110
- error "The refresh task only works with local gems"
1111
- end
1112
- end
1796
+ # merb:source:uninstall merb-core merb-slices # uninstall all specified gems
1797
+ # merb:source:uninstall merb-core --wipe # force-uninstall a gem and clear repo
1113
1798
 
1114
- # This task should be executed as part of a deployment setup, where
1115
- # the deployment system runs this after the app has been installed.
1116
- # Usually triggered by Capistrano, God...
1117
- #
1118
- # It will regenerate gems from the bundled gems cache for any gem
1119
- # that has C extensions - which need to be recompiled for the target
1120
- # deployment platform.
1121
-
1122
- desc 'redeploy', 'Recreate any binary gems on the target deployment platform'
1123
- def redeploy
1124
- require 'tempfile' # for
1125
- if gem_dir && File.directory?(cache_dir = File.join(gem_dir, 'cache'))
1126
- local_gemspecs.each do |gemspec|
1127
- unless gemspec.extensions.empty?
1128
- if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem"))
1129
- gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file))
1130
- # Copy the gem to a temporary file, because otherwise RubyGems/FileUtils
1131
- # will complain about copying identical files (same source/destination).
1132
- FileUtils.cp(gem_file, gem_file_copy)
1133
- install_gem(gem_file_copy, :install_dir => gem_dir)
1134
- File.delete(gem_file_copy)
1799
+ desc 'uninstall GEM_NAME [GEM_NAME, ...]', 'Unstall a gem (specify --force to remove the repo)'
1800
+ method_options "--version" => :optional, "--dry-run" => :boolean, "--wipe" => :boolean
1801
+ def uninstall(*names)
1802
+ # Remove the repos that contain the gem
1803
+ if options[:wipe]
1804
+ extract_repositories(names).each do |(name, url)|
1805
+ if File.directory?(src = File.join(source_dir, name))
1806
+ if dry_run?
1807
+ note "Removing #{src}..."
1808
+ else
1809
+ info "Removing #{src}..."
1810
+ FileUtils.rm_rf(src)
1135
1811
  end
1136
1812
  end
1137
1813
  end
1138
- else
1139
- error "No application local gems directory found"
1140
1814
  end
1815
+
1816
+ # Use the Merb::Gem#uninstall task to handle this
1817
+ gem_tasks = Merb::Gem.new
1818
+ gem_tasks.options = options
1819
+ gem_tasks.uninstall(*names)
1141
1820
  end
1142
1821
 
1143
- # Install gem with some default options.
1144
- def install_gem(name, opts = {})
1145
- defaults = {}
1146
- defaults[:cache] = false unless gem_dir
1147
- defaults[:install_dir] = gem_dir if gem_dir
1148
- Merb.install_gem(name, defaults.merge(opts))
1149
- end
1822
+ # Update the specified source repositories.
1823
+ #
1824
+ # The arguments can be actual repository names (from Merb::Source.repos)
1825
+ # or names of known merb stack gems. If the repo doesn't exist already,
1826
+ # it will be created and cloned.
1827
+ #
1828
+ # merb:source:pull merb-core # update source of specified gem
1829
+ # merb:source:pull merb-slices # implicitly updates merb-more
1150
1830
 
1151
- # Uninstall gem with some default options.
1152
- def uninstall_gem(name, opts = {})
1153
- defaults = { :ignore => true, :executables => true }
1154
- defaults[:install_dir] = gem_dir if gem_dir
1155
- Merb.uninstall_gem(name, defaults.merge(opts))
1156
- end
1831
+ desc 'pull REPO_NAME [GEM_NAME, ...]', 'Update git source repository from edge'
1832
+ def pull(*names)
1833
+ repos = extract_repositories(names)
1834
+ update_repositories(repos)
1835
+ unless repos.empty?
1836
+ message "Updated the following repositories:"
1837
+ repos.each { |name, url| puts "- #{name}: #{url}" }
1838
+ else
1839
+ warning "No repositories found to update!"
1840
+ end
1841
+ end
1157
1842
 
1158
- protected
1843
+ # Clone a git repository into ./src.
1159
1844
 
1160
- def local_gemspecs(directory = gem_dir)
1161
- if File.directory?(specs_dir = File.join(directory, 'specifications'))
1162
- Dir[File.join(specs_dir, '*.gemspec')].map do |gemspec_path|
1163
- gemspec = Gem::Specification.load(gemspec_path)
1164
- gemspec.loaded_from = gemspec_path
1165
- gemspec
1166
- end
1167
- else
1168
- []
1845
+ # The repository can be a direct git url or a known -named- repository.
1846
+ #
1847
+ # Examples:
1848
+ #
1849
+ # merb:source:clone merb-core
1850
+ # merb:source:clone dm-core awesome-repo
1851
+ # merb:source:clone dm-core --sources ./path/to/sources.yml
1852
+ # merb:source:clone git://github.com/sam/dm-core.git
1853
+
1854
+ desc 'clone (REPO_NAME|URL) [DIR_NAME]', 'Clone git source repository by name or url'
1855
+ def clone(repository, name = nil)
1856
+ if repository =~ /^git:\/\//
1857
+ repository_url = repository
1858
+ repository_name = File.basename(repository_url, '.git')
1859
+ elsif url = Merb::Source.repo(repository, options[:sources])
1860
+ repository_url = url
1861
+ repository_name = repository
1169
1862
  end
1863
+ source_manager.clone(name || repository_name, repository_url)
1170
1864
  end
1171
-
1172
- end
1173
-
1174
- class Tasks < Thor
1175
-
1176
- include MerbThorHelper
1177
-
1178
- # Install Thor, Rake and RSpec into the local gems dir, by copying it from
1179
- # the system-wide rubygems cache - which is OK since we needed it to run
1180
- # this task already.
1865
+
1866
+ # Git repository sources - pass source_config option to load a yaml
1867
+ # configuration file - defaults to ./config/git-sources.yml and
1868
+ # ~/.merb/git-sources.yml - which you need to create yourself.
1181
1869
  #
1182
- # After this we don't need the system-wide rubygems anymore, as all required
1183
- # executables are available in the local ./bin directory.
1870
+ # Example of contents:
1184
1871
  #
1185
- # RSpec is needed here because source installs might fail when running
1186
- # rake tasks where spec/rake/spectask has been required.
1187
-
1188
- desc 'setup', 'Install Thor, Rake and RSpec in the local gems dir'
1189
- method_options "--merb-root" => :optional
1190
- def setup
1191
- if $0 =~ /^(\.\/)?bin\/thor$/
1192
- error "You cannot run the setup from #{$0} - try #{File.basename($0)} merb:tasks:setup instead"
1193
- return
1872
+ # merb-core: git://github.com/myfork/merb-core.git
1873
+ # merb-more: git://github.com/myfork/merb-more.git
1874
+
1875
+ def self.repos(source_config = nil)
1876
+ source_config ||= begin
1877
+ local_config = File.join(Dir.pwd, 'config', 'git-sources.yml')
1878
+ user_config = File.join(ENV["HOME"] || ENV["APPDATA"], '.merb', 'git-sources.yml')
1879
+ File.exists?(local_config) ? local_config : user_config
1194
1880
  end
1195
- create_if_missing(File.join(working_dir, 'gems'))
1196
- Merb.install_gem('thor', :cache => true, :install_dir => gem_dir)
1197
- Merb.install_gem('rake', :cache => true, :install_dir => gem_dir)
1198
- Merb.install_gem('rspec', :cache => true, :install_dir => gem_dir)
1199
- ensure_bin_wrapper_for('thor', 'rake', 'rspec')
1200
- end
1201
-
1202
- # Get the latest merb.thor and install it into the working dir.
1203
-
1204
- desc 'update', 'Fetch the latest merb.thor and install it locally'
1205
- def update
1206
- require 'open-uri'
1207
- url = 'http://merbivore.com/merb.thor'
1208
- remote_file = open(url)
1209
- File.open(File.join(working_dir, 'merb.thor'), 'w') do |f|
1210
- f.write(remote_file.read)
1881
+ if source_config && File.exists?(source_config)
1882
+ default_repos.merge(YAML.load(File.read(source_config)))
1883
+ else
1884
+ default_repos
1211
1885
  end
1212
- success "Installed the latest merb.thor"
1213
- rescue OpenURI::HTTPError
1214
- error "Error opening #{url}"
1215
- rescue => e
1216
- error "An error occurred (#{e.message})"
1217
1886
  end
1218
-
1887
+
1888
+ def self.repo(name, source_config = nil)
1889
+ self.repos(source_config)[name]
1890
+ end
1891
+
1892
+ # Default Git repositories
1893
+ def self.default_repos
1894
+ @_default_repos ||= {
1895
+ 'merb' => "git://github.com/wycats/merb.git",
1896
+ 'merb-plugins' => "git://github.com/wycats/merb-plugins.git",
1897
+ 'extlib' => "git://github.com/sam/extlib.git",
1898
+ 'dm-core' => "git://github.com/sam/dm-core.git",
1899
+ 'dm-more' => "git://github.com/sam/dm-more.git",
1900
+ 'sequel' => "git://github.com/wayneeseguin/sequel.git",
1901
+ 'do' => "git://github.com/sam/do.git",
1902
+ 'thor' => "git://github.com/wycats/thor.git",
1903
+ 'rake' => "git://github.com/jimweirich/rake.git",
1904
+ 'minigems' => "git://github.com/fabien/minigems.git"
1905
+ }
1906
+ end
1907
+
1219
1908
  end
1220
-
1909
+
1221
1910
  end