core/options.rb
module Rubinius
# A simple command line option parser.
class Options
# A single command line option.
class Option
attr_reader :short
attr_reader :long
attr_reader :arg
attr_reader :description
attr_reader :block
def initialize(short, long, arg, description, block)
@short = short
@long = long
@arg = arg
@description = description
@block = block
end
private :initialize
def arg?
@arg != nil
end
def optional?
@arg[0] == ?[ and @arg[-1] == ?]
end
def match?(opt)
opt == @short or opt == @long
end
end
# Raised if incorrect or incomplete formats are passed to #on.
class OptionError < Exception; end
# Raised if an unrecognized option is encountered.
class ParseError < Exception; end
attr_accessor :config
attr_accessor :banner
attr_accessor :width
attr_accessor :options
def initialize(banner="", width=30, config=nil)
@parse = true
@banner = banner
@config = config
@width = width
@options = []
@doc = []
@extra = []
@align = true
@on_extra = lambda { |x|
raise ParseError, "Unrecognized option: #{x}" if x[0] == ?-
@extra << x
}
yield self if block_given?
end
private :initialize
# Documentation for options is left aligned. For example,
#
# -a ARG Some description
# --big Another bit of info
# -c, --class Yet more info
#
# See +option_align+.
def left_align
@align = nil
end
# Documentation for options is aligned by option type. For example,
#
# -a ARG Some description
# --big Another bit of info
# -c, --class Yet more info
#
# See +left_align+
def option_align
@align = true
end
# Registers an option. Acceptable formats for arguments are:
#
# on "-a", "description"
# on "-a", "--abdc", "description"
# on "-a", "ARG", "description"
# on "--abdc", "ARG", "description"
# on "-a", "--abdc", "ARG", "description"
# on "-a", "[ARG]", "description"
#
# The [ARG] form specifies an optional argument. The argument
# must be attached to the option. In the case of a short option,
# the form "-aARG" must be used. For a long option, the form
# '--opt=ARG' must be used.
#
# If an block is passed, it will be invoked when the option is
# matched. Not passing a block is permitted, but nonsensical.
def on(*args, &block)
raise OptionError, "option and description are required" if args.size < 2
description = args.pop
short, long, argument = nil
args.each do |arg|
if arg[0] == ?-
if arg[1] == ?-
long = arg
else
short = arg
end
else
argument = arg
end
end
add short, long, argument, description, block
end
# Adds documentation text for an option and adds an +Option+
# instance to the list of registered options.
def add(short, long, arg, description, block)
pad = @align ? " " : ""
s = short ? short.dup : pad
s << (short ? ", " : pad) if long
doc " #{s}#{long} #{arg}".ljust(@width-1) + " #{description}"
@options << Option.new(short, long, arg, description, block)
end
# Searches all registered options to find a match for +opt+. Returns
# +nil+ if no registered options match.
def match?(opt)
@options.find { |o| o.match? opt }
end
# Processes an option. Calles the #on_extra block (or default) for
# unrecognized options. For registered options, possibly fetches an
# argument and invokes the option's block if it is not nil.
def process(argv, entry, opt, arg)
unless option = match?(opt)
@on_extra[entry]
else
if option.arg?
if arg.nil?
unless option.optional?
arg = argv.shift
end
end
unless arg or option.optional?
raise ParseError, "No argument provided for #{opt}"
end
option.block[arg] if option.block
else
option.block[] if option.block
end
end
option
end
# Splits a string at +n+ characters into the +opt+ and the +rest+.
# The +arg+ is set to +nil+ if +rest+ is an empty string.
def split(str, n)
opt = str[0, n]
rest = str[n, str.size] || ""
arg = rest == "" ? nil : rest
return opt, arg, rest
end
# Parses an array of command line entries, calling blocks for
# registered options.
def parse(argv=ARGV)
argv = Array(argv)
while @parse and entry = argv.shift
# collect everything that is not an option
if entry[0] != ?-
@on_extra[entry]
next
end
# this is a long option
if entry[1] == ?-
opt, arg = entry.split "="
process argv, entry, opt, arg
next
end
# disambiguate short option group from short option with argument
opt, arg, rest = split entry, 2
# process first option
option = process argv, entry, opt, arg
next unless option and not option.arg?
# process the rest of the options
while rest.size > 0
opt, arg, rest = split rest, 1
opt = "-" + opt
option = process argv, opt, opt, arg
break if option.arg?
end
end
@extra
rescue ParseError => e
puts self
puts e
exit 1
end
def start_parsing
@parse = true
end
def stop_parsing
@parse = false
end
# Adds a string of documentation text inline in the text generated
# from the options. See #on and #add.
def doc(str)
@doc << str
end
# Convenience method for providing -v, --version options.
def version(version, &block)
show = block || lambda { puts "#{File.basename $0} #{version}"; exit }
on "-v", "--version", "Show version", &show
end
# Convenience method for providing -h, --help options.
def help(&block)
help = block || lambda { puts self; exit 1 }
on "-h", "--help", "Show this message", &help
end
# Stores a block that will be called with unrecognized options
def on_extra(&block)
@on_extra = block
end
# Returns a string representation of the options and doc strings.
def to_s
@banner + "\n\n" + @doc.join("\n") + "\n"
end
end
end