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}