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