'''
    This code is Copyright(C) Paul Jimenez, 2002, 
        but completely freely redistributable.  Use it as you wish.

    Commandline.py: A better Commandline parser than getopt()

    Module interface is:

        Commandline(argv, validflags, validoptions, 
                          [requiredflags, requiredoptions])
            parses the commandline passed in via argv, looking for
            validflags (which take no parameters) and validoptions
            (which take one parameter each). A lack of the specification
            of requiredflags or requiredoptions results in the raising of
            MissingError. The specification of flags or options other than
            those listed results in the raising of an InvalidError.

        hasOption(o):  
            boolean true or false if the argv specified to this object
            specified the option o.

        getOption(o):  
            the parameter specified to option o, or raises MissingError if
            no option o was specified.

        params():
	    the 'leftover' parameters (or the ones after the --)

        MissingError and InvalidError 
            are both subclasses of CommandlineError, and both have a
            .options() method that returns a tuple containing the
            options that caused the error to be thrown. (accessible after
            they're caught in an except clause via sys.exc_info()[1].options())

'''
import sys

class CommandlineError:
    def __init__(self, desc=""):
        self.desc = desc
    def str(self):
        return "[CommandlineError] "+self.desc

class MissingError(CommandlineError):
    def __init__(self, missingopts):
        self.missingoptions = missingopts
        msg = "Missing required option(s): %s" % ", ".join(missingopts)
        CommandlineError.__init__(self, msg)
    def options(self):
        return self.missingoptions

class InvalidError(CommandlineError):
    def __init__(self, invalidopts):
        self.invalidoptions = invalidopts
        msg = "Invalid option(s): %s" % ", ".join(invalidopts)
        CommandlineError.__init__(self, msg)
    def options(self):
        return self.invalidoptions


class Commandline:

    def __init__(self, argv, validflags, validoptions, 
                             requiredflags=(), requiredoptions=()):

        self._doParse(argv, validflags+requiredflags, 
                            validoptions+requiredoptions)
        missed = ()
        for f in requiredflags:
            if not self.hasOption(f):
                missed += (f,)

        for o in requiredoptions:
            if not self.hasOption(o) or self.getOption(o) == None:
                missed += (o,)

        if missed:
            raise MissingError(missed)


    def _doParse(self, argv, validflags, validoptions):
        
        self._has = {}
        self._params = []
    
        # invalid options
        invalids = ()
        # flag to set at end of args
        moreargs = 1
        # counter thru the args
        i = 1
        while i < len(argv):
            arg = argv[i]
            if moreargs:
                if arg == '--':
                    moreargs = 0
                elif self._isLongOpt(arg):
                    arg = arg[2:]
                    o = 0
                    if arg in validflags:
                        self._setHasFlag(arg)
                    elif arg in validoptions:
                        o += 1
                        param = self._paramFor(argv, i, o)
                        self._setHasOption(arg, param)
                        if param == None:
                            o -= 1
                    else:
                        invalids += (arg,)
                    i += o
                elif self._isOptList(arg):
                    o = 0
                    for c in arg[1:]:
                        if c in validflags:
                            self._setHasFlag(c)
                        elif c in validoptions:
                            o += 1
                            param = self._paramFor(argv, i, o)
                            self._setHasOption(c, param)
                            if param == None:
                                o -= 1
                        else:
                            invalids += (c,)
                        i += o
                else:
                    self._params.append(arg)
            else:
                self._params.append(arg)
            i += 1
    
        if invalids:
            raise InvalidError(invalids)
    
    def _paramFor(self, arglist, optat, argat):
        for i in range(optat+1, optat+argat+1):
            if i >= len(arglist) or self._isOptList(arglist[i]):
                return None
        print "arglist: %d optat: %d argat: %d" % (len(arglist), optat, argat)
        return arglist[optat+argat]
    
    def _setHasFlag(self, flag):
        self._has[flag] = None
    
    def _setHasOption(self, option, value):
        self._has[option] = value
    
    def _isOptList(self, arg):
        return len(arg)>1 and arg[0] == '-'
    
    def _isLongOpt(self, arg):
        return len(arg)>2 and arg[0] == '-' and arg[1] == '-'
    
    ### Begin Public Interface
    
    def params(self):
        return self._params
    
    def hasOption(self, opt):
        return self._has.has_key(opt)
    
    def getOption(self, opt):
        if (self.hasOption(opt)):
            return self._has[opt]
        else:
            raise MissingError((opt,))
    


if __name__ == "__main__":

    args = ('a', 'b', 'c')
    opts = ('A', 'B', 'C')

    try:
        c = Commandline(sys.argv, args, opts, ('d',), ('D',))
    except MissingError:
        print "Usage: %s [-a][-b][-c][-A Aarg][-B Barg][-C Carg] -d -D darg"
        missing = sys.exc_info()[1].options()
        print "Default Message is: %s" % sys.exc_info()[1].str()
        print "Missing required argument(s): %s" % ", ".join(missing)
        sys.exit(1)
    except InvalidError:
        print "Usage: %s [-a][-b][-c][-A Aarg][-B Barg][-C Carg] -d -D darg"
        invalid = sys.exc_info()[1].options()
        print "Invalid argument(s): %s" % ", ".join(invalid)
        sys.exit(1)

    for a in args+('d',):
       if c.hasOption(a):
           print "%s set" % a

    for o in opts+('D',):
       if c.hasOption(o):
           print "%s is %s" % (o, c.getOption(o))



