|
|
# = 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
|