Source code for cloup.styling

"""
This module defines the classes that support the styling and theming of commands
help page.
"""
import dataclasses as dc
from typing import Callable, NamedTuple, Optional

import click

from cloup._util import FrozenSpace, click_version_tuple, identity, pop_many

IStyle = Callable[[str], str]
"""A callable that takes a string and returns a styled version of it."""


# noinspection PyUnresolvedReferences
[docs]class HelpTheme(NamedTuple): """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. """ 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).""" 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, epilog: Optional[IStyle] = None, ) -> 'HelpTheme': kwargs = {key: val for key, val in locals().items() if val is not None} kwargs.pop('self') if kwargs: return self._replace(**kwargs) return self
[docs] @staticmethod def dark(): """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'), epilog=Style(fg='bright_white'), )
[docs] @staticmethod def light(): """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'), epilog=Style(fg='bright_black'), )
[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] = dc.field(init=False, default=None)
[docs] def __call__(self, text: str) -> str: if self._style_kwargs is None: kwargs = pop_many(dc.asdict(self), 'text_transform', '_style_kwargs') if int(click_version_tuple[0]) < 8: # These arguments are not supported in Click < 8. Ignore them. pop_many(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"