J'implémente un programme en ligne de commande qui a une interface comme celle-ci:
cmd [GLOBAL_OPTIONS] {command [COMMAND_OPTS]} [{command [COMMAND_OPTS]} ...]
J'ai parcouru la documentation argparse . Je peux implémenter GLOBAL_OPTIONS
comme argument facultatif en utilisant add_argument
dans argparse
. Et le {command [COMMAND_OPTS]}
en utilisant Sous-commandes .
D'après la documentation, il semble que je ne puisse avoir qu'une seule sous-commande. Mais comme vous pouvez le voir, je dois implémenter une ou plusieurs sous-commandes. Quelle est la meilleure façon d'analyser de tels arguments de ligne de commande en utilisant argparse
?
@mgilson a un bon réponse à cette question. Mais le problème avec le fractionnement de sys.argv moi-même est que je perds tout le message d'aide Nice généré par Argparse pour l'utilisateur. Alors j'ai fini par faire ça:
import argparse
## This function takes the 'extra' attribute from global namespace and re-parses it to create separate namespaces for all other chained commands.
def parse_extra (parser, namespace):
namespaces = []
extra = namespace.extra
while extra:
n = parser.parse_args(extra)
extra = n.extra
namespaces.append(n)
return namespaces
argparser=argparse.ArgumentParser()
subparsers = argparser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help = "command_a help")
## Setup options for parser_a
## Add nargs="*" for zero or more other commands
argparser.add_argument('extra', nargs = "*", help = 'Other commands')
## Do similar stuff for other sub-parsers
Maintenant, après la première analyse, toutes les commandes chaînées sont stockées dans extra
. Je le réanalyse alors qu'il n'est pas vide pour obtenir toutes les commandes chaînées et créer des espaces de noms séparés pour elles. Et j'obtiens une chaîne d'utilisation plus agréable générée par argparse.
Je suis venu avec la même qustion, et il semble que j'ai une meilleure réponse.
La solution est de ne pas simplement imbriquer un sous-analyseur avec un autre analyseur, mais nous pouvons ajouter un analyseur suivant avec un analyseur suivant un autre analyseur.
Le code vous explique comment:
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--user', '-u',
default=getpass.getuser(),
help='username')
parent_parser.add_argument('--debug', default=False, required=False,
action='store_true', dest="debug", help='debug flag')
main_parser = argparse.ArgumentParser()
service_subparsers = main_parser.add_subparsers(title="service",
dest="service_command")
service_parser = service_subparsers.add_parser("first", help="first",
parents=[parent_parser])
action_subparser = service_parser.add_subparsers(title="action",
dest="action_command")
action_parser = action_subparser.add_parser("second", help="second",
parents=[parent_parser])
args = main_parser.parse_args()
parse_known_args
renvoie un espace de noms et une liste de chaînes inconnues. Ceci est similaire au extra
dans la réponse vérifiée.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
sub = parser.add_subparsers()
for i in range(1,4):
sp = sub.add_parser('cmd%i'%i)
sp.add_argument('--foo%i'%i) # optionals have to be distinct
rest = '--foo 0 cmd2 --foo2 2 cmd3 --foo3 3 cmd1 --foo1 1'.split() # or sys.argv
args = argparse.Namespace()
while rest:
args,rest = parser.parse_known_args(rest,namespace=args)
print args, rest
produit:
Namespace(foo='0', foo2='2') ['cmd3', '--foo3', '3', 'cmd1', '--foo1', '1']
Namespace(foo='0', foo2='2', foo3='3') ['cmd1', '--foo1', '1']
Namespace(foo='0', foo1='1', foo2='2', foo3='3') []
Une boucle alternative donnerait à chaque sous-analyseur son propre espace de noms. Cela permet le chevauchement des noms de position.
argslist = []
while rest:
args,rest = parser.parse_known_args(rest)
argslist.append(args)
Vous pouvez essayer arghandler . Il s'agit d'une extension de argparse avec un support explicite pour les sous-commandes.
Vous pouvez toujours diviser la ligne de commande vous-même (split sys.argv
sur vos noms de commande), puis passez uniquement la partie correspondant à la commande particulière à parse_args
- Vous pouvez même utiliser le même Namespace
en utilisant le mot-clé namespace si vous le souhaitez.
Le regroupement de la ligne de commande est facile avec itertools.groupby
:
import sys
import itertools
import argparse
mycommands=['cmd1','cmd2','cmd3']
def groupargs(arg,currentarg=[None]):
if(arg in mycommands):currentarg[0]=arg
return currentarg[0]
commandlines=[list(args) for cmd,args in intertools.groupby(sys.argv,groupargs)]
#setup parser here...
parser=argparse.ArgumentParser()
#...
namespace=argparse.Namespace()
for cmdline in commandlines:
parser.parse_args(cmdline,namespace=namespace)
#Now do something with namespace...
non testé
Un autre paquet qui prend en charge les analyseurs parallèles est "declarative_parser".
import argparse
from declarative_parser import Parser, Argument
supported_formats = ['png', 'jpeg', 'gif']
class InputParser(Parser):
path = Argument(type=argparse.FileType('rb'), optional=False)
format = Argument(default='png', choices=supported_formats)
class OutputParser(Parser):
format = Argument(default='jpeg', choices=supported_formats)
class ImageConverter(Parser):
description = 'This app converts images'
verbose = Argument(action='store_true')
input = InputParser()
output = OutputParser()
parser = ImageConverter()
commands = '--verbose input image.jpeg --format jpeg output --format gif'.split()
namespace = parser.parse_args(commands)
et l'espace de noms devient:
Namespace(
input=Namespace(format='jpeg', path=<_io.BufferedReader name='image.jpeg'>),
output=Namespace(format='gif'),
verbose=True
)
Avertissement: je suis l'auteur. Nécessite Python 3.6. Pour installer, utilisez:
pip3 install declarative_parser
Voici le documentation et voici le repo sur GitHub .