1require 'rubygems'
2require 'rubygems/user_interaction'
3require 'pathname'
4
5##
6# Cleans up after a partially-failed uninstall or for an invalid
7# Gem::Specification.
8#
9# If a specification was removed by hand this will remove any remaining files.
10#
11# If a corrupt specification was installed this will clean up warnings by
12# removing the bogus specification.
13
14class Gem::Doctor
15
16  include Gem::UserInteraction
17
18  ##
19  # Maps a gem subdirectory to the files that are expected to exist in the
20  # subdirectory.
21
22  REPOSITORY_EXTENSION_MAP = [ # :nodoc:
23    ['specifications', '.gemspec'],
24    ['build_info',     '.info'],
25    ['cache',          '.gem'],
26    ['doc',            ''],
27    ['gems',           ''],
28  ]
29
30  raise 'Update REPOSITORY_EXTENSION_MAP' unless
31    Gem::REPOSITORY_SUBDIRECTORIES.sort ==
32      REPOSITORY_EXTENSION_MAP.map { |(k,_)| k }.sort
33
34  ##
35  # Creates a new Gem::Doctor that will clean up +gem_repository+.  Only one
36  # gem repository may be cleaned at a time.
37  #
38  # If +dry_run+ is true no files or directories will be removed.
39
40  def initialize gem_repository, dry_run = false
41    @gem_repository = Pathname(gem_repository)
42    @dry_run        = dry_run
43
44    @installed_specs = nil
45  end
46
47  ##
48  # Specs installed in this gem repository
49
50  def installed_specs # :nodoc:
51    @installed_specs ||= Gem::Specification.map { |s| s.full_name }
52  end
53
54  ##
55  # Are we doctoring a gem repository?
56
57  def gem_repository?
58    not installed_specs.empty?
59  end
60
61  ##
62  # Cleans up uninstalled files and invalid gem specifications
63
64  def doctor
65    @orig_home = Gem.dir
66    @orig_path = Gem.path
67
68    say "Checking #{@gem_repository}"
69
70    Gem.use_paths @gem_repository.to_s
71
72    unless gem_repository? then
73      say 'This directory does not appear to be a RubyGems repository, ' +
74          'skipping'
75      say
76      return
77    end
78
79    doctor_children
80
81    say
82  ensure
83    Gem.use_paths @orig_home, *@orig_path
84  end
85
86  ##
87  # Cleans up children of this gem repository
88
89  def doctor_children # :nodoc:
90    REPOSITORY_EXTENSION_MAP.each do |sub_directory, extension|
91      doctor_child sub_directory, extension
92    end
93  end
94
95  ##
96  # Removes files in +sub_directory+ with +extension+
97
98  def doctor_child sub_directory, extension # :nodoc:
99    directory = @gem_repository + sub_directory
100
101    directory.children.sort.each do |child|
102      next unless child.exist?
103
104      basename = child.basename(extension).to_s
105      next if installed_specs.include? basename
106      next if /^rubygems-\d/ =~ basename
107      next if 'specifications' == sub_directory and 'default' == basename
108
109      type = child.directory? ? 'directory' : 'file'
110
111      action = if @dry_run then
112                 'Extra'
113               else
114                 child.rmtree
115                 'Removed'
116               end
117
118      say "#{action} #{type} #{sub_directory}/#{child.basename}"
119    end
120  rescue Errno::ENOENT
121    # ignore
122  end
123
124end
125
126