1 # A simple, independent option parser originally from the rs project.
4 # Create an instance. If a Hash of options is
5 # given, it is passed to #configure.
6 def initialize(message = '', &block)
7 @allowed, @header, @optdesc = {}, message, ''
9 block.call(self) if block
12 # Adds an option to the parser. The option must be defined as
13 # a string of the format "-s --long Description". The first
14 # element is a dash followed by a character (the "short option"),
15 # the second is two dashes followed by two or more characters
16 # (the "long option"). The rest of the string becomes the third
17 # element which is used as the description of the option's
18 # purpose. The elements are separated by any number of whitespace
21 # By default an option takes no arguments but a second argument
22 # may be given to designate a different argument count. The
23 # two last versions are greedy and will consume all arguments
24 # until the next option switch or the end of input.
27 # :one exactly one argument
30 def option(definition, args = :none)
31 result = definition.scan /-(\w)\s+--(\w\S+?)\s+?(\S.*)/
32 raise ArgumentError, "Option format is '-s --long Description here'" if result.empty?
34 short, long, desc = result.first
36 # Store the data along with a cross-reference
37 @allowed[short] = {:desc => desc, :args => args, :other => long}
38 @allowed[long] = {:desc => desc, :args => args, :other => short}
40 # Add to usage now to maintain order
43 when :any then '[ARG, ...]'
44 when :many then 'ARG1, ARG2[, ...]'
49 @optdesc << " Options:\n\n" if @optdesc.empty?
50 @optdesc << " #{"-#{short} --#{long} #{argdesc}".ljust(30)} #{desc}\n"
53 # Optional error handling block
58 # Text to show above options in usage message
63 # Generate a usage message
70 # The accepted forms for options are:
72 # Short opts: -h, -ht, -h ARG, -ht ARG (same as -h -t ARG).
73 # Long opts: --help, --help ARG, (No joining.)
75 # The returned Hash is indexed by the names of the found
76 # options. These indices point to an Array of arguments
77 # to that option if any, or just true if not. The Hash
78 # also contains a special index, :args, containing all
79 # of the non-option arguments.
81 # Upon encountering an error, the parser will raise an
82 # ArgumentError with an explanation. This behaviour may
83 # be overridden by supplying a block through #on_error.
84 # The block will be given the Options instance and the
87 expecting = nil # An option may be expecting an argument
88 @opts = {} # Options parsed
89 @nonopts = [] # Non-option arguments
91 arguments = arguments.split unless arguments.kind_of?(Array)
92 raise ArgumentError, "Empty input" if arguments.empty?
94 arguments.each do |opt|
96 if opt =~ /(-{1,2})(\S+)/
97 # No more arguments for the previous option
100 # Single args may need to be split --note: only the last one can take an arg!
101 opts = if $1.length == 1
107 # Parse individual flags if any
109 data = @allowed.fetch(o) {|x| raise ArgumentError.new("Invalid option #{x}")}
111 # Option takes arguments?
112 if [:one, :many].include? data[:args]
113 # Prepare for incoming data
114 @opts[o] ||= []; expecting = [o, data[:args]]
116 elsif data[:args] == :any
118 @opts[o] = true; expecting = [o, :many]
122 end # if [:yes, :many]
125 # Nonoption arguments
127 # Previous option accepts arguments
129 @opts[expecting.first] = [] if @opts[expecting.first] == true
131 # Store the argument with the option
132 @opts[expecting.first] << opt
134 # No more arguments unless so specified
135 expecting = nil unless expecting.last == :many
137 # Freestanding argument
142 end # arguments...each
144 # Sanity checks and crossrefs
145 @opts.keys.each do |key|
148 @opts[@allowed[key][:other]] = o
150 case @allowed[key][:args]
152 if o.nil? or o == true or o.size != 1
153 raise ArgumentError, "#{key} must have one argument"
156 if o.nil? or o == true or o.size < 2
157 raise ArgumentError, "Too few arguments for #{key}"
162 @opts[:args] = @nonopts
165 rescue ArgumentError => ex
166 raise ex unless @error_block
167 @error_block.call self, ex