1use crate::ansi::{color_to_ansi, emphasis_to_ansi};
9use crate::lexer::{TagType, Token};
10
11pub fn render(tokens: Vec<Token>) -> String {
25 let mut result = String::new();
26 let mut active: Vec<TagType> = Vec::new();
27 for tok in tokens {
28 match tok {
29 Token::Text(text) => result.push_str(&text),
30 Token::Tag(tag) => match tag {
31 TagType::Color { color, ground } => {
32 result.push_str(&color_to_ansi(&color, ground.clone()));
33 active.push(TagType::Color { color, ground })
34 }
35 TagType::Emphasis(emphasis) => {
36 result.push_str(&emphasis_to_ansi(&emphasis));
37 active.push(TagType::Emphasis(emphasis))
38 }
39 TagType::Reset(None) => result.push_str("\x1b[0m"),
40 TagType::Reset(Some(tag)) => {
41 active.retain(|item| item != tag.as_ref());
42 result.push_str("\x1b[0m");
43 for item in &active {
44 match item {
45 TagType::Color { color, ground } => {
46 result.push_str(&color_to_ansi(color, ground.clone()))
47 }
48 TagType::Emphasis(emphasis) => {
49 result.push_str(&emphasis_to_ansi(emphasis))
50 }
51 _ => unreachable!(),
52 }
53 }
54 }
55 TagType::Prefix(prefix) => result.push_str(&prefix),
56 },
57 }
58 }
59
60 result
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use crate::ansi::{Color, Ground, NamedColor};
67 use crate::lexer::{EmphasisType, TagType, Token};
68
69 #[test]
71 fn test_render_empty_token_list() {
72 let result = render(vec![]);
73 assert_eq!(result, "");
74 }
75 #[test]
76 fn test_render_plain_text_token() {
77 let result = render(vec![Token::Text("hello".into())]);
78 assert_eq!(result, "hello");
79 }
80 #[test]
81 fn test_render_named_color_tag() {
82 let result = render(vec![Token::Tag(TagType::Color {
83 color: Color::Named(NamedColor::Red),
84 ground: Ground::Foreground,
85 })]);
86 assert_eq!(result, "\x1b[31m");
87 }
88 #[test]
89 fn test_render_emphasis_tag_bold() {
90 let result = render(vec![Token::Tag(TagType::Emphasis(EmphasisType::Bold))]);
91 assert_eq!(result, "\x1b[1m");
92 }
93 #[test]
94 fn test_render_reset_tag() {
95 let result = render(vec![Token::Tag(TagType::Reset(None))]);
96 assert_eq!(result, "\x1b[0m");
97 }
98 #[test]
99 fn test_render_color_then_text() {
100 let result = render(vec![
101 Token::Tag(TagType::Color {
102 color: Color::Named(NamedColor::Red),
103 ground: Ground::Foreground,
104 }),
105 Token::Text("hello".into()),
106 ]);
107 assert_eq!(result, "\x1b[31mhello");
108 }
109 #[test]
110 fn test_render_color_text_reset() {
111 let result = render(vec![
112 Token::Tag(TagType::Color {
113 color: Color::Named(NamedColor::Green),
114 ground: Ground::Foreground,
115 }),
116 Token::Text("go".into()),
117 Token::Tag(TagType::Reset(None)),
118 ]);
119 assert_eq!(result, "\x1b[32mgo\x1b[0m");
120 }
121 #[test]
122 fn test_render_multiple_text_tokens() {
123 let result = render(vec![Token::Text("foo".into()), Token::Text("bar".into())]);
124 assert_eq!(result, "foobar");
125 }
126 #[test]
127 fn test_render_ansi256_color_tag() {
128 let result = render(vec![Token::Tag(TagType::Color {
129 color: Color::Ansi256(21),
130 ground: Ground::Foreground,
131 })]);
132 assert_eq!(result, "\x1b[38;5;21m");
133 }
134 #[test]
135 fn test_render_rgb_color_tag() {
136 let result = render(vec![Token::Tag(TagType::Color {
137 color: Color::Rgb(255, 0, 0),
138 ground: Ground::Foreground,
139 })]);
140 assert_eq!(result, "\x1b[38;2;255;0;0m");
141 }
142 #[test]
143 fn test_render_does_not_append_trailing_reset() {
144 let result = render(vec![Token::Text("plain".into())]);
145 assert!(!result.ends_with("\x1b[0m"));
146 }
147 #[test]
148 fn test_render_named_color_background() {
149 let result = render(vec![Token::Tag(TagType::Color {
150 color: Color::Named(NamedColor::Red),
151 ground: Ground::Background,
152 })]);
153 assert_eq!(result, "\x1b[41m");
154 }
155 #[test]
156 fn test_render_ansi256_background() {
157 let result = render(vec![Token::Tag(TagType::Color {
158 color: Color::Ansi256(21),
159 ground: Ground::Background,
160 })]);
161 assert_eq!(result, "\x1b[48;5;21m");
162 }
163 #[test]
164 fn test_render_rgb_background() {
165 let result = render(vec![Token::Tag(TagType::Color {
166 color: Color::Rgb(255, 0, 0),
167 ground: Ground::Background,
168 })]);
169 assert_eq!(result, "\x1b[48;2;255;0;0m");
170 }
171 #[test]
172 fn test_render_fg_and_bg_together() {
173 let result = render(vec![
174 Token::Tag(TagType::Color {
175 color: Color::Named(NamedColor::White),
176 ground: Ground::Foreground,
177 }),
178 Token::Tag(TagType::Color {
179 color: Color::Named(NamedColor::Blue),
180 ground: Ground::Background,
181 }),
182 Token::Text("hello".into()),
183 ]);
184 assert_eq!(result, "\x1b[37m\x1b[44mhello");
185 }
186}
187