Python command-line arguments: Part 4
Tags: python, tutorial
Published: 2020-06-10 13:27:19

Before we catapult command-line arguments to infinity and beyond, we should go via one more item as suggested by an avid reader, Paddy. The `docopt`.  In case you've just popped in, here's what we have done to this moment:

1. [argv](https://thegreencodes.com/python-command-line-arguments-part-1-argv-ck2gd5o7400fewks155jr7isq)
2. [argparse](https://thegreencodes.com/command-line-arguments-part-2-argparse-ck2urwww5006o49s1flu1ntkh)
3. [getopt](https://thegreencodes.com/python-command-line-arguments-part-3-getopt-ck6p7lheb02med9s1crh04zem)

So far so good, but here's the pick-up line; a bonus section.

`docopt` has a lot of similarities to the `argparse`. Like its counterpart, it comes with helper functionality, that way it's easier to know if you did something wrong in your use or if your program is being used by a new user for the first time somewhere. 

The difference, however, comes with how docopt parses. It relies on the docstring you give it as a description. 

> The basic idea is that a good help message has all the necessary information in it to make a parser.

In my walks through the internet, I happened to come across this one comment, and I quote:

> I feel you, man. It's so simple and unorthodox it's confusing. 

And I had to crack or two reading it. I laughed so hard, partially because it is true, such that you might need a background of command-line argument parsing prior to using it.

This said, `docopt` is an external module, meaning it has to be installed. Hence, in a virtual environment:
```bash
pip install docopt
```

In this particular instance, we are going to make a calculator. Nothing fancy, just something to advance from the previous programs we wrote so that you can see the subtle differences.

Our program will do the below, 
- Addition
- Multiplication
- Squares
- Root

So go ahead and create a file: `my_calculator.py`, and paste the below in. We will go through this step at a time.
```python
"""My Advanced Calculator v1.0

Usage:
  my_calculator.py add (<number><number>)...
  my_calculator.py mult (<number><number>)...
  my_calculator.py square [--verbose] <number>
  my_calculator.py root <number>
  my_calculator.py (-h | --help)
  my_calculator.py --version

Examples:
  my_calculator.py add 9 4 67 101
  my_calculator.py mult 88 43 20458 1 134 
  my_calculator.py square --verbose 9 

Options:
  -h --help        Show this screen.
  -v --version     Show version.
     --verbose         Show details verbosely.

"""

import math
from docopt import docopt

class MyCalculator:
    def get_options(self):
        self.args = docopt(__doc__)
        
# loop via commands getting what is needed
        if self.args["add"]:
            self.addition()
        elif self.args["mult"]:
            self.multiply()
        elif self.args["square"]:
            self.get_square()
        else:
            self.get_root()

    def addition(self):
        """
        Get the summ of all numbers passed
        """
        summation = sum([int(number) for number in self.args["<number><number>"]])
        print(f"{summation}")

    def multiply(self):
        """Get the product of the list of numbers"""
        product = math.prod([int(number) for number in self.args["<number><number>"]])
        print(f"{product}")
<another-argument>
    def get_square(self):
        number = int(self.args["<number>"])
        if self.args["--verbose"]:
            print(f"{number} * {number} = {number*number}")
        else:
            print(f"{number*number}")

    def get_root(self):
        """
      Get the square root
      """
        number = self.args["<number>"]
        print(f"{math.sqrt(int(number))}")


if __name__ == "__main__":
    arguments = docopt(__doc__, version="MyCalculator 1.0")
    calculator = MyCalculator()
    calculator.get_options()

```

Now, *docopt* accepts the below :
```
docopt(doc, argv=None, help=True, version=None, options_first=False)
```
The first argument, being `doc`, which essentially, is this, the program description.

```python
"""My Advanced Calculator v1.0
Usage:
  my_calculator.py add (<number><number>)...
  my_calculator.py mult (<number><number>)...
  my_calculator.py square [--verbose] <number>
  my_calculator.py root <number>
  my_calculator.py (-h | --help)
  my_calculator.py --version

Examples:
  my_calculator.py add 9 4 67 101
  my_calculator.py mult 88 43 20458 1 134 
  my_calculator.py square --verbose 9 

Options:
  -h --help        Show this screen.
  -v --version     Show version.
     --verbose         Show details verbosely.

"""
```
It is here that we define the logic of our parser, following the rule that *a good help message has all the necessary information in it to make a parser.* 

In the docstring, we give a `Usage` description, followed by various examples, and the `Options` available in our program for the values passed.
Breaking it down a bit further, 

```python
  my_calculator.py add (<number><number>)...
```

Do take note, `add`, `mult`, `square`, and `root` are commands and not arguments nor are they options.
They tell the user what is going to happen to the arguments parsed if any.

For our addition, we specify that if a user wants to add, they should pass two or more values to the program. To tell the program to create such a parser, we use `(<number><number>)`. Wrapping in `()` is just a specification that lets the program know that it should accept two numbers. The `...` after tells the program, *Hey, create the parser accepting two arguments, but remember, there may be two or more!* , hence the ellipses. The same goes for multiplication, which we have shortened as `mult`.

For the squares and roots, however, our program accepts only one number. By default, arguments passed to `docopt`  are required, therefore, we can safely ignore the `( )`. If we wanted the user to give the argument as an option (i.e, not mandatory), we would wrap our argument(s) in `[ ]`.
Something you may have seen as well is this:

```python
  my_calculator.py square [--verbose] <number>
```
This particular description, along with having a command `square`, has the option `--verbose` in square brackets. The great part about this is that it does not matter where you place this as you get the square of your number. So running ` my_calculator.py square --verbose 9 ` would give the same exact value as running ` my_calculator.py square 9  --verbose`. You can tell the difference from how the other options are used (they reused independent of arguments):

```python
my_calculator.py (-h | --help)
my_calculator.py --version
```

 
A quick run of our program, without positional arguments, would give something like this:
```bash
 python my_calculator.py  
```
Output:
```bash

Usage:
  my_calculator.py add (<number><number>)...
  my_calculator.py mult (<number><number>)...
  my_calculator.py square [--verbose] <number>
  my_calculator.py root <number>
  my_calculator.py (-h | --help)
  my_calculator.py --version

```
Out of the box, we would get the usage detail, from where we would be able to get the commands and options available. Running with the `-h` option for help would give the whole docstring.
Back to the main call:
```python
    arguments = docopt(__doc__, version="MyCalculator 1.0") # give the program version number/name
    calculator = MyCalculator()
    calculator.get_options()
```
We create an instance of `MyCalculator` and call its `get_options`. from where we can get all its arguments and options. So we can call `python my_calculator.py add 1 23 4556 `
```python
# get the arguments and options parsed
self.args = docopt(__doc__)

self.args == {'--help': False,
 '--verbose': False,
 '--version': False,
 '<number>': None,
 '<number><number>': ['1', '23', '4556'], # can accept two or more numbers because of the ellipse
 'add': True,
 'mult': False,
 'root': False,
 'square': False}

```
You can see that this is a dictionary or as web developers may call it, a JSON object. The list of numbers, `<number><number>` contains the numbers as expected, but as strings, So to sum them, we would need to convert each into integers before.

```python
# convert each item in the list to an integer and sum hte resultant list
sum([int(number) for number in self.args["<number><number>"]])
```

So go ahead and run `python my_calculator.py add 1 23 4556 `, see the results for yourself.

As we are not getting into the details of list comprehension in this piece, I will leave the calculations for you to ponder upon. We are getting back to our package:

```python
docopt(doc, argv=None, help=True, version=None, options_first=False)
```

We already gave it the `doc` option,(document description). For `argv`, docopt will take everything passed to it excluding the program name, that is, `sys.argv[1:]`. Do take a look at [argv](https://thegreencodes.com/python-command-line-arguments-part-1-ck2gd5o7400fewks155jr7isq) if you need some clarification with this.
The `help` option is set to **True** by default for the sake of the help message when needed while the `options_first` is disabled and hence we can position our options either before or after or between our arguments while parsing.

It is not just using `commands` that does all the talking. We may well just use `options` and parameters. For example:
```python
"""
Usage:
 arguments_example.py [-vrh] [FILE] ...
"""
```
An example program taking in multiple optional **options** with a *FILE* that is also optionally passed. You can default it to a file in the local directory, much like our file organizer in the [File management series](https://thegreencodes.com/file-management-with-python-part-2-ck10hkxd6001n6ps1m27sswmy)

We can combine this as far as we want (optional arguments inside optional, required arguments inside optional or even mutually exclusive).

```python
# have an optional command taking optional arguments
my_program [command --option <argument>]

# arguments are optional, but if <one-argument> is passed, <another-argument> should be present.
my_program [(<one-argument> <another-argument>)]
```

On and one it could go, but this is up to you to do more digging to find the particular use-case you need.
@[Paddy McCarthy](@Paddy3118) I hope I got the Knitty details, I know it took a while.

For any lover of the command-line out there, you can get docopt working for your language of preference be it 
- C++
- C
- PHP
- Haskel

The full list of supported languages can be found [here](https://github.com/docopt). Till next time, [@codes_green](https://twitter.com/codes_green). Code, as usual, can be found from [TheGreenCodes](https://github.com/TheGreenCodes).