colourful/
lib.rs

1#[cfg(not(no_std))]
2use std::{io::IsTerminal, sync::atomic::{AtomicU8, Ordering}};
3
4
5#[cfg(not(no_std))]
6static METADATA : AtomicU8 = AtomicU8::new(u8::MAX);
7
8
9pub fn flush_metadata() {
10    METADATA.store(u8::MAX, Ordering::Relaxed);
11    init_metadata();
12}
13
14
15#[cfg(not(no_std))]
16fn init_metadata() -> (bool, bool, bool) {
17    let mut val = METADATA.load(std::sync::atomic::Ordering::Relaxed);
18    if val == u8::MAX {
19        let mut compute = 0b00000000;
20        if std::env::var("NO_COLOR").is_ok() {
21            compute += 2u8.pow(1);
22        }
23
24        
25        if std::env::var("FORCE_COLOR").is_ok() {
26            compute += 2u8.pow(2);
27        }
28
29        
30        if std::io::stdin().is_terminal() {
31            compute += 2u8.pow(3);
32        }
33
34        val = compute;
35        METADATA.store(compute, std::sync::atomic::Ordering::Relaxed);
36    }
37
38    (
39        (val & 2u8.pow(1) == 2u8.pow(1)),
40        (val & 2u8.pow(2) == 2u8.pow(2)),
41        (val & 2u8.pow(3) == 2u8.pow(3)),
42    )
43}
44
45
46pub enum Operation {
47    Colour(Colour), BgColour(Colour),
48    Bold,
49    Dim,
50    Italic,
51    Underline,
52    Blinking,
53    Inverse,
54    Hidden,
55    Strikethrough,
56    Reset,
57}
58
59
60#[derive(Clone, Copy)]
61pub struct Colour {
62    r: u8,
63    g: u8,
64    b: u8,
65}
66
67impl Colour {
68    pub fn rgb(r: u8, g: u8, b: u8) -> Self { Self { r, g, b } }
69}
70
71
72pub struct ColouredString<T> {
73    string: T,
74
75    fg_colour: Option<Colour>,
76    bg_colour: Option<Colour>,
77    is_bold: bool,
78    is_dim: bool,
79    is_italic: bool,
80    is_underline: bool,
81    is_blinking: bool,
82    is_inverse: bool,
83    is_hidden: bool,
84    is_strikethrough: bool,
85}
86
87
88impl<T> ColouredString<T> {
89    pub fn colour(mut self, colour: Colour) -> Self {
90        self.fg_colour = Some(colour);
91        self
92    }
93
94    
95    pub fn bg_colour(mut self, colour: Colour) -> Self {
96        self.bg_colour = Some(colour);
97        self
98    }
99
100
101    pub fn bold(mut self) -> Self {
102        self.is_bold = true;
103        self
104    }
105
106
107    pub fn dim(mut self) -> Self {
108        self.is_dim = true;
109        self
110    }
111
112
113    pub fn italic(mut self) -> Self {
114        self.is_italic = true;
115        self
116    }
117
118
119    pub fn underline(mut self) -> Self {
120        self.is_underline = true;
121        self
122    }
123
124
125    pub fn blinking(mut self) -> Self {
126        self.is_blinking = true;
127        self
128    }
129
130
131    pub fn inverse(mut self) -> Self {
132        self.is_inverse = true;
133        self
134    }
135
136
137    pub fn hidden(mut self) -> Self {
138        self.is_hidden = true;
139        self
140    }
141
142
143    pub fn strikethrough(mut self) -> Self {
144        self.is_strikethrough = true;
145        self
146    }
147
148
149    fn write_ansi(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150        write!(f, "\u{001b}[")?;
151        let mut has_updated = false;
152
153        if let Some(Colour { r, g, b }) = self.fg_colour {
154            has_updated = true;
155            write!(f, "38;2;{r};{g};{b}")?;
156        }
157
158
159        if let Some(Colour { r, g, b }) = self.bg_colour {
160            if has_updated { write!(f, ";")?; }
161            has_updated = true;
162            write!(f, "48;2;{r};{g};{b}")?;
163        }
164
165
166        if self.is_bold {
167            if has_updated { write!(f, ";")?; }
168            has_updated = true;
169            write!(f, "1")?;
170        }
171
172
173        if self.is_dim {
174            if has_updated { write!(f, ";")?; }
175            has_updated = true;
176            write!(f, "2")?;
177        }
178
179
180        if self.is_italic {
181            if has_updated { write!(f, ";")?; }
182            has_updated = true;
183            write!(f, "3")?;
184        }
185
186
187        if self.is_underline {
188            if has_updated { write!(f, ";")?; }
189            has_updated = true;
190            write!(f, "4")?;
191        }
192
193
194        if self.is_blinking {
195            if has_updated { write!(f, ";")?; }
196            has_updated = true;
197            write!(f, "5")?;
198        }
199
200
201        if self.is_inverse {
202            if has_updated { write!(f, ";")?; }
203            has_updated = true;
204            write!(f, "7")?;
205        }
206
207
208        if self.is_hidden {
209            if has_updated { write!(f, ";")?; }
210            has_updated = true;
211            write!(f, "8")?;
212        }
213
214
215        if self.is_strikethrough {
216            if has_updated { write!(f, ";")?; }
217            write!(f, "9")?;
218        }
219
220
221        write!(f, "m")
222    }
223}
224
225
226impl<T: core::fmt::Display> core::fmt::Display for ColouredString<T> {
227    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
228        #[cfg(not(no_std))]
229        {
230            let (no_colour, force_colour, is_terminal) = init_metadata();
231            if !force_colour && (no_colour || !is_terminal) {
232                return self.string.fmt(f)
233            }
234        }
235
236        self.write_ansi(f)?;
237
238        write!(f, "{}\u{001b}[0m", self.string)
239    }
240}
241
242
243impl<T: core::fmt::Debug> core::fmt::Debug for ColouredString<T> {
244    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
245        #[cfg(not(no_std))]
246        {
247            let (no_colour, force_colour, is_terminal) = init_metadata();
248            if !force_colour && (no_colour || !is_terminal) {
249                return self.string.fmt(f)
250            }
251        }
252
253        self.write_ansi(f)?;
254
255        self.string.fmt(f)?;
256
257        write!(f, "\u{001b}[0m")
258    }
259}
260
261
262macro_rules! binding {
263    ($($ident: ident)*) => {
264        $(
265            #[inline(always)]
266            fn $ident(self) -> ColouredString<Self> {
267                self.as_brush().$ident()
268            }
269        )*
270    }
271}
272
273
274pub trait ColourBrush: Sized {
275    #[inline(always)]
276    fn as_brush(self) -> ColouredString<Self> {
277        ColouredString {
278            string: self,
279            fg_colour: None, 
280            bg_colour: None, 
281            is_bold: false, 
282            is_dim: false, 
283            is_italic: false, 
284            is_underline: false, 
285            is_blinking: false, 
286            is_inverse: false, 
287            is_hidden: false, 
288            is_strikethrough: false
289        }
290    }
291
292
293    #[inline(always)]
294    fn colour(self, colour: Colour) -> ColouredString<Self> {
295        self.as_brush().colour(colour)
296    }
297
298
299    #[inline(always)]
300    fn bg_colour(self, colour: Colour) -> ColouredString<Self> {
301        self.as_brush().bg_colour(colour)
302    }
303    
304
305    binding!(
306        bold
307        dim
308        italic
309        underline
310        blinking
311        inverse
312        hidden
313        strikethrough
314
315        black 
316        dark_grey 
317        brown
318        navy_blue
319        olive_green
320        teal
321        red 
322        green 
323        blue 
324        magenta 
325        yellow 
326        orange
327        pink
328        light_grey
329        cyan 
330        white 
331        bg_dark_grey 
332        bg_brown
333        bg_navy_blue
334        bg_olive_green
335        bg_teal
336        bg_red 
337        bg_green 
338        bg_blue 
339        bg_magenta 
340        bg_yellow 
341        bg_orange
342        bg_pink
343        bg_light_grey
344        bg_cyan 
345        bg_white 
346    );
347}
348
349
350
351macro_rules! colours {
352    ($($name: ident : $r: literal $g: literal $b: literal),*) => {
353        $(
354            #[inline(always)]
355            pub fn $name(self) -> Self { self.colour(Colour { r: $r, g: $g, b: $b }) }
356        )*
357    }
358}
359
360
361macro_rules! bg_colours {
362    ($($name: ident : $r: literal $g: literal $b: literal),*) => {
363        $(
364            #[inline(always)]
365            pub fn $name(self) -> Self { self.bg_colour(Colour { r: $r, g: $g, b: $b }) }
366        )*
367    }
368}
369
370
371impl<T> ColouredString<T> {
372    colours! {
373        black      :    0   0   0, 
374        dark_grey  :  100 100 100, 
375        brown      :  165  42  42,
376        navy_blue  :    0   0 128,
377        olive_green:  128 128   0,
378        teal       :    0 128 128,
379        red        :  255   0   0, 
380        green      :    0 255   0, 
381        blue       :    0   0 255, 
382        magenta    :  255   0 255, 
383        yellow     :  255 255   0, 
384        orange     :  255 165   0,
385        pink       :  255 192 203,
386        light_grey :  200 200 200,
387        cyan       :    0 255 255, 
388        white      :  255 255 255
389    }
390
391
392    bg_colours! {
393        bg_black      :    0   0   0, 
394        bg_dark_grey  :  100 100 100, 
395        bg_brown      :  165  42  42,
396        bg_navy_blue  :    0   0 128,
397        bg_olive_green:  128 128   0,
398        bg_teal       :    0 128 128,
399        bg_red        :  255   0   0, 
400        bg_green      :    0 255   0, 
401        bg_blue       :    0   0 255, 
402        bg_magenta    :  255   0 255, 
403        bg_yellow     :  255 255   0, 
404        bg_orange     :  255 165   0,
405        bg_pink       :  255 192 203,
406        bg_light_grey :  200 200 200,
407        bg_cyan       :    0 255 255, 
408        bg_white      :  255 255 255
409    }
410}
411
412
413impl<T> ColourBrush for T {}