command_manager.rb   [plain text]


#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++

require 'rubygems/command'
require 'rubygems/user_interaction'

##
# The command manager registers and installs all the individual sub-commands
# supported by the gem command.
#
# Extra commands can be provided by writing a rubygems_plugin.rb
# file in an installed gem.  You should register your command against the
# Gem::CommandManager instance, like this:
#
#   # file rubygems_plugin.rb
#   require 'rubygems/command_manager'
#
#   Gem::CommandManager.instance.register_command :edit
#
# You should put the implementation of your command in rubygems/commands.
#
#   # file rubygems/commands/edit_command.rb
#   class Gem::Commands::EditCommand < Gem::Command
#     # ...
#   end
#
# See Gem::Command for instructions on writing gem commands.

class Gem::CommandManager

  include Gem::UserInteraction

  ##
  # Return the authoritative instance of the command manager.

  def self.instance
    @command_manager ||= new
  end

  ##
  # Returns self. Allows a CommandManager instance to stand
  # in for the class itself.

  def instance
    self
  end

  ##
  # Reset the authoritative instance of the command manager.

  def self.reset
    @command_manager = nil
  end

  ##
  # Register all the subcommands supported by the gem command.

  def initialize
    require 'timeout'
    @commands = {}
    register_command :build
    register_command :cert
    register_command :check
    register_command :cleanup
    register_command :contents
    register_command :dependency
    register_command :environment
    register_command :fetch
    register_command :generate_index
    register_command :help
    register_command :install
    register_command :list
    register_command :lock
    register_command :mirror
    register_command :outdated
    register_command :owner
    register_command :pristine
    register_command :push
    register_command :query
    register_command :rdoc
    register_command :search
    register_command :server
    register_command :sources
    register_command :specification
    register_command :stale
    register_command :uninstall
    register_command :unpack
    register_command :update
    register_command :which
    register_command :yank
  end

  ##
  # Register the Symbol +command+ as a gem command.

  def register_command(command, obj=false)
    @commands[command] = obj
  end

  ##
  # Unregister the Symbol +command+ as a gem command.

  def unregister_command(command)
    @commands.delete command
  end

  ##
  # Returns a Command instance for +command_name+

  def [](command_name)
    command_name = command_name.intern
    return nil if @commands[command_name].nil?
    @commands[command_name] ||= load_and_instantiate(command_name)
  end

  ##
  # Return a sorted list of all command names as strings.

  def command_names
    @commands.keys.collect {|key| key.to_s}.sort
  end

  ##
  # Run the command specified by +args+.

  def run(args, build_args=nil)
    process_args(args, build_args)
  rescue StandardError, Timeout::Error => ex
    alert_error "While executing gem ... (#{ex.class})\n    #{ex.to_s}"
    ui.backtrace ex

    if Gem.configuration.really_verbose and \
         ex.kind_of?(Gem::Exception) and ex.source_exception
      e = ex.source_exception

      ui.errs.puts "Because of: (#{e.class})\n    #{e.to_s}"
      ui.backtrace e
    end

    terminate_interaction(1)
  rescue Interrupt
    alert_error "Interrupted"
    terminate_interaction(1)
  end

  def process_args(args, build_args=nil)
    args = args.to_str.split(/\s+/) if args.respond_to?(:to_str)

    if args.empty? then
      say Gem::Command::HELP
      terminate_interaction 1
    end

    case args.first
    when '-h', '--help' then
      say Gem::Command::HELP
      terminate_interaction 0
    when '-v', '--version' then
      say Gem::VERSION
      terminate_interaction 0
    when /^-/ then
      alert_error "Invalid option: #{args.first}.  See 'gem --help'."
      terminate_interaction 1
    else
      cmd_name = args.shift.downcase
      cmd = find_command cmd_name
      cmd.invoke_with_build_args args, build_args
    end
  end

  def find_command(cmd_name)
    possibilities = find_command_possibilities cmd_name

    if possibilities.size > 1 then
      raise Gem::CommandLineError,
            "Ambiguous command #{cmd_name} matches [#{possibilities.join(', ')}]"
    elsif possibilities.empty? then
      raise Gem::CommandLineError, "Unknown command #{cmd_name}"
    end

    self[possibilities.first]
  end

  def find_command_possibilities(cmd_name)
    len = cmd_name.length

    found = command_names.select { |name| cmd_name == name[0, len] }

    exact = found.find { |name| name == cmd_name }

    exact ? [exact] : found
  end

  private

  def load_and_instantiate(command_name)
    command_name = command_name.to_s
    const_name = command_name.capitalize.gsub(/_(.)/) { $1.upcase } << "Command"
    load_error = nil

    begin
      begin
        require "rubygems/commands/#{command_name}_command"
      rescue LoadError => e
        load_error = e
      end
      Gem::Commands.const_get(const_name).new
    rescue Exception => e
      e = load_error if load_error

      alert_error "Loading command: #{command_name} (#{e.class})\n\t#{e}"
      ui.backtrace e
    end
  end

end