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#[allow(clippy::exhaustive_enums)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)]
13pub enum WriteStyle {
14 #[default]
16 Auto,
17 Always,
19 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#[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#[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 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 pub(crate) fn target(&mut self, target: Target) -> &mut Self {
90 self.target = target;
91 self
92 }
93
94 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 pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
105 self.write_style = write_style;
106 self
107 }
108
109 #[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 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}