344 lines
7.9 KiB
Ruby
344 lines
7.9 KiB
Ruby
# = ArgVector
|
|
#
|
|
# == Synopsis
|
|
#
|
|
# ArgVector provides a very simple means of parsing
|
|
# command line arguments.
|
|
#
|
|
# Unlike other more complex libs this provides only
|
|
# the most basic and standard parsing functionality.
|
|
# In many cases that's all one really needs.
|
|
#
|
|
# Usage is straight foward. Simply instantiate the
|
|
# class and query it for the particular "views" of
|
|
# the command line you want.
|
|
#
|
|
# cargs = ArgVector.new("-a foo -b=2")
|
|
#
|
|
# cargs.parameters #=> [['foo'],{'a'=>true,'b'=>'2'}]
|
|
# cargs.flags #=> ['a']
|
|
# cargs.preoptions #=> {'a'=>true}
|
|
# cargs.preflags #=> ['a']
|
|
# cargs.subcommand #=> ['foo', [], {'b'=>'2'}]
|
|
#
|
|
# == Authors
|
|
#
|
|
# * Thomas Sawyer
|
|
#
|
|
# == Copying
|
|
#
|
|
# Copyright (c) 2006 Thomas Sawyer
|
|
#
|
|
# Ruby License
|
|
#
|
|
# This module is free software. You may use, modify, and/or
|
|
# redistribute this software under the same terms as Ruby.
|
|
#
|
|
# This program is distributed in the hope that it will be
|
|
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
# PURPOSE.
|
|
|
|
require 'shellwords'
|
|
|
|
# = Argvector
|
|
#
|
|
# Argvector provides a very simple means of parsing
|
|
# command line arguments.
|
|
#
|
|
# Unlike other more complex libs this provides only
|
|
# the most basic and standard parsing functionality.
|
|
# In many cases that's all one really needs.
|
|
#
|
|
# Usage is straight foward. Simply instantiate the
|
|
# class and query it for the particular "views" of
|
|
# the command line you want.
|
|
#
|
|
# cargs = Argvector.new("-a foo -b=2")
|
|
#
|
|
# cargs.parameters #=> [['foo'],{'a'=>true,'b'=>'2'}]
|
|
# cargs.flags #=> ['a']
|
|
# cargs.preoptions #=> {'a'=>true}
|
|
# cargs.preflags #=> ['a']
|
|
# cargs.subcommand #=> ['foo', [], {'b'=>'2'}]
|
|
#
|
|
class Argvector
|
|
|
|
def self.parameters(*args)
|
|
new.parameters(*args)
|
|
end
|
|
|
|
attr :line
|
|
attr :argv
|
|
attr :arity
|
|
##attr :opts
|
|
|
|
##alias :flags :options
|
|
|
|
# Takes the command line string (or array) and options.
|
|
# Options have flags and end with a hash of option arity.
|
|
#
|
|
def initialize(line=nil, arity=nil)
|
|
@line, @argv = parse_line(line)
|
|
@arity = parse_arity(arity||{})
|
|
parse
|
|
end
|
|
|
|
public
|
|
|
|
# Returns operand array.
|
|
def operands
|
|
@operands
|
|
end
|
|
|
|
# deprecate alias ?
|
|
alias arguments operands
|
|
|
|
# Returns options hash.
|
|
def options
|
|
@options
|
|
end
|
|
|
|
# Returns [operands, options], which is good for plugging
|
|
# directly into a method.
|
|
def parameters
|
|
return @operands, @options
|
|
end
|
|
|
|
# Return flags, which are true options.
|
|
def flags
|
|
f = []
|
|
@options.each do |k, v|
|
|
if TrueClass===v or FalseClass===v # not that it's ever false
|
|
f << k
|
|
end
|
|
end
|
|
return f
|
|
end
|
|
|
|
# Assumes the first operand is a "subcommand" and
|
|
# returns it and the argments following it as
|
|
# parameters.
|
|
def subcommand_with_parameters
|
|
opts, args = *parse_preoptions(argv)
|
|
cmd = args.shift
|
|
subargs = self.class.new(args, @arity)
|
|
return [cmd, *subargs.parameters]
|
|
end
|
|
|
|
alias subcommand subcommand_with_parameters
|
|
|
|
#
|
|
def subcommand_with_preoptions
|
|
pre, args = *parse_preoptions(argv)
|
|
cmd = args.shift
|
|
subargs = self.class.new(args, @arity)
|
|
args, opts = *subargs.parameters
|
|
return [cmd, args, pre.merge(opts)]
|
|
end
|
|
|
|
# Assumes the first operand is a "subcommand" and
|
|
# returns it and the argments following it as
|
|
# another Arguments object.
|
|
#
|
|
# TODO: This probably should be called 'subcommand'.
|
|
#
|
|
def subcommand_with_arguments
|
|
opts, args = *parse_preoptions(argv)
|
|
cmd = args.shift
|
|
subargs = self.class.new(args, @arity)
|
|
return cmd, subargs
|
|
end
|
|
|
|
# Returns a hash of options that occur before
|
|
# the first operand. This works well with
|
|
# subcommand to get the main command's options.
|
|
#
|
|
# line = "--trace stamp --file VERSION"
|
|
# cargs = Argvector.new(line)
|
|
# opts = cargs.preoptions
|
|
# opts #=> {"trace"=>true}
|
|
#
|
|
def preoptions
|
|
preopts, remainder = *parse_preoptions(argv)
|
|
return preopts
|
|
end
|
|
|
|
# Same as +flags+ but only returns flags in the
|
|
# preoptions.
|
|
def preflags
|
|
preopts, remainder = *parse_preoptions(argv)
|
|
f = []
|
|
preopts.each do |k, v|
|
|
if TrueClass===v or FalseClass===v # not that it's ever false
|
|
f << k
|
|
end
|
|
end
|
|
return f
|
|
end
|
|
|
|
# Like parameters but without allowing for duplicate options.
|
|
def parameters_without_duplicates
|
|
opts = {}
|
|
@options.each do |k,v|
|
|
if Array===v
|
|
opts[k] = v[0]
|
|
else
|
|
opts[k] = v
|
|
end
|
|
end
|
|
return @operands, opts
|
|
end
|
|
|
|
#private
|
|
|
|
# Basic parser partitions the command line into options and
|
|
# operands. Options are converted to a hash and the two
|
|
# parts are returned.
|
|
#
|
|
# line = "--trace stamp --file=VERSION"
|
|
# argv = Argvector.new(line)
|
|
#
|
|
# args, keys = *argv.parse
|
|
#
|
|
# args #=> ["stamp"]
|
|
# keys #=> {"trace"=>true, "file"=>"VERSION"}
|
|
#
|
|
def parse
|
|
args = assoc_options(argv) #, *opts_arity)
|
|
|
|
opts, opds = args.partition{ |a| Array === a }
|
|
|
|
@operands = opds
|
|
@options = format_options(opts)
|
|
|
|
return @operands, @options
|
|
end
|
|
|
|
# First pass parser to split the command line into an
|
|
# array using Shellwords, if not already so divided.
|
|
def parse_line(line=nil)
|
|
if line
|
|
case line
|
|
when String
|
|
argv = Shellwords.shellwords(line)
|
|
else
|
|
argv = line.to_ary.dup
|
|
line = argv.join(' ')
|
|
end
|
|
else
|
|
argv = ARGV.dup
|
|
line = argv.join(' ')
|
|
end
|
|
return line, argv
|
|
end
|
|
|
|
## Ensure opts are a uniform.
|
|
##
|
|
##def clean_opts( opts )
|
|
## opts2 = opts.collect{ |o| o.to_sym }
|
|
## opts2 = opts2 & [:simple, :repeat] # valid options only
|
|
## return opts2
|
|
##end
|
|
|
|
# Ensure arity is uniform.
|
|
def parse_arity(arity)
|
|
arity2 = {}
|
|
arity.each{ |k,v| arity2[k.to_s] = v.to_i }
|
|
return arity2
|
|
end
|
|
|
|
# Parse preoptions. A "preoption" is one that
|
|
# occurs before the first operans (if any).
|
|
def parse_preoptions(args)
|
|
##args = args.dup
|
|
args = multi_flag(args) #unless opts.include?(:simple)
|
|
|
|
flags = []
|
|
while args.first =~ /^-/
|
|
key = args.shift
|
|
key.sub!(/^-{1,2}/,'')
|
|
if key.index('=')
|
|
key, val = key.split('=')
|
|
elsif a = arity[key]
|
|
val = args.slice!(0,a)
|
|
val = val.first if a == 1
|
|
else
|
|
val = true
|
|
end
|
|
flags << [key, val]
|
|
end
|
|
|
|
flags = format_options(flags)
|
|
|
|
return flags, args
|
|
end
|
|
|
|
# Parse flags takes the command line and transforms it such that
|
|
# flags (eg. -x and --x) are elemental associative arrays.
|
|
#
|
|
# line = "--foo hello --try=this"
|
|
# argv = Argvector.new(line)
|
|
#
|
|
# args = line.split(/\s/)
|
|
# argv.assoc_options(args) #=> [ ["foo",true], "hello", ["try","this"] ]
|
|
#
|
|
def assoc_options(args)
|
|
##args = args.dup
|
|
args = multi_flag(args) #unless opts.include?(:simple)
|
|
i = 0
|
|
while i < args.size
|
|
arg = args[i]
|
|
case arg
|
|
when /^-/
|
|
arg = arg.sub(/^-{1,2}/,'')
|
|
if arg.index('=')
|
|
key, val = arg.split('=')
|
|
args[i] = [key, val||true]
|
|
elsif arity.key?(arg)
|
|
cnt = arity[arg]
|
|
key = arg
|
|
val = args[i+1,cnt]
|
|
args[i,cnt+1] = [[key, *val]]
|
|
i += (cnt - 1)
|
|
else
|
|
key = arg
|
|
args[i] = [key,true]
|
|
end
|
|
end
|
|
i += 1
|
|
end
|
|
return args
|
|
end
|
|
|
|
# Split single letter option groupings into separate options.
|
|
# ie. -xyz => -x -y -z
|
|
def multi_flag(args=nil)
|
|
args ||= argv
|
|
args.collect { |arg|
|
|
if md = /^-(\w{2,})/.match( arg )
|
|
md[1].split(//).collect { |c| "-#{c}" }
|
|
else
|
|
arg.dup
|
|
end
|
|
}.flatten
|
|
end
|
|
|
|
# Format flag options. This converts the associative array of
|
|
# options/flags into a hash. Repeat options will be placed in arrays.
|
|
def format_options(assoc_options)
|
|
opts = {}
|
|
assoc_options.each do |k,v|
|
|
if opts.key?(k)
|
|
opts[k] = [opts[k]].flatten << v
|
|
else
|
|
opts[k] = v
|
|
end
|
|
end
|
|
return opts
|
|
end
|
|
|
|
end
|
|
|
|
ArgVector = Argvector
|