# = LICENCE # # == Authors # See doc/AUTHORS. # # # == Copyright # Copyright (c) 2007, 2008 Eero Saynatkari, all rights reserved. # # # == Licence # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # - Redistributions of source code must retain the above copyright # notice, this list of conditions, the following disclaimer and # attribution to the original authors. # # - Redistributions in binary form must reproduce the above copyright # notice, this list of conditions, the following disclaimer and # attribution to the original authors in the documentation and/or # other materials provided with the distribution. # # - The names of the authors may not be used to endorse or promote # products derived from this software without specific prior # written permission. # # # == Disclaimer # This software is provided "as is" and without any express or # implied warranties, including, without limitation, the implied # warranties of merchantability and fitness for a particular purpose. # Authors are not responsible for any damages, direct or indirect. # A simple, independent option parser originally from the rs project. class Options # Create an instance. If a block is given, the new instance # is yielded. def initialize(header = '', &block) @allowed, @header, @optdesc = {}, header, '' block.call(self) if block end # initialize # Adds an option to the parser. The option must be defined as # a string of the format "-s --long Description". The first # element is a dash followed by a character (the "short option"), # the second is two dashes followed by two or more characters # (the "long option"). The rest of the string becomes the third # element which is used as the description of the option's # purpose. The elements are separated by any number of whitespace # characters. # # By default an option takes no arguments but a second argument # may be given to designate a different argument count. The # two last versions are greedy and will consume all arguments # until the next option switch or the end of input. # # :none no arguments # :one exactly one argument # :many one or more # :any zero or more def option(definition, args = :none) result = definition.scan /-(\w)\s+--(\w\S+?)\s+?(\S.*)/ raise ArgumentError, "Option format is '-s --long Description here'" if result.empty? short, long, desc = result.first # Store the data along with a cross-reference @allowed[short] = {:desc => desc, :args => args, :other => long} @allowed[long] = {:desc => desc, :args => args, :other => short} # Add to usage now to maintain order argdesc = case args when :one then 'ARG' when :any then '[ARG, ...]' when :many then 'ARG1, ARG2[, ...]' else '' end @optdesc << " Options:\n\n" if @optdesc.empty? @optdesc << " #{"-#{short} --#{long} #{argdesc}".ljust(30)} #{desc}\n" end # option # Optional error handling block def on_error(&block) @error_block = block end # Text to show above options in usage message def header(message) @header = message end # header # Generate a usage message def usage() @header + @optdesc end # usage alias :help :usage # The accepted forms for options are: # # Short opts: -h, -ht, -h ARG, -ht ARG (same as -h -t ARG). # Long opts: --help, --help ARG, (No joining.) # # The returned Hash is indexed by the names of the found # options. These indices point to an Array of arguments # to that option if any, or just true if not. The Hash # also contains a special index, :args, containing all # of the non-option arguments. # # Upon encountering an error, the parser will raise an # ArgumentError with an explanation. This behaviour may # be overridden by supplying a block through #on_error. # The block will be given the Options instance and the # Exception object. def parse(arguments = []) expecting = nil # An option may be expecting an argument @opts = {} # Options parsed @nonopts = [] # Non-option arguments arguments = arguments.split unless arguments.kind_of?(Array) arguments.each do |opt| # Option type if opt =~ /\A(-{1,2})(\S+)/ # No more arguments for the previous option expecting = nil # Single args may need to be split --note: only the last one can take an arg! opts = if $1.length == 1 $2.split(//) else [$2] end # Parse individual flags if any opts.each do |o| data = @allowed.fetch(o) {|x| raise ArgumentError.new("Invalid option #{x}")} # Option takes arguments? case data[:args] when :one, :many # Prepare for incoming data @opts[o] ||= []; expecting = [o, data[:args]] when :any # Cheat @opts[o] = true; expecting = [o, :many] else @opts[o] = true end end # opts.each # Nonoption arguments else # Previous option accepts arguments if expecting @opts[expecting.first] = [] if @opts[expecting.first] == true # Store the argument with the option @opts[expecting.first] << opt # No more arguments unless so specified expecting = nil unless expecting.last == :many # Freestanding argument else @nonopts << opt end # if expecting end # case opt end # arguments...each # Sanity checks and crossrefs @opts.keys.each do |key| o = @opts[key] @opts[@allowed[key][:other]] = o case @allowed[key][:args] when :one if o.nil? or o == true or o.size != 1 raise ArgumentError, "#{key} must have one argument" else @opts[key] = o.first @opts[@allowed[key][:other]] = o.first end when :many if o.nil? or o == true or o.size < 2 raise ArgumentError, "Too few arguments for #{key}" end end end @opts[:args] = @nonopts @opts rescue ArgumentError => ex raise ex unless @error_block @error_block.call self, ex end # parse end # class Options