views:

68

answers:

1

My Python script (for todo lists) is started from the command line like this:

todo [options] <command> [command-options]

Some options can not be used together, for example

todo add --pos=3 --end "Ask Stackoverflow"

would specify both the third position and the end of the list. Likewise

todo list --brief --informative

would confuse my program about being brief or informative. Since I want to have quite a powerful option control, cases like these will be a bunch, and new ones will surely arise in the future. If a users passes a bad combination of options, I want to give an informative message, preferably along with the usage help provided by optparse. Currently I handle this with an if-else statement that I find really ugly and poor. My dream is to have something like this in my code:

parser.set_not_allowed(combination=["--pos", "--end"], 
                       message="--pos and --end can not be used together")

and the OptionParser would use this when parsing the options.

Since this doesn't exist as far as I know, I ask the SO community: How do you handle this?

+4  A: 

Possibly by extending optparse.OptionParser:

class Conflict(object):
    __slots__ = ("combination", "message")

    def __init__(self, combination, message, parser):
        self.combination = combination
        self.message = str(message)
        self.parser = parser

    def accepts(self, options):
        count = sum(1 for option in self.combination \
                    if parser.has_option(option))
        return count <= 1

class ConflictError(Exception):
    def __init__(self, conflict):
        self.conflict = conflict

    def __str__(self):
        return self.conflict.message

class MyOptionParser(optparse.OptionParser):
    def __init__(self, *args, **kwds):
        super(MyOptionParser, self).__init__(*args, **kwds)
        self.conflicts = []

    def set_not_allowed(self, combination, message):
        self.conflicts.append(Conflict(combination, message, self))

    def parse_args(self, *args, **kwds):
        options, args = super(MyOptionParser, self).parse_args(*args, **kwds)
        for conflict in conflicts:
            if not conflict.accepts(options):
                raise ConflictError(conflict)
        return options, args

You can then handle ConflictError where you call parse_args:

try:
    options, args = parser.parse_args()
except ConflictError as err:
    parser.error(err.message)
Tamás
Terrific solution!
Joel