1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
|
"""
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."""
@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."""
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
@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'),
)
@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'),
)
@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)
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()
|