ruby/lib/rubygems/command.rb

407 строки
11 KiB
Ruby

#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'optparse'
require 'rubygems/user_interaction'
module Gem
# Base class for all Gem commands. When creating a new gem command, define
# #arguments, #defaults_str, #description and #usage (as appropriate).
class Command
include UserInteraction
# The name of the command.
attr_reader :command
# The options for the command.
attr_reader :options
# The default options for the command.
attr_accessor :defaults
# The name of the command for command-line invocation.
attr_accessor :program_name
# A short description of the command.
attr_accessor :summary
# Initializes a generic gem command named +command+. +summary+ is a short
# description displayed in `gem help commands`. +defaults+ are the
# default options. Defaults should be mirrored in #defaults_str, unless
# there are none.
#
# Use add_option to add command-line switches.
def initialize(command, summary=nil, defaults={})
@command = command
@summary = summary
@program_name = "gem #{command}"
@defaults = defaults
@options = defaults.dup
@option_groups = Hash.new { |h,k| h[k] = [] }
@parser = nil
@when_invoked = nil
end
# True if +long+ begins with the characters from +short+.
def begins?(long, short)
return false if short.nil?
long[0, short.length] == short
end
# Override to provide command handling.
def execute
fail "Generic command has no actions"
end
# Get all gem names from the command line.
def get_all_gem_names
args = options[:args]
if args.nil? or args.empty? then
raise Gem::CommandLineError,
"Please specify at least one gem name (e.g. gem build GEMNAME)"
end
gem_names = args.select { |arg| arg !~ /^-/ }
end
# Get the single gem name from the command line. Fail if there is no gem
# name or if there is more than one gem name given.
def get_one_gem_name
args = options[:args]
if args.nil? or args.empty? then
raise Gem::CommandLineError,
"Please specify a gem name on the command line (e.g. gem build GEMNAME)"
end
if args.size > 1 then
raise Gem::CommandLineError,
"Too many gem names (#{args.join(', ')}); please specify only one"
end
args.first
end
# Get a single optional argument from the command line. If more than one
# argument is given, return only the first. Return nil if none are given.
def get_one_optional_argument
args = options[:args] || []
args.first
end
# Override to provide details of the arguments a command takes.
# It should return a left-justified string, one argument per line.
def arguments
""
end
# Override to display the default values of the command
# options. (similar to +arguments+, but displays the default
# values).
def defaults_str
""
end
# Override to display a longer description of what this command does.
def description
nil
end
# Override to display the usage for an individual gem command.
def usage
program_name
end
# Display the help message for the command.
def show_help
parser.program_name = usage
say parser
end
# Invoke the command with the given list of arguments.
def invoke(*args)
handle_options(args)
if options[:help]
show_help
elsif @when_invoked
@when_invoked.call(options)
else
execute
end
end
# Call the given block when invoked.
#
# Normal command invocations just executes the +execute+ method of
# the command. Specifying an invocation block allows the test
# methods to override the normal action of a command to determine
# that it has been invoked correctly.
def when_invoked(&block)
@when_invoked = block
end
# Add a command-line option and handler to the command.
#
# See OptionParser#make_switch for an explanation of +opts+.
#
# +handler+ will be called with two values, the value of the argument and
# the options hash.
def add_option(*opts, &handler) # :yields: value, options
group_name = Symbol === opts.first ? opts.shift : :options
@option_groups[group_name] << [opts, handler]
end
# Remove previously defined command-line argument +name+.
def remove_option(name)
@option_groups.each do |_, option_list|
option_list.reject! { |args, _| args.any? { |x| x =~ /^#{name}/ } }
end
end
# Merge a set of command options with the set of default options
# (without modifying the default option hash).
def merge_options(new_options)
@options = @defaults.clone
new_options.each do |k,v| @options[k] = v end
end
# True if the command handles the given argument list.
def handles?(args)
begin
parser.parse!(args.dup)
return true
rescue
return false
end
end
# Handle the given list of arguments by parsing them and recording
# the results.
def handle_options(args)
args = add_extra_args(args)
@options = @defaults.clone
parser.parse!(args)
@options[:args] = args
end
def add_extra_args(args)
result = []
s_extra = Command.specific_extra_args(@command)
extra = Command.extra_args + s_extra
while ! extra.empty?
ex = []
ex << extra.shift
ex << extra.shift if extra.first.to_s =~ /^[^-]/
result << ex if handles?(ex)
end
result.flatten!
result.concat(args)
result
end
private
# Create on demand parser.
def parser
create_option_parser if @parser.nil?
@parser
end
def create_option_parser
@parser = OptionParser.new
@parser.separator("")
regular_options = @option_groups.delete :options
configure_options "", regular_options
@option_groups.sort_by { |n,_| n.to_s }.each do |group_name, option_list|
configure_options group_name, option_list
end
configure_options "Common", Command.common_options
@parser.separator("")
unless arguments.empty?
@parser.separator(" Arguments:")
arguments.split(/\n/).each do |arg_desc|
@parser.separator(" #{arg_desc}")
end
@parser.separator("")
end
@parser.separator(" Summary:")
wrap(@summary, 80 - 4).split("\n").each do |line|
@parser.separator(" #{line.strip}")
end
if description then
formatted = description.split("\n\n").map do |chunk|
wrap(chunk, 80 - 4)
end.join("\n")
@parser.separator ""
@parser.separator " Description:"
formatted.split("\n").each do |line|
@parser.separator " #{line.rstrip}"
end
end
unless defaults_str.empty?
@parser.separator("")
@parser.separator(" Defaults:")
defaults_str.split(/\n/).each do |line|
@parser.separator(" #{line}")
end
end
end
def configure_options(header, option_list)
return if option_list.nil? or option_list.empty?
header = header.to_s.empty? ? '' : "#{header} "
@parser.separator " #{header}Options:"
option_list.each do |args, handler|
dashes = args.select { |arg| arg =~ /^-/ }
@parser.on(*args) do |value|
handler.call(value, @options)
end
end
@parser.separator ''
end
# Wraps +text+ to +width+
def wrap(text, width)
text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
end
##################################################################
# Class methods for Command.
class << self
def common_options
@common_options ||= []
end
def add_common_option(*args, &handler)
Gem::Command.common_options << [args, handler]
end
def extra_args
@extra_args ||= []
end
def extra_args=(value)
case value
when Array
@extra_args = value
when String
@extra_args = value.split
end
end
# Return an array of extra arguments for the command. The extra
# arguments come from the gem configuration file read at program
# startup.
def specific_extra_args(cmd)
specific_extra_args_hash[cmd]
end
# Add a list of extra arguments for the given command. +args+
# may be an array or a string to be split on white space.
def add_specific_extra_args(cmd,args)
args = args.split(/\s+/) if args.kind_of? String
specific_extra_args_hash[cmd] = args
end
# Accessor for the specific extra args hash (self initializing).
def specific_extra_args_hash
@specific_extra_args_hash ||= Hash.new do |h,k|
h[k] = Array.new
end
end
end
# ----------------------------------------------------------------
# Add the options common to all commands.
add_common_option('-h', '--help',
'Get help on this command') do
|value, options|
options[:help] = true
end
add_common_option('-V', '--[no-]verbose',
'Set the verbose level of output') do |value, options|
# Set us to "really verbose" so the progress meter works
if Gem.configuration.verbose and value then
Gem.configuration.verbose = 1
else
Gem.configuration.verbose = value
end
end
add_common_option('-q', '--quiet', 'Silence commands') do |value, options|
Gem.configuration.verbose = false
end
# Backtrace and config-file are added so they show up in the help
# commands. Both options are actually handled before the other
# options get parsed.
add_common_option('--config-file FILE',
"Use this config file instead of default") do
end
add_common_option('--backtrace',
'Show stack backtrace on errors') do
end
add_common_option('--debug',
'Turn on Ruby debugging') do
end
# :stopdoc:
HELP = %{
RubyGems is a sophisticated package manager for Ruby. This is a
basic help message containing pointers to more information.
Usage:
gem -h/--help
gem -v/--version
gem command [arguments...] [options...]
Examples:
gem install rake
gem list --local
gem build package.gemspec
gem help install
Further help:
gem help commands list all 'gem' commands
gem help examples show some examples of usage
gem help platforms show information about platforms
gem help <COMMAND> show help on COMMAND
(e.g. 'gem help install')
Further information:
http://rubygems.rubyforge.org
}.gsub(/^ /, "")
# :startdoc:
end # class
# This is where Commands will be placed in the namespace
module Commands; end
end