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 { |
26 | fn default() -> Self { |
27 | Target::Stderr |
28 | } |
29 | } |
30 | |
31 | impl 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. |
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 { |
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 { |
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)] |
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 { |
122 | fn default() -> Self { |
123 | WriteStyle::Auto |
124 | } |
125 | } |
126 | |
127 | #[cfg (feature = "color" )] |
128 | impl 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. |
139 | pub(crate) struct Writer { |
140 | inner: BufferWriter, |
141 | } |
142 | |
143 | impl 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 | |
157 | impl 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)] |
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. |
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 | |
248 | impl Default for Builder { |
249 | fn default() -> Self { |
250 | Builder::new() |
251 | } |
252 | } |
253 | |
254 | 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 ] |
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 | |