1 | mod buffer; |
2 | mod target; |
3 | |
4 | use self::buffer::BufferWriter; |
5 | use std::{io, mem, sync::Mutex}; |
6 | |
7 | pub(super) use self::buffer::Buffer; |
8 | |
9 | pub use target::Target; |
10 | |
11 | /// Whether or not to print styles to the target. |
12 | #[allow (clippy::exhaustive_enums)] // By definition don't need more |
13 | #[derive (Clone, Copy, Debug, Eq, Hash, PartialEq, Default)] |
14 | pub enum WriteStyle { |
15 | /// Try to print styles, but don't force the issue. |
16 | #[default] |
17 | Auto, |
18 | /// Try very hard to print styles. |
19 | Always, |
20 | /// Never print styles. |
21 | Never, |
22 | } |
23 | |
24 | #[cfg (feature = "color" )] |
25 | impl From<anstream::ColorChoice> for WriteStyle { |
26 | fn from(choice: anstream::ColorChoice) -> Self { |
27 | match choice { |
28 | anstream::ColorChoice::Auto => Self::Auto, |
29 | anstream::ColorChoice::Always => Self::Always, |
30 | anstream::ColorChoice::AlwaysAnsi => Self::Always, |
31 | anstream::ColorChoice::Never => Self::Never, |
32 | } |
33 | } |
34 | } |
35 | |
36 | #[cfg (feature = "color" )] |
37 | impl From<WriteStyle> for anstream::ColorChoice { |
38 | fn from(choice: WriteStyle) -> Self { |
39 | match choice { |
40 | WriteStyle::Auto => anstream::ColorChoice::Auto, |
41 | WriteStyle::Always => anstream::ColorChoice::Always, |
42 | WriteStyle::Never => anstream::ColorChoice::Never, |
43 | } |
44 | } |
45 | } |
46 | |
47 | /// A terminal target with color awareness. |
48 | #[derive (Debug)] |
49 | pub(crate) struct Writer { |
50 | inner: BufferWriter, |
51 | } |
52 | |
53 | impl Writer { |
54 | pub(crate) fn write_style(&self) -> WriteStyle { |
55 | self.inner.write_style() |
56 | } |
57 | |
58 | pub(super) fn buffer(&self) -> Buffer { |
59 | self.inner.buffer() |
60 | } |
61 | |
62 | pub(super) fn print(&self, buf: &Buffer) -> io::Result<()> { |
63 | self.inner.print(buf) |
64 | } |
65 | } |
66 | |
67 | /// A builder for a terminal writer. |
68 | /// |
69 | /// The target and style choice can be configured before building. |
70 | #[derive (Debug)] |
71 | pub(crate) struct Builder { |
72 | target: Target, |
73 | write_style: WriteStyle, |
74 | is_test: bool, |
75 | built: bool, |
76 | } |
77 | |
78 | impl Builder { |
79 | /// Initialize the writer builder with defaults. |
80 | pub(crate) fn new() -> Self { |
81 | Builder { |
82 | target: Default::default(), |
83 | write_style: Default::default(), |
84 | is_test: false, |
85 | built: false, |
86 | } |
87 | } |
88 | |
89 | /// Set the target to write to. |
90 | pub(crate) fn target(&mut self, target: Target) -> &mut Self { |
91 | self.target = target; |
92 | self |
93 | } |
94 | |
95 | /// Parses a style choice string. |
96 | /// |
97 | /// See the [Disabling colors] section for more details. |
98 | /// |
99 | /// [Disabling colors]: ../index.html#disabling-colors |
100 | pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self { |
101 | self.write_style(parse_write_style(write_style)) |
102 | } |
103 | |
104 | /// Whether or not to print style characters when writing. |
105 | pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self { |
106 | self.write_style = write_style; |
107 | self |
108 | } |
109 | |
110 | /// Whether or not to capture logs for `cargo test`. |
111 | #[allow (clippy::wrong_self_convention)] |
112 | pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self { |
113 | self.is_test = is_test; |
114 | self |
115 | } |
116 | |
117 | /// Build a terminal writer. |
118 | pub(crate) fn build(&mut self) -> Writer { |
119 | assert!(!self.built, "attempt to re-use consumed builder" ); |
120 | self.built = true; |
121 | |
122 | let color_choice = self.write_style; |
123 | #[cfg (feature = "auto-color" )] |
124 | let color_choice = if color_choice == WriteStyle::Auto { |
125 | match &self.target { |
126 | Target::Stdout => anstream::AutoStream::choice(&io::stdout()).into(), |
127 | Target::Stderr => anstream::AutoStream::choice(&io::stderr()).into(), |
128 | Target::Pipe(_) => color_choice, |
129 | } |
130 | } else { |
131 | color_choice |
132 | }; |
133 | let color_choice = if color_choice == WriteStyle::Auto { |
134 | WriteStyle::Never |
135 | } else { |
136 | color_choice |
137 | }; |
138 | |
139 | let writer = match mem::take(&mut self.target) { |
140 | Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), |
141 | Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), |
142 | Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe)), color_choice), |
143 | }; |
144 | |
145 | Writer { inner: writer } |
146 | } |
147 | } |
148 | |
149 | impl Default for Builder { |
150 | fn default() -> Self { |
151 | Builder::new() |
152 | } |
153 | } |
154 | |
155 | fn parse_write_style(spec: &str) -> WriteStyle { |
156 | match spec { |
157 | "auto" => WriteStyle::Auto, |
158 | "always" => WriteStyle::Always, |
159 | "never" => WriteStyle::Never, |
160 | _ => Default::default(), |
161 | } |
162 | } |
163 | |
164 | #[cfg (test)] |
165 | mod tests { |
166 | use super::*; |
167 | |
168 | #[test ] |
169 | fn parse_write_style_valid() { |
170 | let inputs = vec![ |
171 | ("auto" , WriteStyle::Auto), |
172 | ("always" , WriteStyle::Always), |
173 | ("never" , WriteStyle::Never), |
174 | ]; |
175 | |
176 | for (input, expected) in inputs { |
177 | assert_eq!(expected, parse_write_style(input)); |
178 | } |
179 | } |
180 | |
181 | #[test ] |
182 | fn parse_write_style_invalid() { |
183 | let inputs = vec!["" , "true" , "false" , "NEVER!!" ]; |
184 | |
185 | for input in inputs { |
186 | assert_eq!(WriteStyle::Auto, parse_write_style(input)); |
187 | } |
188 | } |
189 | } |
190 | |