class GemLeaves
Amateurish prototype of a tool to check the installed gems looking for leaves that can be removed.
I call leaves those gems with no reverse dependency that can be removed without breaking anything. Of course, users may desire to keep something around even if nothing depends on it, so this tool looks for and loads a simple YAML configuration file:
ignore: RedCloth: >= 0 capistrano: >= 2 mongrel_cluster: >= 0 piston: >= 0 rake: >= 0.7 rubygems-update: >= 0 ruport-util: = 0.10.0 sources: >= 0
gem_leaves
can generate its own configuration file merging the leaves list with the content of an already loaded configuration file (if any). The tool looks for the default configuration file, .gem_leaves.yml
, in the current working directory and the user’s home directory.
Constants
- VERSION
Public Class Methods
# File lib/gem_leaves.rb 31 def initialize(args) 32 @options = {:color => "d0ed0e"} 33 @configuration = {} 34 @leaves = [] 35 parse_options(args) 36 end
Public Instance Methods
# File lib/gem_leaves.rb 38 def run 39 load_config_file 40 find_leaves 41 show_leaves 42 generate_config_file 43 end
Protected Instance Methods
Build a DOT diagram of the installed gems, highlighting the leaves with a customizable color, leaving those gems that must be kept according to the current configuration in the standard color.
# File lib/gem_leaves.rb 144 def diagrammatical_output 145 diagram = "digraph leaves_diagram {\n" 146 Gem::Specification.all.each do |g| 147 if @options[:reverse] 148 g.dependent_gems.each {|d| diagram << %Q( "#{d[0].full_name}" -> "#{g.full_name}"\n)} 149 else 150 g.dependent_gems.each {|d| diagram << %Q( "#{g.full_name}" -> "#{d[0].full_name}"\n)} 151 end 152 if g.dependent_gems.empty? 153 str = %Q( "#{g.full_name}") 154 if @leaves.include?(g) 155 str += %Q( [color="##{@options[:color]}", style=filled]\n) 156 else 157 str += "\n" 158 end 159 diagram << str 160 end 161 end 162 diagram << "}" 163 puts diagram 164 end
Lifted from RubyGems 0.9.5; it’s a semi-portable way to find the user’s home directory.
# File lib/gem_leaves.rb 88 def find_home 89 ['HOME', 'USERPROFILE'].each do |homekey| 90 return ENV[homekey] if ENV[homekey] 91 end 92 if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] 93 return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}" 94 end 95 begin 96 File.expand_path("~") 97 rescue StandardError => ex 98 if File::ALT_SEPARATOR 99 "C:/" 100 else 101 "/" 102 end 103 end 104 end
Looks at the installed gems to find the leaves.
# File lib/gem_leaves.rb 107 def find_leaves 108 ignorable = @configuration['ignore'].map do |k, v| 109 Gem::Specification.find_all_by_name k, v 110 end.flatten 111 @leaves = Gem::Specification.select {|s| s.dependent_gems.empty?} - ignorable 112 end
Writes the new configuration file merging the optionally already loaded one with the leaves just found.
# File lib/gem_leaves.rb 168 def generate_config_file 169 if @options[:new_config_file] 170 @leaves.sort.each do |leaf| 171 @configuration['ignore']["#{leaf.name}"] = "= #{leaf.version}" 172 end 173 File.open(@options[:new_config_file], 'w') do |out| 174 YAML.dump(@configuration, out) 175 end 176 end 177 end
Looks for a YAML configuration file in:
-
the PWD;
-
the user’s home directory.
Optionally load a specific configuration file whose name has been set by the user.
# File lib/gem_leaves.rb 72 def load_config_file 73 if @options[:ignore] 74 @configuration = {'ignore' => {}} 75 else 76 if @options[:config_file].nil? 77 cf = ['.gem_leaves.yml', File.join(find_home, '.gem_leaves.yml')] 78 cf.each {|f| (@configuration = YAML.load_file(f); return) rescue nil} 79 @configuration = {'ignore' => {}} 80 else 81 @configuration = YAML.load_file(@options[:config_file]) 82 end 83 end 84 end
Parses the command line arguments.
# File lib/gem_leaves.rb 48 def parse_options(args) 49 OptionParser.new('Usage: gem_leaves [OPTIONS]') do |p| 50 p.separator '' 51 p.on('-c', '--config-file=FILE', 'Load the named configuration file') {|v| @options[:config_file] = v} 52 p.on('-C', '--color=COLOR', "Set the leaves' color in the DOT diagram") {|v| @options[:color] = v} 53 p.on('-d', '--diagram', 'Generate a DOT diagram on stdout') {|v| @options[:diagram] = v} 54 p.on('-g', '--generate-config-file=FILE', 55 "Generate a new configuration file merging", 56 "the leaves' list with the content of the", 57 "old configuration file (if any)") {|v| @options[:new_config_file] = v} 58 p.on('-i', '--ignore', 'Ignore every configuration file') {|v| @options[:ignore] = v} 59 p.on('-p', '--pipe', 'Pipe Format (name --version ver)') {|v| @options[:pipe] = v} 60 p.on('-r', '--reverse', "Reverse the edges in the DOT diagram") {|v| @options[:reverse] = v} 61 p.parse(args) 62 end 63 end
Simply puts the list of leaves to STDOUT in a format suitable to pipe it to RubyGems.
# File lib/gem_leaves.rb 127 def pipe_output 128 @leaves.sort.each do |leaf| 129 puts "#{leaf.name} --version #{leaf.version}" 130 end 131 end
Show the leaves using one of the supported output format.
# File lib/gem_leaves.rb 115 def show_leaves 116 if @options[:diagram] 117 diagrammatical_output 118 elsif @options[:pipe] 119 pipe_output 120 else 121 textual_output 122 end 123 end
Simply puts the list of leaves to STDOUT. It optionally merges the content of the already loaded configuration file with the leaves list.
# File lib/gem_leaves.rb 135 def textual_output 136 @leaves.sort.each do |leaf| 137 puts "#{leaf.name} (#{leaf.version})" 138 end 139 end