From a2d55f81bc99762c7797b95dcf97736713539a49 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 12:50:12 -0600 Subject: [PATCH 01/13] refactor(fmt): Move impls next to types --- src/fmt/mod.rs | 58 ++++++++++++ src/fmt/writer/mod.rs | 23 +++-- src/fmt/writer/termcolor/extern_impl.rs | 116 +++++------------------- src/fmt/writer/termcolor/shim_impl.rs | 4 +- 4 files changed, 102 insertions(+), 99 deletions(-) diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index ded40278..bc5006cc 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -35,6 +35,8 @@ use std::io::prelude::*; use std::rc::Rc; use std::{fmt, io, mem}; +#[cfg(feature = "color")] +use log::Level; use log::Record; #[cfg(feature = "humantime")] @@ -122,6 +124,62 @@ impl Formatter { } } +#[cfg(feature = "color")] +impl Formatter { + /// Begin a new [`Style`]. + /// + /// # Examples + /// + /// Create a bold, red colored style and use it to print the log level: + /// + /// ``` + /// use std::io::Write; + /// use env_logger::fmt::Color; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let mut level_style = buf.style(); + /// + /// level_style.set_color(Color::Red).set_bold(true); + /// + /// writeln!(buf, "{}: {}", + /// level_style.value(record.level()), + /// record.args()) + /// }); + /// ``` + /// + /// [`Style`]: struct.Style.html + pub fn style(&self) -> Style { + Style { + buf: self.buf.clone(), + spec: termcolor::ColorSpec::new(), + } + } + + /// Get the default [`Style`] for the given level. + /// + /// The style can be used to print other values besides the level. + pub fn default_level_style(&self, level: Level) -> Style { + let mut level_style = self.style(); + match level { + Level::Trace => level_style.set_color(Color::Cyan), + Level::Debug => level_style.set_color(Color::Blue), + Level::Info => level_style.set_color(Color::Green), + Level::Warn => level_style.set_color(Color::Yellow), + Level::Error => level_style.set_color(Color::Red).set_bold(true), + }; + level_style + } + + /// Get a printable [`Style`] for the given level. + /// + /// The style can only be used to print the level. + pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> { + self.default_level_style(level).into_value(level) + } +} + impl Write for Formatter { fn write(&mut self, buf: &[u8]) -> io::Result { self.buf.borrow_mut().write(buf) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 7f4b6f94..bb57ec6b 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -101,6 +101,17 @@ impl Default for WriteStyle { } } +#[cfg(feature = "color")] +impl WriteStyle { + fn into_color_choice(self) -> ::termcolor::ColorChoice { + match self { + WriteStyle::Always => ::termcolor::ColorChoice::Always, + WriteStyle::Auto => ::termcolor::ColorChoice::Auto, + WriteStyle::Never => ::termcolor::ColorChoice::Never, + } + } +} + /// A terminal target with color awareness. pub(crate) struct Writer { inner: BufferWriter, @@ -121,6 +132,12 @@ impl Writer { } } +impl fmt::Debug for Writer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Writer").finish() + } +} + /// A builder for a terminal writer. /// /// The target and style choice can be configured before building. @@ -210,12 +227,6 @@ impl Default for Builder { } } -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, diff --git a/src/fmt/writer/termcolor/extern_impl.rs b/src/fmt/writer/termcolor/extern_impl.rs index 89c38223..5d7304fd 100644 --- a/src/fmt/writer/termcolor/extern_impl.rs +++ b/src/fmt/writer/termcolor/extern_impl.rs @@ -5,80 +5,19 @@ use std::io::{self, Write}; use std::rc::Rc; use std::sync::Mutex; -use log::Level; -use termcolor::{self, ColorChoice, ColorSpec, WriteColor}; +use termcolor::{self, ColorSpec, WriteColor}; -use crate::fmt::{Formatter, WritableTarget, WriteStyle}; +use crate::fmt::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) mod glob { pub use super::*; } -impl Formatter { - /// Begin a new [`Style`]. - /// - /// # Examples - /// - /// Create a bold, red colored style and use it to print the log level: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut level_style = buf.style(); - /// - /// level_style.set_color(Color::Red).set_bold(true); - /// - /// writeln!(buf, "{}: {}", - /// level_style.value(record.level()), - /// record.args()) - /// }); - /// ``` - /// - /// [`Style`]: struct.Style.html - pub fn style(&self) -> Style { - Style { - buf: self.buf.clone(), - spec: ColorSpec::new(), - } - } - - /// Get the default [`Style`] for the given level. - /// - /// The style can be used to print other values besides the level. - pub fn default_level_style(&self, level: Level) -> Style { - let mut level_style = self.style(); - match level { - Level::Trace => level_style.set_color(Color::Cyan), - Level::Debug => level_style.set_color(Color::Blue), - Level::Info => level_style.set_color(Color::Green), - Level::Warn => level_style.set_color(Color::Yellow), - Level::Error => level_style.set_color(Color::Red).set_bold(true), - }; - level_style - } - - /// Get a printable [`Style`] for the given level. - /// - /// The style can only be used to print the level. - pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> { - self.default_level_style(level).into_value(level) - } -} - pub(in crate::fmt::writer) struct BufferWriter { inner: termcolor::BufferWriter, uncolored_target: Option, } -pub(in crate::fmt) struct Buffer { - inner: termcolor::Buffer, - has_uncolored_target: bool, -} - impl BufferWriter { pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { BufferWriter { @@ -140,6 +79,11 @@ impl BufferWriter { } } +pub(in crate::fmt) struct Buffer { + inner: termcolor::Buffer, + has_uncolored_target: bool, +} + impl Buffer { pub(in crate::fmt) fn clear(&mut self) { self.inner.clear() @@ -176,16 +120,6 @@ impl Buffer { } } -impl WriteStyle { - fn into_color_choice(self) -> ColorChoice { - match self { - WriteStyle::Always => ColorChoice::Always, - WriteStyle::Auto => ColorChoice::Auto, - WriteStyle::Never => ColorChoice::Never, - } - } -} - /// A set of styles to apply to the terminal output. /// /// Call [`Formatter::style`] to get a `Style` and use the builder methods to @@ -240,18 +174,8 @@ impl WriteStyle { /// [`value`]: #method.value #[derive(Clone)] pub struct Style { - buf: Rc>, - spec: ColorSpec, -} - -/// A value that can be printed using the given styles. -/// -/// It is the result of calling [`Style::value`]. -/// -/// [`Style::value`]: struct.Style.html#method.value -pub struct StyledValue<'a, T> { - style: Cow<'a, Style>, - value: T, + pub(in crate::fmt) buf: Rc>, + pub(in crate::fmt) spec: ColorSpec, } impl Style { @@ -426,6 +350,22 @@ impl Style { } } +impl fmt::Debug for Style { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Style").field("spec", &self.spec).finish() + } +} + +/// A value that can be printed using the given styles. +/// +/// It is the result of calling [`Style::value`]. +/// +/// [`Style::value`]: struct.Style.html#method.value +pub struct StyledValue<'a, T> { + style: Cow<'a, Style>, + value: T, +} + impl<'a, T> StyledValue<'a, T> { fn write_fmt(&self, f: F) -> fmt::Result where @@ -445,12 +385,6 @@ impl<'a, T> StyledValue<'a, T> { } } -impl fmt::Debug for Style { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Style").field("spec", &self.spec).finish() - } -} - macro_rules! impl_styled_value_fmt { ($($fmt_trait:path),*) => { $( diff --git a/src/fmt/writer/termcolor/shim_impl.rs b/src/fmt/writer/termcolor/shim_impl.rs index 0705770c..84449092 100644 --- a/src/fmt/writer/termcolor/shim_impl.rs +++ b/src/fmt/writer/termcolor/shim_impl.rs @@ -8,8 +8,6 @@ pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, } -pub(in crate::fmt) struct Buffer(Vec); - impl BufferWriter { pub(in crate::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { @@ -51,6 +49,8 @@ impl BufferWriter { } } +pub(in crate::fmt) struct Buffer(Vec); + impl Buffer { pub(in crate::fmt) fn clear(&mut self) { self.0.clear(); From 62e7fc2a2fdb257a83862cbcb2e022862edcd157 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 13:15:48 -0600 Subject: [PATCH 02/13] refactor(fmt): Move Style closer to API representation --- src/fmt/mod.rs | 5 + src/fmt/style.rs | 351 +++++++++++++++++++++++ src/fmt/writer/mod.rs | 1 - src/fmt/writer/termcolor/extern_impl.rs | 357 +----------------------- src/fmt/writer/termcolor/shim_impl.rs | 2 - 5 files changed, 358 insertions(+), 358 deletions(-) create mode 100644 src/fmt/style.rs diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index bc5006cc..30b1384e 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -43,6 +43,11 @@ use log::Record; mod humantime; pub(crate) mod writer; +#[cfg(feature = "color")] +mod style; +#[cfg(feature = "color")] +pub use style::{Color, Style, StyledValue}; + #[cfg(feature = "humantime")] pub use self::humantime::Timestamp; pub use self::writer::glob::*; diff --git a/src/fmt/style.rs b/src/fmt/style.rs new file mode 100644 index 00000000..dd4463ac --- /dev/null +++ b/src/fmt/style.rs @@ -0,0 +1,351 @@ +use std::borrow::Cow; +use std::cell::RefCell; +use std::fmt; +use std::rc::Rc; + +use super::Buffer; + +/// A set of styles to apply to the terminal output. +/// +/// Call [`Formatter::style`] to get a `Style` and use the builder methods to +/// set styling properties, like [color] and [weight]. +/// To print a value using the style, wrap it in a call to [`value`] when the log +/// record is formatted. +/// +/// # Examples +/// +/// Create a bold, red colored style and use it to print the log level: +/// +/// ``` +/// use std::io::Write; +/// use env_logger::fmt::Color; +/// +/// let mut builder = env_logger::Builder::new(); +/// +/// builder.format(|buf, record| { +/// let mut level_style = buf.style(); +/// +/// level_style.set_color(Color::Red).set_bold(true); +/// +/// writeln!(buf, "{}: {}", +/// level_style.value(record.level()), +/// record.args()) +/// }); +/// ``` +/// +/// Styles can be re-used to output multiple values: +/// +/// ``` +/// use std::io::Write; +/// use env_logger::fmt::Color; +/// +/// let mut builder = env_logger::Builder::new(); +/// +/// builder.format(|buf, record| { +/// let mut bold = buf.style(); +/// +/// bold.set_bold(true); +/// +/// writeln!(buf, "{}: {} {}", +/// bold.value(record.level()), +/// bold.value("some bold text"), +/// record.args()) +/// }); +/// ``` +/// +/// [`Formatter::style`]: struct.Formatter.html#method.style +/// [color]: #method.set_color +/// [weight]: #method.set_bold +/// [`value`]: #method.value +#[derive(Clone)] +pub struct Style { + pub(in crate::fmt) buf: Rc>, + pub(in crate::fmt) spec: termcolor::ColorSpec, +} + +impl Style { + /// Set the text color. + /// + /// # Examples + /// + /// Create a style with red text: + /// + /// ``` + /// use std::io::Write; + /// use env_logger::fmt::Color; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let mut style = buf.style(); + /// + /// style.set_color(Color::Red); + /// + /// writeln!(buf, "{}", style.value(record.args())) + /// }); + /// ``` + pub fn set_color(&mut self, color: Color) -> &mut Style { + self.spec.set_fg(Some(color.into_termcolor())); + self + } + + /// Set the text weight. + /// + /// If `yes` is true then text will be written in bold. + /// If `yes` is false then text will be written in the default weight. + /// + /// # Examples + /// + /// Create a style with bold text: + /// + /// ``` + /// use std::io::Write; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let mut style = buf.style(); + /// + /// style.set_bold(true); + /// + /// writeln!(buf, "{}", style.value(record.args())) + /// }); + /// ``` + pub fn set_bold(&mut self, yes: bool) -> &mut Style { + self.spec.set_bold(yes); + self + } + + /// Set the text intensity. + /// + /// If `yes` is true then text will be written in a brighter color. + /// If `yes` is false then text will be written in the default color. + /// + /// # Examples + /// + /// Create a style with intense text: + /// + /// ``` + /// use std::io::Write; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let mut style = buf.style(); + /// + /// style.set_intense(true); + /// + /// writeln!(buf, "{}", style.value(record.args())) + /// }); + /// ``` + pub fn set_intense(&mut self, yes: bool) -> &mut Style { + self.spec.set_intense(yes); + self + } + + /// Set whether the text is dimmed. + /// + /// If `yes` is true then text will be written in a dimmer color. + /// If `yes` is false then text will be written in the default color. + /// + /// # Examples + /// + /// Create a style with dimmed text: + /// + /// ``` + /// use std::io::Write; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let mut style = buf.style(); + /// + /// style.set_dimmed(true); + /// + /// writeln!(buf, "{}", style.value(record.args())) + /// }); + /// ``` + pub fn set_dimmed(&mut self, yes: bool) -> &mut Style { + self.spec.set_dimmed(yes); + self + } + + /// Set the background color. + /// + /// # Examples + /// + /// Create a style with a yellow background: + /// + /// ``` + /// use std::io::Write; + /// use env_logger::fmt::Color; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let mut style = buf.style(); + /// + /// style.set_bg(Color::Yellow); + /// + /// writeln!(buf, "{}", style.value(record.args())) + /// }); + /// ``` + pub fn set_bg(&mut self, color: Color) -> &mut Style { + self.spec.set_bg(Some(color.into_termcolor())); + self + } + + /// Wrap a value in the style. + /// + /// The same `Style` can be used to print multiple different values. + /// + /// # Examples + /// + /// Create a bold, red colored style and use it to print the log level: + /// + /// ``` + /// use std::io::Write; + /// use env_logger::fmt::Color; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let mut style = buf.style(); + /// + /// style.set_color(Color::Red).set_bold(true); + /// + /// writeln!(buf, "{}: {}", + /// style.value(record.level()), + /// record.args()) + /// }); + /// ``` + pub fn value(&self, value: T) -> StyledValue { + StyledValue { + style: Cow::Borrowed(self), + value, + } + } + + /// Wrap a value in the style by taking ownership of it. + pub(crate) fn into_value(self, value: T) -> StyledValue<'static, T> { + StyledValue { + style: Cow::Owned(self), + value, + } + } +} + +impl fmt::Debug for Style { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Style").field("spec", &self.spec).finish() + } +} + +/// A value that can be printed using the given styles. +/// +/// It is the result of calling [`Style::value`]. +/// +/// [`Style::value`]: struct.Style.html#method.value +pub struct StyledValue<'a, T> { + style: Cow<'a, Style>, + value: T, +} + +impl<'a, T> StyledValue<'a, T> { + fn write_fmt(&self, f: F) -> fmt::Result + where + F: FnOnce() -> fmt::Result, + { + self.style + .buf + .borrow_mut() + .set_color(&self.style.spec) + .map_err(|_| fmt::Error)?; + + // Always try to reset the terminal style, even if writing failed + let write = f(); + let reset = self.style.buf.borrow_mut().reset().map_err(|_| fmt::Error); + + write.and(reset) + } +} + +macro_rules! impl_styled_value_fmt { + ($($fmt_trait:path),*) => { + $( + impl<'a, T: $fmt_trait> $fmt_trait for StyledValue<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { + self.write_fmt(|| T::fmt(&self.value, f)) + } + } + )* + }; +} + +impl_styled_value_fmt!( + fmt::Debug, + fmt::Display, + fmt::Pointer, + fmt::Octal, + fmt::Binary, + fmt::UpperHex, + fmt::LowerHex, + fmt::UpperExp, + fmt::LowerExp +); + +// The `Color` type is copied from https://github.com/BurntSushi/termcolor + +/// The set of available colors for the terminal foreground/background. +/// +/// The `Ansi256` and `Rgb` colors will only output the correct codes when +/// paired with the `Ansi` `WriteColor` implementation. +/// +/// The `Ansi256` and `Rgb` color types are not supported when writing colors +/// on Windows using the console. If they are used on Windows, then they are +/// silently ignored and no colors will be emitted. +/// +/// This set may expand over time. +/// +/// This type has a `FromStr` impl that can parse colors from their human +/// readable form. The format is as follows: +/// +/// 1. Any of the explicitly listed colors in English. They are matched +/// case insensitively. +/// 2. A single 8-bit integer, in either decimal or hexadecimal format. +/// 3. A triple of 8-bit integers separated by a comma, where each integer is +/// in decimal or hexadecimal format. +/// +/// Hexadecimal numbers are written with a `0x` prefix. +#[allow(missing_docs)] +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Color { + Black, + Blue, + Green, + Red, + Cyan, + Magenta, + Yellow, + White, + Ansi256(u8), + Rgb(u8, u8, u8), +} + +impl Color { + fn into_termcolor(self) -> termcolor::Color { + match self { + Color::Black => termcolor::Color::Black, + Color::Blue => termcolor::Color::Blue, + Color::Green => termcolor::Color::Green, + Color::Red => termcolor::Color::Red, + Color::Cyan => termcolor::Color::Cyan, + Color::Magenta => termcolor::Color::Magenta, + Color::Yellow => termcolor::Color::Yellow, + Color::White => termcolor::Color::White, + Color::Ansi256(value) => termcolor::Color::Ansi256(value), + Color::Rgb(r, g, b) => termcolor::Color::Rgb(r, g, b), + } + } +} diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index bb57ec6b..e1365a49 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -6,7 +6,6 @@ use self::termcolor::BufferWriter; use std::{fmt, io, mem, sync::Mutex}; pub(super) mod glob { - pub use super::termcolor::glob::*; pub use super::*; } diff --git a/src/fmt/writer/termcolor/extern_impl.rs b/src/fmt/writer/termcolor/extern_impl.rs index 5d7304fd..efcf0c3d 100644 --- a/src/fmt/writer/termcolor/extern_impl.rs +++ b/src/fmt/writer/termcolor/extern_impl.rs @@ -1,18 +1,10 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::fmt; use std::io::{self, Write}; -use std::rc::Rc; use std::sync::Mutex; use termcolor::{self, ColorSpec, WriteColor}; use crate::fmt::{WritableTarget, WriteStyle}; -pub(in crate::fmt::writer) mod glob { - pub use super::*; -} - pub(in crate::fmt::writer) struct BufferWriter { inner: termcolor::BufferWriter, uncolored_target: Option, @@ -101,7 +93,7 @@ impl Buffer { self.inner.as_slice() } - fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + pub(in crate::fmt) fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { // Ignore styles for test captured logs because they can't be printed if !self.has_uncolored_target { self.inner.set_color(spec) @@ -110,7 +102,7 @@ impl Buffer { } } - fn reset(&mut self) -> io::Result<()> { + pub(in crate::fmt) fn reset(&mut self) -> io::Result<()> { // Ignore styles for test captured logs because they can't be printed if !self.has_uncolored_target { self.inner.reset() @@ -119,348 +111,3 @@ impl Buffer { } } } - -/// A set of styles to apply to the terminal output. -/// -/// Call [`Formatter::style`] to get a `Style` and use the builder methods to -/// set styling properties, like [color] and [weight]. -/// To print a value using the style, wrap it in a call to [`value`] when the log -/// record is formatted. -/// -/// # Examples -/// -/// Create a bold, red colored style and use it to print the log level: -/// -/// ``` -/// use std::io::Write; -/// use env_logger::fmt::Color; -/// -/// let mut builder = env_logger::Builder::new(); -/// -/// builder.format(|buf, record| { -/// let mut level_style = buf.style(); -/// -/// level_style.set_color(Color::Red).set_bold(true); -/// -/// writeln!(buf, "{}: {}", -/// level_style.value(record.level()), -/// record.args()) -/// }); -/// ``` -/// -/// Styles can be re-used to output multiple values: -/// -/// ``` -/// use std::io::Write; -/// use env_logger::fmt::Color; -/// -/// let mut builder = env_logger::Builder::new(); -/// -/// builder.format(|buf, record| { -/// let mut bold = buf.style(); -/// -/// bold.set_bold(true); -/// -/// writeln!(buf, "{}: {} {}", -/// bold.value(record.level()), -/// bold.value("some bold text"), -/// record.args()) -/// }); -/// ``` -/// -/// [`Formatter::style`]: struct.Formatter.html#method.style -/// [color]: #method.set_color -/// [weight]: #method.set_bold -/// [`value`]: #method.value -#[derive(Clone)] -pub struct Style { - pub(in crate::fmt) buf: Rc>, - pub(in crate::fmt) spec: ColorSpec, -} - -impl Style { - /// Set the text color. - /// - /// # Examples - /// - /// Create a style with red text: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_color(Color::Red); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_color(&mut self, color: Color) -> &mut Style { - self.spec.set_fg(Some(color.into_termcolor())); - self - } - - /// Set the text weight. - /// - /// If `yes` is true then text will be written in bold. - /// If `yes` is false then text will be written in the default weight. - /// - /// # Examples - /// - /// Create a style with bold text: - /// - /// ``` - /// use std::io::Write; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_bold(true); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_bold(&mut self, yes: bool) -> &mut Style { - self.spec.set_bold(yes); - self - } - - /// Set the text intensity. - /// - /// If `yes` is true then text will be written in a brighter color. - /// If `yes` is false then text will be written in the default color. - /// - /// # Examples - /// - /// Create a style with intense text: - /// - /// ``` - /// use std::io::Write; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_intense(true); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_intense(&mut self, yes: bool) -> &mut Style { - self.spec.set_intense(yes); - self - } - - /// Set whether the text is dimmed. - /// - /// If `yes` is true then text will be written in a dimmer color. - /// If `yes` is false then text will be written in the default color. - /// - /// # Examples - /// - /// Create a style with dimmed text: - /// - /// ``` - /// use std::io::Write; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_dimmed(true); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_dimmed(&mut self, yes: bool) -> &mut Style { - self.spec.set_dimmed(yes); - self - } - - /// Set the background color. - /// - /// # Examples - /// - /// Create a style with a yellow background: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_bg(Color::Yellow); - /// - /// writeln!(buf, "{}", style.value(record.args())) - /// }); - /// ``` - pub fn set_bg(&mut self, color: Color) -> &mut Style { - self.spec.set_bg(Some(color.into_termcolor())); - self - } - - /// Wrap a value in the style. - /// - /// The same `Style` can be used to print multiple different values. - /// - /// # Examples - /// - /// Create a bold, red colored style and use it to print the log level: - /// - /// ``` - /// use std::io::Write; - /// use env_logger::fmt::Color; - /// - /// let mut builder = env_logger::Builder::new(); - /// - /// builder.format(|buf, record| { - /// let mut style = buf.style(); - /// - /// style.set_color(Color::Red).set_bold(true); - /// - /// writeln!(buf, "{}: {}", - /// style.value(record.level()), - /// record.args()) - /// }); - /// ``` - pub fn value(&self, value: T) -> StyledValue { - StyledValue { - style: Cow::Borrowed(self), - value, - } - } - - /// Wrap a value in the style by taking ownership of it. - pub(crate) fn into_value(self, value: T) -> StyledValue<'static, T> { - StyledValue { - style: Cow::Owned(self), - value, - } - } -} - -impl fmt::Debug for Style { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Style").field("spec", &self.spec).finish() - } -} - -/// A value that can be printed using the given styles. -/// -/// It is the result of calling [`Style::value`]. -/// -/// [`Style::value`]: struct.Style.html#method.value -pub struct StyledValue<'a, T> { - style: Cow<'a, Style>, - value: T, -} - -impl<'a, T> StyledValue<'a, T> { - fn write_fmt(&self, f: F) -> fmt::Result - where - F: FnOnce() -> fmt::Result, - { - self.style - .buf - .borrow_mut() - .set_color(&self.style.spec) - .map_err(|_| fmt::Error)?; - - // Always try to reset the terminal style, even if writing failed - let write = f(); - let reset = self.style.buf.borrow_mut().reset().map_err(|_| fmt::Error); - - write.and(reset) - } -} - -macro_rules! impl_styled_value_fmt { - ($($fmt_trait:path),*) => { - $( - impl<'a, T: $fmt_trait> $fmt_trait for StyledValue<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result { - self.write_fmt(|| T::fmt(&self.value, f)) - } - } - )* - }; -} - -impl_styled_value_fmt!( - fmt::Debug, - fmt::Display, - fmt::Pointer, - fmt::Octal, - fmt::Binary, - fmt::UpperHex, - fmt::LowerHex, - fmt::UpperExp, - fmt::LowerExp -); - -// The `Color` type is copied from https://github.com/BurntSushi/termcolor - -/// The set of available colors for the terminal foreground/background. -/// -/// The `Ansi256` and `Rgb` colors will only output the correct codes when -/// paired with the `Ansi` `WriteColor` implementation. -/// -/// The `Ansi256` and `Rgb` color types are not supported when writing colors -/// on Windows using the console. If they are used on Windows, then they are -/// silently ignored and no colors will be emitted. -/// -/// This set may expand over time. -/// -/// This type has a `FromStr` impl that can parse colors from their human -/// readable form. The format is as follows: -/// -/// 1. Any of the explicitly listed colors in English. They are matched -/// case insensitively. -/// 2. A single 8-bit integer, in either decimal or hexadecimal format. -/// 3. A triple of 8-bit integers separated by a comma, where each integer is -/// in decimal or hexadecimal format. -/// -/// Hexadecimal numbers are written with a `0x` prefix. -#[allow(missing_docs)] -#[non_exhaustive] -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Color { - Black, - Blue, - Green, - Red, - Cyan, - Magenta, - Yellow, - White, - Ansi256(u8), - Rgb(u8, u8, u8), -} - -impl Color { - fn into_termcolor(self) -> termcolor::Color { - match self { - Color::Black => termcolor::Color::Black, - Color::Blue => termcolor::Color::Blue, - Color::Green => termcolor::Color::Green, - Color::Red => termcolor::Color::Red, - Color::Cyan => termcolor::Color::Cyan, - Color::Magenta => termcolor::Color::Magenta, - Color::Yellow => termcolor::Color::Yellow, - Color::White => termcolor::Color::White, - Color::Ansi256(value) => termcolor::Color::Ansi256(value), - Color::Rgb(r, g, b) => termcolor::Color::Rgb(r, g, b), - } - } -} diff --git a/src/fmt/writer/termcolor/shim_impl.rs b/src/fmt/writer/termcolor/shim_impl.rs index 84449092..7e702627 100644 --- a/src/fmt/writer/termcolor/shim_impl.rs +++ b/src/fmt/writer/termcolor/shim_impl.rs @@ -2,8 +2,6 @@ use std::{io, sync::Mutex}; use crate::fmt::{WritableTarget, WriteStyle}; -pub(in crate::fmt::writer) mod glob {} - pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, } From b92dd4511ee96ed610d55214063b613d83878d42 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 13:24:27 -0600 Subject: [PATCH 03/13] refactor(fmt): Rename termcolor mod to buffer --- src/fmt/writer/{termcolor => buffer}/extern_impl.rs | 0 src/fmt/writer/{termcolor => buffer}/mod.rs | 0 src/fmt/writer/{termcolor => buffer}/shim_impl.rs | 0 src/fmt/writer/mod.rs | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/fmt/writer/{termcolor => buffer}/extern_impl.rs (100%) rename src/fmt/writer/{termcolor => buffer}/mod.rs (100%) rename src/fmt/writer/{termcolor => buffer}/shim_impl.rs (100%) diff --git a/src/fmt/writer/termcolor/extern_impl.rs b/src/fmt/writer/buffer/extern_impl.rs similarity index 100% rename from src/fmt/writer/termcolor/extern_impl.rs rename to src/fmt/writer/buffer/extern_impl.rs diff --git a/src/fmt/writer/termcolor/mod.rs b/src/fmt/writer/buffer/mod.rs similarity index 100% rename from src/fmt/writer/termcolor/mod.rs rename to src/fmt/writer/buffer/mod.rs diff --git a/src/fmt/writer/termcolor/shim_impl.rs b/src/fmt/writer/buffer/shim_impl.rs similarity index 100% rename from src/fmt/writer/termcolor/shim_impl.rs rename to src/fmt/writer/buffer/shim_impl.rs diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index e1365a49..785c78c7 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -1,15 +1,15 @@ mod atty; -mod termcolor; +mod buffer; use self::atty::{is_stderr, is_stdout}; -use self::termcolor::BufferWriter; +use self::buffer::BufferWriter; use std::{fmt, io, mem, sync::Mutex}; pub(super) mod glob { pub use super::*; } -pub(super) use self::termcolor::Buffer; +pub(super) use self::buffer::Buffer; /// Log target, either `stdout`, `stderr` or a custom pipe. #[non_exhaustive] From aac6824ef072932963e24b1fd67735477fd7aeca Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 13:39:32 -0600 Subject: [PATCH 04/13] refactor(fmt): Rename buffer strategy mods --- src/fmt/writer/buffer/mod.rs | 13 ++++++++----- src/fmt/writer/buffer/{shim_impl.rs => plain.rs} | 0 .../writer/buffer/{extern_impl.rs => termcolor.rs} | 0 3 files changed, 8 insertions(+), 5 deletions(-) rename src/fmt/writer/buffer/{shim_impl.rs => plain.rs} (100%) rename src/fmt/writer/buffer/{extern_impl.rs => termcolor.rs} (100%) diff --git a/src/fmt/writer/buffer/mod.rs b/src/fmt/writer/buffer/mod.rs index 20f01979..d3b44de6 100644 --- a/src/fmt/writer/buffer/mod.rs +++ b/src/fmt/writer/buffer/mod.rs @@ -5,8 +5,11 @@ Its public API is available when the `termcolor` crate is available. The terminal printing is shimmed when the `termcolor` crate is not available. */ -#[cfg_attr(feature = "color", path = "extern_impl.rs")] -#[cfg_attr(not(feature = "color"), path = "shim_impl.rs")] -mod imp; - -pub(in crate::fmt) use self::imp::*; +#[cfg(feature = "color")] +mod termcolor; +#[cfg(feature = "color")] +pub(in crate::fmt) use termcolor::*; +#[cfg(not(feature = "color"))] +mod plain; +#[cfg(not(feature = "color"))] +pub(in crate::fmt) use plain::*; diff --git a/src/fmt/writer/buffer/shim_impl.rs b/src/fmt/writer/buffer/plain.rs similarity index 100% rename from src/fmt/writer/buffer/shim_impl.rs rename to src/fmt/writer/buffer/plain.rs diff --git a/src/fmt/writer/buffer/extern_impl.rs b/src/fmt/writer/buffer/termcolor.rs similarity index 100% rename from src/fmt/writer/buffer/extern_impl.rs rename to src/fmt/writer/buffer/termcolor.rs From f4a0c2c34d82b23664f1292944f1540b9c2ebb15 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 14:47:59 -0600 Subject: [PATCH 05/13] refactor(fmt): Decouple target from buffer --- src/fmt/writer/mod.rs | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 785c78c7..55384b9f 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -54,22 +54,6 @@ pub(super) enum WritableTarget { 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!( @@ -142,7 +126,7 @@ impl fmt::Debug for Writer { /// The target and style choice can be configured before building. #[derive(Debug)] pub(crate) struct Builder { - target: WritableTarget, + target: Target, write_style: WriteStyle, is_test: bool, built: bool, @@ -161,7 +145,7 @@ impl Builder { /// Set the target to write to. pub(crate) fn target(&mut self, target: Target) -> &mut Self { - self.target = target.into(); + self.target = target; self } @@ -195,9 +179,9 @@ impl Builder { let color_choice = match self.write_style { WriteStyle::Auto => { if match &self.target { - WritableTarget::Stderr => is_stderr(), - WritableTarget::Stdout => is_stdout(), - WritableTarget::Pipe(_) => false, + Target::Stderr => is_stderr(), + Target::Stdout => is_stdout(), + Target::Pipe(_) => false, } { WriteStyle::Auto } else { @@ -208,9 +192,9 @@ impl Builder { }; 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), + Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), + Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), + Target::Pipe(pipe) => BufferWriter::pipe(color_choice, Box::new(Mutex::new(pipe))), }; Writer { From 6d55509b3eb9645945bffd555f418382a378751f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 15:11:06 -0600 Subject: [PATCH 06/13] refactor(fmt): Track WriteStyle with the BufferWriter --- src/fmt/writer/buffer/plain.rs | 9 +++++---- src/fmt/writer/buffer/termcolor.rs | 14 ++++++++++---- src/fmt/writer/mod.rs | 15 ++++++++------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index 7e702627..b4356af1 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -19,15 +19,16 @@ impl BufferWriter { } } - pub(in crate::fmt::writer) fn pipe( - _write_style: WriteStyle, - pipe: Box>, - ) -> Self { + pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { BufferWriter { target: WritableTarget::Pipe(pipe), } } + pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { + WriteStyle::Never + } + pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { Buffer(Vec::new()) } diff --git a/src/fmt/writer/buffer/termcolor.rs b/src/fmt/writer/buffer/termcolor.rs index efcf0c3d..9d7374a7 100644 --- a/src/fmt/writer/buffer/termcolor.rs +++ b/src/fmt/writer/buffer/termcolor.rs @@ -8,6 +8,7 @@ use crate::fmt::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) struct BufferWriter { inner: termcolor::BufferWriter, uncolored_target: Option, + write_style: WriteStyle, } impl BufferWriter { @@ -19,6 +20,7 @@ impl BufferWriter { } else { None }, + write_style, } } @@ -30,20 +32,24 @@ impl BufferWriter { } else { None }, + write_style, } } - pub(in crate::fmt::writer) fn pipe( - write_style: WriteStyle, - pipe: Box>, - ) -> Self { + pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { + let write_style = WriteStyle::Never; BufferWriter { // The inner Buffer is never printed from, but it is still needed to handle coloring and other formatting inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), uncolored_target: Some(WritableTarget::Pipe(pipe)), + write_style, } } + pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { + self.write_style + } + pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { Buffer { inner: self.inner.buffer(), diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 55384b9f..d3e7345a 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -98,12 +98,11 @@ impl WriteStyle { /// 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 + self.inner.write_style() } pub(super) fn buffer(&self) -> Buffer { @@ -190,17 +189,19 @@ impl Builder { } color_choice => color_choice, }; + let color_choice = if self.is_test { + WriteStyle::Never + } else { + color_choice + }; let writer = match mem::take(&mut self.target) { Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), - Target::Pipe(pipe) => BufferWriter::pipe(color_choice, Box::new(Mutex::new(pipe))), + Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe))), }; - Writer { - inner: writer, - write_style: self.write_style, - } + Writer { inner: writer } } } From 87008fdf78b861202ca9da4869475649e72b8e82 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 16:19:58 -0600 Subject: [PATCH 07/13] fix(fmt): Don't panic on broken pipes without termcolor Fixes #221 --- src/fmt/writer/buffer/plain.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index b4356af1..094f80b4 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -4,24 +4,28 @@ use crate::fmt::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, + is_test: bool, } impl BufferWriter { - pub(in crate::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self { + pub(in crate::fmt::writer) fn stderr(is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { target: WritableTarget::Stderr, + is_test, } } - pub(in crate::fmt::writer) fn stdout(_is_test: bool, _write_style: WriteStyle) -> Self { + pub(in crate::fmt::writer) fn stdout(is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { target: WritableTarget::Stdout, + is_test, } } pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { BufferWriter { target: WritableTarget::Pipe(pipe), + is_test: false, } } @@ -34,14 +38,22 @@ impl BufferWriter { } pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { + use std::io::Write as _; + // This impl uses the `eprint` and `print` macros // instead of using the streams directly. // This is so their output can be captured by `cargo test`. - match &self.target { + match (&self.target, self.is_test) { // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(&buf.0)?, - WritableTarget::Stdout => print!("{}", String::from_utf8_lossy(&buf.0)), - WritableTarget::Stderr => eprint!("{}", String::from_utf8_lossy(&buf.0)), + (WritableTarget::Pipe(pipe), _) => pipe.lock().unwrap().write_all(&buf.0)?, + (WritableTarget::Stdout, true) => print!("{}", String::from_utf8_lossy(&buf.0)), + (WritableTarget::Stdout, false) => { + write!(std::io::stdout(), "{}", String::from_utf8_lossy(&buf.0))? + } + (WritableTarget::Stderr, true) => eprint!("{}", String::from_utf8_lossy(&buf.0)), + (WritableTarget::Stderr, false) => { + write!(std::io::stderr(), "{}", String::from_utf8_lossy(&buf.0))? + } } Ok(()) From f5f3392886cb3f2a1a3180122666fa40a2150efe Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 16:31:19 -0600 Subject: [PATCH 08/13] refactor(fmt): Pull is_test into the target --- src/fmt/writer/buffer/plain.rs | 30 +++++++++++++++++------------- src/fmt/writer/buffer/termcolor.rs | 10 ++++++---- src/fmt/writer/mod.rs | 20 ++++++++++++++------ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index 094f80b4..0ebd6533 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -4,28 +4,32 @@ use crate::fmt::{WritableTarget, WriteStyle}; pub(in crate::fmt::writer) struct BufferWriter { target: WritableTarget, - is_test: bool, } impl BufferWriter { pub(in crate::fmt::writer) fn stderr(is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { - target: WritableTarget::Stderr, - is_test, + target: if is_test { + WritableTarget::PrintStderr + } else { + WritableTarget::WriteStderr + }, } } pub(in crate::fmt::writer) fn stdout(is_test: bool, _write_style: WriteStyle) -> Self { BufferWriter { - target: WritableTarget::Stdout, - is_test, + target: if is_test { + WritableTarget::PrintStdout + } else { + WritableTarget::WriteStdout + }, } } pub(in crate::fmt::writer) fn pipe(pipe: Box>) -> Self { BufferWriter { target: WritableTarget::Pipe(pipe), - is_test: false, } } @@ -43,17 +47,17 @@ impl BufferWriter { // This impl uses the `eprint` and `print` macros // instead of using the streams directly. // This is so their output can be captured by `cargo test`. - match (&self.target, self.is_test) { - // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - (WritableTarget::Pipe(pipe), _) => pipe.lock().unwrap().write_all(&buf.0)?, - (WritableTarget::Stdout, true) => print!("{}", String::from_utf8_lossy(&buf.0)), - (WritableTarget::Stdout, false) => { + match &self.target { + WritableTarget::WriteStdout => { write!(std::io::stdout(), "{}", String::from_utf8_lossy(&buf.0))? } - (WritableTarget::Stderr, true) => eprint!("{}", String::from_utf8_lossy(&buf.0)), - (WritableTarget::Stderr, false) => { + WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(&buf.0)), + WritableTarget::WriteStderr => { write!(std::io::stderr(), "{}", String::from_utf8_lossy(&buf.0))? } + WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(&buf.0)), + // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. + WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(&buf.0)?, } Ok(()) diff --git a/src/fmt/writer/buffer/termcolor.rs b/src/fmt/writer/buffer/termcolor.rs index 9d7374a7..f854ae6e 100644 --- a/src/fmt/writer/buffer/termcolor.rs +++ b/src/fmt/writer/buffer/termcolor.rs @@ -16,7 +16,7 @@ impl BufferWriter { BufferWriter { inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()), uncolored_target: if is_test { - Some(WritableTarget::Stderr) + Some(WritableTarget::PrintStderr) } else { None }, @@ -28,7 +28,7 @@ impl BufferWriter { BufferWriter { inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()), uncolored_target: if is_test { - Some(WritableTarget::Stdout) + Some(WritableTarget::PrintStdout) } else { None }, @@ -65,8 +65,10 @@ impl BufferWriter { let log = String::from_utf8_lossy(buf.bytes()); match target { - WritableTarget::Stderr => eprint!("{}", log), - WritableTarget::Stdout => print!("{}", log), + WritableTarget::WriteStdout => print!("{}", log), + WritableTarget::PrintStdout => print!("{}", log), + WritableTarget::WriteStderr => eprint!("{}", log), + WritableTarget::PrintStderr => eprint!("{}", log), WritableTarget::Pipe(pipe) => write!(pipe.lock().unwrap(), "{}", log)?, } diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index d3e7345a..67648435 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -46,10 +46,16 @@ impl fmt::Debug for Target { /// /// 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 written to standard output. + #[allow(dead_code)] + WriteStdout, + /// Logs will be printed to standard output. + PrintStdout, + /// Logs will be written to standard error. + #[allow(dead_code)] + WriteStderr, + /// Logs will be printed to standard error. + PrintStderr, /// Logs will be sent to a custom pipe. Pipe(Box>), } @@ -60,8 +66,10 @@ impl fmt::Debug for WritableTarget { f, "{}", match self { - Self::Stdout => "stdout", - Self::Stderr => "stderr", + Self::WriteStdout => "stdout", + Self::PrintStdout => "stdout", + Self::WriteStderr => "stderr", + Self::PrintStderr => "stderr", Self::Pipe(_) => "pipe", } ) From e8674a237b62460b6dbc4ed8d8b48b6db0a97de4 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 16:41:36 -0600 Subject: [PATCH 09/13] refactor(fmt): Consolidate target printing --- src/fmt/mod.rs | 2 +- src/fmt/writer/buffer/plain.rs | 23 ++--------------------- src/fmt/writer/buffer/termcolor.rs | 17 ++--------------- src/fmt/writer/mod.rs | 22 ++++++++++++++++++++++ 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 30b1384e..5f1d5c8f 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -468,7 +468,7 @@ mod tests { fmt.write(&record).expect("failed to write record"); let buf = buf.borrow(); - String::from_utf8(buf.bytes().to_vec()).expect("failed to read record") + String::from_utf8(buf.as_bytes().to_vec()).expect("failed to read record") } fn write_target(target: &str, fmt: DefaultFormat) -> String { diff --git a/src/fmt/writer/buffer/plain.rs b/src/fmt/writer/buffer/plain.rs index 0ebd6533..e6809b06 100644 --- a/src/fmt/writer/buffer/plain.rs +++ b/src/fmt/writer/buffer/plain.rs @@ -42,25 +42,7 @@ impl BufferWriter { } pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { - use std::io::Write as _; - - // This impl uses the `eprint` and `print` macros - // instead of using the streams directly. - // This is so their output can be captured by `cargo test`. - match &self.target { - WritableTarget::WriteStdout => { - write!(std::io::stdout(), "{}", String::from_utf8_lossy(&buf.0))? - } - WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(&buf.0)), - WritableTarget::WriteStderr => { - write!(std::io::stderr(), "{}", String::from_utf8_lossy(&buf.0))? - } - WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(&buf.0)), - // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(&buf.0)?, - } - - Ok(()) + self.target.print(buf) } } @@ -80,8 +62,7 @@ impl Buffer { Ok(()) } - #[cfg(test)] - pub(in crate::fmt) fn bytes(&self) -> &[u8] { + pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { &self.0 } } diff --git a/src/fmt/writer/buffer/termcolor.rs b/src/fmt/writer/buffer/termcolor.rs index f854ae6e..d3090a17 100644 --- a/src/fmt/writer/buffer/termcolor.rs +++ b/src/fmt/writer/buffer/termcolor.rs @@ -59,20 +59,7 @@ impl BufferWriter { pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { if let Some(target) = &self.uncolored_target { - // This impl uses the `eprint` and `print` macros - // instead of `termcolor`'s buffer. - // This is so their output can be captured by `cargo test` - let log = String::from_utf8_lossy(buf.bytes()); - - match target { - WritableTarget::WriteStdout => print!("{}", log), - WritableTarget::PrintStdout => print!("{}", log), - WritableTarget::WriteStderr => eprint!("{}", log), - WritableTarget::PrintStderr => eprint!("{}", log), - WritableTarget::Pipe(pipe) => write!(pipe.lock().unwrap(), "{}", log)?, - } - - Ok(()) + target.print(buf) } else { self.inner.print(&buf.inner) } @@ -97,7 +84,7 @@ impl Buffer { self.inner.flush() } - pub(in crate::fmt) fn bytes(&self) -> &[u8] { + pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { self.inner.as_slice() } diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 67648435..8dafd264 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -60,6 +60,28 @@ pub(super) enum WritableTarget { Pipe(Box>), } +impl WritableTarget { + fn print(&self, buf: &Buffer) -> io::Result<()> { + use std::io::Write as _; + + let buf = buf.as_bytes(); + match self { + WritableTarget::WriteStdout => { + write!(std::io::stdout(), "{}", String::from_utf8_lossy(buf))? + } + WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), + WritableTarget::WriteStderr => { + write!(std::io::stderr(), "{}", String::from_utf8_lossy(buf))? + } + WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(buf)), + // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. + WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(buf)?, + } + + Ok(()) + } +} + impl fmt::Debug for WritableTarget { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( From 7428386da32c2a78b80f7e67a04b5fd5449601d5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 17:07:13 -0600 Subject: [PATCH 10/13] refactor(fmt): Pull out stream lookup from write --- src/fmt/writer/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 8dafd264..52265d46 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -67,15 +67,20 @@ impl WritableTarget { let buf = buf.as_bytes(); match self { WritableTarget::WriteStdout => { - write!(std::io::stdout(), "{}", String::from_utf8_lossy(buf))? + let mut stream = std::io::stdout().lock(); + write!(stream, "{}", String::from_utf8_lossy(buf))?; } WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), WritableTarget::WriteStderr => { - write!(std::io::stderr(), "{}", String::from_utf8_lossy(buf))? + let mut stream = std::io::stderr().lock(); + write!(stream, "{}", String::from_utf8_lossy(buf))?; } WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(buf)), // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. - WritableTarget::Pipe(pipe) => pipe.lock().unwrap().write_all(buf)?, + WritableTarget::Pipe(pipe) => { + let mut stream = pipe.lock().unwrap(); + write!(stream, "{}", String::from_utf8_lossy(buf))?; + } } Ok(()) From 2b3f26fc29952296fdf79fa43d5beb35ddacbf40 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 17:07:58 -0600 Subject: [PATCH 11/13] perf(fmt): Avoid UTF-8 validation --- src/fmt/writer/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index 52265d46..fa7ce1f1 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -68,18 +68,18 @@ impl WritableTarget { match self { WritableTarget::WriteStdout => { let mut stream = std::io::stdout().lock(); - write!(stream, "{}", String::from_utf8_lossy(buf))?; + stream.write_all(buf)?; } WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), WritableTarget::WriteStderr => { let mut stream = std::io::stderr().lock(); - write!(stream, "{}", String::from_utf8_lossy(buf))?; + stream.write_all(buf)?; } WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(buf)), // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. WritableTarget::Pipe(pipe) => { let mut stream = pipe.lock().unwrap(); - write!(stream, "{}", String::from_utf8_lossy(buf))?; + stream.write_all(buf)?; } } From c088820ae84318077e5a37a3bed6d20b3f7aa3f3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 17:10:24 -0600 Subject: [PATCH 12/13] fix(fmt): Ensure stream gets flushed Fixes #278 --- src/fmt/writer/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index fa7ce1f1..ba0ec7ed 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -69,17 +69,20 @@ impl WritableTarget { WritableTarget::WriteStdout => { let mut stream = std::io::stdout().lock(); stream.write_all(buf)?; + stream.flush()?; } WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), WritableTarget::WriteStderr => { let mut stream = std::io::stderr().lock(); stream.write_all(buf)?; + stream.flush()?; } WritableTarget::PrintStderr => eprint!("{}", String::from_utf8_lossy(buf)), // Safety: If the target type is `Pipe`, `target_pipe` will always be non-empty. WritableTarget::Pipe(pipe) => { let mut stream = pipe.lock().unwrap(); stream.write_all(buf)?; + stream.flush()?; } } From 939687dd75b606186989aa72fe43e716eaca5659 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 17 Jan 2024 19:37:48 -0600 Subject: [PATCH 13/13] style: Make clippy happy --- src/fmt/writer/buffer/mod.rs | 2 +- src/fmt/writer/mod.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/fmt/writer/buffer/mod.rs b/src/fmt/writer/buffer/mod.rs index d3b44de6..4e678b2d 100644 --- a/src/fmt/writer/buffer/mod.rs +++ b/src/fmt/writer/buffer/mod.rs @@ -8,7 +8,7 @@ The terminal printing is shimmed when the `termcolor` crate is not available. #[cfg(feature = "color")] mod termcolor; #[cfg(feature = "color")] -pub(in crate::fmt) use termcolor::*; +pub(in crate::fmt) use self::termcolor::*; #[cfg(not(feature = "color"))] mod plain; #[cfg(not(feature = "color"))] diff --git a/src/fmt/writer/mod.rs b/src/fmt/writer/mod.rs index ba0ec7ed..41466b92 100644 --- a/src/fmt/writer/mod.rs +++ b/src/fmt/writer/mod.rs @@ -67,13 +67,15 @@ impl WritableTarget { let buf = buf.as_bytes(); match self { WritableTarget::WriteStdout => { - let mut stream = std::io::stdout().lock(); + let stream = std::io::stdout(); + let mut stream = stream.lock(); stream.write_all(buf)?; stream.flush()?; } WritableTarget::PrintStdout => print!("{}", String::from_utf8_lossy(buf)), WritableTarget::WriteStderr => { - let mut stream = std::io::stderr().lock(); + let stream = std::io::stderr(); + let mut stream = stream.lock(); stream.write_all(buf)?; stream.flush()?; }