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};
7
8 pub(in crate::fmt) mod glob {
9 pub use super::termcolor::glob::*;
10 pub use super::*;
11 }
12
13 pub(in crate::fmt) use self::termcolor::Buffer;
14
15 /// Log target, either `stdout` or `stderr`.
16 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
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 }
23
24 impl Default for Target {
default() -> Self25 fn default() -> Self {
26 Target::Stderr
27 }
28 }
29
30 /// Whether or not to print styles to the target.
31 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
32 pub enum WriteStyle {
33 /// Try to print styles, but don't force the issue.
34 Auto,
35 /// Try very hard to print styles.
36 Always,
37 /// Never print styles.
38 Never,
39 }
40
41 impl Default for WriteStyle {
default() -> Self42 fn default() -> Self {
43 WriteStyle::Auto
44 }
45 }
46
47 /// A terminal target with color awareness.
48 pub(crate) struct Writer {
49 inner: BufferWriter,
50 write_style: WriteStyle,
51 }
52
53 impl Writer {
write_style(&self) -> WriteStyle54 pub fn write_style(&self) -> WriteStyle {
55 self.write_style
56 }
57
buffer(&self) -> Buffer58 pub(in crate::fmt) fn buffer(&self) -> Buffer {
59 self.inner.buffer()
60 }
61
print(&self, buf: &Buffer) -> io::Result<()>62 pub(in crate::fmt) 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 pub(crate) struct Builder {
71 target: Target,
72 write_style: WriteStyle,
73 is_test: bool,
74 built: bool,
75 }
76
77 impl Builder {
78 /// Initialize the writer builder with defaults.
new() -> Self79 pub(crate) fn new() -> Self {
80 Builder {
81 target: Default::default(),
82 write_style: Default::default(),
83 is_test: false,
84 built: false,
85 }
86 }
87
88 /// Set the target to write to.
target(&mut self, target: Target) -> &mut Self89 pub(crate) fn target(&mut self, target: Target) -> &mut Self {
90 self.target = target;
91 self
92 }
93
94 /// Parses a style choice string.
95 ///
96 /// See the [Disabling colors] section for more details.
97 ///
98 /// [Disabling colors]: ../index.html#disabling-colors
parse_write_style(&mut self, write_style: &str) -> &mut Self99 pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self {
100 self.write_style(parse_write_style(write_style))
101 }
102
103 /// Whether or not to print style characters when writing.
write_style(&mut self, write_style: WriteStyle) -> &mut Self104 pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
105 self.write_style = write_style;
106 self
107 }
108
109 /// Whether or not to capture logs for `cargo test`.
is_test(&mut self, is_test: bool) -> &mut Self110 pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self {
111 self.is_test = is_test;
112 self
113 }
114
115 /// Build a terminal writer.
build(&mut self) -> Writer116 pub(crate) fn build(&mut self) -> Writer {
117 assert!(!self.built, "attempt to re-use consumed builder");
118 self.built = true;
119
120 let color_choice = match self.write_style {
121 WriteStyle::Auto => {
122 if match self.target {
123 Target::Stderr => is_stderr(),
124 Target::Stdout => is_stdout(),
125 } {
126 WriteStyle::Auto
127 } else {
128 WriteStyle::Never
129 }
130 }
131 color_choice => color_choice,
132 };
133
134 let writer = match self.target {
135 Target::Stderr => BufferWriter::stderr(self.is_test, color_choice),
136 Target::Stdout => BufferWriter::stdout(self.is_test, color_choice),
137 };
138
139 Writer {
140 inner: writer,
141 write_style: self.write_style,
142 }
143 }
144 }
145
146 impl Default for Builder {
default() -> Self147 fn default() -> Self {
148 Builder::new()
149 }
150 }
151
152 impl fmt::Debug for Builder {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result153 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154 f.debug_struct("Logger")
155 .field("target", &self.target)
156 .field("write_style", &self.write_style)
157 .finish()
158 }
159 }
160
161 impl fmt::Debug for Writer {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result162 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163 f.debug_struct("Writer").finish()
164 }
165 }
166
parse_write_style(spec: &str) -> WriteStyle167 fn parse_write_style(spec: &str) -> WriteStyle {
168 match spec {
169 "auto" => WriteStyle::Auto,
170 "always" => WriteStyle::Always,
171 "never" => WriteStyle::Never,
172 _ => Default::default(),
173 }
174 }
175
176 #[cfg(test)]
177 mod tests {
178 use super::*;
179
180 #[test]
parse_write_style_valid()181 fn parse_write_style_valid() {
182 let inputs = vec![
183 ("auto", WriteStyle::Auto),
184 ("always", WriteStyle::Always),
185 ("never", WriteStyle::Never),
186 ];
187
188 for (input, expected) in inputs {
189 assert_eq!(expected, parse_write_style(input));
190 }
191 }
192
193 #[test]
parse_write_style_invalid()194 fn parse_write_style_invalid() {
195 let inputs = vec!["", "true", "false", "NEVER!!"];
196
197 for input in inputs {
198 assert_eq!(WriteStyle::Auto, parse_write_style(input));
199 }
200 }
201 }
202