[getopt-sig] my current argument parser
Russ Cox
rsc@plan9.bell-labs.com
Wed, 13 Feb 2002 16:32:23 -0500
[The docstring at the beginning explains all. Comments welcome.]
from __future__ import generators
import sys, copy
"""Simple command-line argument iterator.
A typical example looks like:
arg = ArgParser('usage: example [-d] [-r root] database\n')
for o in arg:
if o=='-d':
debug=1
elif o=='-r':
root = arg.nextarg()
else:
arg.error('unknown option '+o)
if len(arg.argv) != 1:
arg.error()
This illustrates all the important points:
* arg is a generator that returns the next argument.
>>>The behavior of the generator depends on the behavior
of the body of your for loop.<<< This is why you don't have
to tell the ArgParser about your options a priori. If an option
has an argument, you call arg.nextarg() to fetch it. (If you
want an option to have two arguments, call arg.nextarg twice.)
* After the loop has finished, arg.argv contains the
remaining command-line arguments.
* Calling arg.error prints the optional passed string,
then prints the usage message, then exits.
A few other points:
* Consistent with typical Unix conventions, option parsing ends
after seeing '--', before seeing '-', or before any command-line argument
not beginning with a - (that wasn't gobbled as an option argument).
Assuming that -c does not take an argument but -f does, the following
set of command lines and parsings illustrates the rules:
foo -c -- -a
opts: -c
args: -a
foo -c - -a
opts: -c
args: - -a
foo -c bar baz -a
opts: -c
args: bar baz -a
foo -cfbar baz -a
opts: -c -f
(-f took bar)
args: baz -a
foo -cf bar baz -a
opts: -c -f
(-f took bar)
args: baz -a
foo -fc bar baz -a
opts: -f
(-f took c)
args: bar baz -a
* Single character short options begin with - and take arguments from the
rest of the current argument or from the next argument. For example,
foo -cbar
foo -c bar
are equivalent. There is no support for optional arguments, since that
introduces command-line parsing ambiguities.
* Long options begin with -- and take arguments from an optional
``=ARG'' suffix. For example,
foo --long=bar
is the only way to specify an argument to the --long option.
If the handler for '--long' does not fetch the argument with arg.nextarg,
the parser will call arg.error automatically. There is support for optional
arguments to long options, since that does not introduce any ambiguities.
To fetch an optional argument call arg.nextarg(allownone=1). If there
is no argument, None will be returned.
"""
class ArgError(Exception):
def __init__(self, msg=None):
if msg:
self.msg = msg
class ArgParser:
def __init__(self, usage, argv=sys.argv):
self.argv0 = argv[0]
self.argv = argv[1:]
self.usage = usage
self.waitingarg = ''
def __iter__(self):
# this assumes the "
while self.argv:
if self.argv[0]=='-' or self.argv[0][0]!='-':
break
a = self.argv.pop(0)
if a=='--':
break
if a[0:2]=='--':
i = a.find('=')
if i==-1:
self.waitingarg=None
self.option = a
yield self.option
self.option = None
else:
self.waitingarg = a[i+1:]
self.option = a[0:i]
yield self.option
if self.waitingarg: # wasn't fetched using optarg
self.error(self.option+' does not take an argument')
self.option = None
continue
self.waitingarg = a[1:]
while self.waitingarg:
a = self.waitingarg[0:1]
self.waitingarg = self.waitingarg[1:]
self.option = '-'+a
yield self.option
self.option = None
def nextarg(self, allownone=0):
if self.waitingarg==None:
if allownone:
return None
self.error(self.option+' requires an argument')
elif self.waitingarg:
ret = self.waitingarg
self.waitingarg=''
else:
try:
ret = self.argv.pop(0)
except IndexError:
self.error(self.option+' requires an argument')
return ret
def error(self, msg=None):
if msg:
sys.stderr.write('argument error: '+msg+'\n')
sys.stderr.write(self.usage)
sys.stderr.flush()
sys.exit(1)