Skip to main content

mago_reporting/
output.rs

1use std::io::Write;
2use std::io::{self};
3use std::sync::Arc;
4use std::sync::Mutex;
5
6/// Target for report output.
7#[derive(strum::Display, strum::EnumString, strum::VariantNames)]
8#[strum(serialize_all = "kebab-case")]
9#[derive(Default)]
10pub enum ReportingTarget {
11    /// Write to standard output.
12    #[strum(serialize = "stdout", serialize = "out")]
13    #[default]
14    Stdout,
15    /// Write to standard error.
16    #[strum(serialize = "stderr", serialize = "err")]
17    Stderr,
18    /// Write to a custom writer.
19    #[strum(disabled)]
20    Writer(Arc<Mutex<Box<dyn Write + Send>>>),
21}
22
23impl Clone for ReportingTarget {
24    fn clone(&self) -> Self {
25        match self {
26            ReportingTarget::Stdout => ReportingTarget::Stdout,
27            ReportingTarget::Stderr => ReportingTarget::Stderr,
28            ReportingTarget::Writer(writer) => ReportingTarget::Writer(writer.clone()),
29        }
30    }
31}
32
33impl std::fmt::Debug for ReportingTarget {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            ReportingTarget::Stdout => write!(f, "ReportingTarget::Stdout"),
37            ReportingTarget::Stderr => write!(f, "ReportingTarget::Stderr"),
38            ReportingTarget::Writer(_) => write!(f, "ReportingTarget::Writer(..)"),
39        }
40    }
41}
42
43impl ReportingTarget {
44    /// Create a target that writes to a shared buffer.
45    ///
46    /// This is useful for testing where you want to capture the output.
47    ///
48    /// # Returns
49    ///
50    /// A tuple of (target, buffer) where the buffer can be read after reporting.
51    #[must_use]
52    pub fn buffer() -> (Self, Arc<Mutex<Vec<u8>>>) {
53        let buffer = Arc::new(Mutex::new(Vec::new()));
54        let writer_buffer = buffer.clone();
55        let target = ReportingTarget::Writer(Arc::new(Mutex::new(Box::new(BufferWriter { buffer: writer_buffer }))));
56        (target, buffer)
57    }
58
59    /// Resolve this target to an actual writer.
60    ///
61    /// # Returns
62    ///
63    /// A boxed writer that can be written to.
64    pub(crate) fn resolve(&self) -> Box<dyn Write + '_> {
65        match self {
66            ReportingTarget::Stdout => Box::new(io::stdout()),
67            ReportingTarget::Stderr => Box::new(io::stderr()),
68            ReportingTarget::Writer(writer) => Box::new(LockedWriter { writer: writer.clone() }),
69        }
70    }
71}
72
73/// A wrapper that provides Write implementation for Arc<Mutex<Box<dyn Write>>>.
74struct LockedWriter {
75    writer: Arc<Mutex<Box<dyn Write + Send>>>,
76}
77
78impl Write for LockedWriter {
79    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
80        self.writer.lock().unwrap().write(buf)
81    }
82
83    fn flush(&mut self) -> io::Result<()> {
84        self.writer.lock().unwrap().flush()
85    }
86}
87
88/// A writer that writes to a shared buffer.
89struct BufferWriter {
90    buffer: Arc<Mutex<Vec<u8>>>,
91}
92
93impl Write for BufferWriter {
94    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
95        self.buffer.lock().unwrap().write(buf)
96    }
97
98    fn flush(&mut self) -> io::Result<()> {
99        self.buffer.lock().unwrap().flush()
100    }
101}