Option groups

Usage

The recommended way of defining option groups is through the decorator cloup.option_group(). This decorator is overloaded with two signatures; the only difference between the two is how you provide the help argument:

# help as keyword argument
@option_group(name, *options, [help], [constraint])

# help as 2nd positional argument
@option_group(name, help, *options, [constraint])
  • name is used as title of the option group help section,

  • help is an optional extended description of the option group, shown below the name,

  • constraint is an optional instance of Constraint (see Constraints for more info); a description of the constraint is shown between squared brackets in the option group title.

import cloup
from cloup import option_group, option
from cloup.constraints import SetAtLeast

@cloup.command('clouptest')
@option_group(
    "Input options",
    "This is a very long description of the option group. I don't think this is "
    "needed very often; still, if you want to provide it, you can pass it as 2nd "
    "positional argument or as keyword argument 'help' after all options.",
    option('-o', '--one', help='1st input option'),
    option('--two', help='2nd input option'),
    option('--three', help='3rd input option'),
)
@option_group(
    'Output options',
    option('--four / --no-four', help='1st output option'),
    option('--five', help='2nd output option'),
    option('--six', help='3rd output option'),
    constraint=RequireAtLeast(1),
)
# Options that don't belong to any option group (including --help)
# are shown under "Other options"
@option('--seven', help='first uncategorized option',
        type=click.Choice(['yes', 'no', 'ask']))
@option('--height', help='second uncategorized option')
def cli(**kwargs):
    """ A CLI that does nothing. """
    print(kwargs)
Usage: clouptest [OPTIONS]

  A CLI that does nothing.

Input options:
  This is a very long description of the option group. I don't think this is
  needed very often; still, if you want to provide it, you can pass it as
  2nd positional argument or as keyword argument 'help' after all options.
  -o, --one TEXT        1st input option
  --two TEXT            2nd input option
  --three TEXT          3rd input option

Output options [at least 1 required]:
  --four / --no-four    1st output option
  --five TEXT           2nd output option
  --six TEXT            3rd output option

Other options:
  --seven [yes|no|ask]  first uncategorized option
  --height TEXT         second uncategorized option
  --help                Show this message and exit.

The default option group

Options that are not assigned to any user-defined option group are listed under a section which is shows at the bottom. This section is titled “Other options”, unless the default group is the only one defined, in which case cloup.Command behaves like a normal click.Command, naming it just “Options”.

In the example above, I used the cloup.option() decorator to define options but this is entirely optional as you can use click.option() as well. The only difference is that cloup.option() uses GroupedOption as default option class, which is just a click.Option with an additional attribute called group. Nonetheless, you don’t need to use GroupedOption, because the attribute group will be added to any type of Option via monkey-patching anyway.

By default, the help columns of all option groups are aligned; this is consistent with how argparse format option groups and I think it’s visually pleasing. Nonetheless, you can also format each option group independently passing align_option_groups=False to @command().

Alternative usage (flat style)

In “flat style”, you first define your option groups. Then, you use the option() decorator of OptionGroup:

from cloup import OptionGroup
from cloup.constraints import SetAtLeast

input_grp = OptionGroup(
    'Input options', help='This is a very useful description of the group')
output_grp = OptionGroup(
    'Output options', constraint=SetAtLeast(1))

@cloup.command('clouptest')
# Input options
@input_grp.option('-o', '--one', help='1st input option')
@input_grp.option('--two', help='2nd input option')
# Output options
@output_grp.option('--four / --no-four', help='1st output option')
@output_grp.option('--five', help='2nd output option')
def cli_flat(**kwargs):
    """ A CLI that does nothing. """
    print(kwargs)

Equivalently, you could pass the option group as an argument to cloup.option:

@option('-o', '--one', help='1st input option', group=input_grp)

Note that this works only with cloup.option, not click.option.

How it works

This feature is implemented simply by adding an attribute group to options, monkey-patching them if they are not of type GroupedOption. This attribute is of type OptionGroup or None.

When the command is initialized, OptionGroupMixin just groups all options by their group attribute. Options that don’t have a group attribute or have it set to None are put into the “default option group” (together with --help).

In order to show option groups in the command help, OptionGroupMixin “overrides” Command.format_options.

Feature support

Note

If you use command classes/decorators (re)defined by Cloup, you can skip this section.

This features depends on:

Note that cloup.Command includes both while cloup.Group doesn’t include neither of them: groups should have only a few options, so they should not need neither option groups nor constraints. (If you disagree, open an issue describing why you need it.) Anyway, you can always add this mixins to Group with two lines of code.