Skip to content

Commit

Permalink
Merge pull request #296 from epage/refactor
Browse files Browse the repository at this point in the history
fix(fmt): Address a couple of bugs
  • Loading branch information
epage authored Jan 18, 2024
2 parents 7bc51b0 + 939687d commit 6ec4104
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 319 deletions.
65 changes: 64 additions & 1 deletion src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,19 @@ 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")]
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::*;
Expand Down Expand Up @@ -122,6 +129,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<usize> {
self.buf.borrow_mut().write(buf)
Expand Down Expand Up @@ -405,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 {
Expand Down
219 changes: 19 additions & 200 deletions src/fmt/writer/termcolor/extern_impl.rs → src/fmt/style.rs
Original file line number Diff line number Diff line change
@@ -1,190 +1,9 @@
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 log::Level;
use termcolor::{self, ColorChoice, ColorSpec, WriteColor};

use crate::fmt::{Formatter, 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<WritableTarget>,
}

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 {
inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()),
uncolored_target: if is_test {
Some(WritableTarget::Stderr)
} else {
None
},
}
}

pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self {
BufferWriter {
inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()),
uncolored_target: if is_test {
Some(WritableTarget::Stdout)
} else {
None
},
}
}

pub(in crate::fmt::writer) fn pipe(
write_style: WriteStyle,
pipe: Box<Mutex<dyn io::Write + Send + 'static>>,
) -> Self {
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)),
}
}

pub(in crate::fmt::writer) fn buffer(&self) -> Buffer {
Buffer {
inner: self.inner.buffer(),
has_uncolored_target: self.uncolored_target.is_some(),
}
}

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::Stderr => eprint!("{}", log),
WritableTarget::Stdout => print!("{}", log),
WritableTarget::Pipe(pipe) => write!(pipe.lock().unwrap(), "{}", log)?,
}

Ok(())
} else {
self.inner.print(&buf.inner)
}
}
}

impl Buffer {
pub(in crate::fmt) fn clear(&mut self) {
self.inner.clear()
}

pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}

pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}

pub(in crate::fmt) fn bytes(&self) -> &[u8] {
self.inner.as_slice()
}

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)
} else {
Ok(())
}
}

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()
} else {
Ok(())
}
}
}

impl WriteStyle {
fn into_color_choice(self) -> ColorChoice {
match self {
WriteStyle::Always => ColorChoice::Always,
WriteStyle::Auto => ColorChoice::Auto,
WriteStyle::Never => ColorChoice::Never,
}
}
}
use super::Buffer;

/// A set of styles to apply to the terminal output.
///
Expand Down Expand Up @@ -240,18 +59,8 @@ impl WriteStyle {
/// [`value`]: #method.value
#[derive(Clone)]
pub struct Style {
buf: Rc<RefCell<Buffer>>,
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<RefCell<Buffer>>,
pub(in crate::fmt) spec: termcolor::ColorSpec,
}

impl Style {
Expand Down Expand Up @@ -426,6 +235,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<F>(&self, f: F) -> fmt::Result
where
Expand All @@ -445,12 +270,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),*) => {
$(
Expand Down
13 changes: 8 additions & 5 deletions src/fmt/writer/termcolor/mod.rs → src/fmt/writer/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 self::termcolor::*;
#[cfg(not(feature = "color"))]
mod plain;
#[cfg(not(feature = "color"))]
pub(in crate::fmt) use plain::*;
Loading

0 comments on commit 6ec4104

Please sign in to comment.