Source code for hubvault.entry.style

"""
ANSI-aware CLI styling helpers for :mod:`hubvault.entry`.

This module centralizes optional terminal styling so command implementations do
not need to repeat environment checks or ad hoc color branching. Styling stays
strictly optional and can be disabled completely through standard or
repository-specific environment variables.

The module contains:

* :func:`colors_enabled` - Decide whether ANSI styling should be emitted
* :func:`style_text` - Style one text fragment when colors are enabled
* :func:`echo` - Click-backed output helper with centralized color handling

Example::

    >>> style_text("hello", tone="success")  # doctest: +ELLIPSIS
    '\\x1b...hello\\x1b[0m'
    >>> style_text("hello", tone="success", env={"NO_COLOR": "1"})
    'hello'

.. note::
   Styling is disabled whenever ``NO_COLOR`` or ``HUBVAULT_NO_COLOR`` is
   present in the environment.
"""

import os
from typing import IO, Mapping, Optional

import click

_TONE_STYLES = {
    "success": {"fg": "green", "bold": True},
    "warning": {"fg": "yellow", "bold": True},
    "error": {"fg": "red", "bold": True},
    "accent": {"fg": "cyan", "bold": True},
}


[docs] def colors_enabled(env: Optional[Mapping[str, str]] = None) -> bool: """ Return whether CLI ANSI styling is enabled. :param env: Environment mapping to inspect. Defaults to :data:`os.environ` :type env: Optional[Mapping[str, str]], optional :return: ``True`` when styling may be emitted :rtype: bool Example:: >>> colors_enabled(env={}) True >>> colors_enabled(env={"HUBVAULT_NO_COLOR": "1"}) False """ env = os.environ if env is None else env return "NO_COLOR" not in env and "HUBVAULT_NO_COLOR" not in env
[docs] def style_text( text: object, tone: Optional[str] = None, env: Optional[Mapping[str, str]] = None, **style_kwargs: object ) -> str: """ Style one text fragment when ANSI output is enabled. :param text: Source text fragment :type text: object :param tone: Named style tone such as ``"success"`` or ``"error"`` :type tone: Optional[str], optional :param env: Environment mapping used for color-disable detection :type env: Optional[Mapping[str, str]], optional :param style_kwargs: Extra :func:`click.style` keyword arguments :type style_kwargs: object :return: Styled or plain text :rtype: str Example:: >>> style_text("warning", tone="warning", env={"NO_COLOR": "1"}) 'warning' """ text = str(text) if not colors_enabled(env=env): return text merged_kwargs = dict(_TONE_STYLES.get(str(tone), {})) merged_kwargs.update(style_kwargs) if not merged_kwargs: return text return click.style(text, **merged_kwargs)
[docs] def echo( message: Optional[object] = None, tone: Optional[str] = None, file: Optional[IO] = None, err: bool = False, nl: bool = True, color: Optional[bool] = None, env: Optional[Mapping[str, str]] = None, **style_kwargs: object ) -> None: """ Emit CLI output with centralized optional ANSI styling. :param message: Text to print :type message: Optional[object] :param tone: Named style tone such as ``"success"`` or ``"error"`` :type tone: Optional[str], optional :param file: Optional output stream :type file: Optional[IO], optional :param err: Whether to write to stderr, defaults to ``False`` :type err: bool, optional :param nl: Whether to append a trailing newline, defaults to ``True`` :type nl: bool, optional :param color: Explicit Click color mode. When omitted, color is disabled automatically if the environment requests plain output. :type color: Optional[bool], optional :param env: Environment mapping used for color-disable detection :type env: Optional[Mapping[str, str]], optional :param style_kwargs: Extra :func:`click.style` keyword arguments :type style_kwargs: object :return: ``None``. :rtype: None Example:: >>> echo("plain output", env={"NO_COLOR": "1"}) # doctest: +SKIP """ environment = os.environ if env is None else env if message is not None: message = style_text(message, tone=tone, env=environment, **style_kwargs) if color is None: if not colors_enabled(env=environment): color = False else: context = click.get_current_context(silent=True) if context is not None: color = context.color click.echo(message=message, file=file, err=err, nl=nl, color=color)