aboutsummaryrefslogtreecommitdiffstats
path: root/crates/colors/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/colors/src')
-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
4 files changed, 491 insertions, 0 deletions
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;