Fix up Rubinius specific library specs.
[rbx.git] / lib / options.rb
blobd90c4f998bf43ba227c4f4e595c2125606d5ede7
1 # A simple, independent option parser originally from the rs project.
2 class Options
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
10   end                                 # initialize
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 
19   # characters.
20   #
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.
25   #
26   #   :none   no arguments
27   #   :one    exactly one argument 
28   #   :many   one or more
29   #   :any    zero or more
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
41     argdesc   = case args
42                   when :one then 'ARG'
43                   when :any then '[ARG, ...]'
44                   when :many then 'ARG1, ARG2[, ...]'
45                   else         
46                     ''
47                 end
48                 
49     @optdesc << "        Options:\n\n" if @optdesc.empty?
50     @optdesc << "        #{"-#{short} --#{long} #{argdesc}".ljust(30)} #{desc}\n" 
51   end                                 # option
53   # Optional error handling block
54   def on_error(&block)
55     @error_block = block
56   end
58   # Text to show above options in usage message
59   def header(message)
60     @header = message
61   end                                 # header
63   # Generate a usage message
64   def usage()
65     @header + @optdesc
66   end                                 # usage
68   alias :help :usage
70   # The accepted forms for options are:
71   #
72   # Short opts: -h, -ht, -h ARG, -ht ARG (same as -h -t ARG).
73   # Long opts:  --help, --help ARG, (No joining.)
74   #
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.
80   #
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
85   # Exception object.
86   def parse(arguments)
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|
95       # Option type
96       if opt =~ /(-{1,2})(\S+)/
97         # No more arguments for the previous option
98         expecting = nil
100         # Single args may need to be split --note: only the last one can take an arg!
101         opts = if $1.length == 1
102                 $2.split(//)
103                else
104                 [$2]
105                end
107         # Parse individual flags if any
108         opts.each do |o|
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]]
115             
116           elsif data[:args] == :any
117             # Cheat
118             @opts[o] = true; expecting = [o, :many] 
120           else
121             @opts[o] = true
122           end                       # if [:yes, :many]
123         end                         # opts.each
125       # Nonoption arguments
126       else
127         # Previous option accepts arguments
128         if expecting
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
138         else
139           @nonopts << opt
140         end                           # if expecting
141       end                             # case opt
142     end                               # arguments...each
144     # Sanity checks and crossrefs
145     @opts.keys.each do |key| 
146       o = @opts[key]
148       @opts[@allowed[key][:other]] = o
150       case @allowed[key][:args]
151         when :one
152           if o.nil? or o == true or o.size != 1
153             raise ArgumentError, "#{key} must have one argument" 
154           end
155         when :many
156           if o.nil? or o == true or o.size < 2
157             raise ArgumentError, "Too few arguments for #{key}" 
158           end
159       end
160     end 
162     @opts[:args] = @nonopts
163     @opts
165   rescue ArgumentError => ex
166     raise ex unless @error_block
167     @error_block.call self, ex
168   end                                 # parse
169 end                                   # class Options