Skip to content

rgreinho/docopt-subcommands-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DocOpt Subcommands Example

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.

The example

This example depicts a imaginary video game where we control a player, and we can make him/her jump, run and greet other players.

Usage example

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>]

main.py

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.

Find the command to execute

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()

commands.py

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)!')

About

A simple example of DocOpt subcommands

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages