summaryrefslogtreecommitdiff
path: root/lib/rubygems/command.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/command.rb')
-rw-r--r--lib/rubygems/command.rb664
1 files changed, 664 insertions, 0 deletions
diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb
new file mode 100644
index 0000000000..d38363f293
--- /dev/null
+++ b/lib/rubygems/command.rb
@@ -0,0 +1,664 @@
+# frozen_string_literal: true
+
+#--
+# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
+# All rights reserved.
+# See LICENSE.txt for permissions.
+#++
+
+require_relative "vendored_optparse"
+require_relative "requirement"
+require_relative "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
+
+ Gem::OptionParser.accept Symbol, &:to_sym
+
+ ##
+ # 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.is_a? 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 && !errors.empty?
+ msg << ", here is why:\n"
+ errors.each {|x| msg << " #{x.wordy}\n" }
+ else
+ if required_by && 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? || args.empty?
+ raise Gem::CommandLineError,
+ "Please specify at least one gem name (e.g. gem build GEMNAME)"
+ end
+
+ args.reject {|arg| arg.start_with?("-") }
+ 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|
+ extract_gem_name_and_version(name)
+ end
+ end
+
+ def extract_gem_name_and_version(name) # :nodoc:
+ if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name
+ [$1, $2]
+ else
+ [name]
+ 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? || 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 = 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 Gem::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
+
+ raise "Do not pass an empty string in opts" if opts.include?("")
+
+ @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|
+ next unless 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
+
+ ##
+ # 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)
+ parser.parse!(args.dup)
+ true
+ rescue StandardError
+ false
+ 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 /^[^-]/.match?(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].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.each_line 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.each_line do |line|
+ @parser.separator " #{line.rstrip}"
+ end
+ end
+
+ def add_parser_summary # :nodoc:
+ return unless @summary
+
+ @parser.separator nil
+ @parser.separator " Summary:"
+ wrap(@summary, 80 - 4).each_line 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 = Gem::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? || 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 && 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
+RubyGems is a package manager for Ruby.
+
+ Usage:
+ gem -h/--help
+ gem -v/--version
+ gem [global options...] command [arguments...] [options...]
+
+ Global options:
+ -C PATH run as if gem was started in <PATH>
+ instead of the current working directory
+
+ Examples:
+ gem install rake
+ gem list --local
+ gem build package.gemspec
+ gem push package-0.0.1.gem
+ 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')
+ Further information:
+ https://guides.rubygems.org
+ HELP
+
+ # :startdoc:
+end
+
+##
+# \Commands will be placed in this namespace
+
+module Gem::Commands
+end