"""Echo Color Module."""
__all__ = (
"COLORIZE",
"EnumLower",
"Color",
"SYMBOL",
"Symbol"
)
import enum
import os
from pathlib import Path
from typing import IO, Any, cast
try:
# nodeps[echo] extras
import click as click # type: ignore[attr-defined] # noqa: PLC0414
import typer as typer # type: ignore[attr-defined] # noqa: PLC0414
except ModuleNotFoundError:
click = None
typer = None
COLORIZE = os.environ.get("COLORIZE")
"""Force showing or hiding colors and other styles colorized output."""
def msg_click_typer():
if click is None or typer is None:
msg = "click and/or typer are not installed: installed with 'pip install nodeps[echo]'"
raise ImportError(msg)
[docs]
class EnumLower(enum.Enum):
"""EnumLower class."""
def _generate_next_value_(self: str, start, count: int, last_values) -> str:
return str(self).lower()
class _Color(EnumLower):
# noinspection PyShadowingBuiltins
def __call__(self,
message: Any = "",
exit: int | None = None, # noqa: A002
stderr: bool = True,
file: IO[Any] | str | None = None,
newline: bool = True,
bg: str | int | tuple[int, int, int] | None = None,
bold: bool | None = None,
dim: bool | None = None,
underline: bool | None = None,
overline: bool | None = None,
italic: bool | None = None,
blink: bool | None = None,
reverse: bool | None = None,
strikethrough: bool | None = None,
reset: bool = True,
colorize: bool | None = None) -> None:
"""Wrapper for :func:`click.secho` getting the `fg` color from the enum.
To force showing or hiding colors and other styles colorized output use ``COLORIZE`` environment variable,
or set `colorize` to True or False respectively.
This function combines echo and style into one call. As such the following two calls are the same:
- click.secho('Hello World!', fg='green')
- click.echo(click.style('Hello World!', fg='green'))
All keyword arguments are forwarded to the underlying functions depending on which one they go with.
Non-string types will be converted to str. However, bytes are passed directly to echo without applying style.
If you want to style bytes that represent text, call `bytes.decode` first
See `click.secho <https://click.palletsprojects.com/en/8.0.x/api/#click.secho>`_ for more information.
Examples:
>>> from nodeps import Color
>>> Color.GREEN('Hello World!',)
Arguments:
message: Text to append to symbol (default: "")
exit: Exit code, will exit if not None (default: None)
stderr: Write to ``stderr`` instead of ``stdout`` (default: True)
file: The file to write to. (default: ``stdout``)
newline: Output new line (default: False)
bg: Background color (default: None)
bold: Bold text (default: None)
dim: Dim (default: None)
underline: Underline (default: None)
overline: Overline (default: None)
italic: Italic (default: None)
blink: Blink (default: None)
reverse: Reverse (default: None)
strikethrough: Strikethrough (default: None)
reset: Reset (default: True)
colorize: Force showing or hiding colors and other styles. By default, click will remove color
if the output does not look like an interactive terminal (default: ``COLORIZE`` environment variable)
Returns:
None
"""
msg_click_typer()
click.secho(message, err=stderr, file=file, nl=newline, color=colorize or COLORIZE,
fg=self.value, bg=bg, bold=bold, dim=dim, underline=underline,
overline=overline, italic=italic, blink=blink, reverse=reverse,
strikethrough=strikethrough, reset=reset)
if exit is not None:
raise typer.Exit(exit)
[docs]
class Color(_Color):
""":func:`click.secho` and :func:`click.style` foreground color wrapper class."""
BLACK = enum.auto()
"""might be a gray"""
BLUE = enum.auto()
CYAN = enum.auto()
GREEN = enum.auto()
MAGENTA = enum.auto()
RED = enum.auto()
WHITE = enum.auto()
"""might be an grey"""
YELLOW = enum.auto()
"""might be an orange"""
BRIGHT_BLACK = enum.auto()
BRIGHT_BLUE = enum.auto()
BRIGHT_CYAN = enum.auto()
BRIGHT_GREEN = enum.auto()
BRIGHT_MAGENTA = enum.auto()
BRIGHT_RED = enum.auto()
BRIGHT_WHITE = enum.auto()
BRIGHT_YELLOW = enum.auto()
RESET = enum.auto()
"""reset the color only, not styles: bold, underline, etc."""
[docs]
def style(self, text: Any,
bg: str | int | tuple[int, int, int] | None = None,
bold: bool | None = None,
dim: bool | None = None,
underline: bool | None = None,
overline: bool | None = None,
italic: bool | None = None,
blink: bool | None = None,
reverse: bool | None = None,
strikethrough: bool | None = None,
reset: bool = True) -> str:
"""Wrapper for :func:`click.style` getting the `fg` color from the enum.
Styles a text with ANSI styles and returns the new string.
By default, the styling is self-contained which means that at the end of the string a
reset code is issued (this can be prevented by passing reset=False.
If the terminal supports it, color may also be specified as:
- An integer in the interval [0, 255]. The terminal must support 8-bit/256-color mode.
- An RGB tuple of three integers in [0, 255]. The terminal must support 24-bit/true-color mode
See `click.style <https://click.palletsprojects.com/en/8.0.x/api/#click.style>`_ for more information.
Arguments:
text: Text to apply style
bg: Background color (default: None)
bold: Bold text (default: None)
dim: Dim (default: None)
underline: Underline (default: None)
overline: Overline (default: None)
italic: Italic (default: None)
blink: Blink (default: None)
reverse: Reverse (default: None)
strikethrough: Strikethrough (default: None)
reset: Reset (default: True)
Returns:
Formatted text
"""
return click.style(text,
fg=self.BLACK.value, bg=bg, bold=bold, dim=dim, underline=underline,
overline=overline, italic=italic, blink=blink, reverse=reverse,
strikethrough=strikethrough, reset=reset)
COLOR_FIRST_OTHER = {
"first": {"bold": True, "italic": False, },
"other": {"bold": False, "italic": True, },
}
"""Print format for the `first` part of text and the `other` part when calling :class:`Symbol`."""
SYMBOL = {
"CRITICAL": {"text": "✘", "fg": Color.RED, "blink": True, },
"ERROR": {"text": "✘", "fg": Color.RED, },
"OK": {"text": "✔", "fg": Color.GREEN, },
"NOTICE": {"text": "‼", "fg": Color.CYAN, },
"SUCCESS": {"text": "◉", "fg": Color.BLUE, },
"VERBOSE": {"text": "+", "fg": Color.MAGENTA, }, # noqa: RUF001
"WARNING": {"text": "!", "fg": Color.YELLOW, }, # noqa: RUF001
"MINUS": {"text": "-", "fg": Color.RED, }, # noqa: RUF001
"MORE": {"text": ">", "fg": Color.MAGENTA, },
"MULTIPLY": {"text": "×", "fg": Color.BLUE, }, # noqa: RUF001
"PLUS": {"text": "+", "fg": Color.RED, },
"WAIT": {"text": "…", "fg": Color.YELLOW, },
}
class _Symbol(enum.Enum):
def _generate_next_value_(self, start, count, last_values):
if click is None or typer is None:
return None
return click.style(self, fg=cast(str, SYMBOL[self]["fg"].value), blink=SYMBOL[self].get("blink"), bold=True)
# noinspection PyShadowingBuiltins
def __call__(self,
first: Any = "",
other: Any = "",
separator: str = ":",
exit: int | None = None, # noqa: A002
stderr: bool = True,
file: IO[Any] | str | None = None,
newline: bool = True,
colorize: bool | None = None) -> None:
"""Print symbol from :obj:`SYMBOL`, with text in `first` and `other` according to :obj:`FIRST_OTHER` format.
Wrapper for :func:`click.echo` getting the `fg` color for the symbol from the :obj:`SYMBOL["fg"]`.
If `other` is specified will be appended to `first` text in :obj:`FIRST_OTHER["other"]` format with `separator`.
To force showing or hiding colors and other styles colorized output use ``COLORIZE`` environment variable,
or set `colorize` to True or False respectively.
Print a message and newline to stdout or a file. This should be used instead of print because it
provides better support for different data, files, and environments.
Compared to print, this does the following:
- Ensures that the output encoding is not misconfiguration on Linux.
- Supports Unicode in the Windows console.
- Supports writing to binary outputs, and supports writing bytes to text outputs.
- Supports colors and styles on Windows.
- Removes ANSI color and style codes if the output does not look like an interactive terminal.
- Always flushes the output.
Arguments:
first: First part of the text to append to :obj:`SYMBOL["text"]`
in :obj:`FIRST_OTHER["first"]` format (default: "")
other: Other parts to append to the `first` text in italic with `separator`
in :obj:`FIRST_OTHER["other"]` format (default: "None")
separator: Separator between `first` and `after` (default: ":")
exit: Exit code, will exit if not None (default: None)
stderr: Write to ``stderr`` instead of ``stdout`` (default: True)
file: The file to write to. (default: ``stdout``)
newline: Output new line (default: False)
colorize: Force showing or hiding colors and other styles. By default, click will remove color
if the output does not look like an interactive terminal (default: ``COLORIZE`` environment variable)
"""
msg_click_typer()
click.echo(
" ".join([
self.value,
click.style(f"{first}{separator if other else ''}", **COLOR_FIRST_OTHER["first"]),
click.style(other, **COLOR_FIRST_OTHER["other"]),
]),
err=stderr, file=Path(file) if file else file, nl=newline, color=colorize or COLORIZE
)
if exit is not None:
raise typer.Exit(exit)
[docs]
class Symbol(_Symbol):
""":func:`click.echo` and :func:`click.style` wrapper class for :data:`SYMBOLS`.
Examples:
>>> from nodeps import Symbol
>>>
>>> Symbol.OK() # OK
>>>
>>> Symbol.OK("Install") # OK Install
>>>
>>> Symbol.OK("Install", "Complete") # OK Install: Complete
>>>
>>> Symbol.OK("Install", "Complete", stderr=False) # doctest: +SKIP
>>>
>>> Symbol.OK("Debug", "Error", " |", stderr=False) # doctest: +SKIP
>>>
>>> Symbol.OK("Value", "2", " ==", file="/tmp/test.txt") # doctest: +SKIP
>>>
>>> Symbol.OK("Value", "2", newline=False) # OK Value: 2
"""
CRITICAL = enum.auto()
"""symbol: '…', color: YELLOW (blink)"""
ERROR = enum.auto()
"""symbol: '✘', color: RED"""
OK = enum.auto()
"""symbol: '✔', color: GREEN"""
NOTICE = enum.auto()
"""symbol: '‼', color: CYAN"""
SUCCESS = enum.auto()
"""symbol: '◉', color: BLUE"""
VERBOSE = enum.auto()
"""symbol: '+', color: MAGENTA""" # noqa: RUF001
WARNING = enum.auto()
"""symbol: '!', color: YELLOW""" # noqa: RUF001
MINUS = enum.auto()
"""letter: '-', color: RED"""
MORE = enum.auto()
"""letter: '>, color: MAGENTA"""
MULTIPLY = enum.auto()
"""letter: 'x', color: BLUE"""
PLUS = enum.auto()
"""letter: '+', color: GREEN"""
WAIT = enum.auto()
"""symbol: '…', color: YELLOW (blink)"""