2011-12-14 974 views
5

Implementando subcomandos "anidados" en Python con cmdln.¿Cómo debo implementar los subcomandos "anidados" en Python?

No estoy seguro de que estoy utilizando la terminología correcta aquí. Estoy tratando de implementar una herramienta de línea de comandos usando cmdln que permite subcomandos "anidados". Aquí hay un ejemplo del mundo real:

git svn rebase 

¿Cuál es la mejor manera de implementar esto? He estado buscando más información sobre esto en el documento, aquí y en la web en general, pero han aparecido vacíos. (Tal vez estaba buscando con los términos incorrectos.)

A falta de una característica no documentada que hace esto automáticamente, mi idea inicial era tener el controlador del subcomando anterior determinar que hay otro subcomando y despachar el despachador de comandos de nuevo. Sin embargo, he examinado las partes internas de cmdln y el despachador es un método privado, _dispatch_cmd. Lo siguiente que pienso es crear mi propio despachador de subconcesiones, pero eso parece menos que ideal y desordenado.

Cualquier ayuda sería apreciada.

Respuesta

5

argparse hace subcomandos muy fácil.

+1

La empresa en la que estoy trabajando para una línea de base tiene v2.6 por lo que usar argparse es un problema, ya que tendría que ser incluido como una biblioteca externa y sólo si es necesario cargado. Lejos de ser imposible, simplemente no es ideal. En cuanto a la biblioteca cmdln, me da un poco de funcionalidad básica que preferiría no recrear. Dicho esto, me opongo a usar otra cosa. – tima

4

Siento que hay una pequeña limitación con los sub_parsers en argparse, por ejemplo, tiene un conjunto de herramientas que pueden tener opciones similares que pueden extenderse a diferentes niveles. Puede ser raro tener esta situación, pero si está escribiendo código enchufable/modular, podría suceder.

Tengo el siguiente ejemplo. Es inverosímil y no está bien explicado en este momento, ya que es bastante tarde, pero aquí va:

Usage: tool [-y] {a, b} 
    a [-x] {create, delete} 
    create [-x] 
    delete [-y] 
    b [-y] {push, pull} 
    push [-x] 
    pull [-x] 
from argparse import ArgumentParser 

parser = ArgumentParser() 
parser.add_argument('-x', action = 'store_true') 
parser.add_argument('-y', action = 'store_true') 

subparsers = parser.add_subparsers(dest = 'command') 

parser_a = subparsers.add_parser('a') 
parser_a.add_argument('-x', action = 'store_true') 
subparsers_a = parser_a.add_subparsers(dest = 'sub_command') 
parser_a_create = subparsers_a.add_parser('create') 
parser_a_create.add_argument('-x', action = 'store_true') 
parser_a_delete = subparsers_a.add_parser('delete') 
parser_a_delete.add_argument('-y', action = 'store_true') 

parser_b = subparsers.add_parser('b') 
parser_b.add_argument('-y', action = 'store_true') 
subparsers_b = parser_b.add_subparsers(dest = 'sub_command') 
parser_b_create = subparsers_b.add_parser('push') 
parser_b_create.add_argument('-x', action = 'store_true') 
parser_b_delete = subparsers_b.add_parser('pull') 
parser_b_delete.add_argument('-y', action = 'store_true') 

print parser.parse_args(['-x', 'a', 'create']) 
print parser.parse_args(['a', 'create', '-x']) 
print parser.parse_args(['b', '-y', 'pull', '-y']) 
print parser.parse_args(['-x', 'b', '-y', 'push', '-x'])

salida

Namespace(command='a', sub_command='create', x=True, y=False) 
Namespace(command='a', sub_command='create', x=True, y=False) 
Namespace(command='b', sub_command='pull', x=False, y=True) 
Namespace(command='b', sub_command='push', x=True, y=True)

Como se puede ver, es difícil distinguir dónde a lo largo de la cadena se estableció cada argumento. Puede resolver esto cambiando el nombre de cada variable. Por ejemplo, puedes establecer 'dest' en 'x', 'a_x', 'a_create_x', 'b_push_x', etc., pero eso sería doloroso y difícil de separar.

Una alternativa sería detener el ArgumentParser una vez que llega a un subcomando y pasar los argumentos restantes a otro analizador independiente para que pueda generar objetos separados. Puede intentar lograrlo utilizando 'parse_known_args()' y no definiendo argumentos para cada subcomando. Sin embargo, eso no sería bueno porque cualquier argumento no analizado de antes todavía estaría allí y podría confundir el programa.

Me parece un poco barato, pero una solución útil es tener argparse interpretar los siguientes argumentos como cadenas en una lista. Esto se puede hacer estableciendo el prefijo en un terminador nulo '\ 0' (o algún otro carácter 'difícil de usar') - si el prefijo está vacío, el código emitirá un error, al menos en Python 2.7. 3.

Ejemplo:

parser = ArgumentParser() 
parser.add_argument('-x', action = 'store_true') 
parser.add_argument('-y', action = 'store_true') 
subparsers = parser.add_subparsers(dest = 'command') 
parser_a = subparsers.add_parser('a' prefix_chars = '\0') 
parser_a.add_argument('args', type = str, nargs = '*') 

print parser.parse_args(['-xy', 'a', '-y', '12'])

Salida:

Namespace(args=['-y', '12'], command='a', x=True, y=True) 

Tenga en cuenta que no consume la segunda opción -y. A continuación, puede pasar el resultado 'args' a otro ArgumentParser.

inconvenientes:

  • Ayuda no podría manejarse bien. Tendría que hacer algo más de solución con esto
  • Encontrar errores podría ser difícil de rastrear y requerir un esfuerzo adicional para asegurarse de que los mensajes de error estén correctamente encadenados.
  • Un poco más de sobrecarga asociada con los múltiples ArgumentParsers.

Si alguien tiene más información al respecto, házmelo saber.

5

Tarde para la fiesta aquí, pero he tenido que hacer esto bastante y he encontrado argparse bastante torpe para hacer esto. Esto me motivó a escribir una extensión en argparse llamada arghandler, que tiene un soporte explícito para esto - es posible implementar subcomandos con básicamente cero líneas de código.

He aquí un ejemplo:

from arghandler import * 

@subcmd 
def push(context,args): 
    print 'command: push' 

@subcmd 
def pull(context,args): 
    print 'command: pull' 

# run the command - which will gather up all the subcommands 
handler = ArgumentHandler() 
handler.run() 
Cuestiones relacionadas