1mod atty; 2mod termcolor; 3 4use self::atty::{is_stderr, is_stdout}; 5use self::termcolor::BufferWriter; 6use std::{fmt, io, mem, sync::Mutex}; 7 8pub(super) mod glob { 9 pub use super::termcolor::glob::*; 10 pub use super::*; 11} 12 13pub(super) use self::termcolor::Buffer; 14 15/// Log target, either `stdout`, `stderr` or a custom pipe. 16#[non_exhaustive] 17pub 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 26impl Default for Target { 27 fn default() -> Self { 28 Target::Stderr 29 } 30} 31 32impl 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. 49pub(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 58impl 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) => Self::Pipe(Box::new(Mutex::new(pipe))), 64 } 65 } 66} 67 68impl Default for WritableTarget { 69 fn default() -> Self { 70 Self::from(Target::default()) 71 } 72} 73 74impl 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)] 89pub 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 98impl Default for WriteStyle { 99 fn default() -> Self { 100 WriteStyle::Auto 101 } 102} 103 104/// A terminal target with color awareness. 105pub(crate) struct Writer { 106 inner: BufferWriter, 107 write_style: WriteStyle, 108} 109 110impl 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)] 128pub(crate) struct Builder { 129 target: WritableTarget, 130 write_style: WriteStyle, 131 is_test: bool, 132 built: bool, 133} 134 135impl 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 pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self { 169 self.is_test = is_test; 170 self 171 } 172 173 /// Build a terminal writer. 174 pub(crate) fn build(&mut self) -> Writer { 175 assert!(!self.built, "attempt to re-use consumed builder"); 176 self.built = true; 177 178 let color_choice = match self.write_style { 179 WriteStyle::Auto => { 180 if match &self.target { 181 WritableTarget::Stderr => is_stderr(), 182 WritableTarget::Stdout => is_stdout(), 183 WritableTarget::Pipe(_) => false, 184 } { 185 WriteStyle::Auto 186 } else { 187 WriteStyle::Never 188 } 189 } 190 color_choice => color_choice, 191 }; 192 193 let writer = match mem::take(&mut self.target) { 194 WritableTarget::Stderr => BufferWriter::stderr(self.is_test, color_choice), 195 WritableTarget::Stdout => BufferWriter::stdout(self.is_test, color_choice), 196 WritableTarget::Pipe(pipe) => BufferWriter::pipe(color_choice, pipe), 197 }; 198 199 Writer { 200 inner: writer, 201 write_style: self.write_style, 202 } 203 } 204} 205 206impl Default for Builder { 207 fn default() -> Self { 208 Builder::new() 209 } 210} 211 212impl fmt::Debug for Writer { 213 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 214 f.debug_struct("Writer").finish() 215 } 216} 217 218fn parse_write_style(spec: &str) -> WriteStyle { 219 match spec { 220 "auto" => WriteStyle::Auto, 221 "always" => WriteStyle::Always, 222 "never" => WriteStyle::Never, 223 _ => Default::default(), 224 } 225} 226 227#[cfg(test)] 228mod tests { 229 use super::*; 230 231 #[test] 232 fn parse_write_style_valid() { 233 let inputs = vec![ 234 ("auto", WriteStyle::Auto), 235 ("always", WriteStyle::Always), 236 ("never", WriteStyle::Never), 237 ]; 238 239 for (input, expected) in inputs { 240 assert_eq!(expected, parse_write_style(input)); 241 } 242 } 243 244 #[test] 245 fn parse_write_style_invalid() { 246 let inputs = vec!["", "true", "false", "NEVER!!"]; 247 248 for input in inputs { 249 assert_eq!(WriteStyle::Auto, parse_write_style(input)); 250 } 251 } 252} 253