env_logger/writer/
mod.rs

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