1# frozen_string_literal: true
2#--
3# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
4# All rights reserved.
5# See LICENSE.txt for permissions.
6#++
7
8require 'optparse'
9require 'rubygems/requirement'
10require 'rubygems/user_interaction'
11
12##
13# Base class for all Gem commands.  When creating a new gem command, define
14# #initialize, #execute, #arguments, #defaults_str, #description and #usage
15# (as appropriate).  See the above mentioned methods for details.
16#
17# A very good example to look at is Gem::Commands::ContentsCommand
18
19class Gem::Command
20
21  include Gem::UserInteraction
22
23  ##
24  # The name of the command.
25
26  attr_reader :command
27
28  ##
29  # The options for the command.
30
31  attr_reader :options
32
33  ##
34  # The default options for the command.
35
36  attr_accessor :defaults
37
38  ##
39  # The name of the command for command-line invocation.
40
41  attr_accessor :program_name
42
43  ##
44  # A short description of the command.
45
46  attr_accessor :summary
47
48  ##
49  # Arguments used when building gems
50
51  def self.build_args
52    @build_args ||= []
53  end
54
55  def self.build_args=(value)
56    @build_args = value
57  end
58
59  def self.common_options
60    @common_options ||= []
61  end
62
63  def self.add_common_option(*args, &handler)
64    Gem::Command.common_options << [args, handler]
65  end
66
67  def self.extra_args
68    @extra_args ||= []
69  end
70
71  def self.extra_args=(value)
72    case value
73    when Array
74      @extra_args = value
75    when String
76      @extra_args = value.split
77    end
78  end
79
80  ##
81  # Return an array of extra arguments for the command.  The extra arguments
82  # come from the gem configuration file read at program startup.
83
84  def self.specific_extra_args(cmd)
85    specific_extra_args_hash[cmd]
86  end
87
88  ##
89  # Add a list of extra arguments for the given command.  +args+ may be an
90  # array or a string to be split on white space.
91
92  def self.add_specific_extra_args(cmd,args)
93    args = args.split(/\s+/) if args.kind_of? String
94    specific_extra_args_hash[cmd] = args
95  end
96
97  ##
98  # Accessor for the specific extra args hash (self initializing).
99
100  def self.specific_extra_args_hash
101    @specific_extra_args_hash ||= Hash.new do |h,k|
102      h[k] = Array.new
103    end
104  end
105
106  ##
107  # Initializes a generic gem command named +command+.  +summary+ is a short
108  # description displayed in `gem help commands`.  +defaults+ are the default
109  # options.  Defaults should be mirrored in #defaults_str, unless there are
110  # none.
111  #
112  # When defining a new command subclass, use add_option to add command-line
113  # switches.
114  #
115  # Unhandled arguments (gem names, files, etc.) are left in
116  # <tt>options[:args]</tt>.
117
118  def initialize(command, summary=nil, defaults={})
119    @command = command
120    @summary = summary
121    @program_name = "gem #{command}"
122    @defaults = defaults
123    @options = defaults.dup
124    @option_groups = Hash.new { |h,k| h[k] = [] }
125    @parser = nil
126    @when_invoked = nil
127  end
128
129  ##
130  # True if +long+ begins with the characters from +short+.
131
132  def begins?(long, short)
133    return false if short.nil?
134    long[0, short.length] == short
135  end
136
137  ##
138  # Override to provide command handling.
139  #
140  # #options will be filled in with your parsed options, unparsed options will
141  # be left in <tt>options[:args]</tt>.
142  #
143  # See also: #get_all_gem_names, #get_one_gem_name,
144  # #get_one_optional_argument
145
146  def execute
147    raise Gem::Exception, "generic command has no actions"
148  end
149
150  ##
151  # Display to the user that a gem couldn't be found and reasons why
152  #--
153  # TODO: replace +domain+ with a parameter to suppress suggestions
154
155  def show_lookup_failure(gem_name, version, errors, domain, required_by = nil)
156    gem = "'#{gem_name}' (#{version})"
157    msg = String.new "Could not find a valid gem #{gem}"
158
159    if errors and !errors.empty?
160      msg << ", here is why:\n"
161      errors.each { |x| msg << "          #{x.wordy}\n" }
162    else
163      if required_by and gem != required_by
164        msg << " (required by #{required_by}) in any repository"
165      else
166        msg << " in any repository"
167      end
168    end
169
170    alert_error msg
171
172    unless domain == :local  # HACK
173      suggestions = Gem::SpecFetcher.fetcher.suggest_gems_from_name gem_name
174
175      unless suggestions.empty?
176        alert_error "Possible alternatives: #{suggestions.join(", ")}"
177      end
178    end
179  end
180
181  ##
182  # Get all gem names from the command line.
183
184  def get_all_gem_names
185    args = options[:args]
186
187    if args.nil? or args.empty?
188      raise Gem::CommandLineError,
189            "Please specify at least one gem name (e.g. gem build GEMNAME)"
190    end
191
192    args.select { |arg| arg !~ /^-/ }
193  end
194
195  ##
196  # Get all [gem, version] from the command line.
197  #
198  # An argument in the form gem:ver is pull apart into the gen name and version,
199  # respectively.
200  def get_all_gem_names_and_versions
201    get_all_gem_names.map do |name|
202      if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name
203        [$1, $2]
204      else
205        [name]
206      end
207    end
208  end
209
210  ##
211  # Get a single gem name from the command line.  Fail if there is no gem name
212  # or if there is more than one gem name given.
213
214  def get_one_gem_name
215    args = options[:args]
216
217    if args.nil? or args.empty?
218      raise Gem::CommandLineError,
219            "Please specify a gem name on the command line (e.g. gem build GEMNAME)"
220    end
221
222    if args.size > 1
223      raise Gem::CommandLineError,
224            "Too many gem names (#{args.join(', ')}); please specify only one"
225    end
226
227    args.first
228  end
229
230  ##
231  # Get a single optional argument from the command line.  If more than one
232  # argument is given, return only the first. Return nil if none are given.
233
234  def get_one_optional_argument
235    args = options[:args] || []
236    args.first
237  end
238
239  ##
240  # Override to provide details of the arguments a command takes.  It should
241  # return a left-justified string, one argument per line.
242  #
243  # For example:
244  #
245  #   def usage
246  #     "#{program_name} FILE [FILE ...]"
247  #   end
248  #
249  #   def arguments
250  #     "FILE          name of file to find"
251  #   end
252
253  def arguments
254    ""
255  end
256
257  ##
258  # Override to display the default values of the command options. (similar to
259  # +arguments+, but displays the default values).
260  #
261  # For example:
262  #
263  #   def defaults_str
264  #     --no-gems-first --no-all
265  #   end
266
267  def defaults_str
268    ""
269  end
270
271  ##
272  # Override to display a longer description of what this command does.
273
274  def description
275    nil
276  end
277
278  ##
279  # Override to display the usage for an individual gem command.
280  #
281  # The text "[options]" is automatically appended to the usage text.
282
283  def usage
284    program_name
285  end
286
287  ##
288  # Display the help message for the command.
289
290  def show_help
291    parser.program_name = usage
292    say parser
293  end
294
295  ##
296  # Invoke the command with the given list of arguments.
297
298  def invoke(*args)
299    invoke_with_build_args args, nil
300  end
301
302  ##
303  # Invoke the command with the given list of normal arguments
304  # and additional build arguments.
305
306  def invoke_with_build_args(args, build_args)
307    handle_options args
308
309    options[:build_args] = build_args
310
311    if options[:silent]
312      old_ui = self.ui
313      self.ui = ui = Gem::SilentUI.new
314    end
315
316    if options[:help]
317      show_help
318    elsif @when_invoked
319      @when_invoked.call options
320    else
321      execute
322    end
323  ensure
324    if ui
325      self.ui = old_ui
326      ui.close
327    end
328  end
329
330  ##
331  # Call the given block when invoked.
332  #
333  # Normal command invocations just executes the +execute+ method of the
334  # command.  Specifying an invocation block allows the test methods to
335  # override the normal action of a command to determine that it has been
336  # invoked correctly.
337
338  def when_invoked(&block)
339    @when_invoked = block
340  end
341
342  ##
343  # Add a command-line option and handler to the command.
344  #
345  # See OptionParser#make_switch for an explanation of +opts+.
346  #
347  # +handler+ will be called with two values, the value of the argument and
348  # the options hash.
349  #
350  # If the first argument of add_option is a Symbol, it's used to group
351  # options in output.  See `gem help list` for an example.
352
353  def add_option(*opts, &handler) # :yields: value, options
354    group_name = Symbol === opts.first ? opts.shift : :options
355
356    @option_groups[group_name] << [opts, handler]
357  end
358
359  ##
360  # Remove previously defined command-line argument +name+.
361
362  def remove_option(name)
363    @option_groups.each do |_, option_list|
364      option_list.reject! { |args, _| args.any? { |x| x.is_a?(String) && x =~ /^#{name}/ } }
365    end
366  end
367
368  ##
369  # Merge a set of command options with the set of default options (without
370  # modifying the default option hash).
371
372  def merge_options(new_options)
373    @options = @defaults.clone
374    new_options.each do |k,v| @options[k] = v end
375  end
376
377  ##
378  # True if the command handles the given argument list.
379
380  def handles?(args)
381    begin
382      parser.parse!(args.dup)
383      return true
384    rescue
385      return false
386    end
387  end
388
389  ##
390  # Handle the given list of arguments by parsing them and recording the
391  # results.
392
393  def handle_options(args)
394    args = add_extra_args(args)
395    @options = Marshal.load Marshal.dump @defaults # deep copy
396    parser.parse!(args)
397    @options[:args] = args
398  end
399
400  ##
401  # Adds extra args from ~/.gemrc
402
403  def add_extra_args(args)
404    result = []
405
406    s_extra = Gem::Command.specific_extra_args(@command)
407    extra = Gem::Command.extra_args + s_extra
408
409    until extra.empty? do
410      ex = []
411      ex << extra.shift
412      ex << extra.shift if extra.first.to_s =~ /^[^-]/
413      result << ex if handles?(ex)
414    end
415
416    result.flatten!
417    result.concat(args)
418    result
419  end
420
421  private
422
423  def add_parser_description # :nodoc:
424    return unless description
425
426    formatted = description.split("\n\n").map do |chunk|
427      wrap chunk, 80 - 4
428    end.join "\n"
429
430    @parser.separator nil
431    @parser.separator "  Description:"
432    formatted.split("\n").each do |line|
433      @parser.separator "    #{line.rstrip}"
434    end
435  end
436
437  def add_parser_options # :nodoc:
438    @parser.separator nil
439
440    regular_options = @option_groups.delete :options
441
442    configure_options "", regular_options
443
444    @option_groups.sort_by { |n,_| n.to_s }.each do |group_name, option_list|
445      @parser.separator nil
446      configure_options group_name, option_list
447    end
448  end
449
450  ##
451  # Adds a section with +title+ and +content+ to the parser help view.  Used
452  # for adding command arguments and default arguments.
453
454  def add_parser_run_info(title, content)
455    return if content.empty?
456
457    @parser.separator nil
458    @parser.separator "  #{title}:"
459    content.split(/\n/).each do |line|
460      @parser.separator "    #{line}"
461    end
462  end
463
464  def add_parser_summary # :nodoc:
465    return unless @summary
466
467    @parser.separator nil
468    @parser.separator "  Summary:"
469    wrap(@summary, 80 - 4).split("\n").each do |line|
470      @parser.separator "    #{line.strip}"
471    end
472  end
473
474  ##
475  # Create on demand parser.
476
477  def parser
478    create_option_parser if @parser.nil?
479    @parser
480  end
481
482  ##
483  # Creates an option parser and fills it in with the help info for the
484  # command.
485
486  def create_option_parser
487    @parser = OptionParser.new
488
489    add_parser_options
490
491    @parser.separator nil
492    configure_options "Common", Gem::Command.common_options
493
494    add_parser_run_info "Arguments", arguments
495    add_parser_summary
496    add_parser_description
497    add_parser_run_info "Defaults", defaults_str
498  end
499
500  def configure_options(header, option_list)
501    return if option_list.nil? or option_list.empty?
502
503    header = header.to_s.empty? ? '' : "#{header} "
504    @parser.separator "  #{header}Options:"
505
506    option_list.each do |args, handler|
507      @parser.on(*args) do |value|
508        handler.call(value, @options)
509      end
510    end
511
512    @parser.separator ''
513  end
514
515  ##
516  # Wraps +text+ to +width+
517
518  def wrap(text, width) # :doc:
519    text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
520  end
521
522  # ----------------------------------------------------------------
523  # Add the options common to all commands.
524
525  add_common_option('-h', '--help',
526                    'Get help on this command') do |value, options|
527    options[:help] = true
528  end
529
530  add_common_option('-V', '--[no-]verbose',
531                    'Set the verbose level of output') do |value, options|
532    # Set us to "really verbose" so the progress meter works
533    if Gem.configuration.verbose and value
534      Gem.configuration.verbose = 1
535    else
536      Gem.configuration.verbose = value
537    end
538  end
539
540  add_common_option('-q', '--quiet', 'Silence command progress meter') do |value, options|
541    Gem.configuration.verbose = false
542  end
543
544  add_common_option("--silent",
545                    "Silence RubyGems output") do |value, options|
546    options[:silent] = true
547  end
548
549  # Backtrace and config-file are added so they show up in the help
550  # commands.  Both options are actually handled before the other
551  # options get parsed.
552
553  add_common_option('--config-file FILE',
554                    'Use this config file instead of default') do
555  end
556
557  add_common_option('--backtrace',
558                    'Show stack backtrace on errors') do
559  end
560
561  add_common_option('--debug',
562                    'Turn on Ruby debugging') do
563  end
564
565  add_common_option('--norc',
566                    'Avoid loading any .gemrc file') do
567  end
568
569
570  # :stopdoc:
571
572  HELP = <<-HELP.freeze
573RubyGems is a sophisticated package manager for Ruby.  This is a
574basic help message containing pointers to more information.
575
576  Usage:
577    gem -h/--help
578    gem -v/--version
579    gem command [arguments...] [options...]
580
581  Examples:
582    gem install rake
583    gem list --local
584    gem build package.gemspec
585    gem help install
586
587  Further help:
588    gem help commands            list all 'gem' commands
589    gem help examples            show some examples of usage
590    gem help gem_dependencies    gem dependencies file guide
591    gem help platforms           gem platforms guide
592    gem help <COMMAND>           show help on COMMAND
593                                   (e.g. 'gem help install')
594    gem server                   present a web page at
595                                 http://localhost:8808/
596                                 with info about installed gems
597  Further information:
598    http://guides.rubygems.org
599  HELP
600
601  # :startdoc:
602
603end
604
605##
606# \Commands will be placed in this namespace
607
608module Gem::Commands
609end
610