
Option groups
=============

.. highlight:: none

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

.. code-block:: python

    # 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 :class:`~cloup.constraints.Constraint`
  (see :doc:`constraints` for more info); a description of the constraint is shown
  between squared brackets in the option group title.

.. tabbed:: Code
    :new-group:

    .. code-block:: python

        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)

.. tabbed:: Generated help

    .. code-block:: none

        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.

.. admonition:: 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 :func:`cloup.option` decorator to define
options but this is **entirely optional** as you can use :func:`click.option` as
well. The only difference is that :func:`cloup.option` uses
:class:`~cloup.GroupedOption` as default option class, which is just a
:class:`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
:meth:`~cloup.OptionGroup.option` decorator of :class:`~cloup.OptionGroup`:

.. code-block:: python

    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``:

.. code-block:: python

    @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:

- (*required*) :class:`~cloup.OptionGroupMixin`
- (*optional*) :class:`~cloup.ConstraintMixin`, if you want to use constraints.

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.
