Class: Cri::OptionParser

Inherits:
Object
  • Object
show all
Defined in:
lib/SANStore/cri/option_parser.rb

Overview

Cri::OptionParser is used for parsing commandline options.

Defined Under Namespace

Classes: IllegalOptionError, OptionRequiresAnArgumentError

Class Method Summary collapse

Class Method Details

.parse(arguments_and_options, definitions) ⇒ Object

Parses the commandline arguments in arguments_and_options, using the commandline option definitions in definitions.

arguments_and_options is an array of commandline arguments and options. This will usually be ARGV.

definitions contains a list of hashes defining which options are allowed and how they will be handled. Such a hash has three keys:

:short

The short name of the option, e.g. a. Do not include the ‘-’ prefix.

:long

The long name of the option, e.g. all. Do not include the ‘–’ prefix.

:argument

Whether this option’s argument is required (:required), optional (:optional) or forbidden (:forbidden).

A sample array of definition hashes could look like this:

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

During parsing, two errors can be raised:

IllegalOptionError

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

OptionRequiresAnArgumentError

An option was found that did not have a value, even though this value was required.

What will be returned, is a hash with two keys, :arguments and :options. The :arguments value contains a list of arguments, and the :options value contains a hash with key-value pairs for each option. Options without values will have a nil value instead.

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'
  }
}


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
# File 'lib/SANStore/cri/option_parser.rb', line 88

def self.parse(arguments_and_options, definitions)
  # Don't touch original argument
  unprocessed_arguments_and_options = arguments_and_options.dup

  # Initialize
  arguments = []
  options   = {}

  # Determines whether we've passed the '--' marker or not
  no_more_options = false

  loop do
    # 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
        options[definition[:long].to_sym] = option_value
      else
        # Store option
        options[definition[:long].to_sym] = 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
          options[definition[:long].to_sym] = option_value
        else
          # Store option
          options[definition[:long].to_sym] = true
        end
      end
    # Handle normal arguments
    else
      arguments << e
    end
  end

  { :options => options, :arguments => arguments }
end