2010-04-28 9 views
7

guión Mi Python (para listas de tareas pendientes) se inicia desde la línea de comando como este:¿Cómo se manejan las opciones que no se pueden usar juntas (usando OptionParser)?

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

Algunas opciones no se pueden utilizar conjuntamente, por ejemplo

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

debía especificar la tercera posición y el final de la lista. Del mismo modo

todo list --brief --informative 

confundiría mi programa trata de ser breve o informativo. Como quiero tener un control de opción bastante poderoso, casos como estos serán un montón, y seguramente surjan nuevos en el futuro. Si un usuario pasa una mala combinación de opciones, quiero dar un mensaje informativo, preferiblemente junto con la ayuda de uso proporcionada por optparse. Actualmente manejo esto con una declaración if-else que encuentro realmente fea y pobre. Mi sueño es tener algo como esto en mi código:

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

y la OptionParser utilizaría esta al analizar las opciones.

Dado que esto no existe por lo que sé, le pregunto a la comunidad SO: ¿Cómo manejas esto?

Respuesta

6

Posiblemente extendiendo 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) 

entonces usted puede manejar ConflictError donde se llama a parse_args: Respuesta

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

solución Terrific! – Joel

+0

'super()' no funcionará para Python 2.X, ya que 'OptionParser' se crea como una clase de estilo antiguo. Se proporciona una solución en [Pregunta de desbordamiento de pila 2023940] (http://stackoverflow.com/questions/2023940/using-super-when-subclassing-python-class-that-is-not-derived-from-objectold). – gotgenes

+0

Gracias, lo he solucionado deletreando la superclase explícitamente. –

3

Tamas es un buen comienzo, pero no pude conseguir que funcione, ya que tiene (o ha tenido) varios errores, incluido , una llamada interrumpida al super , "parser" falta en Conflict.__slots__, siempre provoca un error cuando se produce un conflicto especificada por el uso de parser.has_option() en Conflicts.accepts(), etc.

Desde que realmente se necesita esta función, rodé mi propia solución y han puesto a disposición de la Python Package Index como ConflictsOptionParser. Funciona prácticamente como un reemplazo en reemplazo para optparse.OptionParser. (Lo que sí sé es argparse es la nueva línea de comando que analiza el picor, pero no está disponible en Python 2.6 y versiones posteriores y tiene menos adopción actualmente que optparse. Envíame un correo electrónico si deseas piratear o hackear un argparse adicional solución basada) la clave es dos nuevos métodos, register_conflict() y, en menor medida, 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() 

tiene algunas ventajas con respecto a la solución iniciada por Támas:

  • funciona fuera de la caja y es instalable a través de pip (o easy_install, si debes).
  • Las opciones en un conflicto se pueden especificar ya sea por sus cadenas de opciones o por sus instancias optparse.Option, lo que ayuda con el principio DRY; si usa las instancias, puede cambiar las cadenas reales sin preocuparse por romper el código de conflicto.
  • Sigue el comportamiento normal de optparse.OptionParser.parse_args() y llama automáticamente al optparse.OptionParser.error() cuando detecta opciones conflictivas en los argumentos de la línea de comandos, en lugar de arrojar el error directamente. (Esto es tanto una función y un error;. Clase de un error en el diseño general de optparse 's, sino una característica de este paquete en el que por lo menos es consistente con el comportamiento optparse)
Cuestiones relacionadas