tabled/settings/color/
mod.rs

1//! This module contains a configuration of a [`Border`] or a [`Table`] to set its borders color via [`Color`].
2//!
3//! [`Border`]: crate::settings::Border
4//! [`Table`]: crate::Table
5
6use std::{fmt, ops::BitOr};
7
8use crate::{
9    grid::{
10        ansi::{ANSIBuf, ANSIFmt, ANSIStr as StaticColor},
11        config::{ColoredConfig, Entity},
12    },
13    settings::{CellOption, TableOption},
14};
15
16/// Color represents a color which can be set to things like [`Border`], [`Padding`] and [`Margin`].
17///
18/// # Example
19///
20/// ```
21/// use tabled::{settings::Color, Table};
22///
23/// let data = [
24///     (0u8, "Hello"),
25///     (1u8, "World"),
26/// ];
27///
28/// let table = Table::new(data)
29///     .with(Color::BG_BLUE)
30///     .to_string();
31///
32/// println!("{}", table);
33/// ```
34///
35/// [`Padding`]: crate::settings::Padding
36/// [`Margin`]: crate::settings::Margin
37/// [`Border`]: crate::settings::Border
38#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct Color {
40    inner: ColorInner,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
44enum ColorInner {
45    Static(StaticColor<'static>),
46    Buf(ANSIBuf),
47}
48
49#[rustfmt::skip]
50impl Color {
51    /// A color representation.
52    /// 
53    /// Notice that the colors are constants so you can't combine them.
54    pub const FG_BLACK:          Self = Self::new_static("\u{1b}[30m", "\u{1b}[39m");
55    /// A color representation.
56    /// 
57    /// Notice that the colors are constants so you can't combine them.
58    pub const FG_BLUE:           Self = Self::new_static("\u{1b}[34m", "\u{1b}[39m");
59    /// A color representation.
60    /// 
61    /// Notice that the colors are constants so you can't combine them.
62    pub const FG_BRIGHT_BLACK:   Self = Self::new_static("\u{1b}[90m", "\u{1b}[39m");
63    /// A color representation.
64    /// 
65    /// Notice that the colors are constants so you can't combine them.
66    pub const FG_BRIGHT_BLUE:    Self = Self::new_static("\u{1b}[94m", "\u{1b}[39m");
67    /// A color representation.
68    /// 
69    /// Notice that the colors are constants so you can't combine them.
70    pub const FG_BRIGHT_CYAN:    Self = Self::new_static("\u{1b}[96m", "\u{1b}[39m");
71    /// A color representation.
72    /// 
73    /// Notice that the colors are constants so you can't combine them.
74    pub const FG_BRIGHT_GREEN:   Self = Self::new_static("\u{1b}[92m", "\u{1b}[39m");
75    /// A color representation.
76    /// 
77    /// Notice that the colors are constants so you can't combine them.
78    pub const FG_BRIGHT_MAGENTA: Self = Self::new_static("\u{1b}[95m", "\u{1b}[39m");
79    /// A color representation.
80    /// 
81    /// Notice that the colors are constants so you can't combine them.
82    pub const FG_BRIGHT_RED:     Self = Self::new_static("\u{1b}[91m", "\u{1b}[39m");
83    /// A color representation.
84    /// 
85    /// Notice that the colors are constants so you can't combine them.
86    pub const FG_BRIGHT_WHITE:   Self = Self::new_static("\u{1b}[97m", "\u{1b}[39m");
87    /// A color representation.
88    /// 
89    /// Notice that the colors are constants so you can't combine them.
90    pub const FG_BRIGHT_YELLOW:  Self = Self::new_static("\u{1b}[93m", "\u{1b}[39m");
91    /// A color representation.
92    /// 
93    /// Notice that the colors are constants so you can't combine them.
94    pub const FG_CYAN:           Self = Self::new_static("\u{1b}[36m", "\u{1b}[39m");
95    /// A color representation.
96    /// 
97    /// Notice that the colors are constants so you can't combine them.
98    pub const FG_GREEN:          Self = Self::new_static("\u{1b}[32m", "\u{1b}[39m");
99    /// A color representation.
100    /// 
101    /// Notice that the colors are constants so you can't combine them.
102    pub const FG_MAGENTA:        Self = Self::new_static("\u{1b}[35m", "\u{1b}[39m");
103    /// A color representation.
104    /// 
105    /// Notice that the colors are constants so you can't combine them.
106    pub const FG_RED:            Self = Self::new_static("\u{1b}[31m", "\u{1b}[39m");
107    /// A color representation.
108    /// 
109    /// Notice that the colors are constants so you can't combine them.
110    pub const FG_WHITE:          Self = Self::new_static("\u{1b}[37m", "\u{1b}[39m");
111    /// A color representation.
112    /// 
113    /// Notice that the colors are constants so you can't combine them.
114    pub const FG_YELLOW:         Self = Self::new_static("\u{1b}[33m", "\u{1b}[39m");
115    /// A color representation.
116    /// 
117    /// Notice that the colors are constants so you can't combine them.
118    pub const BG_BLACK:          Self = Self::new_static("\u{1b}[40m", "\u{1b}[49m");
119    /// A color representation.
120    /// 
121    /// Notice that the colors are constants so you can't combine them.
122    pub const BG_BLUE:           Self = Self::new_static("\u{1b}[44m", "\u{1b}[49m");
123    /// A color representation.
124    /// 
125    /// Notice that the colors are constants so you can't combine them.
126    pub const BG_BRIGHT_BLACK:   Self = Self::new_static("\u{1b}[100m", "\u{1b}[49m");
127    /// A color representation.
128    /// 
129    /// Notice that the colors are constants so you can't combine them.
130    pub const BG_BRIGHT_BLUE:    Self = Self::new_static("\u{1b}[104m", "\u{1b}[49m");
131    /// A color representation.
132    /// 
133    /// Notice that the colors are constants so you can't combine them.
134    pub const BG_BRIGHT_CYAN:    Self = Self::new_static("\u{1b}[106m", "\u{1b}[49m");
135    /// A color representation.
136    /// 
137    /// Notice that the colors are constants so you can't combine them.
138    pub const BG_BRIGHT_GREEN:   Self = Self::new_static("\u{1b}[102m", "\u{1b}[49m");
139    /// A color representation.
140    /// 
141    /// Notice that the colors are constants so you can't combine them.
142    pub const BG_BRIGHT_MAGENTA: Self = Self::new_static("\u{1b}[105m", "\u{1b}[49m");
143    /// A color representation.
144    /// 
145    /// Notice that the colors are constants so you can't combine them.
146    pub const BG_BRIGHT_RED:     Self = Self::new_static("\u{1b}[101m", "\u{1b}[49m");
147    /// A color representation.
148    /// 
149    /// Notice that the colors are constants so you can't combine them.
150    pub const BG_BRIGHT_WHITE:   Self = Self::new_static("\u{1b}[107m", "\u{1b}[49m");
151    /// A color representation.
152    /// 
153    /// Notice that the colors are constants so you can't combine them.
154    pub const BG_BRIGHT_YELLOW:  Self = Self::new_static("\u{1b}[103m", "\u{1b}[49m");
155    /// A color representation.
156    /// 
157    /// Notice that the colors are constants so you can't combine them.
158    pub const BG_CYAN:           Self = Self::new_static("\u{1b}[46m", "\u{1b}[49m");
159    /// A color representation.
160    /// 
161    /// Notice that the colors are constants so you can't combine them.
162    pub const BG_GREEN:          Self = Self::new_static("\u{1b}[42m", "\u{1b}[49m");
163    /// A color representation.
164    /// 
165    /// Notice that the colors are constants so you can't combine them.
166    pub const BG_MAGENTA:        Self = Self::new_static("\u{1b}[45m", "\u{1b}[49m");
167    /// A color representation.
168    /// 
169    /// Notice that the colors are constants so you can't combine them.
170    pub const BG_RED:            Self = Self::new_static("\u{1b}[41m", "\u{1b}[49m");
171    /// A color representation.
172    /// 
173    /// Notice that the colors are constants so you can't combine them.
174    pub const BG_WHITE:          Self = Self::new_static("\u{1b}[47m", "\u{1b}[49m");
175    /// A color representation.
176    /// 
177    /// Notice that the colors are constants so you can't combine them.
178    pub const BG_YELLOW:         Self = Self::new_static("\u{1b}[43m", "\u{1b}[49m");
179    /// A color representation.
180    /// 
181    /// Notice that the colors are constants so you can't combine them.
182    pub const BOLD:              Self = Self::new_static("\u{1b}[1m", "\u{1b}[22m");
183    /// A color representation.
184    /// 
185    /// Notice that the colors are constants so you can't combine them.
186    pub const UNDERLINE:         Self = Self::new_static("\u{1b}[4m", "\u{1b}[24m");
187}
188
189impl Color {
190    /// Creates a new [`Color`]` instance, with ANSI prefix and ANSI suffix.
191    /// You can use [`TryFrom`] to construct it from [`String`].
192    pub fn new<P, S>(prefix: P, suffix: S) -> Self
193    where
194        P: Into<String>,
195        S: Into<String>,
196    {
197        let color = ANSIBuf::new(prefix, suffix);
198        let inner = ColorInner::Buf(color);
199
200        Self { inner }
201    }
202
203    /// Creates a new empty [`Color`]`.
204    pub const fn empty() -> Self {
205        Self::new_static("", "")
206    }
207
208    /// Return a prefix.
209    pub fn get_prefix(&self) -> &str {
210        match &self.inner {
211            ColorInner::Static(color) => color.get_prefix(),
212            ColorInner::Buf(color) => color.get_prefix(),
213        }
214    }
215
216    /// Return a suffix.
217    pub fn get_suffix(&self) -> &str {
218        match &self.inner {
219            ColorInner::Static(color) => color.get_suffix(),
220            ColorInner::Buf(color) => color.get_suffix(),
221        }
222    }
223
224    /// Tries to get a static value of the color.
225    pub fn as_ansi_str(&self) -> Option<StaticColor<'static>> {
226        match self.inner {
227            ColorInner::Static(value) => Some(value),
228            ColorInner::Buf(_) => None,
229        }
230    }
231
232    /// Parses the string,
233    ///
234    /// # Panics
235    ///
236    /// PANICS if the input string incorrectly built.
237    /// Use [`std::convert::TryFrom`] instead if you are not sure about the input.
238    #[cfg(feature = "ansi")]
239    pub fn parse<S>(text: S) -> Self
240    where
241        S: AsRef<str>,
242    {
243        std::convert::TryFrom::try_from(text.as_ref()).unwrap()
244    }
245
246    /// Create a 24 bit foreground color with RGB
247    pub fn rgb_fg(r: u8, g: u8, b: u8) -> Self {
248        Self {
249            inner: ColorInner::Buf(ANSIBuf::new(
250                format!("\u{1b}[38;2;{};{};{}m", r, g, b),
251                "\u{1b}[39m",
252            )),
253        }
254    }
255
256    /// Create a 24 bit background color with RGB.
257    ///
258    /// The terminal need to support the escape sequence
259    pub fn rgb_bg(r: u8, g: u8, b: u8) -> Self {
260        Self {
261            inner: ColorInner::Buf(ANSIBuf::new(
262                format!("\u{1b}[48;2;{};{};{}m", r, g, b),
263                "\u{1b}[49m",
264            )),
265        }
266    }
267
268    /// Colorize a string.
269    pub fn colorize<S>(&self, text: S) -> String
270    where
271        S: AsRef<str>,
272    {
273        let mut buf = String::new();
274        for (i, line) in text.as_ref().lines().enumerate() {
275            if i > 0 {
276                buf.push('\n');
277            }
278
279            buf.push_str(self.get_prefix());
280            buf.push_str(line);
281            buf.push_str(self.get_suffix());
282        }
283
284        buf
285    }
286
287    const fn new_static(prefix: &'static str, suffix: &'static str) -> Self {
288        let color = StaticColor::new(prefix, suffix);
289        let inner = ColorInner::Static(color);
290
291        Self { inner }
292    }
293}
294
295impl Default for Color {
296    fn default() -> Self {
297        Self {
298            inner: ColorInner::Static(StaticColor::default()),
299        }
300    }
301}
302
303impl From<Color> for ANSIBuf {
304    fn from(color: Color) -> Self {
305        match color.inner {
306            ColorInner::Static(color) => ANSIBuf::from(color),
307            ColorInner::Buf(color) => color,
308        }
309    }
310}
311
312impl From<ANSIBuf> for Color {
313    fn from(color: ANSIBuf) -> Self {
314        Self {
315            inner: ColorInner::Buf(color),
316        }
317    }
318}
319
320impl From<StaticColor<'static>> for Color {
321    fn from(color: StaticColor<'static>) -> Self {
322        Self {
323            inner: ColorInner::Static(color),
324        }
325    }
326}
327
328impl BitOr for Color {
329    type Output = Color;
330
331    fn bitor(self, rhs: Self) -> Self::Output {
332        let l_prefix = self.get_prefix();
333        let l_suffix = self.get_suffix();
334        let r_prefix = rhs.get_prefix();
335        let r_suffix = rhs.get_suffix();
336
337        let mut prefix = l_prefix.to_string();
338        if l_prefix != r_prefix {
339            prefix.push_str(r_prefix);
340        }
341
342        let mut suffix = l_suffix.to_string();
343        if l_suffix != r_suffix {
344            suffix.push_str(r_suffix);
345        }
346
347        Self::new(prefix, suffix)
348    }
349}
350
351#[cfg(feature = "ansi")]
352impl std::convert::TryFrom<&str> for Color {
353    type Error = ();
354
355    fn try_from(value: &str) -> Result<Self, Self::Error> {
356        let buf = ANSIBuf::try_from(value)?;
357
358        Ok(Color {
359            inner: ColorInner::Buf(buf),
360        })
361    }
362}
363
364#[cfg(feature = "ansi")]
365impl std::convert::TryFrom<String> for Color {
366    type Error = ();
367
368    fn try_from(value: String) -> Result<Self, Self::Error> {
369        let buf = ANSIBuf::try_from(value)?;
370
371        Ok(Color {
372            inner: ColorInner::Buf(buf),
373        })
374    }
375}
376
377impl<R, D> TableOption<R, ColoredConfig, D> for Color {
378    fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
379        let color = self.into();
380        let _ = cfg.set_color(Entity::Global, color);
381    }
382
383    fn hint_change(&self) -> Option<Entity> {
384        None
385    }
386}
387
388impl<R> CellOption<R, ColoredConfig> for Color {
389    fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
390        let color = self.into();
391        let _ = cfg.set_color(entity, color);
392    }
393
394    fn hint_change(&self) -> Option<Entity> {
395        None
396    }
397}
398
399impl<R> CellOption<R, ColoredConfig> for &Color {
400    fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
401        let color = self.clone().into();
402        let _ = cfg.set_color(entity, color);
403    }
404
405    fn hint_change(&self) -> Option<Entity> {
406        None
407    }
408}
409
410impl ANSIFmt for Color {
411    fn fmt_ansi_prefix<W: fmt::Write>(&self, f: &mut W) -> fmt::Result {
412        match &self.inner {
413            ColorInner::Static(color) => color.fmt_ansi_prefix(f),
414            ColorInner::Buf(color) => color.fmt_ansi_prefix(f),
415        }
416    }
417
418    fn fmt_ansi_suffix<W: fmt::Write>(&self, f: &mut W) -> fmt::Result {
419        match &self.inner {
420            ColorInner::Static(color) => color.fmt_ansi_suffix(f),
421            ColorInner::Buf(color) => color.fmt_ansi_suffix(f),
422        }
423    }
424}
425
426#[cfg(test)]
427mod tests {
428    use super::*;
429
430    #[cfg(feature = "ansi")]
431    use std::convert::TryFrom;
432
433    #[test]
434    fn test_xor_operation() {
435        assert_eq!(
436            Color::FG_BLACK | Color::FG_BLUE,
437            Color::new("\u{1b}[30m\u{1b}[34m", "\u{1b}[39m")
438        );
439        assert_eq!(
440            Color::FG_BRIGHT_GREEN | Color::BG_BLUE,
441            Color::new("\u{1b}[92m\u{1b}[44m", "\u{1b}[39m\u{1b}[49m")
442        );
443        assert_eq!(
444            Color::new("...", "!!!") | Color::new("@@@", "###"),
445            Color::new("...@@@", "!!!###")
446        );
447        assert_eq!(
448            Color::new("...", "!!!") | Color::new("@@@", "###") | Color::new("$$$", "%%%"),
449            Color::new("...@@@$$$", "!!!###%%%")
450        );
451    }
452
453    #[cfg(feature = "ansi")]
454    #[test]
455    fn test_try_from() {
456        assert_eq!(Color::try_from(""), Err(()));
457        assert_eq!(
458            Color::try_from("\u{1b}[31m\u{1b}[42m\u{1b}[39m\u{1b}[49m"),
459            Err(())
460        );
461        assert_eq!(Color::try_from("."), Ok(Color::new("", "")));
462        assert_eq!(Color::try_from("...."), Ok(Color::new("", "")));
463        assert_eq!(
464            Color::try_from(String::from("\u{1b}[31m\u{1b}[42m.\u{1b}[39m\u{1b}[49m")),
465            Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
466        );
467        assert_eq!(
468            Color::try_from(String::from("\u{1b}[31m\u{1b}[42m...\u{1b}[39m\u{1b}[49m")),
469            Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
470        );
471        assert_eq!(
472            Color::try_from(String::from(
473                "\u{1b}[31m\u{1b}[42m.\n.\n.\u{1b}[39m\u{1b}[49m"
474            )),
475            Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
476        );
477        assert_eq!(
478            Color::try_from(String::from(
479                "\u{1b}[31m\u{1b}[42m.\n.\n.\n\u{1b}[39m\u{1b}[49m"
480            )),
481            Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
482        );
483        assert_eq!(
484            Color::try_from(String::from("\u{1b}[31m\u{1b}[42m\n\u{1b}[39m\u{1b}[49m")),
485            Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m"))
486        );
487    }
488
489    #[test]
490    fn test_rgb_color() {
491        assert_eq!(
492            Color::rgb_bg(255, 255, 255),
493            Color::new("\u{1b}[48;2;255;255;255m", "\u{1b}[49m")
494        );
495        assert_eq!(
496            Color::rgb_bg(0, 255, 128),
497            Color::new("\u{1b}[48;2;0;255;128m", "\u{1b}[49m")
498        );
499
500        assert_eq!(
501            Color::rgb_fg(0, 255, 128),
502            Color::new("\u{1b}[38;2;0;255;128m", "\u{1b}[39m")
503        );
504        assert_eq!(
505            Color::rgb_fg(255, 255, 255),
506            Color::new("\u{1b}[38;2;255;255;255m", "\u{1b}[39m")
507        );
508
509        assert_eq!(
510            Color::rgb_bg(255, 255, 255) | Color::rgb_fg(0, 0, 0),
511            Color::new(
512                "\u{1b}[48;2;255;255;255m\u{1b}[38;2;0;0;0m",
513                "\u{1b}[49m\u{1b}[39m"
514            )
515        )
516    }
517}