use std::{
borrow::Cow,
io::{self, IsTerminal as _},
mem, str,
};
use console::Style;
use derive_more::with_trait::{Deref, DerefMut, Display, From, Into};
use super::Coloring;
#[derive(Clone, Debug)]
pub struct Styles {
pub ok: Style,
pub skipped: Style,
pub err: Style,
pub retry: Style,
pub header: Style,
pub bold: Style,
pub term_width: Option<u16>,
pub is_present: bool,
}
impl Default for Styles {
fn default() -> Self {
Self {
ok: Style::new().green(),
skipped: Style::new().cyan(),
err: Style::new().red(),
retry: Style::new().magenta(),
header: Style::new().blue(),
bold: Style::new().bold(),
term_width: console::Term::stdout().size_checked().map(|(_h, w)| w),
is_present: io::stdout().is_terminal() && console::colors_enabled(),
}
}
}
impl Styles {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn apply_coloring(&mut self, color: Coloring) {
let is_present = match color {
Coloring::Always => true,
Coloring::Never => false,
Coloring::Auto => return,
};
let this = mem::take(self);
self.ok = this.ok.force_styling(is_present);
self.skipped = this.skipped.force_styling(is_present);
self.err = this.err.force_styling(is_present);
self.retry = this.retry.force_styling(is_present);
self.header = this.header.force_styling(is_present);
self.bold = this.bold.force_styling(is_present);
self.is_present = is_present;
}
#[must_use]
pub fn bright(&self) -> Self {
Self {
ok: self.ok.clone().bright(),
skipped: self.skipped.clone().bright(),
err: self.err.clone().bright(),
retry: self.retry.clone().bright(),
header: self.header.clone().bright(),
bold: self.bold.clone().bright(),
term_width: self.term_width,
is_present: self.is_present,
}
}
#[must_use]
pub fn ok<'a>(&self, input: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
if self.is_present {
self.ok.apply_to(input.into()).to_string().into()
} else {
input.into()
}
}
#[must_use]
pub fn skipped<'a>(&self, input: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
if self.is_present {
self.skipped.apply_to(input.into()).to_string().into()
} else {
input.into()
}
}
#[must_use]
pub fn err<'a>(&self, input: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
if self.is_present {
self.err.apply_to(input.into()).to_string().into()
} else {
input.into()
}
}
#[must_use]
pub fn retry<'a>(&self, input: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
if self.is_present {
self.retry.apply_to(input.into()).to_string().into()
} else {
input.into()
}
}
#[must_use]
pub fn header<'a>(&self, input: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
if self.is_present {
self.header.apply_to(input.into()).to_string().into()
} else {
input.into()
}
}
#[must_use]
pub fn bold<'a>(&self, input: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
if self.is_present {
self.bold.apply_to(input.into()).to_string().into()
} else {
input.into()
}
}
#[must_use]
pub fn lines_count(&self, s: impl AsRef<str>) -> usize {
let div_ceil = |l, r| {
let d = l / r;
let rem = l % r;
if rem > 0 && r > 0 { d + 1 } else { d }
};
s.as_ref()
.lines()
.map(|l| {
self.term_width.map_or(1, |w| div_ceil(l.len(), usize::from(w)))
})
.sum()
}
}
pub trait WriteStrExt: io::Write {
fn write_str(&mut self, string: impl AsRef<str>) -> io::Result<()> {
self.write_all(string.as_ref().as_bytes())
}
fn write_line(&mut self, string: impl AsRef<str>) -> io::Result<()> {
self.write_str(string.as_ref())
.and_then(|()| self.write_str("\n"))
.map(drop)
}
fn move_cursor_up(&mut self, n: usize) -> io::Result<()> {
if n > 0 { self.write_str(format!("\x1b[{n}A")) } else { Ok(()) }
}
fn move_cursor_down(&mut self, n: usize) -> io::Result<()> {
if n > 0 { self.write_str(format!("\x1b[{n}B")) } else { Ok(()) }
}
fn clear_last_lines(&mut self, n: usize) -> io::Result<()> {
for _ in 0..n {
self.move_cursor_up(1)?;
self.clear_line()?;
}
Ok(())
}
fn clear_line(&mut self) -> io::Result<()> {
self.write_str("\r\x1b[2K")
}
}
impl<T: io::Write + ?Sized> WriteStrExt for T {}
#[derive(
Clone,
Debug,
Deref,
DerefMut,
Display,
Eq,
From,
Hash,
Into,
Ord,
PartialEq,
PartialOrd,
)]
pub struct WritableString(pub String);
impl io::Write for WritableString {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.push_str(
str::from_utf8(buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}