#--
# 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'
##
# Base class for all Gem commands. When creating a new gem command, define
# #new, #execute, #arguments, #defaults_str, #description and #usage
# (as appropriate). See the above mentioned methods for details.
#
# A very good example to look at is Gem::Commands::ContentsCommand
class Gem::Command
include Gem::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
##
# Arguments used when building gems
def self.build_args
@build_args ||= []
end
def self.build_args=(value)
@build_args = value
end
def self.common_options
@common_options ||= []
end
def self.add_common_option(*args, &handler)
Gem::Command.common_options << [args, handler]
end
def self.extra_args
@extra_args ||= []
end
def self.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 self.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 self.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 self.specific_extra_args_hash
@specific_extra_args_hash ||= Hash.new do |h,k|
h[k] = Array.new
end
end
##
# 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.
#
# When defining a new command subclass, use add_option to add command-line
# switches.
#
# Unhandled arguments (gem names, files, etc.) are left in
# options[:args].
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.
#
# #options will be filled in with your parsed options, unparsed options will
# be left in options[:args].
#
# See also: #get_all_gem_names, #get_one_gem_name,
# #get_one_optional_argument
def execute
raise Gem::Exception, "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.
#
# For example:
#
# def usage
# "#{program_name} FILE [FILE ...]"
# end
#
# def arguments
# "FILE name of file to find"
# end
def arguments
""
end
##
# Override to display the default values of the command options. (similar to
# +arguments+, but displays the default values).
#
# For example:
#
# def defaults_str
# --no-gems-first --no-all
# end
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.
#
# The text "[options]" is automatically appended to the usage text.
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.
#
# If the first argument of add_option is a Symbol, it's used to group
# options in output. See `gem help list` for an example.
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
##
# Adds extra args from ~/.gemrc
def add_extra_args(args)
result = []
s_extra = Gem::Command.specific_extra_args(@command)
extra = Gem::Command.extra_args + s_extra
until extra.empty? do
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", Gem::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) # :doc:
text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
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 show help on COMMAND
(e.g. 'gem help install')
gem server present a web page at
http://localhost:8808/
with info about installed gems
Further information:
http://rubygems.rubyforge.org
}.gsub(/^ /, '')
# :startdoc:
end
##
# This is where Commands will be placed in the namespace
module Gem::Commands
end