Class List Method List File List

# Class: Cri::OptionParser

Inherits:
  Object
  - Object
  - Cri::OptionParser
  show all
Defined in:
  lib/cri/option_parser.rb

## Overview

Cri::OptionParser is used for parsing commandline 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 },
      { :short => 'p', :long => 'port', :argument => :required  },
    ]

For example, the following commandline 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'
      }
    }

## Defined Under Namespace

Classes: IllegalOptionError, OptionRequiresAnArgumentError

## Instance Attribute Summary (collapse)

- - (Array) arguments readonly

  The arguments that have already been parsed.

- - (#option_added, #argument_added) delegate

  The delegate to which events will be sent.

- - (Hash) options readonly

  The options that have already been parsed.

- - (Array) unprocessed_arguments_and_options readonly

  The options and arguments that have not yet been processed.

## Class Method Summary (collapse)

- + (Cri::OptionParser) parse(arguments_and_options, definitions)

  Parses the commandline arguments.

## Instance Method Summary (collapse)

- - (OptionParser) initialize(arguments_and_options, definitions) constructor

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

- - (Cri::OptionParser) run

  Parses the commandline arguments into options and arguments.

- - (Boolean) running?

  True if the parser is running, false otherwise.

- - (void) stop

  Stops the parser.

## Constructor Details

### - (`OptionParser`) initialize(arguments_and_options, definitions)

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

Parameters:

- arguments_and_options (`Array<String>`) —

  An array containing the commandline arguments (will probably be `ARGS` for a root command)

- definitions (`Array<Hash>`) —

  An array of option definitions

    121
    122
    123
    124
    125
    126
    127
    128
    129
    130

    # File 'lib/cri/option_parser.rb', line 121

    def initialize(arguments_and_options, definitions)
      @unprocessed_arguments_and_options = arguments_and_options.dup
      @definitions = definitions

      @options   = {}
      @arguments = []

      @running = false
      @no_more_options = false
    end

## Instance Attribute Details

### - (`Array`) arguments (readonly)

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.

Returns:

- (`Array`) —

  The already parsed arguments.

    94
    95
    96

    # File 'lib/cri/option_parser.rb', line 94

    def arguments
      @arguments
    end

### - (`#option_added`, `#argument_added`) delegate

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)`

Returns:

- (`#option_added`, `#argument_added`) —

  The delegate

    76
    77
    78

    # File 'lib/cri/option_parser.rb', line 76

    def delegate
      @delegate
    end

### - (`Hash`) options (readonly)

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.

Returns:

- (`Hash`) —

  The already parsed options.

    85
    86
    87

    # File 'lib/cri/option_parser.rb', line 85

    def options
      @options
    end

### - (`Array`) unprocessed_arguments_and_options (readonly)

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

Returns:

- (`Array`) —

  The not yet parsed options and arguments.

    100
    101
    102

    # File 'lib/cri/option_parser.rb', line 100

    def unprocessed_arguments_and_options
      @unprocessed_arguments_and_options
    end

## Class Method Details

### + (`Cri::OptionParser`) parse(arguments_and_options, definitions)

Parses the commandline arguments. See the instance `parse` method for details.

Parameters:

- arguments_and_options (`Array<String>`) —

  An array containing the commandline arguments (will probably be `ARGS` for a root command)

- definitions (`Array<Hash>`) —

  An array of option definitions

Returns:

- (`Cri::OptionParser`) —

  The option parser self

    111
    112
    113

    # File 'lib/cri/option_parser.rb', line 111

    def self.parse(arguments_and_options, definitions)
      self.new(arguments_and_options, definitions).run
    end

## Instance Method Details

### - (`Cri::OptionParser`) run

Parses the commandline arguments into options and arguments.

During parsing, two errors can be raised:

Returns:

- (`Cri::OptionParser`) —

  The option parser self

Raises:

- IllegalOptionError if an unrecognised option was encountered, i.e. an option that is not present in the list of option definitions

- OptionRequiresAnArgumentError if an option was found that did not have a value, even though this value was required.

    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

    # File 'lib/cri/option_parser.rb', line 156

    def run
      @running = true

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

        # Handle end-of-options marker
        if e == '--'
          @no_more_options = true
        # Handle incomplete options
        elsif e =~ /^--./ and !@no_more_options
          # Get option key, and option value if included
          if e =~ /^--([^=]+)=(.+)$/
            option_key   = $1
            option_value = $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 = @unprocessed_arguments_and_options.shift
              if option_value.nil? || option_value =~ /^-/
                if definition[:argument] == :required
                  raise OptionRequiresAnArgumentError.new(option_key)
                else
                  @unprocessed_arguments_and_options.unshift(option_value)
                  option_value = true
                end
              end
            end

            # Store option
            add_option(definition, option_value)
          else
            # Store option
            add_option(definition, true)
          end
        # Handle -xyz options
        elsif e =~ /^-./ and !@no_more_options
          # 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 option_keys.length > 1 and definition[:argument] == :required
              # This is a combined option and it requires an argument, so complain
              raise OptionRequiresAnArgumentError.new(option_key)
            elsif [ :required, :optional ].include?(definition[:argument])
              # Get option value
              option_value = @unprocessed_arguments_and_options.shift
              if option_value.nil? || option_value =~ /^-/
                if definition[:argument] == :required
                  raise OptionRequiresAnArgumentError.new(option_key)
                else
                  @unprocessed_arguments_and_options.unshift(option_value)
                  option_value = true
                end
              end

              # Store option
              add_option(definition, option_value)
            else
              # Store option
              add_option(definition, true)
            end
          end
        # Handle normal arguments
        else
          add_argument(e)
        end
      end
      self
    ensure
      @running = false
    end

### - (`Boolean`) running?

Returns true if the parser is running, false otherwise.

Returns:

- (`Boolean`) —

  true if the parser is running, false otherwise.

    133
    134
    135

    # File 'lib/cri/option_parser.rb', line 133

    def running?
      @running
    end

### - (`void`) stop

This method returns an undefined value.

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

    141
    142
    143

    # File 'lib/cri/option_parser.rb', line 141

    def stop
      @running = false
    end