Skip to main content

farben_core/
debug.rs

1//! Inverse rendering: turn parsed tokens back into farben markup syntax.
2//!
3//! Powers the `expand!` macro and other debugging utilities by round-tripping
4//! tokens through a human-readable representation.
5
6use crate::{
7    ansi::{Color, Ground, NamedColor},
8    lexer::{EmphasisType, ResetKind, TagType, Token},
9};
10
11/// Converts a `TagType` into its farben markup representation.
12///
13/// Example: `TagType::Emphasis(EmphasisType::Bold)` → `"bold"`.
14#[must_use]
15pub fn tag_to_markup_part(tag: &TagType) -> String {
16    match tag {
17        TagType::Emphasis(e) => match e {
18            EmphasisType::Bold => "bold".to_string(),
19            EmphasisType::Blink => "blink".to_string(),
20            EmphasisType::Dim => "dim".to_string(),
21            EmphasisType::Italic => "italic".to_string(),
22            EmphasisType::Strikethrough => "strikethrough".to_string(),
23            EmphasisType::Underline => "underline".to_string(),
24            EmphasisType::DoubleUnderline => "double-underline".to_string(),
25            EmphasisType::Overline => "overline".to_string(),
26            EmphasisType::Invisible => "invisible".to_string(),
27            EmphasisType::Reverse => "reverse".to_string(),
28            EmphasisType::RapidBlink => "rapid-blink".to_string(),
29        },
30        TagType::Color { color, ground } => {
31            let prepend = match ground {
32                Ground::Background => "bg:",
33                Ground::Foreground => "",
34            };
35            match color {
36                Color::Ansi256(a) => format!("{prepend}ansi({a})"),
37                Color::Named(n) => {
38                    let name = match n {
39                        NamedColor::Black => "black",
40                        NamedColor::Red => "red",
41                        NamedColor::Green => "green",
42                        NamedColor::Yellow => "yellow",
43                        NamedColor::Blue => "blue",
44                        NamedColor::Magenta => "magenta",
45                        NamedColor::Cyan => "cyan",
46                        NamedColor::White => "white",
47                        NamedColor::BrightBlack => "bright-black",
48                        NamedColor::BrightRed => "bright-red",
49                        NamedColor::BrightGreen => "bright-green",
50                        NamedColor::BrightYellow => "bright-yellow",
51                        NamedColor::BrightBlue => "bright-blue",
52                        NamedColor::BrightMagenta => "bright-magenta",
53                        NamedColor::BrightCyan => "bright-cyan",
54                        NamedColor::BrightWhite => "bright-white",
55                    };
56                    format!("{prepend}{name}")
57                }
58                Color::Rgb(r, g, b) => format!("{prepend}rgb({r},{g},{b})"),
59            }
60        }
61        TagType::ResetAll => "/".to_string(),
62        TagType::ResetOne(inner) => {
63            let part = match inner {
64                ResetKind::Emphasis(e) => tag_to_markup_part(&TagType::Emphasis(*e)),
65                ResetKind::Color { color, ground } => tag_to_markup_part(&TagType::Color {
66                    color: color.clone(),
67                    ground: *ground,
68                }),
69            };
70            format!("/{part}")
71        }
72        TagType::Prefix(_) => String::new(),
73    }
74}
75
76/// Converts a sequence of `Token`s back into a farben markup string.
77///
78/// The inverse of [`tokenize`](crate::lexer::tokenize).
79#[must_use]
80pub fn tokens_to_markup(tokens: &[Token]) -> String {
81    let mut result = String::new();
82    let mut i = 0;
83
84    while i < tokens.len() {
85        match &tokens[i] {
86            Token::Text(s) => {
87                result.push_str(s);
88                i += 1;
89            }
90            Token::Tag(_) => {
91                let mut parts = Vec::new();
92                while i < tokens.len() {
93                    if let Token::Tag(tag) = &tokens[i] {
94                        parts.push(tag_to_markup_part(tag));
95                        i += 1;
96                    } else {
97                        break;
98                    }
99                }
100                result.push('[');
101                result.push_str(&parts.join(" "));
102                result.push(']');
103            }
104        }
105    }
106
107    result
108}