mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
662 lines
16 KiB
Ruby
662 lines
16 KiB
Ruby
# frozen_string_literal: true
|
|
#--
|
|
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
|
|
# All rights reserved.
|
|
# See LICENSE.txt for permissions.
|
|
#++
|
|
|
|
require 'optparse'
|
|
require 'rubygems/requirement'
|
|
require 'rubygems/user_interaction'
|
|
|
|
##
|
|
# Base class for all Gem commands. When creating a new gem command, define
|
|
# #initialize, #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
|
|
|
|
OptionParser.accept Symbol do |value|
|
|
value.to_sym
|
|
end
|
|
|
|
##
|
|
# 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
|
|
# <tt>options[:args]</tt>.
|
|
|
|
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] = [] }
|
|
@deprecated_options = { command => {} }
|
|
@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 <tt>options[:args]</tt>.
|
|
#
|
|
# 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
|
|
|
|
##
|
|
# Display to the user that a gem couldn't be found and reasons why
|
|
#--
|
|
|
|
def show_lookup_failure(gem_name, version, errors, suppress_suggestions = false, required_by = nil)
|
|
gem = "'#{gem_name}' (#{version})"
|
|
msg = String.new "Could not find a valid gem #{gem}"
|
|
|
|
if errors and !errors.empty?
|
|
msg << ", here is why:\n"
|
|
errors.each { |x| msg << " #{x.wordy}\n" }
|
|
else
|
|
if required_by and gem != required_by
|
|
msg << " (required by #{required_by}) in any repository"
|
|
else
|
|
msg << " in any repository"
|
|
end
|
|
end
|
|
|
|
alert_error msg
|
|
|
|
unless suppress_suggestions
|
|
suggestions = Gem::SpecFetcher.fetcher.suggest_gems_from_name(gem_name, :latest, 10)
|
|
unless suggestions.empty?
|
|
alert_error "Possible alternatives: #{suggestions.join(", ")}"
|
|
end
|
|
end
|
|
end
|
|
|
|
##
|
|
# Get all gem names from the command line.
|
|
|
|
def get_all_gem_names
|
|
args = options[:args]
|
|
|
|
if args.nil? or args.empty?
|
|
raise Gem::CommandLineError,
|
|
"Please specify at least one gem name (e.g. gem build GEMNAME)"
|
|
end
|
|
|
|
args.select { |arg| arg !~ /^-/ }
|
|
end
|
|
|
|
##
|
|
# Get all [gem, version] from the command line.
|
|
#
|
|
# An argument in the form gem:ver is pull apart into the gen name and version,
|
|
# respectively.
|
|
def get_all_gem_names_and_versions
|
|
get_all_gem_names.map do |name|
|
|
if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name
|
|
[$1, $2]
|
|
else
|
|
[name]
|
|
end
|
|
end
|
|
end
|
|
|
|
##
|
|
# Get a 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?
|
|
raise Gem::CommandLineError,
|
|
"Please specify a gem name on the command line (e.g. gem build GEMNAME)"
|
|
end
|
|
|
|
if args.size > 1
|
|
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)
|
|
invoke_with_build_args args, nil
|
|
end
|
|
|
|
##
|
|
# Invoke the command with the given list of normal arguments
|
|
# and additional build arguments.
|
|
|
|
def invoke_with_build_args(args, build_args)
|
|
handle_options args
|
|
|
|
options[:build_args] = build_args
|
|
|
|
if options[:silent]
|
|
old_ui = self.ui
|
|
self.ui = ui = Gem::SilentUI.new
|
|
end
|
|
|
|
if options[:help]
|
|
show_help
|
|
elsif @when_invoked
|
|
@when_invoked.call options
|
|
else
|
|
execute
|
|
end
|
|
ensure
|
|
if ui
|
|
self.ui = old_ui
|
|
ui.close
|
|
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.is_a?(String) && x =~ /^#{name}/ } }
|
|
end
|
|
end
|
|
|
|
##
|
|
# Mark a command-line option as deprecated, and optionally specify a
|
|
# deprecation horizon.
|
|
#
|
|
# Note that with the current implementation, every version of the option needs
|
|
# to be explicitly deprecated, so to deprecate an option defined as
|
|
#
|
|
# add_option('-t', '--[no-]test', 'Set test mode') do |value, options|
|
|
# # ... stuff ...
|
|
# end
|
|
#
|
|
# you would need to explicitly add a call to `deprecate_option` for every
|
|
# version of the option you want to deprecate, like
|
|
#
|
|
# deprecate_option('-t')
|
|
# deprecate_option('--test')
|
|
# deprecate_option('--no-test')
|
|
|
|
def deprecate_option(name, version: nil, extra_msg: nil)
|
|
@deprecated_options[command].merge!({ name => { "rg_version_to_expire" => version, "extra_msg" => extra_msg } })
|
|
end
|
|
|
|
def check_deprecated_options(options)
|
|
options.each do |option|
|
|
if option_is_deprecated?(option)
|
|
deprecation = @deprecated_options[command][option]
|
|
version_to_expire = deprecation["rg_version_to_expire"]
|
|
|
|
deprecate_option_msg = if version_to_expire
|
|
"The \"#{option}\" option has been deprecated and will be removed in Rubygems #{version_to_expire}."
|
|
else
|
|
"The \"#{option}\" option has been deprecated and will be removed in future versions of Rubygems."
|
|
end
|
|
|
|
extra_msg = deprecation["extra_msg"]
|
|
|
|
deprecate_option_msg += " #{extra_msg}" if extra_msg
|
|
|
|
alert_warning(deprecate_option_msg)
|
|
end
|
|
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 { |k,v| @options[k] = v }
|
|
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)
|
|
check_deprecated_options(args)
|
|
@options = Marshal.load Marshal.dump @defaults # deep copy
|
|
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
|
|
|
|
def deprecated?
|
|
false
|
|
end
|
|
|
|
private
|
|
|
|
def option_is_deprecated?(option)
|
|
@deprecated_options[command].has_key?(option)
|
|
end
|
|
|
|
def add_parser_description # :nodoc:
|
|
return unless description
|
|
|
|
formatted = description.split("\n\n").map do |chunk|
|
|
wrap chunk, 80 - 4
|
|
end.join "\n"
|
|
|
|
@parser.separator nil
|
|
@parser.separator " Description:"
|
|
formatted.split("\n").each do |line|
|
|
@parser.separator " #{line.rstrip}"
|
|
end
|
|
end
|
|
|
|
def add_parser_options # :nodoc:
|
|
@parser.separator nil
|
|
|
|
regular_options = @option_groups.delete :options
|
|
|
|
configure_options "", regular_options
|
|
|
|
@option_groups.sort_by { |n,_| n.to_s }.each do |group_name, option_list|
|
|
@parser.separator nil
|
|
configure_options group_name, option_list
|
|
end
|
|
end
|
|
|
|
##
|
|
# Adds a section with +title+ and +content+ to the parser help view. Used
|
|
# for adding command arguments and default arguments.
|
|
|
|
def add_parser_run_info(title, content)
|
|
return if content.empty?
|
|
|
|
@parser.separator nil
|
|
@parser.separator " #{title}:"
|
|
content.split(/\n/).each do |line|
|
|
@parser.separator " #{line}"
|
|
end
|
|
end
|
|
|
|
def add_parser_summary # :nodoc:
|
|
return unless @summary
|
|
|
|
@parser.separator nil
|
|
@parser.separator " Summary:"
|
|
wrap(@summary, 80 - 4).split("\n").each do |line|
|
|
@parser.separator " #{line.strip}"
|
|
end
|
|
end
|
|
|
|
##
|
|
# Create on demand parser.
|
|
|
|
def parser
|
|
create_option_parser if @parser.nil?
|
|
@parser
|
|
end
|
|
|
|
##
|
|
# Creates an option parser and fills it in with the help info for the
|
|
# command.
|
|
|
|
def create_option_parser
|
|
@parser = OptionParser.new
|
|
|
|
add_parser_options
|
|
|
|
@parser.separator nil
|
|
configure_options "Common", Gem::Command.common_options
|
|
|
|
add_parser_run_info "Arguments", arguments
|
|
add_parser_summary
|
|
add_parser_description
|
|
add_parser_run_info "Defaults", defaults_str
|
|
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|
|
|
@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
|
|
Gem.configuration.verbose = 1
|
|
else
|
|
Gem.configuration.verbose = value
|
|
end
|
|
end
|
|
|
|
add_common_option('-q', '--quiet', 'Silence command progress meter') do |value, options|
|
|
Gem.configuration.verbose = false
|
|
end
|
|
|
|
add_common_option("--silent",
|
|
"Silence RubyGems output") do |value, options|
|
|
options[:silent] = true
|
|
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
|
|
|
|
add_common_option('--norc',
|
|
'Avoid loading any .gemrc file') do
|
|
end
|
|
|
|
# :stopdoc:
|
|
|
|
HELP = <<-HELP.freeze
|
|
RubyGems is a package manager for Ruby.
|
|
|
|
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 gem_dependencies gem dependencies file guide
|
|
gem help platforms gem platforms guide
|
|
gem help <COMMAND> 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:
|
|
https://guides.rubygems.org
|
|
HELP
|
|
|
|
# :startdoc:
|
|
|
|
end
|
|
|
|
##
|
|
# \Commands will be placed in this namespace
|
|
|
|
module Gem::Commands
|
|
end
|