I am using docopt in my simple Python program:
#!/usr/bin/env python
"""
Farmers market
Usage:
farmersmarket.py buy -i <item> -q <quantity> [<quantity>] [-p <price>] [-dvh]
farmersmarket.py -d | --debug
farmersmarket.py -v | --version
farmersmarket.py -h | --help
Options:
-i --item Item.
-q --quantity Quantity.
-p --price Price.
-d --debug Show debug messages.
-h --help Show this screen.
-v --version Show version.
"""
from docopt import docopt
print docopt(__doc__)
If I run:
farmersmarket.py buy --item eggs --quantity 100 115 --price 0.25
The expected behaviour is to buy a random quantity of eggs between the values 100 and 115 at the price 0.25. This works without problems at least when it comes to interpreting the arguments. In other words docopt gets everything as intended:
{'--debug': False,
'--help': False,
'--item': True,
'--price': True,
'--quantity': True,
'--version': False,
'<item>': 'eggs',
'<price>': '0.25',
'<quantity>': ['100', '115'],
'buy': True}
However sometimes I do not want to buy a random amount of eggs but a specific amount. In this case the --quantity
option takes only one argument:
farmersmarket.py buy --item eggs --quantity 471 --price 0.25
But this fails as docopt interprets --price 0.25
as a repeating element of --quantity
and loses the value of <price>
:
{'--debug': False,
'--help': False,
'--item': True,
'--price': True,
'--quantity': True,
'--version': False,
'<item>': 'eggs',
'<price>': None,
'<quantity>': ['471', '0.25'],
'buy': True}
How can I get other options to work after repeating elements?
actually, you forgot to add the <price>
argument to the Options:
description part ; the following code:
#!/usr/bin/env python
"""
Farmers market
Usage:
farmersmarket.py buy -i <item> -q <quantity> [<quantity>] [-p <price>] [-dvh]
farmersmarket.py -d | --debug
farmersmarket.py -v | --version
farmersmarket.py -h | --help
Options:
-i --item Item.
-q --quantity Quantity.
-p --price <price> Price.
-d --debug Show debug messages.
-h --help Show this screen.
-v --version Show version.
"""
from docopt import docopt
print docopt(__doc__)
is working as you expect:
% farmersmarket.py buy --item eggs --quantity 100 --price 0.25
{'--debug': False,
'--help': False,
'--item': True,
'--price': '0.25',
'--quantity': True,
'--version': False,
'<item>': 'eggs',
'<quantity>': ['100'],
'buy': True}
edit:
but actually, what you try to achieve is wrong. If you look at the parsed arguments you'll see:
'--quantity': True,
[…]
'<quantity>': ['100'],
which means that you defined a boolean --quantity
argument, and a positional quantity
argument. Both unrelated… Consequently the following:
buy --items eggs --quantity --price 0.25 100 2
gives the result:
'--quantity': True,
[…]
'<quantity>': ['100','2'],
which is not what you want… So let's get back to what you want: you say you want an argument that takes two values to define a min and an optional max. But I'm sorry to tell you that this exact rule is no more possible in geptopt
, argparse
than in docopt
.
The solution will always involve a little logic once the parsing is done, or you need to change the way you're parsing your arguments. So I can see for you four options:
Farmers market
Usage:
farmersmarket.py buy -i <item> -m <min> [-M <max>] [-p <price>] [-dvh]
farmersmarket.py -d | --debug
farmersmarket.py -v | --version
farmersmarket.py -h | --help
Options:
-i --item <item> Item.
-m --min <min> Minimal quantity.
-M --max <min> Maximal quantity.
-p --price <price> Price.
-d --debug Show debug messages.
-h --help Show this screen.
-v --version Show version.
Here, the simplest, yet I think best solution, is to use two different options for setting up the maximal and minimal boundaries. It's the only way that the parser can handle a {1,2}
number of arguments and, afaict, the way it should be implemented.
The closest way to get you to actually have an argument with a list, is to actually "play" with the syntax:
Farmers market
Usage:
farmersmarket.py buy -i <item> -q <quantity> [-p <price>] [-dvh]
farmersmarket.py -d | --debug
farmersmarket.py -v | --version
farmersmarket.py -h | --help
Options:
-i --item <item> Item.
-q --quantity <quantity> Quantity: min[,max]
-p --price <price> Price.
-d --debug Show debug messages.
-h --help Show this screen.
-v --version Show version.
Then you can use:
% farmersmarket.py buy --items eggs -q 100,2 --price 0.25
and get your min and max values through: if len(docopt(__doc__)['--quantity'].split(',')) == 2: qmin, qmax = docopt(__doc__)['--quantity'].split(',')
.
Finally your last solution would be to use positional arguments only:
Farmers market
Usage:
farmersmarket.py buy <item> <min_qty> [<max_qty>] [-p <price>] [-dvh]
farmersmarket.py -d | --debug
farmersmarket.py -v | --version
farmersmarket.py -h | --help
Options:
<item> Item.
<min_qty> Minimum quantity
<max_qty> Maximum quantity
-p --price <price> Price.
-d --debug Show debug messages.
-h --help Show this screen.
-v --version Show version.
and then it works when you call:
% farmersmarket.py buy eggs 100 2 -p 0.25
But it looks like you want to get an argument list, and the only way to achieve that with command line options, is to repeat the option -q 1 -q 2 -q 3…
. To tell docopt that's what you want, you need to add an ellipsis after the option's argument:
Farmers market
Usage:
farmersmarket.py buy -i <item> -q <quantity>... [-p <price>] [-dvh]
farmersmarket.py -d | --debug
farmersmarket.py -v | --version
farmersmarket.py -h | --help
Options:
-i --item <item> Item.
-q --quantity <quantity>... Quantity (-q min -q max)
-p --price <price> Price.
-d --debug Show debug messages.
-h --help Show this screen.
-v --version Show version.
Then you can call:
% farmersmarket.py buy --items eggs -q 100 -q 2 --price 0.25
but then, you'll have to check that len(docopt(__doc__)['--quantity']) < 2
(as the parser will enforce that you give at least one). But then you have to really state clearly in the description how you mean it to be used and give one or two examples.
Finally, I'd advice you to use one of the three other options than the list of options, because they make it more obvious how you want your program to be called.