#[cfg(feature = "pyffi")]
use pyo3::prelude::*;
use super::{Attribute, Fidelity, FormatUpdate, Layer};
use crate::termco::Colorant;
use crate::Translator;
#[cfg_attr(
feature = "pyffi",
pyclass(eq, frozen, hash, module = "prettypretty.color.style")
)]
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Style {
format: FormatUpdate,
foreground: Option<Colorant>,
background: Option<Colorant>,
}
#[cfg_attr(feature = "pyffi", pymethods)]
impl Style {
#[cfg(feature = "pyffi")]
#[new]
pub fn py_new() -> Self {
Self::default()
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn bold(&self) -> Self {
Self {
format: self.format + Attribute::Bold,
..self.clone()
}
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn thin(&self) -> Self {
Self {
format: self.format + Attribute::Thin,
..self.clone()
}
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn italic(&self) -> Self {
Self {
format: self.format + Attribute::Italic,
..self.clone()
}
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn underlined(&self) -> Self {
Self {
format: self.format + Attribute::Underlined,
..self.clone()
}
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn blinking(&self) -> Self {
Self {
format: self.format + Attribute::Blinking,
..self.clone()
}
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn reversed(&self) -> Self {
Self {
format: self.format + Attribute::Reversed,
..self.clone()
}
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn hidden(&self) -> Self {
Self {
format: self.format + Attribute::Hidden,
..self.clone()
}
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn stricken(&self) -> Self {
Self {
format: self.format + Attribute::Stricken,
..self.clone()
}
}
#[cfg(feature = "pyffi")]
#[pyo3(name = "with_foreground")]
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn py_with_foreground(
&self,
#[pyo3(from_py_with = crate::termco::into_colorant)] colorant: Colorant,
) -> Self {
self.with_foreground(colorant)
}
#[cfg(feature = "pyffi")]
#[pyo3(name = "with_background")]
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn py_with_background(
&self,
#[pyo3(from_py_with = crate::termco::into_colorant)] colorant: Colorant,
) -> Self {
self.with_background(colorant)
}
pub fn fidelity(&self) -> Fidelity {
*(!self.format.is_empty())
.then_some(Fidelity::NoColor)
.iter()
.chain(
self.foreground
.as_ref()
.map(core::convert::Into::into)
.iter(),
)
.chain(
self.background
.as_ref()
.map(core::convert::Into::into)
.iter(),
)
.max()
.unwrap_or(&Fidelity::Plain)
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn cap(&self, fidelity: Fidelity, translator: &Translator) -> Self {
let format = self.format.cap(fidelity);
let foreground = self
.foreground
.as_ref()
.and_then(|c| translator.cap_colorant(c, fidelity));
let background = self
.background
.as_ref()
.and_then(|c| translator.cap_colorant(c, fidelity));
Self {
format,
foreground,
background,
}
}
pub fn is_default(&self) -> bool {
self.format.is_empty() && self.foreground.is_none() && self.background.is_none()
}
pub fn format(&self) -> FormatUpdate {
self.format
}
#[cfg(feature = "pyffi")]
#[pyo3(name = "foreground")]
pub fn py_foreground(&self) -> Option<Colorant> {
self.foreground().cloned()
}
#[cfg(feature = "pyffi")]
#[pyo3(name = "background")]
pub fn py_background(&self) -> Option<Colorant> {
self.background().cloned()
}
#[cfg(feature = "pyffi")]
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn __neg__(&self) -> Self {
-self
}
#[cfg(feature = "pyffi")]
pub fn __repr__(&self) -> String {
format!("{:?}", self)
}
#[cfg(feature = "pyffi")]
pub fn __str__(&self) -> String {
format!("{}", self)
}
}
impl Style {
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn with_foreground<C: Into<Colorant>>(&self, color: C) -> Self {
Self {
foreground: Some(color.into()),
..self.clone()
}
}
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn with_background<C: Into<Colorant>>(&self, color: C) -> Self {
Self {
format: self.format,
foreground: self.foreground.clone(),
background: Some(color.into()),
}
}
pub fn foreground(&self) -> Option<&Colorant> {
self.foreground.as_ref()
}
pub fn background(&self) -> Option<&Colorant> {
self.background.as_ref()
}
}
impl core::ops::Neg for &Style {
type Output = Style;
fn neg(self) -> Self::Output {
Style {
format: -self.format,
foreground: self.foreground.as_ref().and_then(|c| -c),
background: self.background.as_ref().and_then(|c| -c),
}
}
}
impl core::ops::Neg for Style {
type Output = Style;
fn neg(self) -> Self::Output {
-(&self)
}
}
impl core::fmt::Display for Style {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_default() {
return Ok(());
}
let mut first = true;
macro_rules! maybe_emit_semicolon {
() => {
if first {
#[allow(unused_assignments)]
{
first = false;
}
} else {
f.write_str(";")?;
}
};
}
f.write_str("\x1b[")?;
for attr in self.format.disable().attributes() {
maybe_emit_semicolon!();
write!(f, "{}", attr.disable_sgr())?;
}
for attr in self.format.enable().attributes() {
maybe_emit_semicolon!();
write!(f, "{}", attr.enable_sgr())?;
}
if let Some(ref colorant) = self.foreground {
maybe_emit_semicolon!();
colorant.write_sgr_params(Layer::Foreground, f)?;
}
if let Some(ref colorant) = self.background {
maybe_emit_semicolon!();
colorant.write_sgr_params(Layer::Background, f)?;
}
f.write_str("m")
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::termco::EmbeddedRgb;
#[test]
fn test_style() {
use super::Attribute::*;
let style = Style::default();
assert_eq!(style.format(), FormatUpdate::default());
assert_eq!(style.foreground(), None);
assert_eq!(style.background(), None);
assert_eq!(format!("{}", style), "");
let negated = -&style;
assert_eq!(negated.format(), FormatUpdate::default());
assert_eq!(negated.foreground(), None);
assert_eq!(negated.background(), None);
assert_eq!(format!("{}", negated), "");
let style = style.bold().underlined();
assert_eq!(style.format(), (Bold + Underlined).into());
assert_eq!(style.foreground(), None);
assert_eq!(style.background(), None);
assert_eq!(format!("{}", style), "\x1b[1;4m");
let negated = -&style;
assert_eq!(negated.format(), -(Bold + Underlined));
assert_eq!(negated.foreground(), None);
assert_eq!(negated.background(), None);
assert_eq!(format!("{}", negated), "\x1b[22;24m");
let style = style.with_foreground(EmbeddedRgb::new(5, 3, 1).unwrap());
assert_eq!(style.format(), (Bold + Underlined).into());
assert_eq!(
style.foreground(),
Some(&Colorant::Embedded(EmbeddedRgb::new(5, 3, 1).unwrap()))
);
assert_eq!(style.background(), None);
assert_eq!(format!("{}", style), "\x1b[1;4;38;5;215m");
let negated = -style;
assert_eq!(negated.format(), -(Bold + Underlined));
assert_eq!(negated.foreground(), Some(&Colorant::Default()));
assert_eq!(negated.background(), None);
assert_eq!(format!("{}", negated), "\x1b[22;24;39m");
}
}