"""
This module contains components that specifically address the styling and theming
of the ``--help`` output.
"""
import dataclasses
import dataclasses as dc
from dataclasses import dataclass
from typing import Any, Callable, Dict, Optional
import click
from cloup._util import FrozenSpace, click_version_tuple, delete_keys, identity
from cloup.typing import MISSING, Possibly
IStyle = Callable[[str], str]
"""A callable that takes a string and returns a styled version of it."""
[docs]
@dataclass(frozen=True)
class HelpTheme:
"""A collection of styles for several elements of the help page.
A "style" is just a function or a callable that takes a string and returns
a styled version of it. This means you can use your favorite styling/color
library (like rich, colorful etc). Nonetheless, given that Click has some
basic styling functionality built-in, Cloup provides the :class:`Style`
class, which is a wrapper of the ``click.style`` function.
:param invoked_command:
Style of the invoked command name (in Usage).
:param command_help:
Style of the invoked command description (below Usage).
:param heading:
Style of help section headings.
:param constraint:
Style of an option group constraint description.
:param section_help:
Style of the help text of a section (the optional paragraph below the heading).
:param col1:
Style of the first column of a definition list (options and command names).
:param col2:
Style of the second column of a definition list (help text).
:param epilog:
Style of the epilog.
:param alias:
Style of subcommand aliases in a definition lists.
:param alias_secondary:
Style of separator and eventual parenthesis/brackets in subcommand alias lists.
If not provided, the ``alias`` style will be used.
"""
invoked_command: IStyle = identity
"""Style of the invoked command name (in Usage)."""
command_help: IStyle = identity
"""Style of the invoked command description (below Usage)."""
heading: IStyle = identity
"""Style of help section headings."""
constraint: IStyle = identity
"""Style of an option group constraint description."""
section_help: IStyle = identity
"""Style of the help text of a section (the optional paragraph below the heading)."""
col1: IStyle = identity
"""Style of the first column of a definition list (options and command names)."""
col2: IStyle = identity
"""Style of the second column of a definition list (help text)."""
alias: IStyle = identity
"""Style of subcommand aliases in a definition lists."""
alias_secondary: Optional[IStyle] = None
"""Style of separator and eventual parenthesis/brackets in subcommand alias lists.
If not provided, the ``alias`` style will be used."""
epilog: IStyle = identity
"""Style of the epilog."""
[docs]
def with_(
self, invoked_command: Optional[IStyle] = None,
command_help: Optional[IStyle] = None,
heading: Optional[IStyle] = None,
constraint: Optional[IStyle] = None,
section_help: Optional[IStyle] = None,
col1: Optional[IStyle] = None,
col2: Optional[IStyle] = None,
alias: Optional[IStyle] = None,
alias_secondary: Possibly[Optional[IStyle]] = MISSING,
epilog: Optional[IStyle] = None,
) -> 'HelpTheme':
kwargs = {key: val for key, val in locals().items() if val is not None}
if alias_secondary is MISSING:
del kwargs["alias_secondary"]
kwargs.pop('self')
if kwargs:
return dataclasses.replace(self, **kwargs)
return self
[docs]
@staticmethod
def dark() -> "HelpTheme":
"""A theme assuming a dark terminal background color."""
return HelpTheme(
invoked_command=Style(fg='bright_yellow'),
heading=Style(fg='bright_white', bold=True),
constraint=Style(fg='magenta'),
col1=Style(fg='bright_yellow'),
alias=Style(fg='yellow'),
alias_secondary=Style(fg='white'),
)
[docs]
@staticmethod
def light() -> "HelpTheme":
"""A theme assuming a light terminal background color."""
return HelpTheme(
invoked_command=Style(fg='yellow'),
heading=Style(fg='bright_blue'),
constraint=Style(fg='red'),
col1=Style(fg='yellow'),
)
[docs]
@dc.dataclass(frozen=True)
class Style:
"""Wraps :func:`click.style` for a better integration with :class:`HelpTheme`.
Available colors are defined as static constants in :class:`Color`.
Arguments are set to ``None`` by default. Passing ``False`` to boolean args
or ``Color.reset`` as color causes a reset code to be inserted.
With respect to :func:`click.style`, this class:
- has an argument less, ``reset``, which is always ``True``
- add the ``text_transform``.
.. warning::
The arguments ``overline``, ``italic`` and ``strikethrough`` are only
supported in Click 8 and will be ignored if you are using Click 7.
:param fg: foreground color
:param bg: background color
:param bold:
:param dim:
:param underline:
:param overline:
:param italic:
:param blink:
:param reverse:
:param strikethrough:
:param text_transform:
a generic string transformation; useful to apply functions like ``str.upper``
.. versionadded:: 0.8.0
"""
fg: Optional[str] = None
bg: Optional[str] = None
bold: Optional[bool] = None
dim: Optional[bool] = None
underline: Optional[bool] = None
overline: Optional[bool] = None
italic: Optional[bool] = None
blink: Optional[bool] = None
reverse: Optional[bool] = None
strikethrough: Optional[bool] = None
text_transform: Optional[IStyle] = None
_style_kwargs: Optional[Dict[str, Any]] = dc.field(init=False, default=None)
def __call__(self, text: str) -> str:
if self._style_kwargs is None:
kwargs = dc.asdict(self)
delete_keys(kwargs, ['text_transform', '_style_kwargs'])
if int(click_version_tuple[0]) < 8:
# These arguments are not supported in Click < 8. Ignore them.
delete_keys(kwargs, ['overline', 'italic', 'strikethrough'])
object.__setattr__(self, '_style_kwargs', kwargs)
else:
kwargs = self._style_kwargs
if self.text_transform:
text = self.text_transform(text)
return click.style(text, **kwargs)
[docs]
class Color(FrozenSpace):
"""Colors accepted by :class:`Style` and :func:`click.style`."""
black = "black"
red = "red"
green = "green"
yellow = "yellow"
blue = "blue"
magenta = "magenta"
cyan = "cyan"
white = "white"
reset = "reset"
bright_black = "bright_black"
bright_red = "bright_red"
bright_green = "bright_green"
bright_yellow = "bright_yellow"
bright_blue = "bright_blue"
bright_magenta = "bright_magenta"
bright_cyan = "bright_cyan"
bright_white = "bright_white"
DEFAULT_THEME = HelpTheme()