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;
18use clawless_core::exit::Exit;
19
20pub use self::terminal::TerminalPresenter;
21use crate::error::CommandResult;
22
23mod line_question;
24mod line_reader;
25mod terminal;
26
27/// Output port for rendering command output
28///
29/// A `Presenter` wraps command execution and controls the output lifecycle. The framework
30/// constructs a presenter with its dependencies (such as an [`EventReceiver`] and rendering
31/// configuration), then calls [`present`] with the command future. The presenter runs the
32/// command, optionally consuming events from its receiver, and returns the command's result.
33///
34/// `present` takes `self` by value because presentation is a one-shot operation. Each presenter
35/// is constructed once, used once, and consumed. This encodes the "call once" invariant in the
36/// type system and avoids interior mutability for resources like [`EventReceiver`] that require
37/// exclusive access for reading.
38///
39/// The trait does not require [`Send`] or [`Sync`] because the presenter lives on the main task
40/// and is never shared across threads.
41///
42/// # Examples
43///
44/// ```rust,ignore
45/// use clawless::presenter::{Presenter, TerminalPresenter};
46///
47/// let presenter = TerminalPresenter::builder().receiver(receiver).build();
48/// let exit = presenter.present(Box::pin(command_future)).await?;
49/// ```
50///
51/// [`EventReceiver`]: clawless_core::event::EventReceiver
52/// [`present`]: Presenter::present
53#[async_trait(?Send)]
54pub trait Presenter {
55    /// Presents the output of a command
56    ///
57    /// Runs the given command future to completion and returns its result. Implementations may
58    /// consume events from their [`EventReceiver`] concurrently with command execution to render
59    /// output in real time.
60    ///
61    /// An implementation returns only when its rendering is complete. The runner writes the text
62    /// of the returned [`Exit`] after `present` returns, and the text must come after all the
63    /// output of the command.
64    ///
65    /// The command future is boxed and pinned because the concrete future type varies by call
66    /// site. The future is [`Send`] because commands execute on Tokio's multi-threaded runtime.
67    ///
68    /// # Errors
69    ///
70    /// Returns the command's error if the command fails. Implementations propagate the
71    /// command's [`CommandResult`] without modification.
72    ///
73    /// [`EventReceiver`]: clawless_core::event::EventReceiver
74    /// [`Exit`]: clawless_core::exit::Exit
75    async fn present(
76        self,
77        command: Pin<Box<dyn Future<Output = CommandResult<Exit>> + Send>>,
78    ) -> CommandResult<Exit>;
79}