Module: Mixlib::CLI

Defined in:
lib/mixlib/cli.rb,
lib/mixlib/cli/version.rb,
lib/mixlib/cli/formatter.rb

Overview

Mixlib::CLI

Adds a DSL for defining command line options and methods for parsing those options to the including class.

Mixlib::CLI does some setup in #initialize, so the including class must call `super()` if it defines a custom initializer.

DSL

When included, Mixlib::CLI also extends the including class with its ClassMethods, which define the DSL. The primary methods of the DSL are ClassMethods#option, which defines a command line option; ClassMethods#banner, which defines the “usage” banner; and ClassMethods#deprecated_option, which defines a deprecated command-line option.

Parsing

Command line options are parsed by calling the instance method #parse_options. After calling this method, the attribute #config will contain a hash of `:option_name => value` pairs.

Defined Under Namespace

Modules: ClassMethods, InheritMethods Classes: Formatter

Constant Summary collapse

VERSION =
"2.1.8".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

Banner for the option parser. If the option parser is printed, e.g., by `puts opt_parser`, this string will be used as the first line.


251
252
253
# File 'lib/mixlib/cli.rb', line 251

def banner
  @banner
end

#cli_argumentsObject

Any arguments which were not parsed and placed in “config”–the leftovers.


247
248
249
# File 'lib/mixlib/cli.rb', line 247

def cli_arguments
  @cli_arguments
end

#configObject

A Hash containing the values supplied by command line options.

The behavior and contents of this Hash vary depending on whether ClassMethods#use_separate_default_options is enabled.

use_separate_default_options disabled

After initialization, config will contain any default values defined via the mixlib-config DSL. When #parse_options is called, user-supplied values (from ARGV) will be merged in.

use_separate_default_options enabled

After initialization, this will be an empty hash. When #parse_options is called, config is populated only with user-supplied values.


236
237
238
# File 'lib/mixlib/cli.rb', line 236

def config
  @config
end

#default_configObject

If ClassMethods#use_separate_default_options is enabled, this will be a Hash containing key value pairs of `:option_name => default_value` (populated during object initialization).

If use_separate_default_options is disabled, it will always be an empty hash.


244
245
246
# File 'lib/mixlib/cli.rb', line 244

def default_config
  @default_config
end

#optionsObject

Gives the command line options definition as configured in the DSL. These are used by #parse_options to generate the option parsing code. To get the values supplied by the user, see #config.


223
224
225
# File 'lib/mixlib/cli.rb', line 223

def options
  @options
end

Class Method Details

.included(receiver) ⇒ Object


448
449
450
451
# File 'lib/mixlib/cli.rb', line 448

def self.included(receiver)
  receiver.extend(Mixlib::CLI::ClassMethods)
  receiver.extend(Mixlib::CLI::InheritMethods)
end

Instance Method Details

#build_option_arguments(opt_setting) ⇒ Object


432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/mixlib/cli.rb', line 432

def build_option_arguments(opt_setting)
  arguments = []

  arguments << opt_setting[:short] if opt_setting[:short]
  arguments << opt_setting[:long] if opt_setting[:long]
  if opt_setting.key?(:description)
    description = opt_setting[:description].dup
    description << " (required)" if opt_setting[:required]
    description << " (valid options: #{Formatter.friendly_opt_list(opt_setting[:in])})" if opt_setting[:in]
    opt_setting[:description] = description
    arguments << description
  end

  arguments
end

#handle_deprecated_options(show_deprecations) ⇒ Object

Iterates through options declared as deprecated, maps values to their replacement options, and prints deprecation warnings.

Returns:

  • NilClass


394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/mixlib/cli.rb', line 394

def handle_deprecated_options(show_deprecations)
  merge_in_values = {}
  config.each_key do |opt_key|
    opt_cfg = options[opt_key]

    # Deprecated entries do not have defaults so no matter what
    # separate_default_options are set, if we see a 'config'
    # entry that contains a deprecated indicator, then the option was
    # explicitly provided by the caller.
    #
    # opt_cfg may not exist if an inheriting application
    # has directly inserted values info config.
    next unless opt_cfg && opt_cfg[:deprecated]

    replacement_key = opt_cfg[:replacement]
    if replacement_key
      # This is the value passed into the deprecated flag. We'll use
      # the declared value mapper (defaults to return the same value if caller hasn't
      # provided a mapper).
      deprecated_val = config[opt_key]

      # We can't modify 'config' since we're iterating it, apply updates
      # at the end.
      merge_in_values[replacement_key] = opt_cfg[:value_mapper].call(deprecated_val)
      config.delete(opt_key) unless opt_cfg[:keep]
    end

    # Warn about the deprecation.
    if show_deprecations
      # Description is also the deprecation message.
      display_name = CLI::Formatter.combined_option_display_name(opt_cfg[:short], opt_cfg[:long])
      puts "#{display_name}: #{opt_cfg[:description]}"
    end
  end
  config.merge!(merge_in_values)
  nil
end

#initialize(*args) ⇒ Object

Create a new Mixlib::CLI class. If you override this, make sure you call super!

Parameters

*args<Array>

The array of arguments passed to the initializer

Returns

object<Mixlib::Config>

Returns an instance of whatever you wanted :)


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
# File 'lib/mixlib/cli.rb', line 260

def initialize(*args)
  @options = {}
  @config  = {}
  @default_config = {}
  @opt_parser = nil

  # Set the banner
  @banner = self.class.banner

  # Dupe the class options for this instance
  klass_options = self.class.options
  klass_options.keys.inject(@options) { |memo, key| memo[key] = klass_options[key].dup; memo }

  # If use_separate_defaults? is on, default values go in @default_config
  defaults_container = if self.class.use_separate_defaults?
                         @default_config
                       else
                         @config
                       end

  # Set the default configuration values for this instance
  @options.each do |config_key, config_opts|
    config_opts[:on] ||= :on
    config_opts[:boolean] ||= false
    config_opts[:required] ||= false
    config_opts[:proc] ||= nil
    config_opts[:show_options] ||= false
    config_opts[:exit] ||= nil
    config_opts[:in] ||= nil
    if config_opts.key?(:default)
      defaults_container[config_key] = config_opts[:default]
    end
  end

  super(*args)
end

#opt_parserObject

The option parser generated from the mixlib-cli DSL. opt_parser can be used to print a help message including the banner and any CLI options via `puts opt_parser`.

Returns

opt_parser<OptionParser>

The option parser object.


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
# File 'lib/mixlib/cli.rb', line 344

def opt_parser
  @opt_parser ||= OptionParser.new do |opts|
    # Set the banner
    opts.banner = banner

    # Create new options
    options.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |opt_key, opt_val|
      opt_args = build_option_arguments(opt_val)
      opt_method = case opt_val[:on]
                   when :on
                     :on
                   when :tail
                     :on_tail
                   when :head
                     :on_head
                   else
                     raise ArgumentError, "You must pass :on, :tail, or :head to :on"
                   end

      parse_block =
        Proc.new do |c|
          config[opt_key] = if opt_val[:proc]
                              if opt_val[:proc].arity == 2
                                # New hotness to allow for reducer-style procs.
                                opt_val[:proc].call(c, config[opt_key])
                              else
                                # Older single-argument proc.
                                opt_val[:proc].call(c)
                              end
                            else
                              # No proc.
                              c
                            end
          puts opts if opt_val[:show_options]
          exit opt_val[:exit] if opt_val[:exit]
        end

      full_opt = [ opt_method ]
      opt_args.inject(full_opt) { |memo, arg| memo << arg; memo }
      full_opt << parse_block
      opts.send(*full_opt)
    end
  end
end

#parse_options(argv = ARGV, show_deprecations: true) ⇒ Object

Parses an array, by default ARGV, for command line options (as configured at the class level).

Parameters

argv<Array>

The array of arguments to parse; defaults to ARGV

Returns

argv<Array>

Returns any un-parsed elements.


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
# File 'lib/mixlib/cli.rb', line 304

def parse_options(argv = ARGV, show_deprecations: true)
  argv = argv.dup
  opt_parser.parse!(argv)
  # Do this before our custom validations, so that custom
  # validations apply to any converted deprecation values;
  # but after parse! so that everything is populated.
  handle_deprecated_options(show_deprecations)

  # Deal with any required values
  options.each do |opt_key, opt_config|
    if opt_config[:required] && !config.key?(opt_key)
      reqarg = opt_config[:short] || opt_config[:long]
      puts "You must supply #{reqarg}!"
      puts @opt_parser
      exit 2
    end
    if opt_config[:in]
      unless opt_config[:in].is_a?(Array)
        raise(ArgumentError, "Options config key :in must receive an Array")
      end

      if config[opt_key] && !opt_config[:in].include?(config[opt_key])
        reqarg = Formatter.combined_option_display_name(opt_config[:short], opt_config[:long])
        puts "#{reqarg}: #{config[opt_key]} is not one of the allowed values: #{Formatter.friendly_opt_list(opt_config[:in])}"
        # TODO - get rid of this. nobody wants to be spammed with a  ton of information, particularly since we just told them the exact problem and how to fix it.
        puts @opt_parser
        exit 2
      end
    end
  end

  @cli_arguments = argv
  argv
end