Subcommand sections

Cloup allows to organize the subcommand of a Group (or, more in general, of a MultiCommand) in multiple help sections. Each such help section is represented by a Section instance, which is just a titled container for commands.

A Section can be:

  • sorted – it lists the commands in alphabetical order

  • unsorted – it lists the commands in the order they are added to the section.

All sections defined by the developer are unsorted by default. You can create a sorted section by passing sorted=True or by using the static method Section.sorted().

You can find a runnable example that implements part of the help of Git here. The code below is based on that example.

Adding sections to a Group

My favorite way of defining sections is doing it all in one place, just below the Group itself, as follows:

import cloup
from .commands import ( # import your subcommands implemented elsewhere
    git_init, git_clone, git_rm, git_sparse_checkout, git_mv)

@cloup.group('git')
def git():
    return 0

git.section(
    'Start a working area (see also: git help tutorial)',
    git_clone,
    git_init
)

git.section(
    'Work on the current change (see also: git help everyday)',
    git_rm,
    git_sparse_checkout,
    git_mv
)

# The following lines are here just to show what happens when you add
# commands without specifying a section: they are added to a default
# **sorted** section, which is titled "Other commands" and shown at the
# bottom of the command help
git.add_command(cloup.command('fake-2', help='Fake command #2')(f))
git.add_command(cloup.command('fake-1', help='Fake command #1')(f))
Usage: git [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Start a working area (see also: git help tutorial):
  clone            Clone a repository into a new directory
  init             Create an empty Git repository or reinitialize an...

Work on the current change (see also: git help everyday):
  rm               Remove files from the working tree and from the index
  sparse-checkout  Initialize and modify the sparse-checkout
  mv               Move or rename a file, a directory, or a symlink

Other commands:
  fake-1           Fake command #1
  fake-2           Fake command #2

The default section

All commands that are not explicitly assigned to a section are assigned to a “default section”, which is sorted. This section is titled “Other commands”, unless it is the only section defined, in which case cloup.Group behaves like a normal click.Group, naming it just “Commands”.

Each call of section() creates a new Section instance and adds it to the Group. When you add a section, all the contained subcommands are of course added to the Group (as if you called add_command for each of them).

In alternative, you can create a list of Section objects and pass it as the sections argument of cloup.group():

import cloup
from cloup import Section

# here, import/define commands git_init, git_clone ecc...

SECTIONS = [
    Section('Start a working area (see also: git help tutorial)',
            git_clone, git_init),
    Section('Work on the current change (see also: git help everyday)',
            git_rm, git_sparse_checkout, git_mv)
]

@cloup.group('git', sections=SECTIONS)
def git():
    return 0

Adding a command to a Section

You can add subcommands one by one as you do in Click, using either:

  • the decorators @group.command and @group.group

  • or group.add_command.

In Cloup, these methods have indeed an additional (optional) argument section.

import cloup
from cloup import Section

# Define sections without filling them.
# I'm using a class as a namespace here. This is not needed.
class Sect:
    START_WORKING_AREA = Section(
        'Start a working area (see also: git help tutorial)')
    WORK_CURRENT_CHANGE = Section(
        'Work on the current change (see also: git help everyday)'

@cloup.group('git')
def git():
    return 0

@git.command('init', section=Sect.START_WORKING_AREA)
def git_init():
    pass

@git.command('mv', section=Sect.WORK_CURRENT_CHANGE)
def git_mv():
    pass

Note that (differently from what happens with option groups) every time you add a subcommand specifying a section, you are mutating the corresponding Section object.