use std::io::prelude::*;
use std::{io, fmt};
use std::rc::Rc;
use std::cell::RefCell;
use termcolor::{ColorSpec, ColorChoice, Buffer, BufferWriter, WriteColor};
use chrono::{DateTime, Utc};
use chrono::format::Item;
pub use termcolor::Color;
pub struct Formatter {
buf: Rc<RefCell<Buffer>>,
}
#[derive(Clone)]
pub struct Style {
buf: Rc<RefCell<Buffer>>,
spec: ColorSpec,
}
pub struct StyledValue<'a, T> {
style: &'a Style,
value: T,
}
pub struct Timestamp(DateTime<Utc>);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Target {
Stdout,
Stderr,
}
impl Default for Target {
fn default() -> Self {
Target::Stderr
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum WriteStyle {
Auto,
Always,
Never,
}
impl Default for WriteStyle {
fn default() -> Self {
WriteStyle::Auto
}
}
pub(crate) struct Writer(BufferWriter);
pub(crate) struct Builder {
target: Target,
write_style: WriteStyle,
}
impl Builder {
pub fn new() -> Self {
Builder {
target: Default::default(),
write_style: Default::default(),
}
}
pub fn target(&mut self, target: Target) -> &mut Self {
self.target = target;
self
}
pub fn parse(&mut self, write_style: &str) -> &mut Self {
self.write_style(parse_write_style(write_style))
}
pub fn write_style(&mut self, write_style: WriteStyle) -> &mut Self {
self.write_style = write_style;
self
}
pub fn build(&mut self) -> Writer {
let color_choice = match self.write_style {
WriteStyle::Auto => ColorChoice::Auto,
WriteStyle::Always => ColorChoice::Always,
WriteStyle::Never => ColorChoice::Never,
};
let writer = match self.target {
Target::Stderr => BufferWriter::stderr(color_choice),
Target::Stdout => BufferWriter::stdout(color_choice),
};
Writer(writer)
}
}
impl Default for Builder {
fn default() -> Self {
Builder::new()
}
}
impl Style {
pub fn set_color(&mut self, color: Color) -> &mut Style {
self.spec.set_fg(Some(color));
self
}
pub fn set_bold(&mut self, yes: bool) -> &mut Style {
self.spec.set_bold(yes);
self
}
pub fn set_bg(&mut self, color: Color) -> &mut Style {
self.spec.set_bg(Some(color));
self
}
pub fn value<T>(&self, value: T) -> StyledValue<T> {
StyledValue {
style: &self,
value
}
}
}
impl Formatter {
pub(crate) fn new(writer: &Writer) -> Self {
Formatter {
buf: Rc::new(RefCell::new(writer.0.buffer())),
}
}
pub fn style(&self) -> Style {
Style {
buf: self.buf.clone(),
spec: ColorSpec::new(),
}
}
pub fn timestamp(&self) -> Timestamp {
Timestamp(Utc::now())
}
pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> {
writer.0.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<'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)?;
let write = f();
let reset = self.style.buf.borrow_mut().reset().map_err(|_| fmt::Error);
write.and(reset)
}
}
impl fmt::Debug for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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::Debug for Writer {
fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result {
f.debug_struct("Writer").finish()
}
}
impl fmt::Debug for Formatter {
fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result {
f.debug_struct("Formatter").finish()
}
}
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 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);
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result {
const ITEMS: &'static [Item<'static>] = {
use chrono::format::Item::*;
use chrono::format::Numeric::*;
use chrono::format::Fixed::*;
use chrono::format::Pad::*;
&[
Numeric(Year, Zero),
Literal("-"),
Numeric(Month, Zero),
Literal("-"),
Numeric(Day, Zero),
Literal("T"),
Numeric(Hour, Zero),
Literal(":"),
Numeric(Minute, Zero),
Literal(":"),
Numeric(Second, Zero),
Fixed(TimezoneOffsetZ),
]
};
self.0.format_with_items(ITEMS.iter().cloned()).fmt(f)
}
}
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));
}
}
}