diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/env_logger/src/fmt | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/env_logger/src/fmt')
9 files changed, 1416 insertions, 0 deletions
diff --git a/third_party/rust/env_logger/src/fmt/humantime/extern_impl.rs b/third_party/rust/env_logger/src/fmt/humantime/extern_impl.rs new file mode 100644 index 0000000000..19dec1b65d --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/humantime/extern_impl.rs @@ -0,0 +1,118 @@ +use std::fmt; +use std::time::SystemTime; + +use humantime::{ + format_rfc3339_micros, format_rfc3339_millis, format_rfc3339_nanos, format_rfc3339_seconds, +}; + +use crate::fmt::{Formatter, TimestampPrecision}; + +pub(in crate::fmt) mod glob { + pub use super::*; +} + +impl Formatter { + /// Get a [`Timestamp`] for the current date and time in UTC. + /// + /// # Examples + /// + /// Include the current timestamp with the log record: + /// + /// ``` + /// use std::io::Write; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let ts = buf.timestamp(); + /// + /// writeln!(buf, "{}: {}: {}", ts, record.level(), record.args()) + /// }); + /// ``` + /// + /// [`Timestamp`]: struct.Timestamp.html + pub fn timestamp(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Seconds, + } + } + + /// Get a [`Timestamp`] for the current date and time in UTC with full + /// second precision. + pub fn timestamp_seconds(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Seconds, + } + } + + /// Get a [`Timestamp`] for the current date and time in UTC with + /// millisecond precision. + pub fn timestamp_millis(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Millis, + } + } + + /// Get a [`Timestamp`] for the current date and time in UTC with + /// microsecond precision. + pub fn timestamp_micros(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Micros, + } + } + + /// Get a [`Timestamp`] for the current date and time in UTC with + /// nanosecond precision. + pub fn timestamp_nanos(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Nanos, + } + } +} + +/// An [RFC3339] formatted timestamp. +/// +/// The timestamp implements [`Display`] and can be written to a [`Formatter`]. +/// +/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt +/// [`Display`]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html +/// [`Formatter`]: struct.Formatter.html +pub struct Timestamp { + time: SystemTime, + precision: TimestampPrecision, +} + +impl fmt::Debug for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// A `Debug` wrapper for `Timestamp` that uses the `Display` implementation. + struct TimestampValue<'a>(&'a Timestamp); + + impl<'a> fmt::Debug for TimestampValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } + } + + f.debug_tuple("Timestamp") + .field(&TimestampValue(&self)) + .finish() + } +} + +impl fmt::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let formatter = match self.precision { + TimestampPrecision::Seconds => format_rfc3339_seconds, + TimestampPrecision::Millis => format_rfc3339_millis, + TimestampPrecision::Micros => format_rfc3339_micros, + TimestampPrecision::Nanos => format_rfc3339_nanos, + }; + + formatter(self.time).fmt(f) + } +} diff --git a/third_party/rust/env_logger/src/fmt/humantime/mod.rs b/third_party/rust/env_logger/src/fmt/humantime/mod.rs new file mode 100644 index 0000000000..ac23ae2493 --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/humantime/mod.rs @@ -0,0 +1,11 @@ +/* +This internal module contains the timestamp implementation. + +Its public API is available when the `humantime` crate is available. +*/ + +#[cfg_attr(feature = "humantime", path = "extern_impl.rs")] +#[cfg_attr(not(feature = "humantime"), path = "shim_impl.rs")] +mod imp; + +pub(in crate::fmt) use self::imp::*; diff --git a/third_party/rust/env_logger/src/fmt/humantime/shim_impl.rs b/third_party/rust/env_logger/src/fmt/humantime/shim_impl.rs new file mode 100644 index 0000000000..906bf9e4c1 --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/humantime/shim_impl.rs @@ -0,0 +1,5 @@ +/* +Timestamps aren't available when we don't have a `humantime` dependency. +*/ + +pub(in crate::fmt) mod glob {} diff --git a/third_party/rust/env_logger/src/fmt/mod.rs b/third_party/rust/env_logger/src/fmt/mod.rs new file mode 100644 index 0000000000..558c4d9d45 --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/mod.rs @@ -0,0 +1,490 @@ +//! Formatting for log records. +//! +//! This module contains a [`Formatter`] that can be used to format log records +//! into without needing temporary allocations. Usually you won't need to worry +//! about the contents of this module and can use the `Formatter` like an ordinary +//! [`Write`]. +//! +//! # Formatting log records +//! +//! The format used to print log records can be customised using the [`Builder::format`] +//! method. +//! Custom formats can apply different color and weight to printed values using +//! [`Style`] builders. +//! +//! ``` +//! use std::io::Write; +//! +//! let mut builder = env_logger::Builder::new(); +//! +//! builder.format(|buf, record| { +//! writeln!(buf, "{}: {}", +//! record.level(), +//! record.args()) +//! }); +//! ``` +//! +//! [`Formatter`]: struct.Formatter.html +//! [`Style`]: struct.Style.html +//! [`Builder::format`]: ../struct.Builder.html#method.format +//! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html + +use std::cell::RefCell; +use std::fmt::Display; +use std::io::prelude::*; +use std::rc::Rc; +use std::{fmt, io, mem}; + +use log::Record; + +mod humantime; +pub(crate) mod writer; + +pub use self::humantime::glob::*; +pub use self::writer::glob::*; + +use self::writer::{Buffer, Writer}; + +pub(crate) mod glob { + pub use super::{Target, TimestampPrecision, WriteStyle}; +} + +/// Formatting precision of timestamps. +/// +/// Seconds give precision of full seconds, milliseconds give thousands of a +/// second (3 decimal digits), microseconds are millionth of a second (6 decimal +/// digits) and nanoseconds are billionth of a second (9 decimal digits). +#[derive(Copy, Clone, Debug)] +pub enum TimestampPrecision { + /// Full second precision (0 decimal digits) + Seconds, + /// Millisecond precision (3 decimal digits) + Millis, + /// Microsecond precision (6 decimal digits) + Micros, + /// Nanosecond precision (9 decimal digits) + Nanos, +} + +/// The default timestamp precision is seconds. +impl Default for TimestampPrecision { + fn default() -> Self { + TimestampPrecision::Seconds + } +} + +/// A formatter to write logs into. +/// +/// `Formatter` implements the standard [`Write`] trait for writing log records. +/// It also supports terminal colors, through the [`style`] method. +/// +/// # Examples +/// +/// Use the [`writeln`] macro to format a log record. +/// An instance of a `Formatter` is passed to an `env_logger` format as `buf`: +/// +/// ``` +/// use std::io::Write; +/// +/// let mut builder = env_logger::Builder::new(); +/// +/// builder.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args())); +/// ``` +/// +/// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html +/// [`writeln`]: https://doc.rust-lang.org/stable/std/macro.writeln.html +/// [`style`]: #method.style +pub struct Formatter { + buf: Rc<RefCell<Buffer>>, + write_style: WriteStyle, +} + +impl Formatter { + pub(crate) fn new(writer: &Writer) -> Self { + Formatter { + buf: Rc::new(RefCell::new(writer.buffer())), + write_style: writer.write_style(), + } + } + + pub(crate) fn write_style(&self) -> WriteStyle { + self.write_style + } + + pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> { + writer.print(&self.buf.borrow()) + } + + pub(crate) fn clear(&mut self) { + self.buf.borrow_mut().clear() + } +} + +impl Write for Formatter { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.buf.borrow_mut().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.buf.borrow_mut().flush() + } +} + +impl fmt::Debug for Formatter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Formatter").finish() + } +} + +pub(crate) type FormatFn = Box<dyn Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send>; + +pub(crate) struct Builder { + pub format_timestamp: Option<TimestampPrecision>, + pub format_module_path: bool, + pub format_level: bool, + pub format_indent: Option<usize>, + pub custom_format: Option<FormatFn>, + built: bool, +} + +impl Default for Builder { + fn default() -> Self { + Builder { + format_timestamp: Some(Default::default()), + format_module_path: true, + format_level: true, + format_indent: Some(4), + custom_format: None, + built: false, + } + } +} + +impl Builder { + /// Convert the format into a callable function. + /// + /// If the `custom_format` is `Some`, then any `default_format` switches are ignored. + /// If the `custom_format` is `None`, then a default format is returned. + /// Any `default_format` switches set to `false` won't be written by the format. + pub fn build(&mut self) -> FormatFn { + assert!(!self.built, "attempt to re-use consumed builder"); + + let built = mem::replace( + self, + Builder { + built: true, + ..Default::default() + }, + ); + + if let Some(fmt) = built.custom_format { + fmt + } else { + Box::new(move |buf, record| { + let fmt = DefaultFormat { + timestamp: built.format_timestamp, + module_path: built.format_module_path, + level: built.format_level, + written_header_value: false, + indent: built.format_indent, + buf, + }; + + fmt.write(record) + }) + } + } +} + +#[cfg(feature = "termcolor")] +type SubtleStyle = StyledValue<'static, &'static str>; +#[cfg(not(feature = "termcolor"))] +type SubtleStyle = &'static str; + +/// The default format. +/// +/// This format needs to work with any combination of crate features. +struct DefaultFormat<'a> { + timestamp: Option<TimestampPrecision>, + module_path: bool, + level: bool, + written_header_value: bool, + indent: Option<usize>, + buf: &'a mut Formatter, +} + +impl<'a> DefaultFormat<'a> { + fn write(mut self, record: &Record) -> io::Result<()> { + self.write_timestamp()?; + self.write_level(record)?; + self.write_module_path(record)?; + self.finish_header()?; + + self.write_args(record) + } + + fn subtle_style(&self, text: &'static str) -> SubtleStyle { + #[cfg(feature = "termcolor")] + { + self.buf + .style() + .set_color(Color::Black) + .set_intense(true) + .clone() + .into_value(text) + } + #[cfg(not(feature = "termcolor"))] + { + text + } + } + + fn write_header_value<T>(&mut self, value: T) -> io::Result<()> + where + T: Display, + { + if !self.written_header_value { + self.written_header_value = true; + + let open_brace = self.subtle_style("["); + write!(self.buf, "{}{}", open_brace, value) + } else { + write!(self.buf, " {}", value) + } + } + + fn write_level(&mut self, record: &Record) -> io::Result<()> { + if !self.level { + return Ok(()); + } + + let level = { + #[cfg(feature = "termcolor")] + { + self.buf.default_styled_level(record.level()) + } + #[cfg(not(feature = "termcolor"))] + { + record.level() + } + }; + + self.write_header_value(format_args!("{:<5}", level)) + } + + fn write_timestamp(&mut self) -> io::Result<()> { + #[cfg(feature = "humantime")] + { + use self::TimestampPrecision::*; + let ts = match self.timestamp { + None => return Ok(()), + Some(Seconds) => self.buf.timestamp_seconds(), + Some(Millis) => self.buf.timestamp_millis(), + Some(Micros) => self.buf.timestamp_micros(), + Some(Nanos) => self.buf.timestamp_nanos(), + }; + + self.write_header_value(ts) + } + #[cfg(not(feature = "humantime"))] + { + // Trick the compiler to think we have used self.timestamp + // Workaround for "field is never used: `timestamp`" compiler nag. + let _ = self.timestamp; + Ok(()) + } + } + + fn write_module_path(&mut self, record: &Record) -> io::Result<()> { + if !self.module_path { + return Ok(()); + } + + if let Some(module_path) = record.module_path() { + self.write_header_value(module_path) + } else { + Ok(()) + } + } + + fn finish_header(&mut self) -> io::Result<()> { + if self.written_header_value { + let close_brace = self.subtle_style("]"); + write!(self.buf, "{} ", close_brace) + } else { + Ok(()) + } + } + + fn write_args(&mut self, record: &Record) -> io::Result<()> { + match self.indent { + // Fast path for no indentation + None => writeln!(self.buf, "{}", record.args()), + + Some(indent_count) => { + // Create a wrapper around the buffer only if we have to actually indent the message + + struct IndentWrapper<'a, 'b: 'a> { + fmt: &'a mut DefaultFormat<'b>, + indent_count: usize, + } + + impl<'a, 'b> Write for IndentWrapper<'a, 'b> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let mut first = true; + for chunk in buf.split(|&x| x == b'\n') { + if !first { + write!(self.fmt.buf, "\n{:width$}", "", width = self.indent_count)?; + } + self.fmt.buf.write_all(chunk)?; + first = false; + } + + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.fmt.buf.flush() + } + } + + // The explicit scope here is just to make older versions of Rust happy + { + let mut wrapper = IndentWrapper { + fmt: self, + indent_count, + }; + write!(wrapper, "{}", record.args())?; + } + + writeln!(self.buf)?; + + Ok(()) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use log::{Level, Record}; + + fn write(fmt: DefaultFormat) -> String { + let buf = fmt.buf.buf.clone(); + + let record = Record::builder() + .args(format_args!("log\nmessage")) + .level(Level::Info) + .file(Some("test.rs")) + .line(Some(144)) + .module_path(Some("test::path")) + .build(); + + fmt.write(&record).expect("failed to write record"); + + let buf = buf.borrow(); + String::from_utf8(buf.bytes().to_vec()).expect("failed to read record") + } + + #[test] + fn format_with_header() { + let writer = writer::Builder::new() + .write_style(WriteStyle::Never) + .build(); + + let mut f = Formatter::new(&writer); + + let written = write(DefaultFormat { + timestamp: None, + module_path: true, + level: true, + written_header_value: false, + indent: None, + buf: &mut f, + }); + + assert_eq!("[INFO test::path] log\nmessage\n", written); + } + + #[test] + fn format_no_header() { + let writer = writer::Builder::new() + .write_style(WriteStyle::Never) + .build(); + + let mut f = Formatter::new(&writer); + + let written = write(DefaultFormat { + timestamp: None, + module_path: false, + level: false, + written_header_value: false, + indent: None, + buf: &mut f, + }); + + assert_eq!("log\nmessage\n", written); + } + + #[test] + fn format_indent_spaces() { + let writer = writer::Builder::new() + .write_style(WriteStyle::Never) + .build(); + + let mut f = Formatter::new(&writer); + + let written = write(DefaultFormat { + timestamp: None, + module_path: true, + level: true, + written_header_value: false, + indent: Some(4), + buf: &mut f, + }); + + assert_eq!("[INFO test::path] log\n message\n", written); + } + + #[test] + fn format_indent_zero_spaces() { + let writer = writer::Builder::new() + .write_style(WriteStyle::Never) + .build(); + + let mut f = Formatter::new(&writer); + + let written = write(DefaultFormat { + timestamp: None, + module_path: true, + level: true, + written_header_value: false, + indent: Some(0), + buf: &mut f, + }); + + assert_eq!("[INFO test::path] log\nmessage\n", written); + } + + #[test] + fn format_indent_spaces_no_header() { + let writer = writer::Builder::new() + .write_style(WriteStyle::Never) + .build(); + + let mut f = Formatter::new(&writer); + + let written = write(DefaultFormat { + timestamp: None, + module_path: false, + level: false, + written_header_value: false, + indent: Some(4), + buf: &mut f, + }); + + assert_eq!("log\n message\n", written); + } +} diff --git a/third_party/rust/env_logger/src/fmt/writer/atty.rs b/third_party/rust/env_logger/src/fmt/writer/atty.rs new file mode 100644 index 0000000000..343539c157 --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/writer/atty.rs @@ -0,0 +1,32 @@ +/* +This internal module contains the terminal detection implementation. + +If the `atty` crate is available then we use it to detect whether we're +attached to a particular TTY. If the `atty` crate is not available we +assume we're not attached to anything. This effectively prevents styles +from being printed. +*/ + +#[cfg(feature = "atty")] +mod imp { + pub(in crate::fmt) fn is_stdout() -> bool { + atty::is(atty::Stream::Stdout) + } + + pub(in crate::fmt) fn is_stderr() -> bool { + atty::is(atty::Stream::Stderr) + } +} + +#[cfg(not(feature = "atty"))] +mod imp { + pub(in crate::fmt) fn is_stdout() -> bool { + false + } + + pub(in crate::fmt) fn is_stderr() -> bool { + false + } +} + +pub(in crate::fmt) use self::imp::*; diff --git a/third_party/rust/env_logger/src/fmt/writer/mod.rs b/third_party/rust/env_logger/src/fmt/writer/mod.rs new file mode 100644 index 0000000000..6ee63a3981 --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/writer/mod.rs @@ -0,0 +1,201 @@ +mod atty; +mod termcolor; + +use self::atty::{is_stderr, is_stdout}; +use self::termcolor::BufferWriter; +use std::{fmt, io}; + +pub(in crate::fmt) mod glob { + pub use super::termcolor::glob::*; + pub use super::*; +} + +pub(in crate::fmt) use self::termcolor::Buffer; + +/// Log target, either `stdout` or `stderr`. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Target { + /// Logs will be sent to standard output. + Stdout, + /// Logs will be sent to standard error. + Stderr, +} + +impl Default for Target { + fn default() -> Self { + Target::Stderr + } +} + +/// 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(in crate::fmt) fn buffer(&self) -> Buffer { + self.inner.buffer() + } + + pub(in crate::fmt) 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. +pub(crate) struct Builder { + target: Target, + 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; + 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`. + 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 { + Target::Stderr => is_stderr(), + Target::Stdout => is_stdout(), + } { + WriteStyle::Auto + } else { + WriteStyle::Never + } + } + color_choice => color_choice, + }; + + let writer = match self.target { + Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), + Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), + }; + + Writer { + inner: writer, + write_style: self.write_style, + } + } +} + +impl Default for Builder { + fn default() -> Self { + Builder::new() + } +} + +impl fmt::Debug for Builder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Logger") + .field("target", &self.target) + .field("write_style", &self.write_style) + .finish() + } +} + +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)); + } + } +} diff --git a/third_party/rust/env_logger/src/fmt/writer/termcolor/extern_impl.rs b/third_party/rust/env_logger/src/fmt/writer/termcolor/extern_impl.rs new file mode 100644 index 0000000000..4324a45bb9 --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/writer/termcolor/extern_impl.rs @@ -0,0 +1,484 @@ +use std::borrow::Cow; +use std::cell::RefCell; +use std::fmt; +use std::io::{self, Write}; +use std::rc::Rc; + +use log::Level; +use termcolor::{self, ColorChoice, ColorSpec, WriteColor}; + +use crate::fmt::{Formatter, Target, 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, + test_target: Option<Target>, +} + +pub(in crate::fmt) struct Buffer { + inner: termcolor::Buffer, + test_target: Option<Target>, +} + +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()), + test_target: if is_test { Some(Target::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()), + test_target: if is_test { Some(Target::Stdout) } else { None }, + } + } + + pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { + Buffer { + inner: self.inner.buffer(), + test_target: self.test_target, + } + } + + pub(in crate::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 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.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>(self, value: T) -> StyledValue<'static, T> { + StyledValue { + style: Cow::Owned(self), + 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)] +#[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) -> 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)), + } + } +} diff --git a/third_party/rust/env_logger/src/fmt/writer/termcolor/mod.rs b/third_party/rust/env_logger/src/fmt/writer/termcolor/mod.rs new file mode 100644 index 0000000000..f3e6768cd0 --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/writer/termcolor/mod.rs @@ -0,0 +1,12 @@ +/* +This internal module contains the style and terminal writing implementation. + +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 = "termcolor", path = "extern_impl.rs")] +#[cfg_attr(not(feature = "termcolor"), path = "shim_impl.rs")] +mod imp; + +pub(in crate::fmt) use self::imp::*; diff --git a/third_party/rust/env_logger/src/fmt/writer/termcolor/shim_impl.rs b/third_party/rust/env_logger/src/fmt/writer/termcolor/shim_impl.rs new file mode 100644 index 0000000000..563f8ad4ff --- /dev/null +++ b/third_party/rust/env_logger/src/fmt/writer/termcolor/shim_impl.rs @@ -0,0 +1,63 @@ +use std::io; + +use crate::fmt::{Target, WriteStyle}; + +pub(in crate::fmt::writer) mod glob {} + +pub(in crate::fmt::writer) struct BufferWriter { + target: Target, +} + +pub(in crate::fmt) struct Buffer(Vec<u8>); + +impl BufferWriter { + pub(in crate::fmt::writer) fn stderr(_is_test: bool, _write_style: WriteStyle) -> Self { + BufferWriter { + target: Target::Stderr, + } + } + + pub(in crate::fmt::writer) fn stdout(_is_test: bool, _write_style: WriteStyle) -> Self { + BufferWriter { + target: Target::Stdout, + } + } + + pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { + Buffer(Vec::new()) + } + + pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { + // 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` + let log = String::from_utf8_lossy(&buf.0); + + match self.target { + Target::Stderr => eprint!("{}", log), + Target::Stdout => print!("{}", log), + } + + Ok(()) + } +} + +impl Buffer { + pub(in crate::fmt) fn clear(&mut self) { + self.0.clear(); + } + + pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.extend(buf); + Ok(buf.len()) + } + + pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + #[cfg(test)] + pub(in crate::fmt) fn bytes(&self) -> &[u8] { + &self.0 + } +} |