1mod atty;
2mod termcolor;
3
4use self::atty::{is_stderr, is_stdout};
5use self::termcolor::BufferWriter;
6use std::{fmt, io, mem, sync::Mutex};
7
8pub(super) mod glob {
9 pub use super::termcolor::glob::*;
10 pub use super::*;
11}
12
13pub(super) use self::termcolor::Buffer;
14
15/// Log target, either `stdout`, `stderr` or a custom pipe.
16#[non_exhaustive]
17pub enum Target {
18 /// Logs will be sent to standard output.
19 Stdout,
20 /// Logs will be sent to standard error.
21 Stderr,
22 /// Logs will be sent to a custom pipe.
23 Pipe(Box<dyn io::Write + Send + 'static>),
24}
25
26impl Default for Target {
27 fn default() -> Self {
28 Target::Stderr
29 }
30}
31
32impl fmt::Debug for Target {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(
35 f,
36 "{}",
37 match self {
38 Self::Stdout => "stdout",
39 Self::Stderr => "stderr",
40 Self::Pipe(_) => "pipe",
41 }
42 )
43 }
44}
45
46/// Log target, either `stdout`, `stderr` or a custom pipe.
47///
48/// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability.
49pub(super) enum WritableTarget {
50 /// Logs will be sent to standard output.
51 Stdout,
52 /// Logs will be sent to standard error.
53 Stderr,
54 /// Logs will be sent to a custom pipe.
55 Pipe(Box<Mutex<dyn io::Write + Send + 'static>>),
56}
57
58impl From<Target> for WritableTarget {
59 fn from(target: Target) -> Self {
60 match target {
61 Target::Stdout => Self::Stdout,
62 Target::Stderr => Self::Stderr,
63 Target::Pipe(pipe: Box) => Self::Pipe(Box::new(Mutex::new(pipe))),
64 }
65 }
66}
67
68impl Default for WritableTarget {
69 fn default() -> Self {
70 Self::from(Target::default())
71 }
72}
73
74impl fmt::Debug for WritableTarget {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(
77 f,
78 "{}",
79 match self {
80 Self::Stdout => "stdout",
81 Self::Stderr => "stderr",
82 Self::Pipe(_) => "pipe",
83 }
84 )
85 }
86}
87/// Whether or not to print styles to the target.
88#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
89pub enum WriteStyle {
90 /// Try to print styles, but don't force the issue.
91 Auto,
92 /// Try very hard to print styles.
93 Always,
94 /// Never print styles.
95 Never,
96}
97
98impl Default for WriteStyle {
99 fn default() -> Self {
100 WriteStyle::Auto
101 }
102}
103
104/// A terminal target with color awareness.
105pub(crate) struct Writer {
106 inner: BufferWriter,
107 write_style: WriteStyle,
108}
109
110impl Writer {
111 pub fn write_style(&self) -> WriteStyle {
112 self.write_style
113 }
114
115 pub(super) fn buffer(&self) -> Buffer {
116 self.inner.buffer()
117 }
118
119 pub(super) fn print(&self, buf: &Buffer) -> io::Result<()> {
120 self.inner.print(buf)
121 }
122}
123
124/// A builder for a terminal writer.
125///
126/// The target and style choice can be configured before building.
127#[derive(Debug)]
128pub(crate) struct Builder {
129 target: WritableTarget,
130 write_style: WriteStyle,
131 is_test: bool,
132 built: bool,
133}
134
135impl Builder {
136 /// Initialize the writer builder with defaults.
137 pub(crate) fn new() -> Self {
138 Builder {
139 target: Default::default(),
140 write_style: Default::default(),
141 is_test: false,
142 built: false,
143 }
144 }
145
146 /// Set the target to write to.
147 pub(crate) fn target(&mut self, target: Target) -> &mut Self {
148 self.target = target.into();
149 self
150 }
151
152 /// Parses a style choice string.
153 ///
154 /// See the [Disabling colors] section for more details.
155 ///
156 /// [Disabling colors]: ../index.html#disabling-colors
157 pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self {
158 self.write_style(parse_write_style(write_style))
159 }
160
161 /// Whether or not to print style characters when writing.
162 pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
163 self.write_style = write_style;
164 self
165 }
166
167 /// Whether or not to capture logs for `cargo test`.
168 #[allow(clippy::wrong_self_convention)]
169 pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self {
170 self.is_test = is_test;
171 self
172 }
173
174 /// Build a terminal writer.
175 pub(crate) fn build(&mut self) -> Writer {
176 assert!(!self.built, "attempt to re-use consumed builder");
177 self.built = true;
178
179 let color_choice = match self.write_style {
180 WriteStyle::Auto => {
181 if match &self.target {
182 WritableTarget::Stderr => is_stderr(),
183 WritableTarget::Stdout => is_stdout(),
184 WritableTarget::Pipe(_) => false,
185 } {
186 WriteStyle::Auto
187 } else {
188 WriteStyle::Never
189 }
190 }
191 color_choice => color_choice,
192 };
193
194 let writer = match mem::take(&mut self.target) {
195 WritableTarget::Stderr => BufferWriter::stderr(self.is_test, color_choice),
196 WritableTarget::Stdout => BufferWriter::stdout(self.is_test, color_choice),
197 WritableTarget::Pipe(pipe) => BufferWriter::pipe(color_choice, pipe),
198 };
199
200 Writer {
201 inner: writer,
202 write_style: self.write_style,
203 }
204 }
205}
206
207impl Default for Builder {
208 fn default() -> Self {
209 Builder::new()
210 }
211}
212
213impl fmt::Debug for Writer {
214 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215 f.debug_struct(name:"Writer").finish()
216 }
217}
218
219fn parse_write_style(spec: &str) -> WriteStyle {
220 match spec {
221 "auto" => WriteStyle::Auto,
222 "always" => WriteStyle::Always,
223 "never" => WriteStyle::Never,
224 _ => Default::default(),
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn parse_write_style_valid() {
234 let inputs = vec![
235 ("auto", WriteStyle::Auto),
236 ("always", WriteStyle::Always),
237 ("never", WriteStyle::Never),
238 ];
239
240 for (input, expected) in inputs {
241 assert_eq!(expected, parse_write_style(input));
242 }
243 }
244
245 #[test]
246 fn parse_write_style_invalid() {
247 let inputs = vec!["", "true", "false", "NEVER!!"];
248
249 for input in inputs {
250 assert_eq!(WriteStyle::Auto, parse_write_style(input));
251 }
252 }
253}
254