class Cri::OptionParser

Cri::OptionParser is used for parsing command-line options.

Option definitions are hashes with the keys `:short`, `:long` and `:argument` (optionally `:description` but this is not used by the option parser, only by the help generator). `:short` is the short, one-character option, without the `-` prefix. `:long` is the long, multi-character option, without the `–` prefix. `:argument` can be :required (if an argument should be provided to the option), :optional (if an argument may be provided) or :forbidden (if an argument should not be provided).

A sample array of definition hashes could look like this:

[
  { :short => 'a', :long => 'all',  :argument => :forbidden, :multiple => true },
  { :short => 'p', :long => 'port', :argument => :required, :multiple => false },
]

For example, the following command-line options (which should not be passed as a string, but as an array of strings):

foo -xyz -a hiss -s -m please --level 50 --father=ani -n luke squeak

with the following option definitions:

[
  { :short => 'x', :long => 'xxx',    :argument => :forbidden },
  { :short => 'y', :long => 'yyy',    :argument => :forbidden },
  { :short => 'z', :long => 'zzz',    :argument => :forbidden },
  { :short => 'a', :long => 'all',    :argument => :forbidden },
  { :short => 's', :long => 'stuff',  :argument => :optional  },
  { :short => 'm', :long => 'more',   :argument => :optional  },
  { :short => 'l', :long => 'level',  :argument => :required  },
  { :short => 'f', :long => 'father', :argument => :required  },
  { :short => 'n', :long => 'name',   :argument => :required  }
]

will be translated into:

{
  :arguments => [ 'foo', 'hiss', 'squeak' ],
  :options => {
    :xxx    => true,
    :yyy    => true,
    :zzz    => true,
    :all    => true,
    :stuff  => true,
    :more   => 'please',
    :level  => '50',
    :father => 'ani',
    :name   => 'luke'
  }
}

Attributes

delegate[RW]

The delegate to which events will be sent. The following methods will be send to the delegate:

  • `option_added(key, value, cmd)`

  • `argument_added(argument, cmd)`

@return [#option_added, argument_added] The delegate

options[R]

The options that have already been parsed.

If the parser was stopped before it finished, this will not contain all options and `unprocessed_arguments_and_options` will contain what is left to be processed.

@return [Hash] The already parsed options.

raw_arguments[R]

@return [Array] The arguments that have already been parsed, including

the -- separator.
unprocessed_arguments_and_options[R]

The options and arguments that have not yet been processed. If the parser wasn’t stopped (using {#stop}), this list will be empty.

@return [Array] The not yet parsed options and arguments.

Public Class Methods

new(arguments_and_options, definitions) click to toggle source

Creates a new parser with the given options/arguments and definitions.

@param [Array<String>] arguments_and_options An array containing the

command-line arguments (will probably be `ARGS` for a root command)

@param [Array<Hash>] definitions An array of option definitions

# File lib/cri/option_parser.rb, line 112
def initialize(arguments_and_options, definitions)
  @unprocessed_arguments_and_options = arguments_and_options.dup
  @definitions = definitions

  @options       = {}
  @raw_arguments = []

  @running = false
  @no_more_options = false
end
parse(arguments_and_options, definitions) click to toggle source

Parses the command-line arguments. See the instance `parse` method for details.

@param [Array<String>] arguments_and_options An array containing the

command-line arguments (will probably be `ARGS` for a root command)

@param [Array<Hash>] definitions An array of option definitions

@return [Cri::OptionParser] The option parser self

# File lib/cri/option_parser.rb, line 102
def self.parse(arguments_and_options, definitions)
  new(arguments_and_options, definitions).run
end

Public Instance Methods

arguments() click to toggle source

Returns the arguments that have already been parsed.

If the parser was stopped before it finished, this will not contain all options and `unprocessed_arguments_and_options` will contain what is left to be processed.

@return [Array] The already parsed arguments.

# File lib/cri/option_parser.rb, line 130
def arguments
  ArgumentArray.new(@raw_arguments).freeze
end
run() click to toggle source

Parses the command-line arguments into options and arguments.

During parsing, two errors can be raised:

@raise IllegalOptionError if an unrecognised option was encountered,

i.e. an option that is not present in the list of option definitions

@raise OptionRequiresAnArgumentError if an option was found that did not

have a value, even though this value was required.

@return [Cri::OptionParser] The option parser self

# File lib/cri/option_parser.rb, line 158
def run
  @running = true

  while running?
    # Get next item
    e = @unprocessed_arguments_and_options.shift
    break if e.nil?

    if e == '--'
      handle_dashdash(e)
    elsif e =~ /^--./ && !@no_more_options
      handle_dashdash_option(e)
    elsif e =~ /^-./ && !@no_more_options
      handle_dash_option(e)
    else
      add_argument(e)
    end
  end

  add_defaults

  self
ensure
  @running = false
end
running?() click to toggle source

@return [Boolean] true if the parser is running, false otherwise.

# File lib/cri/option_parser.rb, line 135
def running?
  @running
end
stop() click to toggle source

Stops the parser. The parser will finish its current parse cycle but will not start parsing new options and/or arguments.

@return [void]

# File lib/cri/option_parser.rb, line 143
def stop
  @running = false
end

Private Instance Methods

add_argument(value) click to toggle source
# File lib/cri/option_parser.rb, line 293
def add_argument(value)
  @raw_arguments << value

  unless value == '--'
    delegate.argument_added(value, self) unless delegate.nil?
  end
end
add_default_option(definition) click to toggle source
# File lib/cri/option_parser.rb, line 274
def add_default_option(definition)
  key = key_for(definition)
  return if options.key?(key)

  value = definition[:default]
  return unless value

  if definition[:multiple]
    options[key] ||= []
    options[key] << value
  else
    options[key] = value
  end
end
add_defaults() click to toggle source
# File lib/cri/option_parser.rb, line 186
def add_defaults
  @definitions.each { |d| add_default_option(d) }
end
add_option(definition, value) click to toggle source
# File lib/cri/option_parser.rb, line 261
def add_option(definition, value)
  key = key_for(definition)

  if definition[:multiple]
    options[key] ||= []
    options[key] << value
  else
    options[key] = value
  end

  delegate.option_added(key, value, self) unless delegate.nil?
end
find_option_value(definition, option_key) click to toggle source
# File lib/cri/option_parser.rb, line 246
def find_option_value(definition, option_key)
  option_value = @unprocessed_arguments_and_options.shift
  if option_value.nil? || option_value =~ /^-/
    if definition[:argument] == :optional && definition[:default]
      option_value = definition[:default]
    elsif definition[:argument] == :required
      raise OptionRequiresAnArgumentError.new(option_key)
    else
      @unprocessed_arguments_and_options.unshift(option_value)
      option_value = true
    end
  end
  option_value
end
handle_dash_option(e) click to toggle source
# File lib/cri/option_parser.rb, line 223
def handle_dash_option(e)
  # Get option keys
  option_keys = e[1..-1].scan(/./)

  # For each key
  option_keys.each do |option_key|
    # Find definition
    definition = @definitions.find { |d| d[:short] == option_key }
    raise IllegalOptionError.new(option_key) if definition.nil?

    if %[required optional].include?(definition[:argument])
      # Get option value
      option_value = find_option_value(definition, option_key)

      # Store option
      add_option(definition, option_value)
    else
      # Store option
      add_option(definition, true)
    end
  end
end
handle_dashdash(e) click to toggle source
# File lib/cri/option_parser.rb, line 190
def handle_dashdash(e)
  add_argument(e)
  @no_more_options = true
end
handle_dashdash_option(e) click to toggle source
# File lib/cri/option_parser.rb, line 195
def handle_dashdash_option(e)
  # Get option key, and option value if included
  if e =~ /^--([^=]+)=(.+)$/
    option_key   = Regexp.last_match[1]
    option_value = Regexp.last_match[2]
  else
    option_key    = e[2..-1]
    option_value  = nil
  end

  # Find definition
  definition = @definitions.find { |d| d[:long] == option_key }
  raise IllegalOptionError.new(option_key) if definition.nil?

  if %[required optional].include?(definition[:argument])
    # Get option value if necessary
    if option_value.nil?
      option_value = find_option_value(definition, option_key)
    end

    # Store option
    add_option(definition, option_value)
  else
    # Store option
    add_option(definition, true)
  end
end
key_for(definition) click to toggle source
# File lib/cri/option_parser.rb, line 289
def key_for(definition)
  (definition[:long] || definition[:short]).to_sym
end