This is a simple example to explain the usage of subcommands with DocOpt. For a more basic usage of DocOpt please refer to the official website.
This example depicts a imaginary video game where we control a player, and we can make him/her jump, run and greet other players.
To start this tutorial, here are some usage examples and their outputs.
The greet
subcommand:
$ python main.py greet
Hi other player(s)!
The run
subcommand:
$ python main.py run --distance=10
player is going to run 10 meters.
The run
subcommand with a name
for our player:
$ python main.py -n Rémy run --distance=10000
Are you crazy? Rémy is not going to do that!
Using a command which has not been created (yet):
$ python main.py -n Rémy shoot
Unknown command. RTFM!.
usage:
control [-hv] [-n NAME] <command> [<args>]
The main command is defined in the main.py
module. Its help screen looks like this:
Control center for an imaginary video game.
usage:
control [-hv] [-n NAME] <command> [<args>...]
options:
-h, --help shows the help
-n NAME --name=NAME sets player name [default: player]
-v, --version shows the version
The subcommands are:
greet greets other players
jump makes the player jump
run makes the player run
This help screen comes directly from the docstring of the program, define in main.py
, line 2-15.
We can see that the program has 3 optional arguments:
- help, which displays the help screen
- name, which sets the player name
- version, which shows the version number
And also expects a command (<command>
) with optional arguments ([<args>]
).
DocOpt
is going to parse the command line for you, but we will have to use it in a certain way to ensure the arguments are passed correctly to the subcommands:
25 args = docopt(__doc__, version='1.0.0', options_first=True)
In this line, we tell DocOpt
to generate the CLI by reading the main docstring of the program (__doc__
), we set the version of the program to 1.0.0
, and we enable the subcommands with options_first=True
. The result is stored in a dictionary named args
.
Now that our args
dictionary is populated, we can extract the name of the subcommand to execute:
28 command_name = args.pop('<command>').capitalize()
As well as its arguments:
31 command_args = args.pop('<args>')
Note
If there is no argument for a command, command_args
must be set to an empty dictionary (DocOpt
sets it to None
otherwise).
32 if command_args is None:
33 command_args = {}
Now our args
dictionary contains only the global arguments. They will be made available to ALL the subcommands.
For this example, it is assumed that a player must have a name, therefore the NAME
argument was made global.
DocOpt does not provide a dispatcher to find the right function to execute, so we have to route the commands ourselves.
The first option is to use a bunch of if ... elif ... else
:
if command_name == 'Greet':
command = Greet(command_args, args)
elif command_name == 'Jump':
command = Jump(command_args, args)
elif command_name == 'Run':
command = Run(command_args, args)
else:
print('Unknown command. RTFM!.')
raise DocoptExit()
But that is not a very clean solution.
A more elegant approach is to leverage the getattr function from the python library:
try:
command_class = getattr(commands, command_name)
except AttributeError:
print('Unknown command. RTFM!.')
raise DocoptExit()
The commands.py
module contains all our subcommands.
We organized them by providing a AbstractCommand
class which will be used as the base class of all our subcommands.
This class uses DocOpt
to parse the command arguments:
14 self.args = docopt(self.__doc__, argv=command_args)
Stores the global arguments provided by the main module:
15 self.global_args = global_args
And also defines all the functions that are expected in each subcommand:
17 def execute(self):
18 """Execute the commands"""
19 raise NotImplementedError
Then each subcommand will be created by subclassing AbstractCommand
:
22 class Run(AbstractCommand):
...
47 class Jump(AbstractCommand):
The class docstring will define the usage, the arguments and the options of the subcommand. Each subcommand will be responsible of defining its own behavior.
Each subcommand will reimplement the execute
function, which will define the actions of the subcommand. For example, the execute
function of the greet
subcommands looks like this:
def execute(self):
print('Hi other player(s)!')