color_print_proc_macro/
color_context.rs

1//! This module permits to determine which ANSI sequences have to be added at a given position in
2//! the format string, by saving the current tags in a "context". When a new tag is encountered, a
3//! diff between the old state and the new state is performed to determine the right ANSI sequences
4//! to add.
5
6use std::convert::TryFrom;
7
8use proc_macro2::Span;
9
10use crate::error::{Error, SpanError};
11
12/// Stores all the current open tags encountered in the format string.
13#[derive(Debug, PartialEq, Default)]
14pub struct Context<'a>(Vec<ColorTag<'a>>);
15
16impl<'a> Context<'a> {
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    /// Applies a group of tags to the current context, and returns a list of the terminfo
22    /// constants (available in the `color-print` package) to be added as named arguments at the
23    /// end of the format arguments.
24    ///
25    /// For each given tag:
26    ///  - if the tag is an open tag, push it into the context;
27    ///  - if it's a valid close tag, pop the last open tag.
28    #[cfg(feature = "terminfo")]
29    pub fn terminfo_apply_tags(
30        &mut self,
31        tag_group: Vec<ColorTag<'a>>,
32    ) -> Result<Vec<String>, SpanError> {
33        let state_diff = self.apply_tags_and_get_diff(tag_group)?;
34        Ok(state_diff.terminfo_token_streams())
35    }
36
37    /// Applies a group of tags to the current context, and returns the ANSI sequences to be
38    /// added into the format string.
39    ///
40    /// For each given tag:
41    ///  - if the tag is an open tag, push it into the context;
42    ///  - if it's a valid close tag, pop the last open tag.
43    #[cfg(not(feature = "terminfo"))]
44    pub fn ansi_apply_tags(&mut self, tag_group: Vec<ColorTag<'a>>) -> Result<String, SpanError> {
45        let state_diff = self.apply_tags_and_get_diff(tag_group)?;
46        Ok(state_diff.ansi_string())
47    }
48
49    /// Applies a group of tags to the current context, with no return on success. Used by the
50    /// macro `untagged!()`.
51    ///
52    /// For each given tag:
53    ///  - if the tag is an open tag, push it into the context;
54    ///  - if it's a valid close tag, pop the last open tag.
55    pub fn apply_tags(&mut self, tag_group: Vec<ColorTag<'a>>) -> Result<(), SpanError> {
56        self.apply_tags_and_get_diff(tag_group).map(|_| ())
57    }
58
59    /// Returns the actual color/style state, which is the result of the changes made by each tag
60    /// sequentially.
61    pub fn state(&self) -> State {
62        let mut state = State::default();
63        for tag in &self.0 {
64            if let Some(ref color) = tag.change_set.foreground {
65                state.foreground = ExtColor::Color(color.clone());
66            }
67            if let Some(ref color) = tag.change_set.background {
68                state.background = ExtColor::Color(color.clone());
69            }
70            state.bold |= tag.change_set.bold;
71            state.dim |= tag.change_set.dim;
72            state.underline |= tag.change_set.underline;
73            state.italics |= tag.change_set.italics;
74            state.blink |= tag.change_set.blink;
75            state.strike |= tag.change_set.strike;
76            state.reverse |= tag.change_set.reverse;
77            state.conceal |= tag.change_set.conceal;
78        }
79        state
80    }
81
82    #[allow(rustdoc::broken_intra_doc_links)]
83    /// Common code betwwen [Self::terminfo_apply_tag()] and [Self::ansi_apply_tag()].
84    fn apply_tags_and_get_diff(&mut self, tags: Vec<ColorTag<'a>>) -> Result<StateDiff, SpanError> {
85        let old_state = self.state();
86
87        for tag in tags {
88            if tag.is_close {
89                let last_tag = self.0.last()
90                    .ok_or_else(|| SpanError::new(Error::NoTagToClose, tag.span))?;
91                // If the tag is "void" (it is a "</>" tag), we don't need to check if the change
92                // sets are matching:
93                if !tag.change_set.is_void() && last_tag.change_set != tag.change_set {
94                    let (last_src, src) = (
95                        // We can unwrap the last tag source, because we know that all the tags
96                        // stored inside the context are *open tags*, and open tag are always taken
97                        // from the source input:
98                        last_tag.source.unwrap(),
99                        // We can unwrap the source of the tag currently being processed, because
100                        // we just checked above that the tag is not void, and non-void tags are
101                        // always taken from the source input:
102                        tag.source.unwrap(),
103                    );
104                    return Err(SpanError::new(
105                        Error::MismatchCloseTag(last_src.to_owned(), src.to_owned()),
106                        tag.span,
107                    ));
108                }
109                self.0.pop().unwrap();
110            } else {
111                self.0.push(tag);
112            }
113        }
114
115        let new_state = self.state();
116        Ok(StateDiff::from_diff(&old_state, &new_state))
117    }
118}
119
120/// Describes the state of each color and style attributes at a given position in the format
121/// string. Two states can be compared together by creating a [`StateDiff`] instance.
122#[derive(Debug, PartialEq, Default)]
123pub struct State {
124    foreground: ExtColor,
125    background: ExtColor,
126    bold: bool,
127    dim: bool,
128    underline: bool,
129    italics: bool,
130    blink: bool,
131    strike: bool,
132    reverse: bool,
133    conceal: bool,
134}
135
136/// The result of the comparison between two [`State`]s.
137///
138/// Each field is an [`Action`], which indicates if the given value has to be changed or left
139/// unchanged in order to reach the new state.
140#[derive(Debug)]
141pub struct StateDiff {
142    foreground: Action<ExtColor>,
143    background: Action<ExtColor>,
144    bold: Action<bool>,
145    dim: Action<bool>,
146    underline: Action<bool>,
147    italics: Action<bool>,
148    blink: Action<bool>,
149    #[cfg(not(feature = "terminfo"))]
150    strike: Action<bool>,
151    reverse: Action<bool>,
152    #[cfg(not(feature = "terminfo"))]
153    conceal: Action<bool>,
154}
155
156impl StateDiff {
157    /// Creates a new [`StateDiff`] by comparing two [`State`]s.
158    pub fn from_diff(old: &State, new: &State) -> Self {
159        StateDiff {
160            foreground: Action::from_diff(Some(old.foreground.clone()), Some(new.foreground.clone())),
161            background: Action::from_diff(Some(old.background.clone()), Some(new.background.clone())),
162            bold: Action::from_diff(Some(old.bold), Some(new.bold)),
163            dim: Action::from_diff(Some(old.dim), Some(new.dim)),
164            underline: Action::from_diff(Some(old.underline), Some(new.underline)),
165            italics: Action::from_diff(Some(old.italics), Some(new.italics)),
166            blink: Action::from_diff(Some(old.blink), Some(new.blink)),
167            #[cfg(not(feature = "terminfo"))]
168            strike: Action::from_diff(Some(old.strike), Some(new.strike)),
169            reverse: Action::from_diff(Some(old.reverse), Some(new.reverse)),
170            #[cfg(not(feature = "terminfo"))]
171            conceal: Action::from_diff(Some(old.conceal), Some(new.conceal)),
172        }
173    }
174
175    /// Returns the list of terminfo constants (available in the `color-print` package) which have
176    /// to be used in order to reach the new state.
177    #[cfg(feature = "terminfo")]
178    pub fn terminfo_token_streams(&self) -> Vec<String> {
179        let mut constants = vec![];
180
181        macro_rules! push_constant {
182            ($s:expr) => {{
183                constants.push($s.to_owned());
184            }};
185        }
186
187        let have_to_reset = or!(
188            matches!(self.foreground, Action::Change(ExtColor::Normal)),
189            matches!(self.background, Action::Change(ExtColor::Normal)),
190            matches!(self.bold, Action::Change(false)),
191            matches!(self.dim, Action::Change(false)),
192            matches!(self.blink, Action::Change(false)),
193            matches!(self.reverse, Action::Change(false)),
194        );
195
196        if have_to_reset {
197            push_constant!("CLEAR");
198            if let Some(ExtColor::Color(Color::Color16(color))) = self.foreground.actual_value() {
199                push_constant!(color.terminfo_constant(true));
200            }
201            if let Some(ExtColor::Color(Color::Color16(color))) = self.background.actual_value() {
202                push_constant!(color.terminfo_constant(false));
203            }
204            if matches!(self.bold.actual_value(), Some(true)) {
205                push_constant!("BOLD");
206            }
207            if matches!(self.dim.actual_value(), Some(true)) {
208                push_constant!("DIM");
209            }
210            if matches!(self.blink.actual_value(), Some(true)) {
211                push_constant!("BLINK");
212            }
213            if matches!(self.underline.actual_value(), Some(true)) {
214                push_constant!("UNDERLINE");
215            }
216            if matches!(self.italics.actual_value(), Some(true)) {
217                push_constant!("ITALICS");
218            }
219            if matches!(self.reverse.actual_value(), Some(true)) {
220                push_constant!("REVERSE");
221            }
222        } else {
223            if let Action::Change(ExtColor::Color(Color::Color16(ref color))) = self.foreground {
224                push_constant!(color.terminfo_constant(true));
225            }
226            if let Action::Change(ExtColor::Color(Color::Color16(ref color))) = self.background {
227                push_constant!(color.terminfo_constant(false));
228            }
229            if let Action::Change(true) = self.bold {
230                push_constant!("BOLD");
231            }
232            if let Action::Change(true) = self.dim {
233                push_constant!("DIM");
234            }
235            if let Action::Change(true) = self.blink {
236                push_constant!("BLINK");
237            }
238            if let Action::Change(true) = self.reverse {
239                push_constant!("REVERSE");
240            }
241            if let Action::Change(underline) = self.underline {
242                let constant = if underline { "UNDERLINE" } else { "NO_UNDERLINE" };
243                push_constant!(constant);
244            }
245            if let Action::Change(italics) = self.italics {
246                let constant = if italics { "ITALICS" } else { "NO_ITALICS" };
247                push_constant!(constant);
248            }
249        }
250
251        constants
252    }
253
254    /// Returns the ANSI sequence(s) which has to added to the format string in order to reach the
255    /// new state.
256    #[cfg(not(feature = "terminfo"))]
257    pub fn ansi_string(&self) -> String {
258        use crate::ansi_constants::*;
259
260        let mut output = String::new();
261
262        macro_rules! push_code {
263            ($($codes:expr),*) => { output.push_str(&generate_ansi_code(&[$($codes),*])) };
264        }
265
266        if let Action::Change(ref ext_color) = self.foreground {
267            match ext_color {
268                ExtColor::Normal => push_code!(DEFAULT_FOREGROUND),
269                ExtColor::Color(Color::Color16(color)) => match color.intensity {
270                    Intensity::Normal => {
271                        push_code!(SET_FOREGROUND_BASE + color.base_color.index())
272                    }
273                    Intensity::Bright => {
274                        push_code!(SET_BRIGHT_FOREGROUND_BASE + color.base_color.index())
275                    }
276                },
277                ExtColor::Color(Color::Color256(color)) => {
278                    push_code!(SET_FOREGROUND, 5, color.0);
279                },
280                ExtColor::Color(Color::ColorRgb(color)) => {
281                    push_code!(SET_FOREGROUND, 2, color.r, color.g, color.b);
282                },
283            }
284        }
285
286        if let Action::Change(ref ext_color) = self.background {
287            match ext_color {
288                ExtColor::Normal => push_code!(DEFAULT_BACKGROUND),
289                ExtColor::Color(Color::Color16(color)) => match color.intensity {
290                    Intensity::Normal => {
291                        push_code!(SET_BACKGROUND_BASE + color.base_color.index())
292                    }
293                    Intensity::Bright => {
294                        push_code!(SET_BRIGHT_BACKGROUND_BASE + color.base_color.index())
295                    }
296                },
297                ExtColor::Color(Color::Color256(color)) => {
298                    push_code!(SET_BACKGROUND, 5, color.0);
299                },
300                ExtColor::Color(Color::ColorRgb(color)) => {
301                    push_code!(SET_BACKGROUND, 2, color.r, color.g, color.b);
302                },
303            }
304        }
305
306        macro_rules! handle_attr {
307            ($attr:expr, $true_val:expr, $false_val:expr) => {
308                match $attr {
309                    Action::Change(true) => push_code!($true_val),
310                    Action::Change(false) => push_code!($false_val),
311                    _ => (),
312                }
313            };
314        }
315
316        handle_attr!(self.bold, BOLD, NO_BOLD);
317        handle_attr!(self.dim, DIM, NO_BOLD);
318        handle_attr!(self.underline, UNDERLINE, NO_UNDERLINE);
319        handle_attr!(self.italics, ITALIC, NO_ITALIC);
320        handle_attr!(self.blink, BLINK, NO_BLINK);
321        handle_attr!(self.strike, STRIKE, NO_STRIKE);
322        handle_attr!(self.reverse, REVERSE, NO_REVERSE);
323        handle_attr!(self.conceal, CONCEAL, NO_CONCEAL);
324
325        output
326    }
327}
328
329/// The action to be performed on a given color/style attribute in order to reach a new state.
330#[derive(Debug, PartialEq)]
331pub enum Action<T> {
332    /// Nothing has to be done, because this value was never modified.
333    None,
334    /// This attribute has to be kept the same.
335    /// With the terminfo implementation, it's not possible to reset each style/color
336    /// independently, so we have to keep track of the values, even with the `Keep` variant.
337    Keep(T),
338    /// This attribute value has to be changed.
339    Change(T),
340}
341
342#[cfg(feature = "terminfo")]
343impl<T> Action<T> {
344    pub fn actual_value(&self) -> Option<&T> {
345        match self {
346            Action::Keep(val) | Action::Change(val) => Some(val),
347            Action::None => None,
348        }
349    }
350}
351
352impl<T> Action<T>
353where
354    T: PartialEq,
355{
356    /// Creates a new [`Action`].
357    pub fn from_diff(old: Option<T>, new: Option<T>) -> Self {
358        let eq = old == new;
359        match (old, new, eq) {
360            (Some(old_val), Some(_), true) | (Some(old_val), None, _) => Action::Keep(old_val),
361            (_, Some(new_val), _) => Action::Change(new_val),
362            _ => Action::None,
363        }
364    }
365}
366
367/// A parsed color/style tag.
368#[derive(Debug, Default)]
369pub struct ColorTag<'a> {
370    /// Source of the tag in the format string.
371    pub source: Option<&'a str>,
372    /// Span of the tag in the format string.
373    pub span: Option<Span>,
374    /// Is it a close tag like `</red>`.
375    pub is_close: bool,
376    /// The changes that are implied by this tag.
377    pub change_set: ChangeSet,
378}
379
380impl<'a> PartialEq for ColorTag<'a> {
381    fn eq(&self, other: &ColorTag<'a>) -> bool {
382        and!(
383            self.source == other.source,
384            self.is_close == other.is_close,
385            self.change_set == other.change_set,
386        )
387    }
388}
389
390impl<'a> ColorTag<'a> {
391    /// Creates a new close tag; only used in order to auto-close unclosed tags at the end of the
392    /// format string.
393    pub fn new_close() -> Self {
394        ColorTag {
395            source: None,
396            span: None,
397            is_close: true,
398            change_set: ChangeSet::default(),
399        }
400    }
401
402    /// Sets the span of the tag.
403    pub fn set_span(&mut self, span: Span) {
404        self.span = Some(span);
405    }
406}
407
408/// The changes that are implied by a tag.
409#[derive(Debug, PartialEq, Default)]
410pub struct ChangeSet {
411    /// If it is `Some`, then the foreground color has to be changed.
412    pub foreground: Option<Color>,
413    /// If it is `Some`, then the background color has to be changed.
414    pub background: Option<Color>,
415    /// If it is `true`, then the bold attribute has to be set (or unset for a close tag).
416    pub bold: bool,
417    /// If it is `true`, then the dim attribute has to be set (or unset for a close tag).
418    pub dim: bool,
419    /// If it is `true`, then the underline attribute has to be set (or unset for a close tag).
420    pub underline: bool,
421    /// If it is `true`, then the italics attribute has to be set (or unset for a close tag).
422    pub italics: bool,
423    /// If it is `true`, then the blink attribute has to be set (or unset for a close tag).
424    pub blink: bool,
425    /// If it is `true`, then the strike attribute has to be set (or unset for a close tag).
426    pub strike: bool,
427    /// If it is `true`, then the reverse attribute has to be set (or unset for a close tag).
428    pub reverse: bool,
429    /// If it is `true`, then the conceal attribute has to be set (or unset for a close tag).
430    pub conceal: bool,
431}
432
433impl ChangeSet {
434    /// Checks if there is nothing to change (used to detect the `</>` tag).
435    pub fn is_void(&self) -> bool {
436        and!(
437            self.foreground.is_none(),
438            self.background.is_none(),
439            !self.bold,
440            !self.dim,
441            !self.underline,
442            !self.italics,
443            !self.blink,
444            !self.strike,
445            !self.reverse,
446            !self.conceal,
447        )
448    }
449}
450
451impl From<&[Change]> for ChangeSet {
452    fn from(changes: &[Change]) -> ChangeSet {
453        let mut change_set = ChangeSet::default();
454        for change in changes {
455            match change {
456                Change::Foreground(color) => change_set.foreground = Some(color.clone()),
457                Change::Background(color) => change_set.background = Some(color.clone()),
458                Change::Bold => change_set.bold = true,
459                Change::Dim => change_set.dim = true,
460                Change::Underline => change_set.underline = true,
461                Change::Italics => change_set.italics = true,
462                Change::Blink => change_set.blink = true,
463                Change::Strike => change_set.strike = true,
464                Change::Reverse => change_set.reverse = true,
465                Change::Conceal => change_set.conceal = true,
466            }
467        }
468        change_set
469    }
470}
471
472/// A single change to be done inside a tag. Tags with multiple keywords like `<red;bold>` will
473/// have multiple [`Change`]s.
474#[derive(Debug, PartialEq, Clone)]
475pub enum Change {
476    Foreground(Color),
477    Background(Color),
478    Bold,
479    Dim,
480    Underline,
481    Italics,
482    Blink,
483    Strike,
484    Reverse,
485    Conceal,
486}
487
488impl TryFrom<&str> for Change {
489    type Error = ();
490
491    /// Tries to convert a keyword like `red`, `bold` into a [`Change`] instance.
492    #[rustfmt::skip]
493    fn try_from(input: &str) -> Result<Self, Self::Error> {
494        macro_rules! color16 {
495            ($kind:ident $intensity:ident $base_color:ident) => {
496                Change::$kind(Color::Color16(Color16::new(
497                    BaseColor::$base_color,
498                    Intensity::$intensity,
499                )))
500            };
501        }
502
503        let change = match input {
504            "s" | "strong" | "bold" | "em" => Change::Bold,
505            "dim" => Change::Dim,
506            "u" | "underline" => Change::Underline,
507            "i" | "italic" | "italics" => Change::Italics,
508            "blink" => Change::Blink,
509            "strike" => Change::Strike,
510            "reverse" | "rev" => Change::Reverse,
511            "conceal" | "hide" => Change::Conceal,
512
513            "k" | "black"   => color16!(Foreground Normal Black),
514            "r" | "red"     => color16!(Foreground Normal Red),
515            "g" | "green"   => color16!(Foreground Normal Green),
516            "y" | "yellow"  => color16!(Foreground Normal Yellow),
517            "b" | "blue"    => color16!(Foreground Normal Blue),
518            "m" | "magenta" => color16!(Foreground Normal Magenta),
519            "c" | "cyan"    => color16!(Foreground Normal Cyan),
520            "w" | "white"   => color16!(Foreground Normal White),
521
522            "k!" | "black!" | "bright-black"     => color16!(Foreground Bright Black),
523            "r!" | "red!" | "bright-red"         => color16!(Foreground Bright Red),
524            "g!" | "green!" | "bright-green"     => color16!(Foreground Bright Green),
525            "y!" | "yellow!" | "bright-yellow"   => color16!(Foreground Bright Yellow),
526            "b!" | "blue!" | "bright-blue"       => color16!(Foreground Bright Blue),
527            "m!" | "magenta!" | "bright-magenta" => color16!(Foreground Bright Magenta),
528            "c!" | "cyan!" | "bright-cyan"       => color16!(Foreground Bright Cyan),
529            "w!" | "white!" | "bright-white"     => color16!(Foreground Bright White),
530
531            "K" | "bg-black"   => color16!(Background Normal Black),
532            "R" | "bg-red"     => color16!(Background Normal Red),
533            "G" | "bg-green"   => color16!(Background Normal Green),
534            "Y" | "bg-yellow"  => color16!(Background Normal Yellow),
535            "B" | "bg-blue"    => color16!(Background Normal Blue),
536            "M" | "bg-magenta" => color16!(Background Normal Magenta),
537            "C" | "bg-cyan"    => color16!(Background Normal Cyan),
538            "W" | "bg-white"   => color16!(Background Normal White),
539
540            "K!" | "bg-black!" | "bg-bright-black"     => color16!(Background Bright Black),
541            "R!" | "bg-red!" | "bg-bright-red"         => color16!(Background Bright Red),
542            "G!" | "bg-green!" | "bg-bright-green"     => color16!(Background Bright Green),
543            "Y!" | "bg-yellow!" | "bg-bright-yellow"   => color16!(Background Bright Yellow),
544            "B!" | "bg-blue!" | "bg-bright-blue"       => color16!(Background Bright Blue),
545            "M!" | "bg-magenta!" | "bg-bright-magenta" => color16!(Background Bright Magenta),
546            "C!" | "bg-cyan!" | "bg-bright-cyan"       => color16!(Background Bright Cyan),
547            "W!" | "bg-white!" | "bg-bright-white"     => color16!(Background Bright White),
548
549            _ => return Err(()),
550        };
551
552        Ok(change)
553    }
554}
555
556/// Which "kind" of color has to be changed.
557#[derive(Debug, PartialEq, Clone)]
558pub enum ColorKind {
559    Background,
560    Foreground,
561}
562
563impl ColorKind {
564    pub fn to_change(&self, color: Color) -> Change {
565        match self {
566            Self::Foreground => Change::Foreground(color),
567            Self::Background => Change::Background(color),
568        }
569    }
570}
571
572/// An "extended" color, which can be either a real color or the "normal", default color.
573#[derive(Debug, PartialEq, Clone)]
574pub enum ExtColor {
575    Normal,
576    Color(Color),
577}
578
579impl Default for ExtColor {
580    fn default() -> Self {
581        Self::Normal
582    }
583}
584
585#[derive(Debug, PartialEq, Clone)]
586#[allow(clippy::enum_variant_names)]
587pub enum Color {
588    Color16(Color16),
589    Color256(Color256),
590    ColorRgb(ColorRgb),
591}
592
593/// A terminal color.
594#[derive(Debug, PartialEq, Clone)]
595pub struct Color16 {
596    base_color: BaseColor,
597    intensity: Intensity,
598}
599
600impl Color16 {
601    pub fn new(base_color: BaseColor, intensity: Intensity) -> Self {
602        Self { base_color, intensity }
603    }
604
605    /// Converts a color to a terminfo constant name (available in the `color-print` package).
606    #[cfg(feature = "terminfo")]
607    pub fn terminfo_constant(&self, is_foreground: bool) -> String {
608        let mut constant = if is_foreground {
609            String::new()
610        } else {
611            "BG_".to_string()
612        };
613
614        if matches!(self.intensity, Intensity::Bright) {
615            constant.push_str("BRIGHT_");
616        }
617
618        constant.push_str(self.base_color.uppercase_str());
619
620        constant
621    }
622}
623
624/// The intensity of a terminal color.
625#[derive(Debug, PartialEq, Copy, Clone)]
626pub enum Intensity {
627    Normal,
628    Bright,
629}
630
631impl Intensity {
632    pub fn new(is_bright: bool) -> Self {
633        if is_bright {
634            Self::Bright
635        } else {
636            Self::Normal
637        }
638    }
639}
640
641/// A "base" terminal color, which has to be completed with an [`Intensity`] in order to describe a
642/// whole terminal color.
643#[derive(Debug, PartialEq, Copy, Clone)]
644pub enum BaseColor {
645    Black,
646    Red,
647    Green,
648    Yellow,
649    Blue,
650    Magenta,
651    Cyan,
652    White,
653}
654
655impl BaseColor {
656    /// Return the index of a color, in the same ordering as the ANSI color sequences.
657    #[cfg(not(feature = "terminfo"))]
658    pub fn index(&self) -> u8 {
659        match self {
660            Self::Black => 0,
661            Self::Red => 1,
662            Self::Green => 2,
663            Self::Yellow => 3,
664            Self::Blue => 4,
665            Self::Magenta => 5,
666            Self::Cyan => 6,
667            Self::White => 7,
668        }
669    }
670
671    /// Used to generate terminfo constants, see [`Color16::terminfo_constant()`].
672    #[cfg(feature = "terminfo")]
673    pub fn uppercase_str(&self) -> &'static str {
674        match self {
675            Self::Black => "BLACK",
676            Self::Red => "RED",
677            Self::Green => "GREEN",
678            Self::Yellow => "YELLOW",
679            Self::Blue => "BLUE",
680            Self::Magenta => "MAGENTA",
681            Self::Cyan => "CYAN",
682            Self::White => "WHITE",
683        }
684    }
685}
686
687/// A color in the 256-color palette.
688#[derive(Debug, PartialEq, Clone)]
689pub struct Color256(pub u8);
690
691/// An RGB color.
692#[derive(Debug, PartialEq, Clone)]
693pub struct ColorRgb {
694    pub r: u8,
695    pub g: u8,
696    pub b: u8,
697}
698
699#[cfg(test)]
700mod tests {
701    #[cfg(feature = "terminfo")]
702    use super::*;
703    #[cfg(feature = "terminfo")]
704    use crate::parse::color_tag;
705
706    #[test]
707    #[cfg(feature = "terminfo")]
708    fn terminfo_apply_tag_to_context() {
709        let mut context = Context::new();
710
711        macro_rules! apply_tag {
712            ($s:expr) => {
713                context
714                    .terminfo_apply_tags(vec![color_tag($s).unwrap().1])
715                    .unwrap()
716            };
717        }
718
719        let constants = apply_tag!("<r>");
720        assert_eq!(constants, ["RED"]);
721        let constants = apply_tag!("</r>");
722        assert_eq!(constants, ["CLEAR"]);
723        let constants = apply_tag!("<r>");
724        assert_eq!(constants, ["RED"]);
725        let constants = apply_tag!("<s>");
726        assert_eq!(constants, ["BOLD"]);
727        let constants = apply_tag!("</s>");
728        assert_eq!(constants, ["CLEAR", "RED"]);
729        let constants = apply_tag!("</r>");
730        assert_eq!(constants, ["CLEAR"]);
731    }
732
733    #[test]
734    #[cfg(feature = "terminfo")]
735    fn terminfo_apply_tag_to_context_2() {
736        let mut context = Context::new();
737
738        macro_rules! apply_tag {
739            ($s:expr) => {
740                context
741                    .terminfo_apply_tags(vec![color_tag($s).unwrap().1])
742                    .unwrap()
743            };
744        }
745
746        let constants = apply_tag!("<r>");
747        assert_eq!(constants, ["RED"]);
748        let constants = apply_tag!("<Y>");
749        assert_eq!(constants, ["BG_YELLOW"]);
750        let constants = apply_tag!("<s>");
751        assert_eq!(constants, ["BOLD"]);
752        let constants = apply_tag!("<u>");
753        assert_eq!(constants, ["UNDERLINE"]);
754        let constants = apply_tag!("</u>");
755        assert_eq!(constants, ["NO_UNDERLINE"]);
756        let constants = apply_tag!("</s>");
757        assert_eq!(constants, ["CLEAR", "RED", "BG_YELLOW"]);
758    }
759
760    #[test]
761    #[cfg(feature = "terminfo")]
762    fn terminfo_apply_tag_to_context_3() {
763        let mut context = Context::new();
764
765        macro_rules! apply_tag {
766            ($s:expr) => {
767                context.terminfo_apply_tags(vec![color_tag($s).unwrap().1])
768            };
769        }
770
771        let res = apply_tag!("</r>");
772        assert_eq!(res, Err(SpanError::new(Error::NoTagToClose, None)));
773        apply_tag!("<r>").unwrap();
774        let res = apply_tag!("</s>");
775        assert_eq!(
776            res,
777            Err(SpanError::new(
778                Error::MismatchCloseTag("<r>".to_owned(), "</s>".to_owned()),
779                None
780            ))
781        );
782    }
783}