Skip to main content

osp_cli/app/
sink.rs

1//! Terminal-facing output sinks used by the host layer.
2//!
3//! This module exists so host entrypoints can emit stdout/stderr through a
4//! small abstraction that works both for the real terminal and for tests.
5//!
6//! Contract:
7//!
8//! - sinks are intentionally tiny and text-oriented
9//! - buffering, snapshotting, or process stdio forwarding belong here
10//! - higher-level rendering and message formatting belong elsewhere
11
12/// Terminal-facing output sink for stdout/stderr emission.
13///
14/// Implementors should forward or buffer the supplied text exactly as received;
15/// higher layers already handle rendering, grouping, and newline decisions.
16/// Callers may write to stdout and stderr independently and can assume that
17/// empty writes are harmless.
18///
19/// # Examples
20///
21/// ```
22/// use osp_cli::app::UiSink;
23///
24/// #[derive(Default)]
25/// struct CaptureSink {
26///     stdout: String,
27///     stderr: String,
28/// }
29///
30/// impl UiSink for CaptureSink {
31///     fn write_stdout(&mut self, text: &str) {
32///         self.stdout.push_str(text);
33///     }
34///
35///     fn write_stderr(&mut self, text: &str) {
36///         self.stderr.push_str(text);
37///     }
38/// }
39///
40/// let mut sink = CaptureSink::default();
41/// sink.write_stdout("ok");
42/// sink.write_stderr("warn");
43///
44/// assert_eq!(sink.stdout, "ok");
45/// assert_eq!(sink.stderr, "warn");
46/// ```
47pub trait UiSink {
48    /// Writes text to the sink's stdout channel.
49    fn write_stdout(&mut self, text: &str);
50
51    /// Writes text to the sink's stderr channel.
52    fn write_stderr(&mut self, text: &str);
53}
54
55/// Sink that forwards output directly to the process stdio streams.
56///
57/// Empty writes are ignored.
58#[derive(Default)]
59pub struct StdIoUiSink;
60
61impl UiSink for StdIoUiSink {
62    fn write_stdout(&mut self, text: &str) {
63        if !text.is_empty() {
64            print!("{text}");
65        }
66    }
67
68    fn write_stderr(&mut self, text: &str) {
69        if !text.is_empty() {
70            eprint!("{text}");
71        }
72    }
73}
74
75/// Sink that buffers stdout and stderr for assertions and snapshot tests.
76///
77/// # Examples
78///
79/// ```
80/// use osp_cli::app::{BufferedUiSink, UiSink};
81///
82/// let mut sink = BufferedUiSink::default();
83/// sink.write_stdout("ok");
84/// sink.write_stderr("warn");
85///
86/// assert_eq!(sink.stdout, "ok");
87/// assert_eq!(sink.stderr, "warn");
88/// ```
89#[derive(Default, Debug)]
90pub struct BufferedUiSink {
91    /// Buffered stdout content in write order.
92    pub stdout: String,
93
94    /// Buffered stderr content in write order.
95    pub stderr: String,
96}
97
98impl UiSink for BufferedUiSink {
99    fn write_stdout(&mut self, text: &str) {
100        self.stdout.push_str(text);
101    }
102
103    fn write_stderr(&mut self, text: &str) {
104        self.stderr.push_str(text);
105    }
106}