2010-04-28 3 views
7

sceneggiatura mio Python (per todo list) viene avviato dalla riga di comando in questo modo:Come gestite le opzioni che non possono essere utilizzate insieme (usando OptionParser)?

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

Alcune opzioni non possono essere utilizzate insieme, per esempio

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

sarebbe specificare sia la terza posizione e la fine della lista. Allo stesso modo

todo list --brief --informative 

confonderebbe il mio programma di essere breve o informativo. Dal momento che voglio avere un controllo piuttosto potente, casi come questi saranno un mucchio e ne sorgeranno sicuramente di nuovi in ​​futuro. Se un utente passa una brutta combinazione di opzioni, voglio dare un messaggio informativo, preferibilmente insieme all'aiuto all'utilizzo fornito da optparse. Attualmente lo gestisco con una dichiarazione if-else che trovo davvero brutta e povera. Il mio sogno è di avere qualcosa di simile nel mio codice:

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

e OptionParser lo userebbe quando analizza le opzioni.

Dal momento che questo non esiste per quanto ne so, chiedo alla comunità SO: Come gestirlo?

risposta

6

Possibilmente estendendo 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) 

È quindi possibile gestire ConflictError cui si chiama parse_args: risposta

try: 
    options, args = parser.parse_args() 
except ConflictError as err: 
    parser.error(err.message) 
+0

soluzione Terrific! – Joel

+0

'super()' non funzionerà con Python 2.X, poiché 'OptionParser' viene creato come una classe vecchio stile. Una soluzione è fornita su [Stack Overflow question 2023940] (http://stackoverflow.com/questions/2023940/using-super-when-subclassing-python-class-that-is-not-derived-from-objectold). – gotgenes

+0

Grazie, l'ho risolto specificando esplicitamente la superclasse. –

3

Tamás di è un buon inizio, ma non ho potuto farlo funzionare, come ha (o avuto) un numero di bug, tra cui una chiamata interrotta a super , "parser" mancante in Conflict.__slots__, sempre generato un errore quando un conflitto è specificata a causa dell'uso di parser.has_option() in Conflicts.accepts(), ecc

Da quando ho davvero bisogno di questa caratteristica, ho arrotolato la mia soluzione e hanno messo a disposizione dalla Python Package Index come ConflictsOptionParser. Funziona praticamente come sostituzione di optparse.OptionParser. (So ​​che argparse è la nuova riga di comando che analizza l'hotness, ma non è disponibile in Python 2.6 e versioni precedenti e ha meno adozione al momento dello optparse. Inviami un'e-mail se desideri eseguire l'hacking o aggiungere un ulteriore argparse soluzione basata su) la chiave è due nuovi metodi, register_conflict(), e, in misura minore, unregister_conflict():.

#/usr/bin/env python 

import conflictsparse 
parser = conflictsparse.ConflictsOptionParser("python %prog [OPTIONS] ARG") 
# You can retain the Option instances for flexibility, in case you change 
# option strings later 
verbose_opt = parser.add_option('-v', '--verbose', action='store_true') 
quiet_opt = parser.add_option('-q', '--quiet', action='store_true') 
# Alternatively, you don't need to keep references to the instances; 
# we can re-use the option strings later 
parser.add_option('--no-output', action='store_true') 
# Register the conflict. Specifying an error message is optional; the 
# generic one that is generated will usually do. 
parser.register_conflict((verbose_opt, quiet_opt, '--no-output')) 
# Now we parse the arguments as we would with 
# optparse.OptionParser.parse_args() 
opts, args = parser.parse_args() 

ha alcuni vantaggi rispetto alla soluzione iniziata da Támas:

  • Esso funziona fuori dalla scatola ed è installabile tramite pip (o easy_install, se devi).
  • Le opzioni in conflitto possono essere specificate dalle loro stringhe di opzioni o dalle loro istanze optparse.Option, il che aiuta con il principio DRY; se si utilizzano le istanze, è possibile modificare le stringhe effettive senza preoccuparsi di interrompere il codice di conflitto.
  • Segue il normale comportamento di optparse.OptionParser.parse_args() e chiama automaticamente optparse.OptionParser.error() quando rileva le opzioni in conflitto negli argomenti della riga di comando, anziché lanciare direttamente l'errore. (Questo è sia una caratteristica e un insetto;. Sorta di un bug nel disegno generale s' optparse, ma una caratteristica per questo pacchetto nel senso che è almeno coerente con optparse comportamento)