aboutsummaryrefslogtreecommitdiff
path: root/prometheus_borgmatic_exporter/cli.py
diff options
context:
space:
mode:
Diffstat (limited to 'prometheus_borgmatic_exporter/cli.py')
-rw-r--r--prometheus_borgmatic_exporter/cli.py143
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,
+ )