1use std::fmt;
2use strum::IntoEnumIterator;
3use strum_macros::EnumIter;
4
5#[derive(EnumIter, Debug, Clone, Copy, PartialEq)]
7pub enum StyleKind {
8 Bold,
10 Italic,
12 Underline,
14 Strikethrough,
16 Link,
18}
19
20impl StyleKind {
21
22 pub const fn to_tag(&self) -> &str {
24 match self {
25 StyleKind::Bold => "b",
26 StyleKind::Italic => "i",
27 StyleKind::Underline => "u",
28 StyleKind::Strikethrough => "s",
29 StyleKind::Link => "url",
30 }
31 }
32
33}
34
35#[derive(EnumIter, Debug, Clone, Copy, PartialEq)]
37pub enum EmoteKind {
38 Smile,
41 Sad,
44 ColonD,
47 ColonThree,
50 Fearful,
53 Sunglasses,
56 Crying,
59 Winking,
62}
63
64impl EmoteKind {
65
66 pub const fn to_tag(&self) -> &str {
68 match self {
69 EmoteKind::Smile => ":)",
70 EmoteKind::Sad => ":(",
71 EmoteKind::ColonD => ":D",
72 EmoteKind::ColonThree => ":3",
73 EmoteKind::Fearful => "D:",
74 EmoteKind::Sunglasses => "B)",
75 EmoteKind::Crying => ";(",
76 EmoteKind::Winking => ";)",
77 }
78 }
79
80 pub const fn to_name(&self) -> &str {
82 match self {
83 EmoteKind::Smile => "smile",
84 EmoteKind::Sad => "sad",
85 EmoteKind::ColonD => "colond",
86 EmoteKind::ColonThree => "colonthree",
87 EmoteKind::Fearful => "fearful",
88 EmoteKind::Sunglasses => "sunglasses",
89 EmoteKind::Crying => "crying",
90 EmoteKind::Winking => "winking",
91 }
92 }
93
94}
95
96#[derive(Default, Debug, Clone, Copy, PartialEq)]
98pub struct Color {
99 pub r: u8,
101 pub g: u8,
103 pub b: u8,
105}
106
107impl Color {
108
109 pub const fn new(r: u8, g: u8, b: u8) -> Self {
111 Self { r, g, b }
112 }
113
114}
115
116impl fmt::Display for Color {
117
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
121 }
122
123}
124
125#[derive(Debug, Clone, PartialEq)]
127pub enum Part {
128 Text(String),
130 Escape,
132 Newline,
134 Style(StyleKind, bool),
136 Color(Color, bool),
138 Emote(EmoteKind),
140}
141
142impl Part {
143
144 fn parse_style_tag(mut body: &str) -> Option<Self> {
146 let mut enable = true;
147
148 if body.starts_with('/') {
150 enable = false;
151 body = &body[1..];
152 }
153
154 for style in StyleKind::iter() {
155 if body == style.to_tag() {
156 return Some(Self::Style(style, enable));
157 }
158 }
159
160 None
161 }
162
163 fn parse_emote_tag(body: &str) -> Option<Self> {
165 for emote in EmoteKind::iter() {
166 if body == emote.to_tag() {
167 return Some(Self::Emote(emote));
168 }
169 }
170
171 None
172 }
173
174 fn parse_color_tag(body: &str) -> Option<Self> {
176 if body.len() == 13 && body.starts_with("color=#") {
177 let r = u8::from_str_radix(&body[ 7.. 9], 16).ok()?;
178 let g = u8::from_str_radix(&body[ 9..11], 16).ok()?;
179 let b = u8::from_str_radix(&body[11..13], 16).ok()?;
180 Some(Self::Color(Color::new(r, g, b), true))
181 } else if body == "/color" {
182 Some(Self::Color(Color::default(), false))
183 } else {
184 None
185 }
186 }
187
188 fn parse_tag(body: &str) -> Option<Self> {
190 if body.is_empty() || body.len() > 32 {
191 return None;
192 }
193
194 Self::parse_style_tag(body)
195 .or_else(|| Self::parse_emote_tag(body))
196 .or_else(|| Self::parse_color_tag(body))
197 }
198
199}
200
201impl fmt::Display for Part {
202
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self {
206 Part::Text(text) => write!(f, "{text}"),
207 Part::Escape => write!(f, "\\"),
208 Part::Newline => write!(f, "\n"),
209 Part::Style(style, enable) => {
210 if *enable {
211 write!(f, "[{}]", style.to_tag())
212 } else {
213 write!(f, "[/{}]", style.to_tag())
214 }
215 }
216 Part::Color(color, enable) => {
217 if *enable {
218 write!(f, "[color={}]", color)
219 } else {
220 write!(f, "[/color]")
221 }
222 }
223 Part::Emote(emote) => write!(f, "[{}]", emote.to_tag()),
224 }
225 }
226
227}
228
229#[derive(Default, Debug)]
231struct Parser {
232 parts: Vec<Part>,
234 buffer: String,
236 escape: bool,
238}
239
240impl Parser {
241
242 fn new() -> Self {
244 Self::default()
245 }
246
247 fn emit(&mut self, part: Part) {
249 self.parts.push(part);
250 }
251
252 fn flush(&mut self) {
254 if !self.buffer.is_empty() {
255 self.emit(Part::Text(self.buffer.clone()));
256 self.buffer.clear();
257 }
258 }
259
260 fn tag(&mut self) -> bool {
262 let index = match self.buffer.rfind('[') {
264 Some(index) => index,
265 None => return false,
266 };
267
268 if index == 0 && matches!(self.parts.last(), Some(Part::Escape)) {
270 return false;
271 }
272
273 let body = &self.buffer[index+1..];
275
276 let part = Part::parse_tag(body);
278
279 if let Some(part) = part {
281 self.buffer.drain(index..);
283 self.flush();
285 self.emit(part);
286 true
288 } else {
289 false
291 }
292 }
293
294 fn parse(mut self, input: &str) -> Vec<Part> {
296 for char in input.chars() {
298 if !self.escape {
300 if char == '\\' {
302 self.escape = true;
303 self.flush();
304 self.emit(Part::Escape);
305 continue;
306 }
307 if char == ']' {
309 if self.tag() {
310 continue;
311 }
312 }
313 }
314
315 self.escape = false;
317
318 if char == '\n' {
320 self.flush();
321 self.emit(Part::Newline);
322 continue;
323 }
324
325 self.buffer.push(char);
327 }
328
329 self.flush();
331
332 self.parts
334 }
335
336}
337
338pub fn parse(input: &str) -> Vec<Part> {
340 Parser::new().parse(input)
341}
342
343pub fn length(parts: &[Part]) -> usize {
345 parts.iter().fold(0, |acc, part| {
346 match part {
347 Part::Text(text) => acc + text.chars().count(),
348 Part::Newline | Part::Emote(_) => acc + 1,
349 _ => acc,
350 }
351 })
352}