env_logger/fmt/writer/
mod.rs

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