2011-12-14 13 views
5

Implementazione di sottocomandi "nidificati" in Python con cmdln.Come devo implementare sottocomandi "nidificati" in Python?

Non sono sicuro di utilizzare la terminologia corretta qui. Sto cercando di implementare uno strumento da riga di comando utilizzando cmdln che consente i sottocomandi "nidificati". Ecco un esempio reale:

git svn rebase 

Qual è il modo migliore di implementarlo? Ho cercato ulteriori informazioni su questo nel documento, qui e sul web in generale, ma sono venuto fuori vuoto. (Forse stavo cercando con i termini sbagliati.)

In mancanza di una funzionalità non documentata che esegue automaticamente questa operazione, il mio pensiero iniziale era di avere il gestore secondario precedente che determina che esiste un altro sottocomando e di inviare di nuovo il dispatcher dei comandi. Ho comunque esaminato le parti interne di cmdln e il dispatcher è un metodo privato, _dispatch_cmd. Il mio prossimo pensiero è quello di creare il mio dispatcher del sub-sub-comando, ma ciò sembra meno che ideale e disordinato.

Qualsiasi aiuto sarebbe apprezzato.

risposta

5

argparse rende i sottocomandi molto facili.

+1

L'azienda per cui sto lavorando per ha una linea di base in modo da utilizzare v2.6 argparse è un problema in quanto avrebbe dovuto essere incluso come una libreria esterna e caricato solo se necessario. Lontano da impossibile, semplicemente non ideale. Per quanto riguarda la libreria cmdln, mi dà un bel po 'di funzionalità di base che preferirei non ricreare. Detto questo, sono contrario a usare qualcos'altro. – tima

4

Mi sembra che ci sia una leggera limitazione con sub_parser in argparse, se si dice che si dispone di una suite di strumenti che potrebbero avere opzioni simili che potrebbero diffondersi a diversi livelli. Potrebbe essere raro avere questa situazione, ma se stai scrivendo codice pluggable/modulare, potrebbe accadere.

Ho il seguente esempio. E 'inverosimile e non ben spiegato in questo momento, perché è piuttosto tardi, ma qui 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'])

uscita

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)

Come si può vedere, è difficile distinguere dove lungo la catena è stato impostato ogni argomento. Si potrebbe risolvere questo cambiando il nome per ogni variabile. Ad esempio, puoi impostare 'dest' su 'x', 'a_x', 'a_create_x', 'b_push_x', ecc., Ma ciò sarebbe doloroso e difficile da separare.

Un'alternativa sarebbe quella di arrestare ArgumentParser quando raggiunge un sottocomando e passare gli argomenti rimanenti a un altro parser indipendente, in modo che possa generare oggetti separati. Puoi provare a farlo usando 'parse_known_args()' e non definendo argomenti per ogni sottocomando. Tuttavia, ciò non sarebbe positivo perché qualsiasi argomento non analizzato di prima sarebbe ancora presente e potrebbe confondere il programma.

Mi sento un po 'a buon mercato, ma utile soluzione è di avere argparse interpretare i seguenti argomenti come stringhe in una lista. Questo può essere fatto impostando il prefisso su un terminatore null '\ 0' (o qualche altro carattere 'difficile da usare') - se il prefisso è vuoto, il codice genererà un errore, almeno in Python 2.7. 3.

Esempio:

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'])

uscita:

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

Nota che non consuma la seconda opzione -y. È quindi possibile passare il risultato 'args' a un altro ArgumentParser.

Svantaggi:

  • Aiuto non potrebbero essere gestiti bene. Occorrerebbe fare qualche altra soluzione con questo
  • Incontrare gli errori potrebbe essere difficile da rintracciare e richiedere qualche sforzo aggiuntivo per assicurarsi che i messaggi di errore siano correttamente concatenati.
  • Un po 'più di overhead associato a più ArgumentParsers.

Se qualcuno ha più input su questo, per favore fatemelo sapere.

5

In ritardo per la festa qui, ma ho dovuto fare questo un bel po 'e ho trovato argparse piuttosto goffo per fare questo con. Questo mi ha motivato a scrivere un'estensione a argparse denominata arghandler, che ha un supporto esplicito per questo: è possibile implementare sottocomandi con linee di codice sostanzialmente zero.

Ecco un esempio:

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() 
Problemi correlati