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 pub fn new() -> (Self, OutputStreams) {
143 let stdout = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
144 let stderr = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
145
146 let test = Self {
147 stdout: std::sync::Arc::clone(&stdout),
148 stderr: std::sync::Arc::clone(&stderr),
149 };
150
151 let streams = OutputStreams {
152 stdout: StdoutBackend::Direct(Box::new(SharedWriter(stdout))),
153 stderr: Box::new(SharedWriter(stderr)),
154 };
155
156 (test, streams)
157 }
158
159 pub fn stdout_string(&self) -> String {
160 let guard = self.stdout.lock().unwrap();
161 String::from_utf8_lossy(&guard).to_string()
162 }
163
164 pub fn stderr_string(&self) -> String {
165 let guard = self.stderr.lock().unwrap();
166 String::from_utf8_lossy(&guard).to_string()
167 }
168}
169
170#[cfg(test)]
171struct SharedWriter(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
172
173#[cfg(test)]
174impl Write for SharedWriter {
175 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
176 let mut guard = self.0.lock().unwrap();
177 guard.extend_from_slice(buf);
178 Ok(buf.len())
179 }
180
181 fn flush(&mut self) -> io::Result<()> {
182 Ok(())
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_output_streams_creation() {
192 let _streams = OutputStreams::new();
193 }
195
196 #[test]
197 fn test_default() {
198 let _streams = OutputStreams::default();
199 }
200
201 #[test]
202 fn test_output_streams_capture() {
203 let (test, mut streams) = TestOutputStreams::new();
204
205 streams.write_result("hello").unwrap();
206 streams.write_diagnostic("world").unwrap();
207
208 assert_eq!(test.stdout_string(), "hello\n");
209 assert_eq!(test.stderr_string(), "world\n");
210 }
211
212 #[test]
213 fn test_finish_non_pager_returns_success() {
214 let streams = OutputStreams::new();
215 let status = streams.finish().unwrap();
216 assert!(status.is_success());
217 }
218}