summaryrefslogtreecommitdiff
path: root/lib/rubygems/command.rb
blob: 860764e6d5415a71e17ab881d5c3f2e463678559 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#--
# 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'

module Gem

  # Base class for all Gem commands.  When creating a new gem command, define
  # #arguments, #defaults_str, #description and #usage (as appropriate).
  class Command

    include 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

    # 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

    # 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.
    def execute
      fail "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.
    def arguments
      ""
    end

    # Override to display the default values of the command
    # options. (similar to +arguments+, but displays the default
    # values).
    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.
    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.
    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

    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

    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", 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)
      text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
    end

    ##################################################################
    # 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

      def extra_args
        @extra_args ||= []
      end

      def 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 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

      # 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
    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 <COMMAND>           show help on COMMAND
                                         (e.g. 'gem help install')
        Further information:
          http://rubygems.rubyforge.org
    }.gsub(/^    /, "")

    # :startdoc:

  end # class

  # This is where Commands will be placed in the namespace
  module Commands; end

end