about summary refs log tree commit diff stats
path: root/crates/colors
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-07-18 18:06:42 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-07-18 18:06:42 +0200
commite2e88fdabe9bfb3ed236983e6e737b9790d50cd2 (patch)
tree170644c481a5cfe4811bd00b2772b064bfdca25a /crates/colors
parentchore(crates/yt/Cargo.toml): Add `pretty-assertions` for tests (diff)
downloadyt-e2e88fdabe9bfb3ed236983e6e737b9790d50cd2.zip
fix(crates/yt): **Always** honor the `config.global.display_colors` config setting
Diffstat (limited to 'crates/colors')
-rw-r--r--crates/colors/Cargo.toml26
-rw-r--r--crates/colors/src/custom.rs65
-rw-r--r--crates/colors/src/lib.rs87
-rw-r--r--crates/colors/src/list.rs223
-rw-r--r--crates/colors/src/support.rs116
5 files changed, 517 insertions, 0 deletions
diff --git a/crates/colors/Cargo.toml b/crates/colors/Cargo.toml
new file mode 100644
index 0000000..4edefcf
--- /dev/null
+++ b/crates/colors/Cargo.toml
@@ -0,0 +1,26 @@
+# yt - A fully featured command line YouTube client
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# Copyright (C) 2025 uutils developers
+# SPDX-License-Identifier: MIT
+#
+# This file is part of Yt.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+[package]
+name = "colors"
+authors.workspace = true
+license.workspace = true
+description = "A owo-colors inspired color crate."
+version.workspace = true
+edition.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+publish = false
+
+[dependencies]
+
+[lints]
+workspace = true
diff --git a/crates/colors/src/custom.rs b/crates/colors/src/custom.rs
new file mode 100644
index 0000000..2adcfa9
--- /dev/null
+++ b/crates/colors/src/custom.rs
@@ -0,0 +1,65 @@
+// Taken from <https://github.com/owo-colors/owo-colors/blob/61f8bba2f5f80e9f4fa600fbfdf2c21656f1d523/src/colors/custom.rs>
+// at 2025-07-16T18:05:55 CEST.
+
+const U8_TO_STR: [[u8; 3]; 256] = generate_lookup();
+
+const fn generate_lookup() -> [[u8; 3]; 256] {
+    let mut table = [[0, 0, 0]; 256];
+
+    let mut i = 0;
+    while i < 256 {
+        table[i] = [
+            b'0' + (i / 100) as u8,
+            b'0' + (i / 10 % 10) as u8,
+            b'0' + (i % 10) as u8,
+        ];
+        i += 1;
+    }
+
+    table
+}
+
+#[derive(Clone, Copy)]
+pub(crate) enum Plane {
+    Fg,
+    Bg,
+}
+
+pub(crate) const fn rgb_to_ansi(r: u8, g: u8, b: u8, plane: Plane) -> [u8; 18] {
+    let mut buf = *b"\x1b[p8;2;rrr;ggg;bbb";
+
+    let r = U8_TO_STR[r as usize];
+    let g = U8_TO_STR[g as usize];
+    let b = U8_TO_STR[b as usize];
+
+    // p 2
+    buf[2] = match plane {
+        Plane::Fg => b'3',
+        Plane::Bg => b'4',
+    };
+
+    // r 7
+    buf[7] = r[0];
+    buf[8] = r[1];
+    buf[9] = r[2];
+
+    // g 11
+    buf[11] = g[0];
+    buf[12] = g[1];
+    buf[13] = g[2];
+
+    // b 15
+    buf[15] = b[0];
+    buf[16] = b[1];
+    buf[17] = b[2];
+
+    buf
+}
+
+/// This exists since [`unwrap()`] isn't const-safe (it invokes formatting infrastructure)
+pub(crate) const fn bytes_to_str(bytes: &'static [u8]) -> &'static str {
+    match core::str::from_utf8(bytes) {
+        Ok(o) => o,
+        Err(_e) => panic!("Const parsing &[u8] to a string failed!"),
+    }
+}
diff --git a/crates/colors/src/lib.rs b/crates/colors/src/lib.rs
new file mode 100644
index 0000000..ee2c1f8
--- /dev/null
+++ b/crates/colors/src/lib.rs
@@ -0,0 +1,87 @@
+use std::fmt::{Display, Write};
+
+use crate::{
+    list::{elements, methods},
+    support::{CSE, CSI, elements_inner},
+};
+
+pub mod custom;
+mod list;
+mod support;
+
+#[derive(Debug)]
+pub struct Canvas<I: Display>(I);
+
+impl<I: Display> Colorize for Canvas<I> {
+    fn render_into(self, base: &mut String, use_colors: bool) {
+        write!(base, "{}", self.0).expect("Is written into a string");
+
+        if use_colors {
+            // Reset the color and style, if we used colours.
+            base.write_str(CSI).expect("In-memory write");
+            base.write_str("0").expect("In-memory write");
+            base.write_str(CSE).expect("In-memory write");
+        }
+    }
+}
+
+pub trait IntoCanvas: Display + Sized {
+    fn into_canvas(self) -> Canvas<Self> {
+        Canvas(self)
+    }
+
+    methods! { IntoCanvas }
+}
+
+impl<I: Display> IntoCanvas for I {}
+
+pub trait Colorize: Sized {
+    /// Turn this colorized struct into a string, by writing into the base.
+    fn render_into(self, base: &mut String, use_colors: bool);
+
+    /// Turn this colorized struct into a string for consumption.
+    fn render(self, use_colors: bool) -> String {
+        let mut base = String::new();
+        self.render_into(&mut base, use_colors);
+        base
+    }
+
+    methods! { Colorize }
+}
+
+elements! {}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Colorize, IntoCanvas};
+
+    #[test]
+    fn test_colorize_basic() {
+        let base = "Base".green().render(true);
+        #[rustfmt::skip]
+        let expected = concat!(
+            "\x1b[32m",
+            "Base",
+            "\x1b[0m",
+        );
+
+        assert_eq!(base.as_str(), expected);
+    }
+
+    #[test]
+    fn test_colorize_combo() {
+        let base = "Base".green().on_red().bold().strike_through().render(true);
+
+        #[rustfmt::skip]
+        let expected = concat!(
+            "\x1b[9m",  // strike_through
+            "\x1b[1m",  // bold
+            "\x1b[41m", // on_red
+            "\x1b[32m", // green
+            "Base",
+            "\x1b[0m",
+        );
+
+        assert_eq!(base.as_str(), expected);
+    }
+}
diff --git a/crates/colors/src/list.rs b/crates/colors/src/list.rs
new file mode 100644
index 0000000..ecbe465
--- /dev/null
+++ b/crates/colors/src/list.rs
@@ -0,0 +1,223 @@
+use crate::support::prepend_input;
+
+prepend_input! {
+    crate::support::methods_inner as methods (($tt:tt) -> {$tt}),
+    crate::support::elements_inner as elements,
+
+    <shared_input>
+    {
+        // Colors
+        Black black 30,
+        OnBlack on_black 40,
+
+        Red red 31,
+        OnRed on_red 41,
+
+        Green green 32,
+        OnGreen on_green 42,
+
+        Yellow yellow 33,
+        OnYellow on_yellow 43,
+
+        Blue blue 34,
+        OnBlue on_blue 44,
+
+        Magenta magenta 35,
+        OnMagenta on_magenta 45,
+
+        Cyan cyan 36,
+        OnCyan on_cyan 46,
+
+        White white 37,
+        OnWhite on_white 47,
+
+        Default default 39,
+        OnDefault on_default 49,
+
+        // Bright bright colors
+        BrightBlack bright_black 90,
+        OnBrightBlack on_bright_black 100,
+
+        BrightRed bright_red 91,
+        OnBrightRed on_bright_red 101,
+
+        BrightGreen bright_green 92,
+        OnBrightGreen on_bright_green 102,
+
+        BrightYellow bright_yellow 93,
+        OnBrightYellow on_bright_yellow 103,
+
+        BrightBlue bright_blue 94,
+        OnBrightBlue on_bright_blue 104,
+
+        BrightMagenta bright_magenta 95,
+        OnBrightMagenta on_bright_magenta 105,
+
+        BrightCyan bright_cyan 96,
+        OnBrightCyan on_bright_cyan 106,
+
+        BrightWhite bright_white 97,
+        OnBrightWhite on_bright_white 107,
+
+        // CSS colors
+        // TODO(@bpeetz): Also support background colors with these values. <2025-07-16>
+        AliceBlue alice_blue (240, 248, 255),
+        AntiqueWhite antique_white (250, 235, 215),
+        Aqua aqua (0, 255, 255),
+        Aquamarine aquamarine (127, 255, 212),
+        Azure azure (240, 255, 255),
+        Beige beige (245, 245, 220),
+        Bisque bisque (255, 228, 196),
+        // Black black (0, 0, 0),
+        BlanchedAlmond blanched_almond (255, 235, 205),
+        // Blue blue (0, 0, 255),
+        BlueViolet blue_violet (138, 43, 226),
+        Brown brown (165, 42, 42),
+        BurlyWood burly_wood (222, 184, 135),
+        CadetBlue cadet_blue (95, 158, 160),
+        Chartreuse chartreuse (127, 255, 0),
+        Chocolate chocolate (210, 105, 30),
+        Coral coral (255, 127, 80),
+        CornflowerBlue cornflower_blue (100, 149, 237),
+        Cornsilk cornsilk (255, 248, 220),
+        Crimson crimson (220, 20, 60),
+        DarkBlue dark_blue (0, 0, 139),
+        DarkCyan dark_cyan (0, 139, 139),
+        DarkGoldenRod dark_golden_rod (184, 134, 11),
+        DarkGray dark_gray (169, 169, 169),
+        DarkGrey dark_grey (169, 169, 169),
+        DarkGreen dark_green (0, 100, 0),
+        DarkKhaki dark_khaki (189, 183, 107),
+        DarkMagenta dark_magenta (139, 0, 139),
+        DarkOliveGreen dark_olive_green (85, 107, 47),
+        DarkOrange dark_orange (255, 140, 0),
+        DarkOrchid dark_orchid (153, 50, 204),
+        DarkRed dark_red (139, 0, 0),
+        DarkSalmon dark_salmon (233, 150, 122),
+        DarkSeaGreen dark_sea_green (143, 188, 143),
+        DarkSlateBlue dark_slate_blue (72, 61, 139),
+        DarkSlateGray dark_slate_gray (47, 79, 79),
+        DarkSlateGrey dark_slate_grey (47, 79, 79),
+        DarkTurquoise dark_turquoise (0, 206, 209),
+        DarkViolet dark_violet (148, 0, 211),
+        DeepPink deep_pink (255, 20, 147),
+        DeepSkyBlue deep_sky_blue (0, 191, 255),
+        DimGray dim_gray (105, 105, 105),
+        DimGrey dim_grey (105, 105, 105),
+        DodgerBlue dodger_blue (30, 144, 255),
+        FireBrick fire_brick (178, 34, 34),
+        FloralWhite floral_white (255, 250, 240),
+        ForestGreen forest_green (34, 139, 34),
+        Fuchsia fuchsia (255, 0, 255),
+        Gainsboro gainsboro (220, 220, 220),
+        GhostWhite ghost_white (248, 248, 255),
+        Gold gold (255, 215, 0),
+        GoldenRod golden_rod (218, 165, 32),
+        Gray gray (128, 128, 128),
+        Grey grey (128, 128, 128),
+        // Green green (0, 128, 0),
+        GreenYellow green_yellow (173, 255, 47),
+        HoneyDew honey_dew (240, 255, 240),
+        HotPink hot_pink (255, 105, 180),
+        IndianRed indian_red (205, 92, 92),
+        Indigo indigo (75, 0, 130),
+        Ivory ivory (255, 255, 240),
+        Khaki khaki (240, 230, 140),
+        Lavender lavender (230, 230, 250),
+        LavenderBlush lavender_blush (255, 240, 245),
+        LawnGreen lawn_green (124, 252, 0),
+        LemonChiffon lemon_chiffon (255, 250, 205),
+        LightBlue light_blue (173, 216, 230),
+        LightCoral light_coral (240, 128, 128),
+        LightCyan light_cyan (224, 255, 255),
+        LightGoldenRodYellow light_golden_rod_yellow (250, 250, 210),
+        LightGray light_gray (211, 211, 211),
+        LightGrey light_grey (211, 211, 211),
+        LightGreen light_green (144, 238, 144),
+        LightPink light_pink (255, 182, 193),
+        LightSalmon light_salmon (255, 160, 122),
+        LightSeaGreen light_sea_green (32, 178, 170),
+        LightSkyBlue light_sky_blue (135, 206, 250),
+        LightSlateGray light_slate_gray (119, 136, 153),
+        LightSlateGrey light_slate_grey (119, 136, 153),
+        LightSteelBlue light_steel_blue (176, 196, 222),
+        LightYellow light_yellow (255, 255, 224),
+        Lime lime (0, 255, 0),
+        LimeGreen lime_green (50, 205, 50),
+        Linen linen (250, 240, 230),
+        // Magenta magenta (255, 0, 255),
+        Maroon maroon (128, 0, 0),
+        MediumAquaMarine medium_aqua_marine (102, 205, 170),
+        MediumBlue medium_blue (0, 0, 205),
+        MediumOrchid medium_orchid (186, 85, 211),
+        MediumPurple medium_purple (147, 112, 219),
+        MediumSeaGreen medium_sea_green (60, 179, 113),
+        MediumSlateBlue medium_slate_blue (123, 104, 238),
+        MediumSpringGreen medium_spring_green (0, 250, 154),
+        MediumTurquoise medium_turquoise (72, 209, 204),
+        MediumVioletRed medium_violet_red (199, 21, 133),
+        MidnightBlue midnight_blue (25, 25, 112),
+        MintCream mint_cream (245, 255, 250),
+        MistyRose misty_rose (255, 228, 225),
+        Moccasin moccasin (255, 228, 181),
+        NavajoWhite navajo_white (255, 222, 173),
+        Navy navy (0, 0, 128),
+        OldLace old_lace (253, 245, 230),
+        Olive olive (128, 128, 0),
+        OliveDrab olive_drab (107, 142, 35),
+        Orange orange (255, 165, 0),
+        OrangeRed orange_red (255, 69, 0),
+        Orchid orchid (218, 112, 214),
+        PaleGoldenRod pale_golden_rod (238, 232, 170),
+        PaleGreen pale_green (152, 251, 152),
+        PaleTurquoise pale_turquoise (175, 238, 238),
+        PaleVioletRed pale_violet_red (219, 112, 147),
+        PapayaWhip papaya_whip (255, 239, 213),
+        PeachPuff peach_puff (255, 218, 185),
+        Peru peru (205, 133, 63),
+        Pink pink (255, 192, 203),
+        Plum plum (221, 160, 221),
+        PowderBlue powder_blue (176, 224, 230),
+        Purple purple (128, 0, 128),
+        RebeccaPurple rebecca_purple (102, 51, 153),
+        // Red red (255, 0, 0),
+        RosyBrown rosy_brown (188, 143, 143),
+        RoyalBlue royal_blue (65, 105, 225),
+        SaddleBrown saddle_brown (139, 69, 19),
+        Salmon salmon (250, 128, 114),
+        SandyBrown sandy_brown (244, 164, 96),
+        SeaGreen sea_green (46, 139, 87),
+        SeaShell sea_shell (255, 245, 238),
+        Sienna sienna (160, 82, 45),
+        Silver silver (192, 192, 192),
+        SkyBlue sky_blue (135, 206, 235),
+        SlateBlue slate_blue (106, 90, 205),
+        SlateGray slate_gray (112, 128, 144),
+        SlateGrey slate_grey (112, 128, 144),
+        Snow snow (255, 250, 250),
+        SpringGreen spring_green (0, 255, 127),
+        SteelBlue steel_blue (70, 130, 180),
+        Tan tan (210, 180, 140),
+        Teal teal (0, 128, 128),
+        Thistle thistle (216, 191, 216),
+        Tomato tomato (255, 99, 71),
+        Turquoise turquoise (64, 224, 208),
+        Violet violet (238, 130, 238),
+        Wheat wheat (245, 222, 179),
+        // White white (255, 255, 255),
+        WhiteSmoke white_smoke (245, 245, 245),
+        // Yellow yellow (255, 255, 0),
+        YellowGreen yellow_green (154, 205, 50),
+
+        // Styles
+        Bold bold 1,
+        Dim dim 2,
+        Italic italic 3,
+        Underline underline 4,
+        Blink blink 5,
+        BlinkFast blink_fast 6,
+        Reversed reversed 7,
+        Hidden hidden 8,
+        StrikeThrough strike_through 9,
+    }
+}
diff --git a/crates/colors/src/support.rs b/crates/colors/src/support.rs
new file mode 100644
index 0000000..b42ce5d
--- /dev/null
+++ b/crates/colors/src/support.rs
@@ -0,0 +1,116 @@
+pub(super) const CSI: &str = "\x1b[";
+pub(super) const CSE: &str = "m";
+
+macro_rules! elements_inner {
+    (
+        $(
+            $name:ident $_:ident $number:tt
+        ),*
+        $(,)?
+    ) => {
+        $(
+            #[derive(Debug)]
+            pub struct $name<I: Colorize>(I);
+
+            impl<I: Colorize> Colorize for $name<I> {
+                fn render_into(self, base: &mut String, use_colors: bool) {
+                    elements_inner! {@parse_number $number}
+
+                    if use_colors {
+                        base.write_str(CSI).expect("In-memory write");
+                        base.write_str(NUMBERS).expect("In-memory write");
+                        base.write_str(CSE).expect("In-memory write");
+                    }
+                    self.0.render_into(base, use_colors);
+                    // The canvas is resetting the colours again.
+                }
+            }
+        )*
+    };
+
+    (@parse_number $single:literal) => {
+        const NUMBERS: &str = stringify!($single);
+    };
+    (@parse_number ($red:literal, $green:literal, $blue:literal)) => {
+        const NUMBERS_U8: [u8; 18] = $crate::custom::rgb_to_ansi($red, $green, $blue, $crate::custom::Plane::Fg);
+
+        const NUMBERS: &str = $crate::custom::bytes_to_str(&NUMBERS_U8);
+    }
+}
+pub(super) use elements_inner;
+
+macro_rules! methods_inner {
+    (
+        Colorize
+
+        $(
+            $struct_name:ident $fn_name:ident $_:tt
+        ),*
+        $(,)?
+    ) => {
+        $(
+            fn $fn_name(self) -> $struct_name<Self> {
+                $struct_name(self)
+            }
+        )*
+    };
+    (
+        IntoCanvas
+
+        $(
+            $struct_name:ident $fn_name:ident $_:tt
+        ),*
+        $(,)?
+    ) => {
+        $(
+            fn $fn_name(self) -> $struct_name<Canvas<Self>> {
+                $struct_name(Canvas(self))
+            }
+        )*
+    };
+}
+pub(super) use methods_inner;
+
+macro_rules! prepend_input {
+    (
+        $(
+            $existing_macro_name:path as $new_macro_name:ident $(($macro_rule:tt -> $macro_apply:tt))?
+        ),*
+        $(,)?
+
+        <shared_input>
+        $shared_input:tt
+    ) => {
+        $(
+            prepend_input! {
+                @generate_macro
+                $existing_macro_name as $new_macro_name $(($macro_rule -> $macro_apply))?
+                <shared_input>
+                $shared_input
+            }
+        )*
+    };
+
+    (
+        @generate_macro
+        $existing_macro_name:path as $new_macro_name:ident $((($($macro_rule:tt)*) -> {$($macro_apply:tt)*}))?
+
+        <shared_input>
+        {
+            $(
+                $shared_input:tt
+            )*
+        }
+    ) => {
+        macro_rules! $new_macro_name {
+            ($($($macro_rule)*)?) => {
+                $existing_macro_name! {
+                    $($($macro_apply)*)?
+                    $($shared_input)*
+                }
+            }
+        }
+        pub(super) use $new_macro_name;
+    }
+}
+pub(crate) use prepend_input;