cargo / anstyle / audit
cargo : anstyle @ 1.0.14
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

src/color.rs

686 lines · rust · 1 line annotation

/// Any ANSI color code scheme#[allow(clippy::exhaustive_enums)]#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]pub enum Color {    /// Available 4-bit ANSI color palette codes    ///    /// The user's terminal defines the meaning of each palette code.    Ansi(AnsiColor),    /// 256 (8-bit) color support    ///    /// - `0..16` are [`AnsiColor`] palette codes    /// - `0..232` map to [`RgbColor`] color values    /// - `232..` map to [`RgbColor`] gray-scale values    Ansi256(Ansi256Color),    /// 24-bit ANSI RGB color codes    Rgb(RgbColor),}impl Color {    /// Create a [`Style`][crate::Style] with this as the foreground    #[inline]    pub fn on(self, background: impl Into<Color>) -> crate::Style {        crate::Style::new()            .fg_color(Some(self))            .bg_color(Some(background.into()))    }    /// Create a [`Style`][crate::Style] with this as the foreground    #[inline]    pub const fn on_default(self) -> crate::Style {        crate::Style::new().fg_color(Some(self))    }    /// Render the ANSI code for a foreground color    #[inline]    pub fn render_fg(self) -> impl core::fmt::Display + Copy {        match self {            Self::Ansi(color) => color.as_fg_buffer(),            Self::Ansi256(color) => color.as_fg_buffer(),            Self::Rgb(color) => color.as_fg_buffer(),        }    }    #[inline]    #[cfg(feature = "std")]    pub(crate) fn write_fg_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {        let buffer = match self {            Self::Ansi(color) => color.as_fg_buffer(),            Self::Ansi256(color) => color.as_fg_buffer(),            Self::Rgb(color) => color.as_fg_buffer(),        };        buffer.write_to(write)    }    /// Render the ANSI code for a background color    #[inline]    pub fn render_bg(self) -> impl core::fmt::Display + Copy {        match self {            Self::Ansi(color) => color.as_bg_buffer(),            Self::Ansi256(color) => color.as_bg_buffer(),            Self::Rgb(color) => color.as_bg_buffer(),        }    }    #[inline]    #[cfg(feature = "std")]    pub(crate) fn write_bg_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {        let buffer = match self {            Self::Ansi(color) => color.as_bg_buffer(),            Self::Ansi256(color) => color.as_bg_buffer(),            Self::Rgb(color) => color.as_bg_buffer(),        };        buffer.write_to(write)    }    #[inline]    pub(crate) fn render_underline(self) -> impl core::fmt::Display + Copy {        match self {            Self::Ansi(color) => color.as_underline_buffer(),            Self::Ansi256(color) => color.as_underline_buffer(),            Self::Rgb(color) => color.as_underline_buffer(),        }    }    #[inline]    #[cfg(feature = "std")]    pub(crate) fn write_underline_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {        let buffer = match self {            Self::Ansi(color) => color.as_underline_buffer(),            Self::Ansi256(color) => color.as_underline_buffer(),            Self::Rgb(color) => color.as_underline_buffer(),        };        buffer.write_to(write)    }}impl From<AnsiColor> for Color {    #[inline]    fn from(inner: AnsiColor) -> Self {        Self::Ansi(inner)    }}impl From<Ansi256Color> for Color {    #[inline]    fn from(inner: Ansi256Color) -> Self {        Self::Ansi256(inner)    }}impl From<RgbColor> for Color {    #[inline]    fn from(inner: RgbColor) -> Self {        Self::Rgb(inner)    }}impl From<u8> for Color {    #[inline]    fn from(inner: u8) -> Self {        Self::Ansi256(inner.into())    }}impl From<(u8, u8, u8)> for Color {    #[inline]    fn from(inner: (u8, u8, u8)) -> Self {        Self::Rgb(inner.into())    }}/// Available 4-bit ANSI color palette codes////// The user's terminal defines the meaning of each palette code.#[allow(clippy::exhaustive_enums)]#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]#[repr(u8)]pub enum AnsiColor {    /// Black: #0 (foreground code `30`, background code `40`).    Black,    /// Red: #1 (foreground code `31`, background code `41`).    Red,    /// Green: #2 (foreground code `32`, background code `42`).    Green,    /// Yellow: #3 (foreground code `33`, background code `43`).    Yellow,    /// Blue: #4 (foreground code `34`, background code `44`).    Blue,    /// Magenta: #5 (foreground code `35`, background code `45`).    Magenta,    /// Cyan: #6 (foreground code `36`, background code `46`).    Cyan,    /// White: #7 (foreground code `37`, background code `47`).    White,    /// Bright black: #0 (foreground code `90`, background code `100`).    BrightBlack,    /// Bright red: #1 (foreground code `91`, background code `101`).    BrightRed,    /// Bright green: #2 (foreground code `92`, background code `102`).    BrightGreen,    /// Bright yellow: #3 (foreground code `93`, background code `103`).    BrightYellow,    /// Bright blue: #4 (foreground code `94`, background code `104`).    BrightBlue,    /// Bright magenta: #5 (foreground code `95`, background code `105`).    BrightMagenta,    /// Bright cyan: #6 (foreground code `96`, background code `106`).    BrightCyan,    /// Bright white: #7 (foreground code `97`, background code `107`).    BrightWhite,}impl AnsiColor {    /// Create a [`Style`][crate::Style] with this as the foreground    #[inline]    pub fn on(self, background: impl Into<Color>) -> crate::Style {        crate::Style::new()            .fg_color(Some(self.into()))            .bg_color(Some(background.into()))    }    /// Create a [`Style`][crate::Style] with this as the foreground    #[inline]    pub const fn on_default(self) -> crate::Style {        crate::Style::new().fg_color(Some(Color::Ansi(self)))    }    /// Render the ANSI code for a foreground color    #[inline]    pub fn render_fg(self) -> impl core::fmt::Display + Copy {        NullFormatter(self.as_fg_str())    }    #[inline]    fn as_fg_str(&self) -> &'static str {        match self {            Self::Black => escape!("3", "0"),            Self::Red => escape!("3", "1"),            Self::Green => escape!("3", "2"),            Self::Yellow => escape!("3", "3"),            Self::Blue => escape!("3", "4"),            Self::Magenta => escape!("3", "5"),            Self::Cyan => escape!("3", "6"),            Self::White => escape!("3", "7"),            Self::BrightBlack => escape!("9", "0"),            Self::BrightRed => escape!("9", "1"),            Self::BrightGreen => escape!("9", "2"),            Self::BrightYellow => escape!("9", "3"),            Self::BrightBlue => escape!("9", "4"),            Self::BrightMagenta => escape!("9", "5"),            Self::BrightCyan => escape!("9", "6"),            Self::BrightWhite => escape!("9", "7"),        }    }    #[inline]    fn as_fg_buffer(&self) -> DisplayBuffer {        DisplayBuffer::default().write_str(self.as_fg_str())    }    /// Render the ANSI code for a background color    #[inline]    pub fn render_bg(self) -> impl core::fmt::Display + Copy {        NullFormatter(self.as_bg_str())    }    #[inline]    fn as_bg_str(&self) -> &'static str {        match self {            Self::Black => escape!("4", "0"),            Self::Red => escape!("4", "1"),            Self::Green => escape!("4", "2"),            Self::Yellow => escape!("4", "3"),            Self::Blue => escape!("4", "4"),            Self::Magenta => escape!("4", "5"),            Self::Cyan => escape!("4", "6"),            Self::White => escape!("4", "7"),            Self::BrightBlack => escape!("10", "0"),            Self::BrightRed => escape!("10", "1"),            Self::BrightGreen => escape!("10", "2"),            Self::BrightYellow => escape!("10", "3"),            Self::BrightBlue => escape!("10", "4"),            Self::BrightMagenta => escape!("10", "5"),            Self::BrightCyan => escape!("10", "6"),            Self::BrightWhite => escape!("10", "7"),        }    }    #[inline]    fn as_bg_buffer(&self) -> DisplayBuffer {        DisplayBuffer::default().write_str(self.as_bg_str())    }    #[inline]    fn as_underline_buffer(&self) -> DisplayBuffer {        // No per-color codes; must delegate to `Ansi256Color`        Ansi256Color::from(*self).as_underline_buffer()    }    /// Change the color to/from bright    #[must_use]    #[inline]    pub fn bright(self, yes: bool) -> Self {        if yes {            match self {                Self::Black => Self::BrightBlack,                Self::Red => Self::BrightRed,                Self::Green => Self::BrightGreen,                Self::Yellow => Self::BrightYellow,                Self::Blue => Self::BrightBlue,                Self::Magenta => Self::BrightMagenta,                Self::Cyan => Self::BrightCyan,                Self::White => Self::BrightWhite,                Self::BrightBlack => self,                Self::BrightRed => self,                Self::BrightGreen => self,                Self::BrightYellow => self,                Self::BrightBlue => self,                Self::BrightMagenta => self,                Self::BrightCyan => self,                Self::BrightWhite => self,            }        } else {            match self {                Self::Black => self,                Self::Red => self,                Self::Green => self,                Self::Yellow => self,                Self::Blue => self,                Self::Magenta => self,                Self::Cyan => self,                Self::White => self,                Self::BrightBlack => Self::Black,                Self::BrightRed => Self::Red,                Self::BrightGreen => Self::Green,                Self::BrightYellow => Self::Yellow,                Self::BrightBlue => Self::Blue,                Self::BrightMagenta => Self::Magenta,                Self::BrightCyan => Self::Cyan,                Self::BrightWhite => Self::White,            }        }    }    /// Report whether the color is bright    #[inline]    pub fn is_bright(self) -> bool {        match self {            Self::Black => false,            Self::Red => false,            Self::Green => false,            Self::Yellow => false,            Self::Blue => false,            Self::Magenta => false,            Self::Cyan => false,            Self::White => false,            Self::BrightBlack => true,            Self::BrightRed => true,            Self::BrightGreen => true,            Self::BrightYellow => true,            Self::BrightBlue => true,            Self::BrightMagenta => true,            Self::BrightCyan => true,            Self::BrightWhite => true,        }    }}/// 256 (8-bit) color support////// - `0..16` are [`AnsiColor`] palette codes/// - `0..232` map to [`RgbColor`] color values/// - `232..` map to [`RgbColor`] gray-scale values#[allow(clippy::exhaustive_structs)]#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]#[repr(transparent)]pub struct Ansi256Color(pub u8);impl Ansi256Color {    /// Create a [`Style`][crate::Style] with this as the foreground    #[inline]    pub fn on(self, background: impl Into<Color>) -> crate::Style {        crate::Style::new()            .fg_color(Some(self.into()))            .bg_color(Some(background.into()))    }    /// Create a [`Style`][crate::Style] with this as the foreground    #[inline]    pub const fn on_default(self) -> crate::Style {        crate::Style::new().fg_color(Some(Color::Ansi256(self)))    }    /// Get the raw value    #[inline]    pub const fn index(self) -> u8 {        self.0    }    /// Convert to [`AnsiColor`] when there is a 1:1 mapping    #[inline]    pub const fn into_ansi(self) -> Option<AnsiColor> {        match self.index() {            0 => Some(AnsiColor::Black),            1 => Some(AnsiColor::Red),            2 => Some(AnsiColor::Green),            3 => Some(AnsiColor::Yellow),            4 => Some(AnsiColor::Blue),            5 => Some(AnsiColor::Magenta),            6 => Some(AnsiColor::Cyan),            7 => Some(AnsiColor::White),            8 => Some(AnsiColor::BrightBlack),            9 => Some(AnsiColor::BrightRed),            10 => Some(AnsiColor::BrightGreen),            11 => Some(AnsiColor::BrightYellow),            12 => Some(AnsiColor::BrightBlue),            13 => Some(AnsiColor::BrightMagenta),            14 => Some(AnsiColor::BrightCyan),            15 => Some(AnsiColor::BrightWhite),            _ => None,        }    }    /// Losslessly convert from [`AnsiColor`]    #[inline]    pub const fn from_ansi(color: AnsiColor) -> Self {        match color {            AnsiColor::Black => Self(0),            AnsiColor::Red => Self(1),            AnsiColor::Green => Self(2),            AnsiColor::Yellow => Self(3),            AnsiColor::Blue => Self(4),            AnsiColor::Magenta => Self(5),            AnsiColor::Cyan => Self(6),            AnsiColor::White => Self(7),            AnsiColor::BrightBlack => Self(8),            AnsiColor::BrightRed => Self(9),            AnsiColor::BrightGreen => Self(10),            AnsiColor::BrightYellow => Self(11),            AnsiColor::BrightBlue => Self(12),            AnsiColor::BrightMagenta => Self(13),            AnsiColor::BrightCyan => Self(14),            AnsiColor::BrightWhite => Self(15),        }    }    /// Render the ANSI code for a foreground color    #[inline]    pub fn render_fg(self) -> impl core::fmt::Display + Copy {        self.as_fg_buffer()    }    #[inline]    fn as_fg_buffer(&self) -> DisplayBuffer {        DisplayBuffer::default()            .write_str("\x1B[38;5;")            .write_code(self.index())            .write_str("m")    }    /// Render the ANSI code for a background color    #[inline]    pub fn render_bg(self) -> impl core::fmt::Display + Copy {        self.as_bg_buffer()    }    #[inline]    fn as_bg_buffer(&self) -> DisplayBuffer {        DisplayBuffer::default()            .write_str("\x1B[48;5;")            .write_code(self.index())            .write_str("m")    }    #[inline]    fn as_underline_buffer(&self) -> DisplayBuffer {        DisplayBuffer::default()            .write_str("\x1B[58;5;")            .write_code(self.index())            .write_str("m")    }}impl From<u8> for Ansi256Color {    #[inline]    fn from(inner: u8) -> Self {        Self(inner)    }}impl From<AnsiColor> for Ansi256Color {    #[inline]    fn from(inner: AnsiColor) -> Self {        Self::from_ansi(inner)    }}/// 24-bit ANSI RGB color codes#[allow(clippy::exhaustive_structs)]#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]pub struct RgbColor(pub u8, pub u8, pub u8);impl RgbColor {    /// Create a [`Style`][crate::Style] with this as the foreground    #[inline]    pub fn on(self, background: impl Into<Color>) -> crate::Style {        crate::Style::new()            .fg_color(Some(self.into()))            .bg_color(Some(background.into()))    }    /// Create a [`Style`][crate::Style] with this as the foreground    #[inline]    pub const fn on_default(self) -> crate::Style {        crate::Style::new().fg_color(Some(Color::Rgb(self)))    }    /// Red    #[inline]    pub const fn r(self) -> u8 {        self.0    }    /// Green    #[inline]    pub const fn g(self) -> u8 {        self.1    }    /// Blue    #[inline]    pub const fn b(self) -> u8 {        self.2    }    /// Render the ANSI code for a foreground color    #[inline]    pub fn render_fg(self) -> impl core::fmt::Display + Copy {        self.as_fg_buffer()    }    #[inline]    fn as_fg_buffer(&self) -> DisplayBuffer {        DisplayBuffer::default()            .write_str("\x1B[38;2;")            .write_code(self.r())            .write_str(";")            .write_code(self.g())            .write_str(";")            .write_code(self.b())            .write_str("m")    }    /// Render the ANSI code for a background color    #[inline]    pub fn render_bg(self) -> impl core::fmt::Display + Copy {        self.as_bg_buffer()    }    #[inline]    fn as_bg_buffer(&self) -> DisplayBuffer {        DisplayBuffer::default()            .write_str("\x1B[48;2;")            .write_code(self.r())            .write_str(";")            .write_code(self.g())            .write_str(";")            .write_code(self.b())            .write_str("m")    }    #[inline]    fn as_underline_buffer(&self) -> DisplayBuffer {        DisplayBuffer::default()            .write_str("\x1B[58;2;")            .write_code(self.r())            .write_str(";")            .write_code(self.g())            .write_str(";")            .write_code(self.b())            .write_str("m")    }}impl From<(u8, u8, u8)> for RgbColor {    #[inline]    fn from(inner: (u8, u8, u8)) -> Self {        let (r, g, b) = inner;        Self(r, g, b)    }}
const DISPLAY_BUFFER_CAPACITY: usize = 19;#[derive(Copy, Clone, Default, Debug)]struct DisplayBuffer {    buffer: [u8; DISPLAY_BUFFER_CAPACITY],    len: usize,}impl DisplayBuffer {    #[must_use]    #[inline(never)]    fn write_str(mut self, part: &'static str) -> Self {        for (i, b) in part.as_bytes().iter().enumerate() {            self.buffer[self.len + i] = *b;        }        self.len += part.len();        self    }    #[must_use]    #[inline(never)]    fn write_code(mut self, code: u8) -> Self {        let c1: u8 = (code / 100) % 10;        let c2: u8 = (code / 10) % 10;        let c3: u8 = code % 10;        let mut printed = false;        if c1 != 0 {            printed = true;            self.buffer[self.len] = b'0' + c1;            self.len += 1;        }        if c2 != 0 || printed {            self.buffer[self.len] = b'0' + c2;            self.len += 1;        }        // If we received a zero value we must still print a value.        self.buffer[self.len] = b'0' + c3;        self.len += 1;        self    }    #[inline]    fn as_str(&self) -> &str {        // SAFETY: Only `&str` can be written to the buffer        #[allow(unsafe_code)]        unsafe {            core::str::from_utf8_unchecked(&self.buffer[0..self.len])        }    }    #[inline]    #[cfg(feature = "std")]    fn write_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {        write.write_all(self.as_str().as_bytes())    }}
Line 568–625

Single unsafe block in the crate: DisplayBuffer::as_str (lines 615-617) calls core::str::from_utf8_unchecked on self.buffer[0..self.len]. The buffer is only written by two methods: write_str (lines 579-585) which copies bytes from a &'static str parameter (already valid UTF-8), and write_code (lines 589-609) which writes ASCII digit bytes (b'0' + cN where cN < 10). Both producers yield valid UTF-8, so the unchecked cast is sound. The // SAFETY: comment on line 613 documents this invariant. The buffer is fixed-size (DISPLAY_BUFFER_CAPACITY = 19 on line 568), so the writes also stay in bounds — write_code's path emits at most 3 bytes and is gated by code: u8. Justifies uses-unsafe, unsafe-safe, unsafe-documented, unsafe-minimal.

impl core::fmt::Display for DisplayBuffer {    #[inline]    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {        f.write_str(self.as_str())    }}#[derive(Copy, Clone, Default, Debug)]struct NullFormatter(&'static str);impl core::fmt::Display for NullFormatter {    #[inline]    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {        f.write_str(self.0)    }}#[cfg(test)]#[cfg(feature = "std")]mod test {    use super::*;    #[test]    fn max_display_buffer() {        let c = RgbColor(255, 255, 255);        let actual = c.render_fg().to_string();        assert_eq!(actual, "\u{1b}[38;2;255;255;255m");        assert_eq!(actual.len(), DISPLAY_BUFFER_CAPACITY);    }    #[test]    fn print_size_of() {        use core::mem::size_of;        dbg!(size_of::<Color>());        dbg!(size_of::<AnsiColor>());        dbg!(size_of::<Ansi256Color>());        dbg!(size_of::<RgbColor>());        dbg!(size_of::<DisplayBuffer>());    }    #[test]    fn no_align() {        #[track_caller]        fn assert_no_align(d: impl core::fmt::Display) {            let expected = format!("{d}");            let actual = format!("{d:<10}");            assert_eq!(expected, actual);        }        assert_no_align(AnsiColor::White.render_fg());        assert_no_align(AnsiColor::White.render_bg());        assert_no_align(Ansi256Color(0).render_fg());        assert_no_align(Ansi256Color(0).render_bg());        assert_no_align(RgbColor(0, 0, 0).render_fg());        assert_no_align(RgbColor(0, 0, 0).render_bg());        assert_no_align(Color::Ansi(AnsiColor::White).render_fg());        assert_no_align(Color::Ansi(AnsiColor::White).render_bg());    }}