diff options
Diffstat (limited to 'lib/rubygems/command.rb')
| -rw-r--r-- | lib/rubygems/command.rb | 894 |
1 files changed, 576 insertions, 318 deletions
diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb index 66855c7c6a..d38363f293 100644 --- a/lib/rubygems/command.rb +++ b/lib/rubygems/command.rb @@ -1,406 +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 'optparse' +require_relative "vendored_optparse" +require_relative "requirement" +require_relative "user_interaction" -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 -module Gem +class Gem::Command + include Gem::UserInteraction - # Base class for all Gem commands. When creating a new gem command, define - # #arguments, #defaults_str, #description and #usage (as appropriate). - class Command + Gem::OptionParser.accept Symbol, &:to_sym - include UserInteraction + ## + # The name of the command. - # The name of the command. - attr_reader :command + attr_reader :command - # The options for the command. - attr_reader :options + ## + # The options for the command. - # The default options for the command. - attr_accessor :defaults + attr_reader :options - # The name of the command for command-line invocation. - attr_accessor :program_name + ## + # The default options for the command. - # A short description of the command. - attr_accessor :summary + attr_accessor :defaults - # 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 + ## + # The name of the command for command-line invocation. - # True if +long+ begins with the characters from +short+. - def begins?(long, short) - return false if short.nil? - long[0, short.length] == short - end + attr_accessor :program_name - # Override to provide command handling. - def execute - fail "Generic command has no actions" - end + ## + # A short description of the command. - # Get all gem names from the command line. - def get_all_gem_names - args = options[:args] + attr_accessor :summary - if args.nil? or args.empty? then - raise Gem::CommandLineError, - "Please specify at least one gem name (e.g. gem build GEMNAME)" - end + ## + # Arguments used when building gems - gem_names = args.select { |arg| arg !~ /^-/ } - end + def self.build_args + @build_args ||= [] + 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] + def self.build_args=(value) + @build_args = value + end - 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 + def self.common_options + @common_options ||= [] + end - if args.size > 1 then - raise Gem::CommandLineError, - "Too many gem names (#{args.join(', ')}); please specify only one" - end + def self.add_common_option(*args, &handler) + Gem::Command.common_options << [args, handler] + end - args.first - end + def self.extra_args + @extra_args ||= [] + 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 + def self.extra_args=(value) + case value + when Array + @extra_args = value + when String + @extra_args = value.split(" ") end + 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 + ## + # Return an array of extra arguments for the command. The extra arguments + # come from the gem configuration file read at program startup. - # Override to display the default values of the command - # options. (similar to +arguments+, but displays the default - # values). - def defaults_str - "" - end + def self.specific_extra_args(cmd) + specific_extra_args_hash[cmd] + end - # Override to display a longer description of what this command does. - def description - nil - 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. - # Override to display the usage for an individual gem command. - def usage - program_name - end + def self.add_specific_extra_args(cmd,args) + args = args.split(/\s+/) if args.is_a? String + specific_extra_args_hash[cmd] = args + end - # Display the help message for the command. - def show_help - parser.program_name = usage - say parser - end + ## + # Accessor for the specific extra args hash (self initializing). - # 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) + 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 - execute + msg << " in any repository" 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}/ } } + 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 - - # 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 + end - # True if the command handles the given argument list. - def handles?(args) - begin - parser.parse!(args.dup) - return true - rescue - return false - 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 - # 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 + 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 - - 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 + + def extract_gem_name_and_version(name) # :nodoc: + if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name + [$1, $2] + else + [name] end + end - private + ## + # 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. - # Create on demand parser. - def parser - create_option_parser if @parser.nil? - @parser - end + def get_one_gem_name + args = options[:args] - def create_option_parser - @parser = OptionParser.new + if args.nil? || args.empty? + raise Gem::CommandLineError, + "Please specify a gem name on the command line (e.g. gem build GEMNAME)" + end - @parser.separator("") - regular_options = @option_groups.delete :options + if args.size > 1 + raise Gem::CommandLineError, + "Too many gem names (#{args.join(", ")}); please specify only one" + end - configure_options "", regular_options + 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 - @option_groups.sort_by { |n,_| n.to_s }.each do |group_name, option_list| - configure_options group_name, option_list + 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 - configure_options "Common", Command.common_options + extra_msg = deprecation["extra_msg"] - @parser.separator("") - unless arguments.empty? - @parser.separator(" Arguments:") - arguments.split(/\n/).each do |arg_desc| - @parser.separator(" #{arg_desc}") - end - @parser.separator("") - end + deprecate_option_msg += " #{extra_msg}" if extra_msg - @parser.separator(" Summary:") - wrap(@summary, 80 - 4).split("\n").each do |line| - @parser.separator(" #{line.strip}") - end + 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 - if description then - formatted = description.split("\n\n").map do |chunk| - wrap(chunk, 80 - 4) - end.join("\n") + result.flatten! + result.concat(args) + result + end - @parser.separator "" - @parser.separator " Description:" - formatted.split("\n").each do |line| - @parser.separator " #{line.rstrip}" - end - end + def deprecated? + false + end - unless defaults_str.empty? - @parser.separator("") - @parser.separator(" Defaults:") - defaults_str.split(/\n/).each do |line| - @parser.separator(" #{line}") - end - end - end + private - def configure_options(header, option_list) - return if option_list.nil? or option_list.empty? + def option_is_deprecated?(option) + @deprecated_options[command].key?(option) + end - header = header.to_s.empty? ? '' : "#{header} " - @parser.separator " #{header}Options:" + def add_parser_description # :nodoc: + return unless description - option_list.each do |args, handler| - dashes = args.select { |arg| arg =~ /^-/ } - @parser.on(*args) do |value| - handler.call(value, @options) - end - end + formatted = description.split("\n\n").map do |chunk| + wrap chunk, 80 - 4 + end.join "\n" - @parser.separator '' + @parser.separator nil + @parser.separator " Description:" + formatted.each_line do |line| + @parser.separator " #{line.rstrip}" end + end - # Wraps +text+ to +width+ - def wrap(text, width) - text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n") - end + def add_parser_options # :nodoc: + @parser.separator nil - ################################################################## - # 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 + regular_options = @option_groups.delete :options - def extra_args - @extra_args ||= [] - end + configure_options "", regular_options - def extra_args=(value) - case value - when Array - @extra_args = value - when String - @extra_args = value.split - end - end + @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 - # 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 + ## + # Adds a section with +title+ and +content+ to the parser help view. Used + # for adding command arguments and default arguments. - # 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 + 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 - # ---------------------------------------------------------------- - # Add the options common to all commands. + def add_parser_summary # :nodoc: + return unless @summary - add_common_option('-h', '--help', - 'Get help on this command') do - |value, options| - options[:help] = true + @parser.separator nil + @parser.separator " Summary:" + wrap(@summary, 80 - 4).each_line do |line| + @parser.separator " #{line.strip}" end + end - add_common_option('-V', '--[no-]verbose', - 'Set the verbose level of output') do |value, options| - # Set us to "really verbose" so the progess meter works - if Gem.configuration.verbose and value then - Gem.configuration.verbose = 1 - else - Gem.configuration.verbose = value - end - end + ## + # Create on demand parser. - add_common_option('-q', '--quiet', 'Silence commands') do |value, options| - Gem.configuration.verbose = false - end + def parser + create_option_parser if @parser.nil? + @parser + 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. + ## + # Creates an option parser and fills it in with the help info for the + # command. - add_common_option('--config-file FILE', - "Use this config file instead of default") do - end + def create_option_parser + @parser = Gem::OptionParser.new - add_common_option('--backtrace', - 'Show stack backtrace on errors') do - end + add_parser_options - add_common_option('--debug', - 'Turn on Ruby debugging') do + @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 - # :stopdoc: - HELP = %{ - RubyGems is a sophisticated package manager for Ruby. This is a - basic help message containing pointers to more information. + @parser.separator "" + end - Usage: - gem -h/--help - gem -v/--version - gem command [arguments...] [options...] + ## + # Wraps +text+ to +width+ - Examples: - gem install rake - gem list --local - gem build package.gemspec - gem help install + def wrap(text, width) # :doc: + text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n") + end - 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(/^ /, "") + # ---------------------------------------------------------------- + # Add the options common to all commands. - # :startdoc: + add_common_option("-h", "--help", + "Get help on this command") do |_value, options| + options[:help] = true + end - end # class + 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 - # This is where Commands will be placed in the namespace - module Commands; end +## +# \Commands will be placed in this namespace +module Gem::Commands end |
