Skip to main content

farben_core/
parser.rs

1use crate::ansi::{Ground, color_to_ansi, emphasis_to_ansi};
2use crate::lexer::{TagType, Token};
3
4/// Renders a token stream into a raw ANSI-escaped string.
5///
6/// Text tokens are appended as-is. Tag tokens are converted to their corresponding
7/// ANSI escape sequences. Does not append a trailing reset; callers are responsible
8/// for that if needed.
9pub fn render(tokens: Vec<Token>) -> String {
10    let mut result = String::new();
11    for tok in tokens {
12        match tok {
13            Token::Text(text) => result.push_str(text.as_str()),
14            Token::Tag(tag) => match tag {
15                TagType::Color { color, ground } => {
16                    result.push_str(color_to_ansi(&color, ground).as_str())
17                }
18                TagType::Emphasis(emphasis) => {
19                    result.push_str(emphasis_to_ansi(&emphasis).as_str())
20                }
21                TagType::Reset => result.push_str("\x1b[0m"),
22            },
23        }
24    }
25
26    result
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use crate::ansi::{Color, Ground, NamedColor};
33    use crate::lexer::{EmphasisType, TagType, Token};
34
35    // --- render ---
36    #[test]
37    fn test_render_empty_token_list() {
38        let result = render(vec![]);
39        assert_eq!(result, "");
40    }
41    #[test]
42    fn test_render_plain_text_token() {
43        let result = render(vec![Token::Text("hello".into())]);
44        assert_eq!(result, "hello");
45    }
46    #[test]
47    fn test_render_named_color_tag() {
48        let result = render(vec![Token::Tag(TagType::Color {
49            color: Color::Named(NamedColor::Red),
50            ground: Ground::Foreground,
51        })]);
52        assert_eq!(result, "\x1b[31m");
53    }
54    #[test]
55    fn test_render_emphasis_tag_bold() {
56        let result = render(vec![Token::Tag(TagType::Emphasis(EmphasisType::Bold))]);
57        assert_eq!(result, "\x1b[1m");
58    }
59    #[test]
60    fn test_render_reset_tag() {
61        let result = render(vec![Token::Tag(TagType::Reset)]);
62        assert_eq!(result, "\x1b[0m");
63    }
64    #[test]
65    fn test_render_color_then_text() {
66        let result = render(vec![
67            Token::Tag(TagType::Color {
68                color: Color::Named(NamedColor::Red),
69                ground: Ground::Foreground,
70            }),
71            Token::Text("hello".into()),
72        ]);
73        assert_eq!(result, "\x1b[31mhello");
74    }
75    #[test]
76    fn test_render_color_text_reset() {
77        let result = render(vec![
78            Token::Tag(TagType::Color {
79                color: Color::Named(NamedColor::Green),
80                ground: Ground::Foreground,
81            }),
82            Token::Text("go".into()),
83            Token::Tag(TagType::Reset),
84        ]);
85        assert_eq!(result, "\x1b[32mgo\x1b[0m");
86    }
87    #[test]
88    fn test_render_multiple_text_tokens() {
89        let result = render(vec![Token::Text("foo".into()), Token::Text("bar".into())]);
90        assert_eq!(result, "foobar");
91    }
92    #[test]
93    fn test_render_ansi256_color_tag() {
94        let result = render(vec![Token::Tag(TagType::Color {
95            color: Color::Ansi256(21),
96            ground: Ground::Foreground,
97        })]);
98        assert_eq!(result, "\x1b[38;5;21m");
99    }
100    #[test]
101    fn test_render_rgb_color_tag() {
102        let result = render(vec![Token::Tag(TagType::Color {
103            color: Color::Rgb(255, 0, 0),
104            ground: Ground::Foreground,
105        })]);
106        assert_eq!(result, "\x1b[38;2;255;0;0m");
107    }
108    #[test]
109    fn test_render_does_not_append_trailing_reset() {
110        let result = render(vec![Token::Text("plain".into())]);
111        assert!(!result.ends_with("\x1b[0m"));
112    }
113    #[test]
114    fn test_render_named_color_background() {
115        let result = render(vec![Token::Tag(TagType::Color {
116            color: Color::Named(NamedColor::Red),
117            ground: Ground::Background,
118        })]);
119        assert_eq!(result, "\x1b[41m");
120    }
121    #[test]
122    fn test_render_ansi256_background() {
123        let result = render(vec![Token::Tag(TagType::Color {
124            color: Color::Ansi256(21),
125            ground: Ground::Background,
126        })]);
127        assert_eq!(result, "\x1b[48;5;21m");
128    }
129    #[test]
130    fn test_render_rgb_background() {
131        let result = render(vec![Token::Tag(TagType::Color {
132            color: Color::Rgb(255, 0, 0),
133            ground: Ground::Background,
134        })]);
135        assert_eq!(result, "\x1b[48;2;255;0;0m");
136    }
137    #[test]
138    fn test_render_fg_and_bg_together() {
139        let result = render(vec![
140            Token::Tag(TagType::Color {
141                color: Color::Named(NamedColor::White),
142                ground: Ground::Foreground,
143            }),
144            Token::Tag(TagType::Color {
145                color: Color::Named(NamedColor::Blue),
146                ground: Ground::Background,
147            }),
148            Token::Text("hello".into()),
149        ]);
150        assert_eq!(result, "\x1b[37m\x1b[44mhello");
151    }
152}
153// Skipped (side effects): none: render() is a pure function.