5

I want to have an option that takes two arguments. I.e. would like to be able to use

$ ./foo --path "old" "new"

Or what I really want is:

$ ./foo --path "old" "new" --path "old" "new"

But I don't know how to do it? (In fact I fear that it might not be possible...)


What I don't want, but which is close

I know how to have a repeating option (see below), but that is really not what I want here.

#!/usr/bin/env python3
'''foo

Usage:
  foo [--path ARG]...

Options:
  --path=ARG  Repeating option.
'''

import docopt

args = docopt.docopt(__doc__)

print(args)

Could be called with

$ ./foo --path "old" --path "new"
Tom de Geus
  • 5,625
  • 2
  • 33
  • 77

3 Answers3

3

This can be easily archived with docopt.

Syntax: Usage: ./foo [--path <src> <dst>]...

Input: ./foo --path src1 dst1 --path src2 dst2

Code:

>>> from docopt import docopt
>>> doc = "Usage: ./foo [--path <src> <dst>]..."
>>> argv = "--path old1 new1 --path old2 new2".split()
>>> args = docopt(doc, argv)

Result:

{
  "--path": 2, 
  "<dst>": [
    "dst1", 
    "dst2"
  ], 
  "<src>": [
    "src1", 
    "src2"
  ]
}

And now do something with it:

>>> if bool(args["--path"]):
>>>     n = 1 if args["--path"] is True else if args["--path"]
>>>     for i in range(0, n)]:
>>>         print("{}, {}".format(args["<old>"][i], args["<new>"][i]))
src1, dst1
src2, dst2

To add a multiple positional arguments, require path option and arguments to be in the correct order with parens "( )".

Usage: ./foo [(--path <src> <dst>)]... <arg>...

Attention: docopt doesn't raise a SystemExit in case the user mixes up the syntax and inputs ./foo arg --path src dst if you use options with multiple arguments like that. You will have to handle it yourself or add another (sub)command in front of it.

Usage: ./foo [(--path <src> <dst>)]... bar <arg>...
Cani
  • 1,129
  • 10
  • 14
  • It is not very robust though... In the sense that it can become mingled with positional arguments, without any warning. – Tom de Geus May 28 '18 at 09:53
  • The only issue I am experiencing is that docopt doesn't raise a `SystemExit` if you mix up the input and use the positional argument in between the options but you could check the parsed option for the correct syntax and raise it yourself. Can you specify your issue with positional arguments? – Cani May 28 '18 at 10:42
  • In this example when I would input `./foo --path a b c` I think that an error should be raised (sure I could this manually, still not very problematic). However with `Usage: ./foo [--path ]... ...` is becomes more problematic. In this particular case ``./foo --path a b c` cannot be parsed, where I think it should? – Tom de Geus May 29 '18 at 08:16
  • You should require `--path` `` and `` to be in the correct order with parens "**( )**" if you want to add positional arguments like that. I've updated my question. – Cani May 29 '18 at 13:52
  • Great. There is really a lot more possible than I knew of! One does have to do some manual testing I guess. I found already this edge case `./foo --path a b f --path c d e` that gives `'': ['d', 'e']`, `'': ['b', 'c']`, `'': ['a', 'f']`. – Tom de Geus May 29 '18 at 14:05
  • 1
    That would be the case with the missing `SystemExit` I was referencing to. It would be a wrong usage since the options are supposed to be in front of the positional arguments ``. It can be avoided with a (sub)command. – Cani May 29 '18 at 14:12
  • `./foo --path a b --path c d bar e f` – Cani May 29 '18 at 14:17
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/172011/discussion-between-cani-and-tom-de-geus). – Cani May 29 '18 at 14:19
1

This is not possible to accomplish with docopt.

The owner of the project gives the following reason:

Because in invocation like prog --foo bar baz qux there's no way for a person to tell if it means prog --foo=bar baz qux or prog --foo=bar,baz qux or prog --foo=bar,baz,qux.

which I think is pretty reasonable.

I would suggest using two options instead, maybe --from and --to, or --old-path and --new-path.

Alternatively, you could use argparse instead, and set the nargs option (e.g. nargs=2).

ash
  • 5,139
  • 2
  • 27
  • 39
  • 1
    Thanks! I was aware of the argparse solution, but I switched to docopt for its simplicity. I guess in my practical problem I could also ask the user to specify `--path="/old/path;/new/path"` and have a `--sep` option. – Tom de Geus May 14 '18 at 17:40
0

you can use click lib http://click.pocoo.org/5/options/

@click.command()
@click.option('--path', '-m', multiple=True)
def run(path):
    print('\n'.join(path))
galaxyan
  • 5,944
  • 2
  • 19
  • 43
  • Thanks for the click suggestion. Your solution however does not seem to work for me: `/foo --path test test Usage: example [OPTIONS] Error: Got unexpected extra argument (test)` – Tom de Geus May 14 '18 at 18:08
  • @TomdeGeus /foo -m test1 -m test2. – galaxyan May 14 '18 at 18:28