• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 mod atty;
2 mod termcolor;
3 
4 use self::atty::{is_stderr, is_stdout};
5 use self::termcolor::BufferWriter;
6 use std::{fmt, io, mem, sync::Mutex};
7 
8 pub(super) mod glob {
9     pub use super::termcolor::glob::*;
10     pub use super::*;
11 }
12 
13 pub(super) use self::termcolor::Buffer;
14 
15 /// Log target, either `stdout`, `stderr` or a custom pipe.
16 #[non_exhaustive]
17 pub 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 
26 impl Default for Target {
default() -> Self27     fn default() -> Self {
28         Target::Stderr
29     }
30 }
31 
32 impl fmt::Debug for Target {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result33     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.
49 pub(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 
58 impl From<Target> for WritableTarget {
from(target: Target) -> Self59     fn from(target: Target) -> Self {
60         match target {
61             Target::Stdout => Self::Stdout,
62             Target::Stderr => Self::Stderr,
63             Target::Pipe(pipe) => Self::Pipe(Box::new(Mutex::new(pipe))),
64         }
65     }
66 }
67 
68 impl Default for WritableTarget {
default() -> Self69     fn default() -> Self {
70         Self::from(Target::default())
71     }
72 }
73 
74 impl fmt::Debug for WritableTarget {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result75     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)]
89 pub 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 
98 impl Default for WriteStyle {
default() -> Self99     fn default() -> Self {
100         WriteStyle::Auto
101     }
102 }
103 
104 /// A terminal target with color awareness.
105 pub(crate) struct Writer {
106     inner: BufferWriter,
107     write_style: WriteStyle,
108 }
109 
110 impl Writer {
write_style(&self) -> WriteStyle111     pub fn write_style(&self) -> WriteStyle {
112         self.write_style
113     }
114 
buffer(&self) -> Buffer115     pub(super) fn buffer(&self) -> Buffer {
116         self.inner.buffer()
117     }
118 
print(&self, buf: &Buffer) -> io::Result<()>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)]
128 pub(crate) struct Builder {
129     target: WritableTarget,
130     write_style: WriteStyle,
131     is_test: bool,
132     built: bool,
133 }
134 
135 impl Builder {
136     /// Initialize the writer builder with defaults.
new() -> Self137     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.
target(&mut self, target: Target) -> &mut Self147     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
parse_write_style(&mut self, write_style: &str) -> &mut Self157     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.
write_style(&mut self, write_style: WriteStyle) -> &mut Self162     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`.
is_test(&mut self, is_test: bool) -> &mut Self168     pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self {
169         self.is_test = is_test;
170         self
171     }
172 
173     /// Build a terminal writer.
build(&mut self) -> Writer174     pub(crate) fn build(&mut self) -> Writer {
175         assert!(!self.built, "attempt to re-use consumed builder");
176         self.built = true;
177 
178         let color_choice = match self.write_style {
179             WriteStyle::Auto => {
180                 if match &self.target {
181                     WritableTarget::Stderr => is_stderr(),
182                     WritableTarget::Stdout => is_stdout(),
183                     WritableTarget::Pipe(_) => false,
184                 } {
185                     WriteStyle::Auto
186                 } else {
187                     WriteStyle::Never
188                 }
189             }
190             color_choice => color_choice,
191         };
192 
193         let writer = match mem::take(&mut self.target) {
194             WritableTarget::Stderr => BufferWriter::stderr(self.is_test, color_choice),
195             WritableTarget::Stdout => BufferWriter::stdout(self.is_test, color_choice),
196             WritableTarget::Pipe(pipe) => BufferWriter::pipe(color_choice, pipe),
197         };
198 
199         Writer {
200             inner: writer,
201             write_style: self.write_style,
202         }
203     }
204 }
205 
206 impl Default for Builder {
default() -> Self207     fn default() -> Self {
208         Builder::new()
209     }
210 }
211 
212 impl fmt::Debug for Writer {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result213     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
214         f.debug_struct("Writer").finish()
215     }
216 }
217 
parse_write_style(spec: &str) -> WriteStyle218 fn parse_write_style(spec: &str) -> WriteStyle {
219     match spec {
220         "auto" => WriteStyle::Auto,
221         "always" => WriteStyle::Always,
222         "never" => WriteStyle::Never,
223         _ => Default::default(),
224     }
225 }
226 
227 #[cfg(test)]
228 mod tests {
229     use super::*;
230 
231     #[test]
parse_write_style_valid()232     fn parse_write_style_valid() {
233         let inputs = vec![
234             ("auto", WriteStyle::Auto),
235             ("always", WriteStyle::Always),
236             ("never", WriteStyle::Never),
237         ];
238 
239         for (input, expected) in inputs {
240             assert_eq!(expected, parse_write_style(input));
241         }
242     }
243 
244     #[test]
parse_write_style_invalid()245     fn parse_write_style_invalid() {
246         let inputs = vec!["", "true", "false", "NEVER!!"];
247 
248         for input in inputs {
249             assert_eq!(WriteStyle::Auto, parse_write_style(input));
250         }
251     }
252 }
253