Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Docopt: options after repeating elements are interpeted as repeating elements

Tags:

python

docopt

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?

like image 270
mat Avatar asked Feb 15 '14 21:02

mat


1 Answers

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:

Two switches

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.

Use a single argument

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(',').

Use positional arguments only

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

List of options

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.

like image 104
zmo Avatar answered Oct 05 '22 02:10

zmo