1use crate::ansi::{Ground, color_to_ansi, emphasis_to_ansi};
2use crate::lexer::{TagType, Token};
3
4pub 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 #[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