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