mod atty; mod termcolor; use self::atty::{is_stderr, is_stdout}; use self::termcolor::BufferWriter; use std::{fmt, io, mem, sync::Mutex}; pub(super) mod glob { pub use super::termcolor::glob::*; pub use super::*; } pub(super) use self::termcolor::Buffer; /// Log target, either `stdout`, `stderr` or a custom pipe. #[non_exhaustive] pub enum Target { /// Logs will be sent to standard output. Stdout, /// Logs will be sent to standard error. Stderr, /// Logs will be sent to a custom pipe. Pipe(Box), } impl Default for Target { fn default() -> Self { Target::Stderr } } impl fmt::Debug for Target { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match self { Self::Stdout => "stdout", Self::Stderr => "stderr", Self::Pipe(_) => "pipe", } ) } } /// Log target, either `stdout`, `stderr` or a custom pipe. /// /// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability. pub(super) enum WritableTarget { /// Logs will be sent to standard output. Stdout, /// Logs will be sent to standard error. Stderr, /// Logs will be sent to a custom pipe. Pipe(Box>), } impl From for WritableTarget { fn from(target: Target) -> Self { match target { Target::Stdout => Self::Stdout, Target::Stderr => Self::Stderr, Target::Pipe(pipe) => Self::Pipe(Box::new(Mutex::new(pipe))), } } } impl Default for WritableTarget { fn default() -> Self { Self::from(Target::default()) } } impl fmt::Debug for WritableTarget { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match self { Self::Stdout => "stdout", Self::Stderr => "stderr", Self::Pipe(_) => "pipe", } ) } } /// Whether or not to print styles to the target. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum WriteStyle { /// Try to print styles, but don't force the issue. Auto, /// Try very hard to print styles. Always, /// Never print styles. Never, } impl Default for WriteStyle { fn default() -> Self { WriteStyle::Auto } } /// A terminal target with color awareness. pub(crate) struct Writer { inner: BufferWriter, write_style: WriteStyle, } impl Writer { pub fn write_style(&self) -> WriteStyle { self.write_style } pub(super) fn buffer(&self) -> Buffer { self.inner.buffer() } pub(super) fn print(&self, buf: &Buffer) -> io::Result<()> { self.inner.print(buf) } } /// A builder for a terminal writer. /// /// The target and style choice can be configured before building. #[derive(Debug)] pub(crate) struct Builder { target: WritableTarget, write_style: WriteStyle, is_test: bool, built: bool, } impl Builder { /// Initialize the writer builder with defaults. pub(crate) fn new() -> Self { Builder { target: Default::default(), write_style: Default::default(), is_test: false, built: false, } } /// Set the target to write to. pub(crate) fn target(&mut self, target: Target) -> &mut Self { self.target = target.into(); self } /// Parses a style choice string. /// /// See the [Disabling colors] section for more details. /// /// [Disabling colors]: ../index.html#disabling-colors pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self { self.write_style(parse_write_style(write_style)) } /// Whether or not to print style characters when writing. pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self { self.write_style = write_style; self } /// Whether or not to capture logs for `cargo test`. #[allow(clippy::wrong_self_convention)] pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self { self.is_test = is_test; self } /// Build a terminal writer. pub(crate) fn build(&mut self) -> Writer { assert!(!self.built, "attempt to re-use consumed builder"); self.built = true; let color_choice = match self.write_style { WriteStyle::Auto => { if match &self.target { WritableTarget::Stderr => is_stderr(), WritableTarget::Stdout => is_stdout(), WritableTarget::Pipe(_) => false, } { WriteStyle::Auto } else { WriteStyle::Never } } color_choice => color_choice, }; let writer = match mem::take(&mut self.target) { WritableTarget::Stderr => BufferWriter::stderr(self.is_test, color_choice), WritableTarget::Stdout => BufferWriter::stdout(self.is_test, color_choice), WritableTarget::Pipe(pipe) => BufferWriter::pipe(color_choice, pipe), }; Writer { inner: writer, write_style: self.write_style, } } } impl Default for Builder { fn default() -> Self { Builder::new() } } impl fmt::Debug for Writer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Writer").finish() } } fn parse_write_style(spec: &str) -> WriteStyle { match spec { "auto" => WriteStyle::Auto, "always" => WriteStyle::Always, "never" => WriteStyle::Never, _ => Default::default(), } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_write_style_valid() { let inputs = vec![ ("auto", WriteStyle::Auto), ("always", WriteStyle::Always), ("never", WriteStyle::Never), ]; for (input, expected) in inputs { assert_eq!(expected, parse_write_style(input)); } } #[test] fn parse_write_style_invalid() { let inputs = vec!["", "true", "false", "NEVER!!"]; for input in inputs { assert_eq!(WriteStyle::Auto, parse_write_style(input)); } } }