ralph_workflow/json_parser/
printer.rs1use std::cell::RefCell;
8use std::io::{self, IsTerminal, Stdout};
9use std::rc::Rc;
10
11#[cfg(test)]
12use std::io::Stderr;
13
14pub trait Printable: std::io::Write {
20 fn is_terminal(&self) -> bool;
25}
26
27#[derive(Debug)]
29pub struct StdoutPrinter {
30 stdout: Stdout,
31 is_terminal: bool,
32}
33
34impl StdoutPrinter {
35 pub fn new() -> Self {
37 let is_terminal = std::io::stdout().is_terminal();
38 Self {
39 stdout: std::io::stdout(),
40 is_terminal,
41 }
42 }
43}
44
45impl Default for StdoutPrinter {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl std::io::Write for StdoutPrinter {
52 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
53 self.stdout.write(buf)
54 }
55
56 fn flush(&mut self) -> io::Result<()> {
57 self.stdout.flush()
58 }
59}
60
61impl Printable for StdoutPrinter {
62 fn is_terminal(&self) -> bool {
63 self.is_terminal
64 }
65}
66
67#[derive(Debug)]
69#[cfg(test)]
70pub struct StderrPrinter {
71 stderr: Stderr,
72 is_terminal: bool,
73}
74
75#[cfg(test)]
76impl StderrPrinter {
77 pub fn new() -> Self {
79 let is_terminal = std::io::stderr().is_terminal();
80 Self {
81 stderr: std::io::stderr(),
82 is_terminal,
83 }
84 }
85}
86
87#[cfg(test)]
88impl Default for StderrPrinter {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94#[cfg(test)]
95impl std::io::Write for StderrPrinter {
96 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
97 self.stderr.write(buf)
98 }
99
100 fn flush(&mut self) -> io::Result<()> {
101 self.stderr.flush()
102 }
103}
104
105#[cfg(test)]
106impl Printable for StderrPrinter {
107 fn is_terminal(&self) -> bool {
108 self.is_terminal
109 }
110}
111
112#[cfg(test)]
117#[derive(Debug, Default)]
118pub struct TestPrinter {
119 output: RefCell<Vec<String>>,
121 buffer: RefCell<String>,
123}
124
125#[cfg(test)]
126impl TestPrinter {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn get_output(&self) -> String {
134 let mut result = self.buffer.borrow().clone();
135 for line in self.output.borrow().iter() {
136 result.push_str(line);
137 }
138 result
139 }
140
141 pub fn get_lines(&self) -> Vec<String> {
143 let mut result: Vec<String> = self.output.borrow().clone();
144 let buffer = self.buffer.borrow();
145 if !buffer.is_empty() {
146 result.push(buffer.clone());
147 }
148 result
149 }
150
151 pub fn clear(&self) {
153 self.output.borrow_mut().clear();
154 self.buffer.borrow_mut().clear();
155 }
156
157 pub fn has_line(&self, line: &str) -> bool {
159 self.get_lines().iter().any(|l| l.contains(line))
160 }
161
162 pub fn count_pattern(&self, pattern: &str) -> usize {
164 self.get_lines()
165 .iter()
166 .filter(|l| l.contains(pattern))
167 .count()
168 }
169
170 pub fn has_duplicate_consecutive_lines(&self) -> bool {
172 let lines = self.get_lines();
173 for i in 1..lines.len() {
174 if lines[i] == lines[i - 1] && !lines[i].is_empty() {
175 return true;
176 }
177 }
178 false
179 }
180
181 pub fn find_duplicate_consecutive_lines(&self) -> Vec<(usize, String)> {
183 let mut duplicates = Vec::new();
184 let lines = self.get_lines();
185 for i in 1..lines.len() {
186 if lines[i] == lines[i - 1] && !lines[i].is_empty() {
187 duplicates.push((i - 1, lines[i - 1].clone()));
188 }
189 }
190 duplicates
191 }
192
193 pub fn get_stats(&self) -> (usize, usize) {
197 let lines = self.get_lines();
198 let char_count: usize = lines.iter().map(String::len).sum();
199 (lines.len(), char_count)
200 }
201}
202
203#[cfg(test)]
204impl std::io::Write for TestPrinter {
205 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
206 let s =
207 std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
208 let mut buffer = self.buffer.borrow_mut();
209 buffer.push_str(s);
210
211 while let Some(newline_pos) = buffer.find('\n') {
213 let line = buffer.drain(..=newline_pos).collect::<String>();
214 self.output.borrow_mut().push(line);
215 }
216
217 Ok(buf.len())
218 }
219
220 fn flush(&mut self) -> io::Result<()> {
221 let mut buffer = self.buffer.borrow_mut();
223 if !buffer.is_empty() {
224 self.output.borrow_mut().push(buffer.clone());
225 buffer.clear();
226 }
227 Ok(())
228 }
229}
230
231#[cfg(test)]
232impl Printable for TestPrinter {
233 fn is_terminal(&self) -> bool {
234 false
236 }
237}
238
239pub type SharedPrinter = Rc<RefCell<dyn Printable>>;
244
245pub fn shared_stdout() -> SharedPrinter {
247 Rc::new(RefCell::new(StdoutPrinter::new()))
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use std::io::Write;
254
255 #[test]
256 fn test_stdout_printer() {
257 let mut printer = StdoutPrinter::new();
258 let result = printer.write_all(b"test\n");
260 assert!(result.is_ok());
261 assert!(printer.flush().is_ok());
262
263 let _is_term = printer.is_terminal();
265 }
266
267 #[cfg(test)]
268 #[test]
269 fn test_printable_trait_is_terminal() {
270 let printer = StdoutPrinter::new();
271 let _should_use_colors = printer.is_terminal();
273 }
274
275 #[test]
276 #[cfg(test)]
277 fn test_stderr_printer() {
278 let mut printer = StderrPrinter::new();
279 let result = printer.write_all(b"test\n");
281 assert!(result.is_ok());
282 assert!(printer.flush().is_ok());
283 }
284
285 #[test]
286 #[cfg(test)]
287 fn test_printer_captures_output() {
288 let mut printer = TestPrinter::new();
289
290 printer
291 .write_all(b"Hello World\n")
292 .expect("Failed to write");
293 printer.flush().expect("Failed to flush");
294
295 let output = printer.get_output();
296 assert!(output.contains("Hello World"));
297 }
298
299 #[test]
300 #[cfg(test)]
301 fn test_printer_get_lines() {
302 let mut printer = TestPrinter::new();
303
304 printer.write_all(b"Line 1\nLine 2\n").unwrap();
305 printer.flush().unwrap();
306
307 let lines = printer.get_lines();
308 assert_eq!(lines.len(), 2);
309 assert!(lines[0].contains("Line 1"));
310 assert!(lines[1].contains("Line 2"));
311 }
312
313 #[test]
314 #[cfg(test)]
315 fn test_printer_clear() {
316 let mut printer = TestPrinter::new();
317
318 printer.write_all(b"Before\n").unwrap();
319 printer.flush().unwrap();
320
321 assert!(!printer.get_output().is_empty());
322
323 printer.clear();
324 assert!(printer.get_output().is_empty());
325 }
326
327 #[cfg(test)]
328 #[test]
329 fn test_printer_has_line() {
330 let mut printer = TestPrinter::new();
331
332 printer.write_all(b"Hello World\n").unwrap();
333 printer.flush().unwrap();
334
335 assert!(printer.has_line("Hello"));
336 assert!(printer.has_line("World"));
337 assert!(!printer.has_line("Goodbye"));
338 }
339
340 #[cfg(test)]
341 #[test]
342 fn test_printer_count_pattern() {
343 let mut printer = TestPrinter::new();
344
345 printer.write_all(b"test\nmore test\ntest again\n").unwrap();
346 printer.flush().unwrap();
347
348 assert_eq!(printer.count_pattern("test"), 3);
349 }
350
351 #[cfg(test)]
352 #[test]
353 fn test_printer_detects_duplicates() {
354 let mut printer = TestPrinter::new();
355
356 printer.write_all(b"Line 1\nLine 1\nLine 2\n").unwrap();
357 printer.flush().unwrap();
358
359 assert!(printer.has_duplicate_consecutive_lines());
360 }
361
362 #[cfg(test)]
363 #[test]
364 fn test_printer_finds_duplicates() {
365 let mut printer = TestPrinter::new();
366
367 printer
368 .write_all(b"Line 1\nLine 1\nLine 2\nLine 3\nLine 3\n")
369 .unwrap();
370 printer.flush().unwrap();
371
372 let duplicates = printer.find_duplicate_consecutive_lines();
373 assert_eq!(duplicates.len(), 2);
374 assert_eq!(duplicates[0].0, 0); assert_eq!(duplicates[0].1, "Line 1\n");
376 assert_eq!(duplicates[1].0, 3); assert_eq!(duplicates[1].1, "Line 3\n");
378 }
379
380 #[cfg(test)]
381 #[test]
382 fn test_printer_no_false_positives() {
383 let mut printer = TestPrinter::new();
384
385 printer.write_all(b"Line 1\nLine 2\nLine 3\n").unwrap();
386 printer.flush().unwrap();
387
388 assert!(!printer.has_duplicate_consecutive_lines());
389 }
390
391 #[cfg(test)]
392 #[test]
393 fn test_printer_buffer_handling() {
394 let mut printer = TestPrinter::new();
395
396 printer.write_all(b"Partial").unwrap();
398
399 assert!(printer.get_output().contains("Partial"));
402
403 printer.write_all(b" content\n").unwrap();
405 printer.flush().unwrap();
406
407 assert!(printer.has_line("Partial content"));
409
410 let output = printer.get_output();
412 assert!(output.contains("Partial content\n"));
413 }
414
415 #[cfg(test)]
416 #[test]
417 fn test_printer_get_stats() {
418 let mut printer = TestPrinter::new();
419
420 printer.write_all(b"Line 1\nLine 2\n").unwrap();
421 printer.flush().unwrap();
422
423 let (line_count, char_count) = printer.get_stats();
424 assert_eq!(line_count, 2);
425 assert!(char_count > 0);
426 }
427
428 #[test]
429 fn test_shared_stdout() {
430 let printer = shared_stdout();
431 let _borrowed = printer.borrow();
433 }
434}