Skip to main content

midenc_log/writer/
mod.rs

1mod buffer;
2mod target;
3
4use std::{io, mem, sync::Mutex};
5
6pub(crate) use buffer::Buffer;
7use buffer::BufferWriter;
8pub use target::Target;
9
10/// Whether or not to print styles to the target.
11#[allow(clippy::exhaustive_enums)] // By definition don't need more
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)]
13pub enum WriteStyle {
14    /// Try to print styles, but don't force the issue.
15    #[default]
16    Auto,
17    /// Try very hard to print styles.
18    Always,
19    /// Never print styles.
20    Never,
21}
22
23#[cfg(feature = "color")]
24impl From<anstream::ColorChoice> for WriteStyle {
25    fn from(choice: anstream::ColorChoice) -> Self {
26        match choice {
27            anstream::ColorChoice::Auto => Self::Auto,
28            anstream::ColorChoice::Always => Self::Always,
29            anstream::ColorChoice::AlwaysAnsi => Self::Always,
30            anstream::ColorChoice::Never => Self::Never,
31        }
32    }
33}
34
35#[cfg(feature = "color")]
36impl From<WriteStyle> for anstream::ColorChoice {
37    fn from(choice: WriteStyle) -> Self {
38        match choice {
39            WriteStyle::Auto => anstream::ColorChoice::Auto,
40            WriteStyle::Always => anstream::ColorChoice::Always,
41            WriteStyle::Never => anstream::ColorChoice::Never,
42        }
43    }
44}
45
46/// A terminal target with color awareness.
47#[derive(Debug)]
48pub(crate) struct Writer {
49    inner: BufferWriter,
50}
51
52impl Writer {
53    pub(crate) fn write_style(&self) -> WriteStyle {
54        self.inner.write_style()
55    }
56
57    pub(crate) fn buffer(&self) -> Buffer {
58        self.inner.buffer()
59    }
60
61    pub(crate) fn print(&self, buf: &Buffer) -> io::Result<()> {
62        self.inner.print(buf)
63    }
64}
65
66/// A builder for a terminal writer.
67///
68/// The target and style choice can be configured before building.
69#[derive(Debug)]
70pub(crate) struct Builder {
71    target: Target,
72    write_style: WriteStyle,
73    is_test: bool,
74    built: bool,
75}
76
77impl Builder {
78    /// Initialize the writer builder with defaults.
79    pub(crate) fn new() -> Self {
80        Builder {
81            target: Default::default(),
82            write_style: Default::default(),
83            is_test: false,
84            built: false,
85        }
86    }
87
88    /// Set the target to write to.
89    pub(crate) fn target(&mut self, target: Target) -> &mut Self {
90        self.target = target;
91        self
92    }
93
94    /// Parses a style choice string.
95    ///
96    /// See the [Disabling colors] section for more details.
97    ///
98    /// [Disabling colors]: ../index.html#disabling-colors
99    pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self {
100        self.write_style(parse_write_style(write_style))
101    }
102
103    /// Whether or not to print style characters when writing.
104    pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
105        self.write_style = write_style;
106        self
107    }
108
109    /// Whether or not to capture logs for `cargo test`.
110    #[allow(clippy::wrong_self_convention)]
111    pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self {
112        self.is_test = is_test;
113        self
114    }
115
116    /// Build a terminal writer.
117    pub(crate) fn build(&mut self) -> Writer {
118        assert!(!self.built, "attempt to re-use consumed builder");
119        self.built = true;
120
121        let color_choice = self.write_style;
122        #[cfg(feature = "auto-color")]
123        let color_choice = if color_choice == WriteStyle::Auto {
124            match &self.target {
125                Target::Stdout => anstream::AutoStream::choice(&io::stdout()).into(),
126                Target::Stderr => anstream::AutoStream::choice(&io::stderr()).into(),
127                Target::Pipe(_) => color_choice,
128            }
129        } else {
130            color_choice
131        };
132        let color_choice = if color_choice == WriteStyle::Auto {
133            WriteStyle::Never
134        } else {
135            color_choice
136        };
137
138        let writer = match mem::take(&mut self.target) {
139            Target::Stdout => BufferWriter::stdout(self.is_test, color_choice),
140            Target::Stderr => BufferWriter::stderr(self.is_test, color_choice),
141            Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe)), color_choice),
142        };
143
144        Writer { inner: writer }
145    }
146}
147
148impl Default for Builder {
149    fn default() -> Self {
150        Builder::new()
151    }
152}
153
154fn parse_write_style(spec: &str) -> WriteStyle {
155    match spec {
156        "auto" => WriteStyle::Auto,
157        "always" => WriteStyle::Always,
158        "never" => WriteStyle::Never,
159        _ => Default::default(),
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn parse_write_style_valid() {
169        let inputs = vec![
170            ("auto", WriteStyle::Auto),
171            ("always", WriteStyle::Always),
172            ("never", WriteStyle::Never),
173        ];
174
175        for (input, expected) in inputs {
176            assert_eq!(expected, parse_write_style(input));
177        }
178    }
179
180    #[test]
181    fn parse_write_style_invalid() {
182        let inputs = vec!["", "true", "false", "NEVER!!"];
183
184        for input in inputs {
185            assert_eq!(WriteStyle::Auto, parse_write_style(input));
186        }
187    }
188}