diff options
Diffstat (limited to '')
| -rw-r--r-- | prometheus_borgmatic_exporter/cli.py | 143 |
1 files changed, 94 insertions, 49 deletions
diff --git a/prometheus_borgmatic_exporter/cli.py b/prometheus_borgmatic_exporter/cli.py index cc6f374..5d91b05 100644 --- a/prometheus_borgmatic_exporter/cli.py +++ b/prometheus_borgmatic_exporter/cli.py @@ -2,6 +2,12 @@ # # SPDX-License-Identifier: BSD-3-Clause +"""Define Click parameter types and styled terminal message helpers.""" + +from collections.abc import Callable +from functools import update_wrapper +from typing import Any, Concatenate + import click_extra as click @@ -11,22 +17,40 @@ class FlexibleColorOption(click.ColorOption): _gnu_optional_value = False -def emit(prefix: str, color: str, *message: str, enabled: bool = True) -> None: - """Render a styled prefix and message to stdout. +def pass_obj_silent[T, **P, R]( + f: Callable[Concatenate[T | None, P], R], +) -> Callable[P, R]: + """Pass the current context object to a callback, if available. + + The current Click context is retrieved silently. If no context exists or + its object is not a dictionary, ``None`` is passed instead. + + The context object is inserted as the first positional argument to *f*. + """ - Internal helper used by error(), msg(), warn(), verbose(), and debug() - to avoid duplicating the Click styling and echo logic. The first message - argument is rendered in bold, subsequent arguments are appended unstyled. + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = click.get_current_context(silent=True) + obj = ctx.obj if ctx is not None else None + return f(obj, *args, **kwargs) - Args: - prefix: The prefix string shown before the message (e.g. "==>", "==> ERROR:"). - color: A Click-compatible color name (e.g. "red", "green") applied to the prefix. - *message: One or more message parts to display. At least one is required. - enabled: If False, the function returns immediately without printing. - Defaults to True. + return update_wrapper(new_func, f) - Raises: - TypeError: If no message arguments are provided. + +def emit( + prefix: str, color: str, *message: str, enabled: bool = True, err: bool = False +) -> None: + """Render a styled prefix and message to stdout. + + This helper is used by :func:`error`, :func:`msg`, :func:`warn`, + :func:`verbose`, and :func:`debug`. The first message argument is rendered in + bold and subsequent arguments are appended unstyled. + + :param prefix: Prefix displayed before the message. + :param color: Click-compatible color name applied to the prefix. + :param message: One or more message parts to display. + :param enabled: Whether the message should be emitted. + :param err: Write to `stderr` instead of `stdout`. + :raises TypeError: If no message arguments are provided. """ if not message: raise TypeError("emit() missing 1 required positional argument: 'message'") @@ -41,76 +65,97 @@ def emit(prefix: str, color: str, *message: str, enabled: bool = True) -> None: click.style(message[0], bold=True), *message[1:], ] - ) + ), + err=err, ) def error(*message: str) -> None: """Print a formatted error message to stdout in red. - The first argument is rendered in bold, subsequent arguments are appended - unstyled. Always prints regardless of quiet or verbose flags. + The first argument is rendered in bold and subsequent arguments are appended + unstyled. Error messages are always printed regardless of quiet or verbose + flags. - Args: - *message: One or more message parts to display. + :param message: One or more message parts to display. """ - emit("==> ERROR:", "red", *message) + emit("==> ERROR:", "red", *message, err=True) -@click.pass_obj -def msg(obj: dict[str, bool], *message: str) -> None: +@pass_obj_silent +def msg(obj: dict[str, Any] | None, *message: str, err: bool = False) -> None: """Print a formatted informational message to stdout in green. - Suppressed when the quiet flag is set. The first argument is rendered - in bold, subsequent arguments are appended unstyled. + The message is suppressed when the quiet flag is set. When called outside a + Click context, the message is emitted normally. The first argument is rendered + in bold and subsequent arguments are appended unstyled. - Args: - *message: One or more message parts to display. + :param message: One or more message parts to display. """ - emit("==>", "green", *message, enabled=not obj.get("quiet", False)) + emit( + "==>", + "green", + *message, + enabled=obj is None or not obj.get("quiet", False), + err=err, + ) -@click.pass_obj -def warn(obj: dict[str, bool], *message: str) -> None: +@pass_obj_silent +def warn(obj: dict[str, Any] | None, *message: str, err: bool = False) -> None: """Print a formatted warning message to stdout in yellow. - Suppressed when the quiet flag is set. The first argument is rendered - in bold, subsequent arguments are appended unstyled. + The message is suppressed when the quiet flag is set. When called outside a + Click context, the message is emitted normally. The first argument is rendered + in bold and subsequent arguments are appended unstyled. - Args: - *message: One or more message parts to display. + :param message: One or more message parts to display. """ - emit("==>", "yellow", *message, enabled=not obj.get("quiet", False)) + emit( + "==>", + "yellow", + *message, + enabled=obj is None or not obj.get("quiet", False), + err=err, + ) -@click.pass_obj -def verbose(obj: dict[str, bool], *message: str) -> None: +@pass_obj_silent +def verbose(obj: dict[str, Any] | None, *message: str, err: bool = False) -> None: """Print a formatted verbose message to stdout in blue. - Only prints when the verbose flag is set and the quiet flag is not. - The first argument is rendered in bold, subsequent arguments are - appended unstyled. + The message is printed only when called from a Click context with the verbose + flag set and the quiet flag unset. Calls made outside a Click context are a + no-op. - Args: - *message: One or more message parts to display. + :param message: One or more message parts to display. """ emit( "==>", "blue", *message, - enabled=obj.get("verbose", False) and not obj.get("quiet", False), + enabled=( + obj is not None + and obj.get("verbose", False) + and not obj.get("quiet", False) + ), + err=err, ) -@click.pass_obj -def debug(obj: dict[str, bool], *message: str) -> None: +@pass_obj_silent +def debug(obj: dict[str, Any] | None, *message: str, err: bool = False) -> None: """Print a formatted debug message to stdout in magenta. - Only prints when the DEBUG environment variable is set to a truthy - value (1, true, yes). The first argument is rendered in bold, - subsequent arguments are appended unstyled. + The message is printed only when called from a Click context with the debug + flag enabled. Calls made outside a Click context are a no-op. - Args: - *message: One or more message parts to display. + :param message: One or more message parts to display. """ - emit("==>", "magenta", *message, enabled=obj.get("debug", False)) + emit( + "==>", + "magenta", + *message, + enabled=obj is not None and obj.get("debug", False), + err=err, + ) |
