1mod color;
5mod concerns;
6mod icons;
7mod keys;
8mod style;
9
10#[cfg(not(feature = "no_logger"))]
11mod custom;
12#[cfg(not(feature = "no_logger"))]
13use custom::CustomStyle;
14#[cfg(not(feature = "no_logger"))]
15use keys::Key;
16
17use keys::KeyList;
18
19pub use concerns::Ansi;
20pub use icons::LogIcon;
21
22#[cfg(not(feature = "no_logger"))]
28pub struct Formatter<'a> {
29 custom_styles: Vec<CustomStyle<'a>>,
30}
31
32#[cfg(not(feature = "no_logger"))]
33impl<'a> Default for Formatter<'a> {
34 fn default() -> Self {
35 Self {
36 custom_styles: vec![],
37 }
38 }
39}
40
41#[cfg(not(feature = "no_logger"))]
42impl<'a> Formatter<'a> {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn new_style(&mut self, key: &str, colors: Vec<&'a str>) -> &mut Self {
61 self.custom_styles.push(CustomStyle::new(key, colors));
62
63 self
64 }
65
66 pub fn colorize(&self, input: &str) -> String {
71 let mut output = input.to_string();
72
73 for key in KeyList::new(&input) {
74 if let Some(style) = self.as_style(&key) {
75 let ansi = style.expand();
76 output = output.replace(&key.to_string(), &ansi);
77 }
78
79 output = output.replace(&key.to_string(), &key.to_ansi());
80 }
81
82 output
83 }
84
85 fn as_style(&self, key: &Key) -> Option<&CustomStyle> {
87 for style in self.custom_styles.iter() {
88 if style.key() == key.contents() {
89 return Some(style);
90 }
91 }
92
93 None
94 }
95}
96
97pub fn format_string<S>(input: S, with_colors: bool) -> String
104where
105 S: Into<String>,
106{
107 let input = input.into();
108 let mut output = input.clone();
109 let empty = "";
110
111 for key in KeyList::new(&input) {
112 if with_colors {
113 output = output.replace(&key.to_string(), &key.to_ansi());
114 continue;
115 }
116
117 output = output.replace(&key.to_string(), empty);
118 }
119
120 output
121}
122
123pub fn colorize_string<S>(input: S) -> String
135where
136 S: Into<String>,
137{
138 format_string(input, true)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 macro_rules! replacement {
146 ($key:ident, $code:expr) => {
147 #[test]
148 fn $key() {
149 let n = stringify!($key);
150
151 let k = format!("<{}>", n);
152 let c = format!("\x1B[{}m", $code);
153
154 let s = format!("has: {:<20} -> {}Test string", n, k);
155 let parsed = colorize_string(s);
156
157 println!("{}", parsed);
159
160 assert!(!parsed.contains(&k));
161 assert!(parsed.contains(&c));
162 }
163 };
164 }
165
166 replacement!(black, 30);
168 replacement!(red, 31);
169 replacement!(green, 32);
170 replacement!(yellow, 33);
171 replacement!(blue, 34);
172 replacement!(magenta, 35);
173 replacement!(cyan, 36);
174 replacement!(white, 37);
175
176 replacement!(bright_black, 90);
178 replacement!(bright_red, 91);
179 replacement!(bright_green, 92);
180 replacement!(bright_yellow, 93);
181 replacement!(bright_blue, 94);
182 replacement!(bright_magenta, 95);
183 replacement!(bright_cyan, 96);
184 replacement!(bright_white, 97);
185
186 replacement!(on_black, 40);
188 replacement!(on_red, 41);
189 replacement!(on_green, 42);
190 replacement!(on_yellow, 43);
191
192 replacement!(on_bright_black, 100);
194 replacement!(on_bright_red, 101);
195 replacement!(on_bright_green, 102);
196 replacement!(on_bright_yellow, 103);
197
198 replacement!(bold, 1);
200 replacement!(dimmed, 2);
201 replacement!(italic, 3);
202 replacement!(underline, 4);
203 replacement!(blink, 5);
204 replacement!(reverse, 7);
205 replacement!(hidden, 8);
206 replacement!(strikethrough, 9);
207
208 #[test]
210 fn reset() {
211 let k = "</>";
212 let c = format!("\x1B[{}m", 0);
213
214 let s = format!("{}Test string", k);
215 let parsed = colorize_string(s);
216
217 assert!(!parsed.contains(&k));
218 assert!(parsed.contains(&c));
219 }
220
221 #[test]
222 fn normal_tags() {
223 let s = String::from("<html> This is normal stuff </html>");
224 let parsed = colorize_string(s);
225
226 assert!(parsed.contains("<html>"));
228 }
229
230 #[test]
231 fn no_colors() {
232 let s = String::from("<bright_green>Something is black</>");
233 let parsed = format_string(s, false);
234 let expected = String::from("Something is black");
235
236 assert!(!parsed.contains("<bright_green>"));
237 assert!(!parsed.contains("</>"));
238
239 assert_eq!(parsed, expected);
240 }
241
242 #[test]
243 #[cfg(not(feature = "no_logger"))]
244 fn custom_style() {
245 let s =
246 String::from("<custom> This has custom styles <lol> Here's some blue shit yoooo </>");
247
248 let mut fmt = Formatter::new();
249 fmt.new_style("custom", vec!["red", "on-green"])
250 .new_style("lol", vec!["cyan", "on-blue"]);
251
252 let parsed = fmt.colorize(&s);
253
254 assert!(!parsed.contains("<custom>"));
255 assert!(!parsed.contains("<lol>"));
256 }
257}