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 { |
27 | fn default() -> Self { |
28 | Target::Stderr |
29 | } |
30 | } |
31 | |
32 | impl fmt::Debug for Target { |
33 | 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 { |
59 | fn from(target: Target) -> Self { |
60 | match target { |
61 | Target::Stdout => Self::Stdout, |
62 | Target::Stderr => Self::Stderr, |
63 | Target::Pipe(pipe: Box) => Self::Pipe(Box::new(Mutex::new(pipe))), |
64 | } |
65 | } |
66 | } |
67 | |
68 | impl Default for WritableTarget { |
69 | fn default() -> Self { |
70 | Self::from(Target::default()) |
71 | } |
72 | } |
73 | |
74 | impl fmt::Debug for WritableTarget { |
75 | 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 { |
99 | 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 { |
111 | pub fn write_style(&self) -> WriteStyle { |
112 | self.write_style |
113 | } |
114 | |
115 | pub(super) fn buffer(&self) -> Buffer { |
116 | self.inner.buffer() |
117 | } |
118 | |
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. |
137 | 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. |
147 | 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 |
157 | 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. |
162 | 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`. |
168 | #[allow (clippy::wrong_self_convention)] |
169 | pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self { |
170 | self.is_test = is_test; |
171 | self |
172 | } |
173 | |
174 | /// Build a terminal writer. |
175 | pub(crate) fn build(&mut self) -> Writer { |
176 | assert!(!self.built, "attempt to re-use consumed builder" ); |
177 | self.built = true; |
178 | |
179 | let color_choice = match self.write_style { |
180 | WriteStyle::Auto => { |
181 | if match &self.target { |
182 | WritableTarget::Stderr => is_stderr(), |
183 | WritableTarget::Stdout => is_stdout(), |
184 | WritableTarget::Pipe(_) => false, |
185 | } { |
186 | WriteStyle::Auto |
187 | } else { |
188 | WriteStyle::Never |
189 | } |
190 | } |
191 | color_choice => color_choice, |
192 | }; |
193 | |
194 | let writer = match mem::take(&mut self.target) { |
195 | WritableTarget::Stderr => BufferWriter::stderr(self.is_test, color_choice), |
196 | WritableTarget::Stdout => BufferWriter::stdout(self.is_test, color_choice), |
197 | WritableTarget::Pipe(pipe) => BufferWriter::pipe(color_choice, pipe), |
198 | }; |
199 | |
200 | Writer { |
201 | inner: writer, |
202 | write_style: self.write_style, |
203 | } |
204 | } |
205 | } |
206 | |
207 | impl Default for Builder { |
208 | fn default() -> Self { |
209 | Builder::new() |
210 | } |
211 | } |
212 | |
213 | impl fmt::Debug for Writer { |
214 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
215 | f.debug_struct(name:"Writer" ).finish() |
216 | } |
217 | } |
218 | |
219 | fn parse_write_style(spec: &str) -> WriteStyle { |
220 | match spec { |
221 | "auto" => WriteStyle::Auto, |
222 | "always" => WriteStyle::Always, |
223 | "never" => WriteStyle::Never, |
224 | _ => Default::default(), |
225 | } |
226 | } |
227 | |
228 | #[cfg (test)] |
229 | mod tests { |
230 | use super::*; |
231 | |
232 | #[test ] |
233 | fn parse_write_style_valid() { |
234 | let inputs = vec![ |
235 | ("auto" , WriteStyle::Auto), |
236 | ("always" , WriteStyle::Always), |
237 | ("never" , WriteStyle::Never), |
238 | ]; |
239 | |
240 | for (input, expected) in inputs { |
241 | assert_eq!(expected, parse_write_style(input)); |
242 | } |
243 | } |
244 | |
245 | #[test ] |
246 | fn parse_write_style_invalid() { |
247 | let inputs = vec!["" , "true" , "false" , "NEVER!!" ]; |
248 | |
249 | for input in inputs { |
250 | assert_eq!(WriteStyle::Auto, parse_write_style(input)); |
251 | } |
252 | } |
253 | } |
254 | |