libpitust 0.1.0

libpitust implements random utility functions I tend to use a lot
Documentation
use std::fmt::{Display, Write};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum Intensity {
    Normal = 22,
    Dim = 2,
    Bold = 1,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
    pub(self) color: u8,
    pub(self) intensity: Intensity,
}
impl Color {
    pub const fn bold(self) -> Color {
        Color {
            intensity: Intensity::Bold,
            ..self
        }
    }
    pub const fn dim(self) -> Color {
        Color {
            intensity: Intensity::Dim,
            ..self
        }
    }
}

pub struct FormatBuffer {
    state: String,
    color: Color,
    set_color: Color,
    ansi: bool,
}
impl FormatBuffer {
    #[inline(always)]
    pub fn with(&mut self, c: Color, g: impl FnOnce(&mut FormatBuffer)) {
        let old = self.set_state(c);
        g(self);
        self.color = old;
    }
    #[inline(always)]
    pub fn set_state(&mut self, new: Color) -> Color {
        let old = self.color;
        self.color = new;
        old
    }

    fn sync_color(&mut self) {
        if !self.ansi {
            return;
        }
        if self.color != self.set_color {
            let mut old_color = self.set_color;
            self.set_color = self.color;
            let new_color = self.set_color;

            let mut seq = vec![];
            if old_color.color != new_color.color {
                seq.push(new_color.color);
                if new_color.color == 0 {
                    old_color = base();
                }
            }
            if old_color.intensity != new_color.intensity {
                match new_color.intensity {
                    Intensity::Normal => seq.push(22),
                    Intensity::Dim => seq.push(2),
                    Intensity::Bold => seq.push(1),
                }
            }
            self.state.push_str("\x1b[");
            for (i, op) in seq.into_iter().enumerate() {
                if i != 0 {
                    self.state.push_str(";");
                }
                self.state.push_str(&format!("{op}"));
            }
            self.state.push_str("m");
        }
    }
    pub fn put_str(&mut self, s: &str) {
        for c in s.bytes() {
            if !c.is_ascii_whitespace() {
                self.sync_color();
                break;
            }
        }
        self.state.push_str(s);
    }
    pub fn put(&mut self, s: impl Display) {
        self.sync_color();
        let _ = self.state.write_fmt(format_args!("{s}"));
    }
    pub fn puti<Item>(
        &mut self,
        iter: impl IntoIterator<Item = Item>,
        mut f: impl FnMut(&mut FormatBuffer, Item),
    ) {
        let mut first = true;
        for item in iter {
            if !first {
                self.put_str(", ");
            }
            first = false;
            f(self, item);
        }
    }
    pub fn put_fmt(&mut self, s: core::fmt::Arguments) {
        self.sync_color();
        let _ = self.state.write_fmt(s);
    }
    pub fn new(ansi: bool) -> FormatBuffer {
        FormatBuffer {
            state: "".into(),
            color: base(),
            set_color: base(),
            ansi,
        }
    }
    pub fn done(mut self) -> String {
        self.color = base();
        self.sync_color();
        self.state
    }
}

#[doc(hidden)]
pub mod __colors {
    use super::Color;
    use super::Intensity;

    macro_rules! gencolor {
        { $($name:ident : $val:literal),* $(,)? } => {
            $(#[inline(always)] pub const fn $name() -> Color { Color { color: $val, intensity: Intensity::Normal } })*
        };
    }
    gencolor! {
        base: 0,
        red: 31,
        green: 32,
        yellow: 33,
        blue: 34,
        pink: 35,
        cyan: 36,
        white: 37,

        gray: 90,
        bright_red: 91,
        bright_green: 92,
        bright_yellow: 93,
        bright_blue: 94,
        bright_pink: 95,
        bright_cyan: 96,
    }
}

pub use __colors::*;