Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you handle options that can't be used together (using OptionParser)?

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?

like image 749
Joel Avatar asked Apr 28 '10 12:04

Joel


People also ask

What is OptionParser in Python?

optparse is a more convenient, flexible, and powerful library for parsing command-line options than the old getopt module. optparse uses a more declarative style of command-line parsing: you create an instance of OptionParser , populate it with options, and parse the command line.

Is Optparse deprecated?

optparse is deprecated since python 3.2 and is not developed anymore.

What is Optparse in Ruby?

OptionParser is a class for command-line option analysis. It is much more advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented solution.


1 Answers

Possibly by extending optparse.OptionParser:

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

    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 hasattr(options, 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):
        optparse.OptionParser.__init__(self, *args, **kwds)
        self.conflicts = []

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

    def parse_args(self, *args, **kwds):
        # Force-ignore the default values and parse the arguments first
        kwds2 = dict(kwds)
        kwds2["values"] = optparse.Values()
        options, _ = optparse.OptionParser.parse_args(self, *args, **kwds2)

        # Check for conflicts
        for conflict in self.conflicts:
            if not conflict.accepts(options):
                raise ConflictError(conflict)

        # Parse the arguments once again, now with defaults
        return optparse.OptionParser.parse_args(self, *args, **kwds)

You can then handle ConflictError where you call parse_args:

try:
    options, args = parser.parse_args()
except ConflictError as err:
    parser.error(err.message)
like image 144
Tamás Avatar answered Sep 22 '22 07:09

Tamás