# Copyright © 2022 Idiap Research Institute <contact@idiap.ch>
#
# SPDX-License-Identifier: BSD-3-Clause
import logging
import typing
import click
[docs]
def verbosity_option(
logger: logging.Logger,
short_name: str = "v",
name: str = "verbose",
dflt: int = 0,
**kwargs: typing.Any,
) -> typing.Callable[..., typing.Any]:
"""Click-option decorator that adds a ``-v``/``--verbose`` option to a cli.
This decorator adds a click option to your CLI to set the log-level on a
provided :py:class:`logging.Logger`. You must specifically determine the
logger that will be affected by this CLI option, via the ``logger`` option.
.. code-block:: python
@verbosity_option(logger=logger)
The verbosity option has the "count" type, and has a default value of 0.
At each time you provide ``-v`` options on the command-line, this value is
increased by one. For example, a CLI setting of ``-vvv`` will set the
value of this option to 3. This is the mapping between the value of this
option (count of ``-v`` CLI options passed) and the log-level set at the
provided logger:
* 0 (no ``-v`` option provided): ``logger.setLevel(logging.ERROR)``
* 1 (``-v``): ``logger.setLevel(logging.WARNING)``
* 2 (``-vv``): ``logger.setLevel(logging.INFO)``
* 3 (``-vvv`` or more): ``logger.setLevel(logging.DEBUG)``
Arguments:
logger: The :py:class:`logging.Logger` to be set.
short_name: Short name of the option. If not set, then use ``v``
name: Long name of the option. If not set, then use ``verbose`` --
this will also become the name of the contextual parameter for
click.
dlft: The default verbosity level to use (defaults to 0).
**kwargs: Further keyword-arguments to be forwarded to the underlying
:py:func:`click.option`
Returns
-------
A callable, that follows the :py:mod:`click`-framework policy for
option decorators. Use it accordingly.
"""
def custom_verbosity_option(f):
def callback(ctx, param, value):
ctx.meta[name] = value
log_level: int = {
0: logging.ERROR,
1: logging.WARNING,
2: logging.INFO,
3: logging.DEBUG,
}[value]
logger.setLevel(log_level)
logger.debug(f'Level of Logger("{logger.name}") was set to {log_level}')
return value
return click.option(
f"-{short_name}",
f"--{name}",
count=True,
type=click.IntRange(min=0, max=3, clamp=True),
default=dflt,
show_default=True,
help=(
f"Increase the verbosity level from 0 (only error and "
f"critical) messages will be displayed, to 1 (like 0, but adds "
f"warnings), 2 (like 1, but adds info messags), and 3 (like 2, "
f"but also adds debugging messages) by adding the --{name} "
f"option as often as desired (e.g. '-vvv' for debug)."
),
callback=callback,
**kwargs,
)(f)
return custom_verbosity_option
[docs]
class AliasedGroup(click.Group):
"""Class that handles prefix aliasing for commands."""
[docs]
def get_command( # type: ignore
self, ctx: click.core.Context, cmd_name: str
) -> click.Command | None:
"""Return the decorated command.
Arguments:
ctx: The current command context being parsed
cmd_name: The subcommand name that was called
Returns
-------
The decorated command with aliasing capabilities
"""
rv = click.Group.get_command(self, ctx, cmd_name)
if rv is not None:
return rv
matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]
if not matches:
return None
if len(matches) == 1:
return click.Group.get_command(self, ctx, matches[0])
ctx.fail("Too many matches: %s" % ", ".join(sorted(matches))) # noqa
[docs]
class PreserveIndentCommand(click.Command):
"""A click command that preserves text indentation."""