blob: 0c2d138792c11f984f354bc58d1f66ac806d5a55 [file] [log] [blame]
use std::borrow::Cow;
use std::fmt;
use std::io::{self, Write};
use std::cell::RefCell;
use std::rc::Rc;
use log::Level;
use termcolor::{self, ColorChoice, ColorSpec, WriteColor};
use ::WriteStyle;
use ::fmt::{Formatter, Target};
pub(in ::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::Black).set_intense(true),
Level::Debug => level_style.set_color(Color::White),
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 ::fmt::writer) struct BufferWriter {
inner: termcolor::BufferWriter,
test_target: Option<Target>,
}
pub(in ::fmt) struct Buffer {
inner: termcolor::Buffer,
test_target: Option<Target>,
}
impl BufferWriter {
pub(in ::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self {
BufferWriter {
inner: termcolor::BufferWriter::stderr(write_style.into_color_choice()),
test_target: if is_test {
Some(Target::Stderr)
} else {
None
},
}
}
pub(in ::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self {
BufferWriter {
inner: termcolor::BufferWriter::stdout(write_style.into_color_choice()),
test_target: if is_test {
Some(Target::Stdout)
} else {
None
},
}
}
pub(in ::fmt::writer) fn buffer(&self) -> Buffer {
Buffer {
inner: self.inner.buffer(),
test_target: self.test_target,
}
}
pub(in ::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> {
if let Some(target) = self.test_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 {
Target::Stderr => eprint!("{}", log),
Target::Stdout => print!("{}", log),
}
Ok(())
} else {
self.inner.print(&buf.inner)
}
}
}
impl Buffer {
pub(in ::fmt) fn clear(&mut self) {
self.inner.clear()
}
pub(in ::fmt) fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
pub(in ::fmt) fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
pub(in ::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.test_target.is_none() {
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.test_target.is_none() {
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,
}
}
}
/// 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 {
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,
}
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(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 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(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<T>(&self, value: T) -> StyledValue<T> {
StyledValue {
style: Cow::Borrowed(self),
value
}
}
/// Wrap a value in the style by taking ownership of it.
pub(crate) fn into_value<T>(&mut self, value: T) -> StyledValue<'static, T> {
StyledValue {
style: Cow::Owned(self.clone()),
value
}
}
}
impl<'a, T> StyledValue<'a, T> {
fn write_fmt<F>(&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)
}
}
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),*) => {
$(
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/ripgrep/tree/master/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)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Color {
Black,
Blue,
Green,
Red,
Cyan,
Magenta,
Yellow,
White,
Ansi256(u8),
Rgb(u8, u8, u8),
#[doc(hidden)]
__Nonexhaustive,
}
impl Color {
fn into_termcolor(self) -> Option<termcolor::Color> {
match self {
Color::Black => Some(termcolor::Color::Black),
Color::Blue => Some(termcolor::Color::Blue),
Color::Green => Some(termcolor::Color::Green),
Color::Red => Some(termcolor::Color::Red),
Color::Cyan => Some(termcolor::Color::Cyan),
Color::Magenta => Some(termcolor::Color::Magenta),
Color::Yellow => Some(termcolor::Color::Yellow),
Color::White => Some(termcolor::Color::White),
Color::Ansi256(value) => Some(termcolor::Color::Ansi256(value)),
Color::Rgb(r, g, b) => Some(termcolor::Color::Rgb(r, g, b)),
_ => None,
}
}
}