Skip to main content

clawless_cli/presenter/
mod.rs

1//! Output port for rendering command output
2//!
3//! This module defines the [`Presenter`] trait, the output port in the hexagonal architecture. A
4//! `Presenter` wraps command execution and controls the output lifecycle. The framework calls
5//! [`present`] with the command future, and the presenter runs the command, optionally consuming
6//! events from its event channel, and returns the command's result.
7//!
8//! Presenter adapters implement this trait to provide different rendering strategies. A stateless
9//! adapter renders each event as it arrives; a stateful adapter queries a surface on each render
10//! frame. The command's code is identical regardless of which adapter is in use.
11//!
12//! [`present`]: Presenter::present
13
14use std::future::Future;
15use std::pin::Pin;
16
17use async_trait::async_trait;
18
19pub use self::terminal::TerminalPresenter;
20use crate::error::CommandResult;
21
22mod terminal;
23
24/// Output port for rendering command output
25///
26/// A `Presenter` wraps command execution and controls the output lifecycle. The framework
27/// constructs a presenter with its dependencies (such as an [`EventReceiver`] and rendering
28/// configuration), then calls [`present`] with the command future. The presenter runs the
29/// command, optionally consuming events from its receiver, and returns the command's result.
30///
31/// `present` takes `self` by value because presentation is a one-shot operation. Each presenter
32/// is constructed once, used once, and consumed. This encodes the "call once" invariant in the
33/// type system and avoids interior mutability for resources like [`EventReceiver`] that require
34/// exclusive access for reading.
35///
36/// The trait does not require [`Send`] or [`Sync`] because the presenter lives on the main task
37/// and is never shared across threads.
38///
39/// # Examples
40///
41/// ```rust,ignore
42/// use clawless::presenter::{Presenter, TerminalPresenter};
43///
44/// let presenter = TerminalPresenter::builder().receiver(receiver).build();
45/// presenter.present(Box::pin(command_future)).await?;
46/// ```
47///
48/// [`EventReceiver`]: clawless_core::event::EventReceiver
49/// [`present`]: Presenter::present
50#[async_trait(?Send)]
51pub trait Presenter {
52    /// Presents the output of a command
53    ///
54    /// Runs the given command future to completion and returns its result. Implementations may
55    /// consume events from their [`EventReceiver`] concurrently with command execution to render
56    /// output in real time.
57    ///
58    /// The command future is boxed and pinned because the concrete future type varies by call
59    /// site. The future is [`Send`] because commands execute on Tokio's multi-threaded runtime.
60    ///
61    /// # Errors
62    ///
63    /// Returns the command's error if the command fails. Implementations propagate the
64    /// command's [`CommandResult`] without modification.
65    ///
66    /// [`EventReceiver`]: clawless_core::event::EventReceiver
67    async fn present(
68        self,
69        command: Pin<Box<dyn Future<Output = CommandResult> + Send>>,
70    ) -> CommandResult;
71}