sqry_cli/output/
stream.rs1use super::pager::{BufferedOutput, PagerConfig, PagerExitStatus};
4use std::io::{self, Write};
5
6enum StdoutBackend {
8 Direct(Box<dyn Write + Send>),
10 Pager(BufferedOutput),
12}
13
14pub struct OutputStreams {
20 stdout: StdoutBackend,
21 stderr: Box<dyn Write + Send>,
22}
23
24impl OutputStreams {
25 #[must_use]
27 pub fn new() -> Self {
28 Self {
29 stdout: StdoutBackend::Direct(Box::new(io::stdout())),
30 stderr: Box::new(io::stderr()),
31 }
32 }
33
34 #[must_use]
41 pub fn with_pager(config: PagerConfig) -> Self {
42 Self {
43 stdout: StdoutBackend::Pager(BufferedOutput::new(config)),
44 stderr: Box::new(io::stderr()),
45 }
46 }
47
48 #[cfg(test)]
50 #[allow(dead_code)] pub fn with_writers<W1, W2>(stdout: W1, stderr: W2) -> Self
52 where
53 W1: Write + Send + 'static,
54 W2: Write + Send + 'static,
55 {
56 Self {
57 stdout: StdoutBackend::Direct(Box::new(stdout)),
58 stderr: Box::new(stderr),
59 }
60 }
61
62 pub fn write_result(&mut self, content: &str) -> io::Result<()> {
67 match &mut self.stdout {
68 StdoutBackend::Direct(writer) => writeln!(writer, "{content}"),
69 StdoutBackend::Pager(buffer) => {
70 buffer.write(content)?;
71 buffer.write("\n")
72 }
73 }
74 }
75
76 pub fn write_diagnostic(&mut self, content: &str) -> io::Result<()> {
81 writeln!(self.stderr, "{content}")
82 }
83
84 #[allow(dead_code)]
86 pub fn flush_stderr(&mut self) -> io::Result<()> {
90 self.stderr.flush()
91 }
92
93 pub fn finish(self) -> io::Result<PagerExitStatus> {
102 match self.stdout {
103 StdoutBackend::Direct(_) => Ok(PagerExitStatus::Success),
104 StdoutBackend::Pager(buffer) => buffer.finish(),
105 }
106 }
107
108 pub fn finish_checked(self) -> anyhow::Result<()> {
119 let status = self.finish()?;
120 if let Some(code) = status.exit_code() {
121 return Err(crate::error::CliError::pager_exit(code).into());
122 }
123 Ok(())
124 }
125}
126
127impl Default for OutputStreams {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133#[cfg(test)]
135pub struct TestOutputStreams {
136 pub stdout: std::sync::Arc<std::sync::Mutex<Vec<u8>>>,
137 pub stderr: std::sync::Arc<std::sync::Mutex<Vec<u8>>>,
138}
139
140#[cfg(test)]
141impl TestOutputStreams {
142 #[must_use]
143 pub fn new() -> (Self, OutputStreams) {
144 let stdout = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
145 let stderr = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
146
147 let test = Self {
148 stdout: std::sync::Arc::clone(&stdout),
149 stderr: std::sync::Arc::clone(&stderr),
150 };
151
152 let streams = OutputStreams {
153 stdout: StdoutBackend::Direct(Box::new(SharedWriter(stdout))),
154 stderr: Box::new(SharedWriter(stderr)),
155 };
156
157 (test, streams)
158 }
159
160 #[must_use]
166 pub fn stdout_string(&self) -> String {
167 let guard = self.stdout.lock().unwrap();
168 String::from_utf8_lossy(&guard).to_string()
169 }
170
171 #[must_use]
177 pub fn stderr_string(&self) -> String {
178 let guard = self.stderr.lock().unwrap();
179 String::from_utf8_lossy(&guard).to_string()
180 }
181}
182
183#[cfg(test)]
184struct SharedWriter(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
185
186#[cfg(test)]
187impl Write for SharedWriter {
188 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
189 let mut guard = self.0.lock().unwrap();
190 guard.extend_from_slice(buf);
191 Ok(buf.len())
192 }
193
194 fn flush(&mut self) -> io::Result<()> {
195 Ok(())
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_output_streams_creation() {
205 let _streams = OutputStreams::new();
206 }
208
209 #[test]
210 fn test_default() {
211 let _streams = OutputStreams::default();
212 }
213
214 #[test]
215 fn test_output_streams_capture() {
216 let (test, mut streams) = TestOutputStreams::new();
217
218 streams.write_result("hello").unwrap();
219 streams.write_diagnostic("world").unwrap();
220
221 assert_eq!(test.stdout_string(), "hello\n");
222 assert_eq!(test.stderr_string(), "world\n");
223 }
224
225 #[test]
226 fn test_finish_non_pager_returns_success() {
227 let streams = OutputStreams::new();
228 let status = streams.finish().unwrap();
229 assert!(status.is_success());
230 }
231}