Revision control
Copy as Markdown
Other Tools
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<dyn io::Write + Send + 'static>),
}
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<Mutex<dyn io::Write + Send + 'static>>),
}
impl From<Target> 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));
}
}
}