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