1mod atty;
2mod buffer;
3
4use self::atty::{is_stderr, is_stdout};
5use self::buffer::BufferWriter;
6use std::{fmt, io, mem, sync::Mutex};
7
8pub(super) mod glob {
9 pub use super::*;
10}
11
12pub(super) use self::buffer::Buffer;
13
14/// Log target, either `stdout`, `stderr` or a custom pipe.
15#[non_exhaustive]
16pub 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
25impl Default for Target {
26 fn default() -> Self {
27 Target::Stderr
28 }
29}
30
31impl fmt::Debug for Target {
32 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.
48pub(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
63impl WritableTarget {
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
95impl fmt::Debug for WritableTarget {
96 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)]
112pub 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
121impl Default for WriteStyle {
122 fn default() -> Self {
123 WriteStyle::Auto
124 }
125}
126
127#[cfg(feature = "color")]
128impl WriteStyle {
129 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.
139pub(crate) struct Writer {
140 inner: BufferWriter,
141}
142
143impl Writer {
144 pub fn write_style(&self) -> WriteStyle {
145 self.inner.write_style()
146 }
147
148 pub(super) fn buffer(&self) -> Buffer {
149 self.inner.buffer()
150 }
151
152 pub(super) fn print(&self, buf: &Buffer) -> io::Result<()> {
153 self.inner.print(buf)
154 }
155}
156
157impl fmt::Debug for Writer {
158 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159 f.debug_struct(name:"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)]
167pub(crate) struct Builder {
168 target: Target,
169 write_style: WriteStyle,
170 is_test: bool,
171 built: bool,
172}
173
174impl Builder {
175 /// Initialize the writer builder with defaults.
176 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.
186 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
196 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.
201 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)]
208 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.
214 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
248impl Default for Builder {
249 fn default() -> Self {
250 Builder::new()
251 }
252}
253
254fn 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)]
264mod tests {
265 use super::*;
266
267 #[test]
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]
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