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