Existe-t-il un équivalent de argparse
's nargs='*'
fonctionnalité pour les arguments facultatifs dans Click?
J'écris un script de ligne de commande, et l'une des options doit pouvoir prendre un nombre illimité d'arguments, comme:
foo --users alice bob charlie --bar baz
Donc users
serait ['alice', 'bob', 'charlie']
et bar
serait 'baz'
.
Dans argparse
, je peux spécifier plusieurs arguments facultatifs pour collecter tous les arguments qui les suivent en définissant nargs='*'
.
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--users', nargs='*')
>>> parser.add_argument('--bar')
>>> parser.parse_args('--users alice bob charlie --bar baz'.split())
Namespace(bar='baz', users=['alice', 'bob', 'charlie'])
Je sais que Click vous permet de spécifier un argument pour accepter des entrées illimitées en définissant nargs=-1
, mais lorsque j'essaie de définir nargs
d'un argument facultatif sur -1, j'obtiens:
TypeError: les options ne peuvent pas avoir de nargs <0
Existe-t-il un moyen de faire en sorte que Click accepte un nombre non spécifié d'arguments pour une option?
J'ai besoin de pouvoir spécifier des options après l'option qui prend des arguments illimités.
La réponse de @Stephen Rauch répond à cette question. Cependant, je ne recommande pas d'utiliser l'approche que je demande ici. Ma demande de fonctionnalité est intentionnellement non implémentée dans Click , car elle peut entraîner des comportements inattendus. L'approche recommandée par Click est d'utiliser multiple=True
:
@click.option('-u', '--user', 'users', multiple=True)
Et dans la ligne de commande, cela ressemblera à:
foo -u alice -u bob -u charlie --bar baz
Une façon d'approcher ce que vous recherchez est d'hériter de click.Option et de personnaliser l'analyseur.
import click
class OptionEatAll(click.Option):
def __init__(self, *args, **kwargs):
self.save_other_options = kwargs.pop('save_other_options', True)
nargs = kwargs.pop('nargs', -1)
assert nargs == -1, 'nargs, if set, must be -1 not {}'.format(nargs)
super(OptionEatAll, self).__init__(*args, **kwargs)
self._previous_parser_process = None
self._eat_all_parser = None
def add_to_parser(self, parser, ctx):
def parser_process(value, state):
# method to hook to the parser.process
done = False
value = [value]
if self.save_other_options:
# grab everything up to the next option
while state.rargs and not done:
for prefix in self._eat_all_parser.prefixes:
if state.rargs[0].startswith(prefix):
done = True
if not done:
value.append(state.rargs.pop(0))
else:
# grab everything remaining
value += state.rargs
state.rargs[:] = []
value = Tuple(value)
# call the actual process
self._previous_parser_process(value, state)
retval = super(OptionEatAll, self).add_to_parser(parser, ctx)
for name in self.opts:
our_parser = parser._long_opt.get(name) or parser._short_opt.get(name)
if our_parser:
self._eat_all_parser = our_parser
self._previous_parser_process = our_parser.process
our_parser.process = parser_process
break
return retval
Pour utiliser la classe personnalisée, passez le paramètre cls
à @click.option()
décorateur comme:
@click.option("--an_option", cls=OptionEatAll)
ou si l'on souhaite que l'option mange tout le reste de la ligne de commande, sans respecter les autres options:
@click.option("--an_option", cls=OptionEatAll, save_other_options=False)
Cela fonctionne parce que click est un framework OO bien conçu. Le décorateur @click.option()
instancie généralement un objet click.Option
Mais permet à ce comportement d'être remplacé par les cls Il est donc relativement facile d'hériter de click.Option
dans notre propre classe et de contourner les méthodes souhaitées.
Dans ce cas, nous chevauchons click.Option.add_to_parser()
et le singe corrige l'analyseur afin que nous puissions manger plus d'un jeton si vous le souhaitez.
@click.command()
@click.option('-g', 'greedy', cls=OptionEatAll, save_other_options=False)
@click.option('--polite', cls=OptionEatAll)
@click.option('--other')
def foo(polite, greedy, other):
click.echo('greedy: {}'.format(greedy))
click.echo('polite: {}'.format(polite))
click.echo('other: {}'.format(other))
if __name__ == "__main__":
commands = (
'-g a b --polite x',
'-g a --polite x y --other o',
'--polite x y --other o',
'--polite x -g a b c --other o',
'--polite x --other o -g a b c',
'-g a b c',
'-g a',
'-g',
'extra',
'--help',
)
import sys, time
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
for cmd in commands:
try:
time.sleep(0.1)
print('-----------')
print('> ' + cmd)
time.sleep(0.1)
foo(cmd.split())
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc, (click.ClickException, SystemExit)):
raise
Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> -g a b --polite x
greedy: ('a', 'b', '--polite', 'x')
polite: None
other: None
-----------
> -g a --polite x y --other o
greedy: ('a', '--polite', 'x', 'y', '--other', 'o')
polite: None
other: None
-----------
> --polite x y --other o
greedy: None
polite: ('x', 'y')
other: o
-----------
> --polite x -g a b c --other o
greedy: ('a', 'b', 'c', '--other', 'o')
polite: ('x',)
other: None
-----------
> --polite x --other o -g a b c
greedy: ('a', 'b', 'c')
polite: ('x',)
other: o
-----------
> -g a b c
greedy: ('a', 'b', 'c')
polite: None
other: None
-----------
> -g a
greedy: ('a',)
polite: None
other: None
-----------
> -g
Error: -g option requires an argument
-----------
> extra
Usage: test.py [OPTIONS]
Error: Got unexpected extra argument (extra)
-----------
> --help
Usage: test.py [OPTIONS]
Options:
-g TEXT
--polite TEXT
--other TEXT
--help Show this message and exit.
Vous pouvez utiliser cette astuce.
import click
@click.command()
@click.option('--users', nargs=0, required=True)
@click.argument('users', nargs=-1)
@click.option('--bar')
def fancy_command(users, bar):
users_str = ', '.join(users)
print('Users: {}. Bar: {}'.format(users_str, bar))
if __name__ == '__main__':
fancy_command()
Ajoutez un faux option
avec un nom nécessaire et aucun argument nargs=0
, puis ajoutez 'argument' avec les arguments illimités nargs=-1
.
$ python foo --users alice bob charlie --bar baz
Users: alice, bob, charlie. Bar: baz
Mais soyez prudent avec les autres options:
$ python foo --users alice bob charlie --bar baz faz
Users: alice, bob, charlie, faz. Bar: baz