1pub(crate) mod ansi;
13pub mod errors;
14pub(crate) mod lexer;
15pub(crate) mod parser;
16
17pub fn try_color(input: impl Into<String>) -> Result<String, errors::LexError> {
24 let input = input.into();
25 let mut res = parser::render(lexer::tokenize(input)?);
26 res.push_str("\x1b[0m");
27 Ok(res)
28}
29
30pub fn color(input: impl Into<String>) -> String {
36 let input = input.into();
37 try_color(input).expect("Failed to colorize")
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
47 fn test_try_color_named_color() {
48 let result = try_color("[red]I'm red!");
49 assert!(result.is_ok());
50 assert_eq!(result.unwrap(), "\x1b[31mI'm red!\x1b[0m");
51 }
52
53 #[test]
54 fn test_try_color_appends_trailing_reset() {
55 let result = try_color("[blue]text");
56 assert!(result.is_ok());
57 assert!(result.unwrap().ends_with("\x1b[0m"));
58 }
59
60 #[test]
61 fn test_try_color_plain_text_no_tags() {
62 let result = try_color("no markup here");
63 assert!(result.is_ok());
64 assert_eq!(result.unwrap(), "no markup here\x1b[0m");
65 }
66
67 #[test]
68 fn test_try_color_empty_string() {
69 let result = try_color("");
70 assert!(result.is_ok());
71 assert_eq!(result.unwrap(), "\x1b[0m");
72 }
73
74 #[test]
75 fn test_try_color_invalid_tag_returns_error() {
76 let result = try_color("[error]text");
77 assert!(result.is_err());
78 }
79
80 #[test]
81 fn test_try_color_unclosed_tag_returns_error() {
82 let result = try_color("[red");
83 assert!(result.is_err());
84 }
85
86 #[test]
87 fn test_try_color_rgb_color() {
88 let result = try_color("[rgb(255,0,0)]red");
89 assert!(result.is_ok());
90 assert_eq!(result.unwrap(), "\x1b[38;2;255;0;0mred\x1b[0m");
91 }
92
93 #[test]
94 fn test_try_color_bold_and_named_color() {
95 let result = try_color("[bold red]hi");
96 assert!(result.is_ok());
97 assert_eq!(result.unwrap(), "\x1b[1m\x1b[31mhi\x1b[0m");
98 }
99
100 #[test]
101 fn test_try_color_escaped_bracket() {
102 let result = try_color("\\[not a tag]");
103 assert!(result.is_ok());
104 assert!(result.unwrap().starts_with('['));
105 }
106
107 #[test]
108 fn test_try_color_inline_reset() {
109 let result = try_color("[red]before[/]after");
110 assert!(result.is_ok());
111 let s = result.unwrap();
112 assert!(s.contains("\x1b[0m"));
113 }
114
115 #[test]
118 fn test_color_valid_input_returns_string() {
119 let result = color("[green]ok");
120 assert_eq!(result, "\x1b[32mok\x1b[0m");
121 }
122
123 #[test]
124 #[should_panic]
125 fn test_color_invalid_input_panics() {
126 color("[notacolor]text");
127 }
128}
129
130