Source code for pun.tui.app

"""TUI application for the project."""

from __future__ import annotations

import sys
from typing import TYPE_CHECKING, ClassVar, final

from textual.app import App, ComposeResult
from textual.containers import ScrollableContainer
from textual.widgets import Static
from typing_extensions import override

from pun.__metadata__ import PROJECT_NAME
from pun.core import app as service
from pun.core.logging_setup import get_logger

if TYPE_CHECKING:
    from collections.abc import Callable

    from textual.binding import BindingType

__all__ = ["InfoApp", "TuiDisplayError", "build_info_message", "main"]

logger = get_logger()


[docs] class TuiDisplayError(RuntimeError): """Raised when the TUI cannot be displayed."""
[docs] def build_info_message() -> str: """Return a short application information message for display. Returns: str: A multi-line summary of the project name, version, and platform. """ payload = service.info_or_unknown() return ( f"{PROJECT_NAME}\n\n" f"Version: {payload['application_version']}\n" f"Python: {payload['python_version']} ({payload['python_implementation']})\n" f"Platform: {payload['platform']}" )
[docs] @final class InfoApp(App[None]): """Simple TUI application displaying project info.""" CSS = """ Screen { align: center middle; background: $surface; } #info-container { width: 60; height: auto; border: solid $primary; background: $panel; } Static { width: 100%; height: auto; padding: 1; } #title { dock: top; height: 3; border-bottom: solid $primary; background: $boost; text-align: center; content-align: center middle; } #footer { dock: bottom; height: 1; border-top: solid $primary; text-align: center; background: $boost; } """ BINDINGS: ClassVar[list[BindingType]] = [("q", "quit", "Quit")] def __init__(self, message: str) -> None: """Initialize the application. Args: message: The formatted information message to display. """ super().__init__() self.message = message
[docs] @override def compose(self) -> ComposeResult: """Compose the TUI layout. Yields: Widget: The widgets that make up the screen layout. """ yield Static(PROJECT_NAME, id="title") with ScrollableContainer(id="info-container"): yield Static(self.message, id="info-text") yield Static("Press 'q' to quit", id="footer")
[docs] def on_mount(self) -> None: """Set focus and style on mount.""" self.title = f"{PROJECT_NAME} Info"
def _run_app(app: InfoApp) -> None: # pragma: no cover """Execute the Textual event loop. The irreducible blocking call separated from UI setup and error handling. """ app.run() def _display_tui(message: str, *, driver: Callable[[InfoApp], None] = _run_app) -> None: """Display the information message in a Textual TUI application. Args: message: The information message to display. driver: Driver callable that runs the application event loop. Defaults to ``_run_app`` (running ``app.run()``). Raises: TuiDisplayError: If Textual fails to run. KeyboardInterrupt: If the user interrupts the running application. SystemExit: If the application requests interpreter exit. """ app = InfoApp(message) try: driver(app) except (KeyboardInterrupt, SystemExit): raise # Let these propagate naturally except Exception as exc: msg = f"Failed to display TUI: {exc}" raise TuiDisplayError(msg) from exc
[docs] def main(*, show_tui: bool = True, driver: Callable[[InfoApp], None] = _run_app) -> int: """Entry point for the TUI script. Args: show_tui: When ``False``, write the info to stdout instead of starting the TUI (useful for headless environments and tests). driver: Optional driver callable for running the TUI app. Returns: int: ``0`` on success, ``1`` if the TUI could not be displayed. """ info_message = build_info_message() logger.info("TUI entry point invoked. show_tui=%s", show_tui) if not show_tui: logger.info("TUI display skipped; writing info to stdout.") _ = sys.stdout.write(f"{info_message}\n") return 0 try: _display_tui(info_message, driver=driver) except TuiDisplayError: logger.exception("Failed to display TUI; falling back to stdout.") _ = sys.stdout.write(f"{info_message}\n") return 1 logger.info("TUI application closed successfully.") return 0