a better getopt than getopt.py? [long]

Roberto@lupex.net Roberto
Thu Feb 22 09:55:35 CET 2001


Sean 'Shaleh' Perry <shaleh at valinux.com> wrote:
>this means I have my options defined twice and then once again in the USAGE
>output.  Is there not a better way to handle arguments?

I have written the attached module as this issue bothers me too.
The code is not well tested (its very recent).

Here's a commented example from a program that uses it:

# -------------------- Assign default for command line args
#                      you can put them in any python dictionary
#                      ( globals() is the dictionary in this case )

low_uids_level = 0
root_check = 1
dup_UIDs = 1
dup_names = 1
verbose = 0
ignore_list = []
passwd_file = '/etc/passwd'

# -------------------- Create a command line parser

cmd = CmdLine2.CmdLine('Passwd file checks', # program title
		usage_msg='[options]', # usage message 
		                       # (default: "[options] args")
		context = globals()    # where to store option values
		                       # should be a dict-like object
				       # (default: an empty dict)
				       # accessible thru cmd.context
		)
cmd.add_help_option() # add a -h --help option
                      # to print the list of available options

cmd.add_set_option(                    # an option to set
  ('low_uids_level',string.atoi),      # context['low_uids_level']
  ['l:','low-uid='],                   # to the integer value
  "Check for UIDs < [argument]")       # of its argument
  
  # if the first argument of cmd_add_XXX_option() is a tuple,
  # the first element is the variable name/item key,
  # the second elemnt is a conversion function that is applied to the arg
  
cmd.add_toggle_option(                 # a boolean flag option, as in:
  'root_check',                        #   root_check = not root_check
  ['r','root'],
  "Enable/disable report of superusers")
  
cmd.add_check_option(                  # a boolean set option:
  'verbose',                           #   verbose = 1
  ['v','verbose'],
  "Print configution before processing")
  
cmd.add_append_option(                 # append arguments to ignore_list
  'ignore_list',
  ['i:','ignore='],
  "Add the specified user to the ignore list")

def option_handler(opt, arg):
    # do something sensible with opt and arg
    pass

cmd.add_action_option(                # custom option that
    option_handler,                   # execute option_handler(opt,arg)
    ['bingo'],
    "Prints lucky numbers for bingo")

cmd.getopt()                           # parse the command line

# the rest of the program

if verbose:
    print "I'll check", passwd_file,":"
    if low_uids_level > 0:
	print "  - for UIDs <", low_uid_level
    if root_check:
	print "  - for root users"
    if dup_UIDs:
	print "  - for duplicated UIDs"
    if dup_names:
	print "  - for duplicated usernames"
    print "Ignore list:", list2str(ignore_list)
    print

...

Here's the help message of the program (generated by add_help_option):

Passwd file checks

chk-passwd [options]

  -h --help             Print this help message
  -l: --low-uid=        Check for UIDs < [argument]
  -r --root             Enable/disable report of superusers
  -u --uids             En/dis report of duplicated UIDs
  -n --names            En/dis report of duplicated usernames
  -v --verbose          Print configution before processing
  -i: --ignore=         Add the specified user to the ignore list
  -p: --passwd=         Process the specified file (default: /etc/passwd)

(the complete script has a few more options than the example code above)

And here's the CmdLine2.py module:

# CmdLine2 - Command Line Processing v2.0
#
#
# 22-feb-2001 - Released as public domain code
#               by Roberto Lupi <Roberto at lupi.ws>

import sys
import getopt
import re
import os

debug = 0

short_option_re = re.compile('.:?$')

def RemoveLast(st, postfix):
    if st[-len(postfix):] == postfix:
        return st[:-len(postfix)]
    else:
        return st

def ListToStr(lst, sep=' '):
    return reduce( lambda a,b,sep=sep: str(a)+sep+str(b), lst)

######################################################################
## 
## Class Option
## 
######################################################################

class Option:
    def __init__(self, opts, help_msg):
        short_op = []
        long_op = []
        for opt in opts:
            if short_option_re.match(opt):
                short_op.append(opt)
            else:
                long_op.append(opt)
        self.short_op = short_op
        self.long_op = long_op
        self.help_msg = help_msg

    def fire(self, opt, arg):
        pass

    def short_options(self, arg_spec=None,op_prefix='-'):
        if self.short_op:
            if arg_spec:
                return map( lambda x,op_prefix=op_prefix: op_prefix + \
		  x, self.short_op )
            else:
                return map( lambda x,op_prefix=op_prefix: op_prefix + \
		  RemoveLast(x,':'), self.short_op )
        else:
            return []

    def long_options(self, arg_spec=None,op_prefix='--'):
        if self.long_op:
            if arg_spec:
                return map( lambda x,op_prefix=op_prefix: op_prefix + \
		  x, self.long_op )
            else:
                return map( lambda x,op_prefix=op_prefix: op_prefix + \
		  RemoveLast(x,'='), self.long_op )
        else:
            return []

    def all_options(self, arg_spec=None):
        return self.short_options(arg_spec) + self.long_options(arg_spec)

    def print_help(self):
        if self.help_msg:
            print "  %-20s  %s" % (ListToStr(self.all_options(1)), \
	      self.help_msg)

######################################################################
## 
## Class OptionAction
## 
######################################################################

class OptionAction(Option):
    def __init__(self, action, ops, help_msg):
        Option.__init__(self,ops,help_msg)
        self.action = action

    def fire(self, opt, arg):
        apply(self.action, (opt, arg))

######################################################################
## 
## Class OptionContextBased
## 
######################################################################
        
class OptionContextBased(Option):
    def __init__(self, action_spec, context, ops, help_msg):
        Option.__init__(self,ops,help_msg)
        self.action_spec = action_spec
        self.context = context

######################################################################
## 
## Class OptionSet
## 
######################################################################

class OptionSet(OptionContextBased):
    def fire(self, opt, arg):
        if type(self.action_spec) == type(""):
            vname = self.action_spec
            cvt = lambda x: x
        else:
            (vname, cvt) = self.action_spec
        self.context[vname] = cvt(arg)

######################################################################
## 
## Class OptionToggle
## 
######################################################################
        
class OptionToggle(OptionContextBased):
    def fire(self, opt, arg):
        vname = self.action_spec
        if self.context.has_key(vname):
            old_value = self.context[vname]
        else:
            old_value = 0
        self.context[vname] = not old_value

######################################################################
## 
## Class OptionCheck
## 
######################################################################

class OptionCheck(OptionContextBased):
    def fire(self, opt, arg):
        vname = self.action_spec
        self.context[vname] = 1

######################################################################
## 
## Class OptionAppend
## 
######################################################################

class OptionAppend(OptionContextBased):
    def fire(self, opt, arg):
        if type(self.action_spec) == type(""):
            vname = self.action_spec
            cvt = lambda x: x
        else:
            (vname, cvt) = self.action_spec
        if self.context.has_key(vname) and self.context[vname]:
            self.context[vname].append(cvt(arg))
        else:
            self.context[vname] = [ cvt(arg) ]

######################################################################
## 
## Class CmdLine
## 
######################################################################

class CmdLine:
    def __init__(self, program_title=None, usage_msg="[options] files", \
      context={}):
        self.program_title = program_title
        self.usage_msg = usage_msg
        self.options = []
        self.context = context

    def getopt(self, args=None):
        if self.program_title:
            print self.program_title
            print
        if args is None:
            args = sys.argv[1:]
        # build option map
        option_map = {}
        short_ops = ""
        long_ops = []
        for opt in self.options:
            if opt.short_op:
                short_ops = short_ops + \
		  ListToStr(opt.short_options(arg_spec=1,op_prefix=''),'')
            if opt.long_op:
                long_ops.extend(opt.long_options(arg_spec=1,op_prefix=''))
            for op in opt.all_options():
                option_map[op] = opt
        if debug:
            print "OPTION MAP:"
            for op_spec, Opt in option_map.items():
                print "  %10s -> %s" % (op_spec, str(Opt))
        # process cmd line
        try:
            opts, other_args = getopt.getopt(args, short_ops, long_ops)
            for opt, arg in opts:
                Op = option_map[opt]
                if debug:
                    print "DEBUG: Firing %s (opt=%s, arg=%s)" % \
		      (str(Op), opt, arg)
                Op.fire(opt, arg)
            return other_args
        except getopt.error, e:
            print str(e)
            sys.exit(1)

    def print_help(self, opt=None, arg=None):
        print "%s %s" % (os.path.basename(sys.argv[0]), self.usage_msg)
        print
        for opt in self.options:
            opt.print_help()
        print
        if opt: # invoked thru -h
            sys.exit(1)

    def add_option(self, Opt):
        self.options.append(Opt)

    def add_help_option(self):
        self.add_option(OptionAction(self.print_help,['h','help'], \
	  "Print this help message"))

    def add_action_option(self, action, ops, help_msg=None):
        self.add_option(OptionAction(action,ops,help_msg))

    def add_set_option(self, vname, ops, help_msg=None):
        self.add_option(OptionSet(vname,self.context,ops,help_msg))

    def add_toggle_option(self, vname, ops, help_msg=None):
        self.add_option(OptionToggle(vname,self.context,ops,help_msg))

    def add_check_option(self, vname, ops, help_msg=None):
        self.add_option(OptionCheck(vname,self.context,ops,help_msg))

    def add_append_option(self, vname, ops, help_msg=None):
        self.add_option(OptionAppend(vname,self.context,ops,help_msg))


-- 
Roberto Lupi



More information about the Python-list mailing list