paris/formatter/
mod.rs

1//! A wrapper around a few functions to make
2//! finding and replacing keys inside a string easier.
3
4mod 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/// Heavier formatter that allows the possibility of
23/// custom styles in strings. That is the only reason
24/// this struct exists, if you don't need custom things
25/// just use the `colorize_string()` function provided
26/// in the module.
27#[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    /// Create a new formatter with no custom styles defined
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Tell the formatter that you want a new style
49    /// and what colors that style equates to so it knows
50    /// what to replace it with when formatting
51    ///
52    /// # Example
53    /// ```
54    /// use paris::formatter::Formatter;
55    ///
56    /// let mut fmt = Formatter::new();
57    /// fmt.new_style("lol", vec!["green", "bold", "on_blue"]);
58    ///
59    /// // '<lol>' is now a key that can be used in strings
60    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    /// Finds all keys in the given input. Keys meaning
67    /// whatever the logger uses. Something that looks like `<key>`.
68    /// And replaces all those keys with their color, style
69    /// or icon equivalent.
70    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    /// Convert a key to a custom style if they match
86    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
97/// Finds all keys in the given input. If with_colors
98/// is true, it will replace all keys with their respective
99/// ANSI color code. Otherwise it will only replace the
100/// keys with an empty string.
101///
102/// #### This function does not take into account custom styles, you need the struct for that.
103pub 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
123/// Finds all keys in the given input. Keys meaning
124/// whatever the logger uses. Something that looks like `<key>`.
125/// And replaces all those keys with their color, style
126/// or icon equivalent.
127///
128/// This is a wrapper around the `format_string` function, it always passes
129/// true for the second parameter. If you want to have the ability to
130/// both colorize and plain remove the tags out of the strings, you should
131/// that function instead.
132///
133/// #### This function does not take into account custom styles, you need the struct for that
134pub 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                // Just to see all the cool colors
158                println!("{}", parsed);
159
160                assert!(!parsed.contains(&k));
161                assert!(parsed.contains(&c));
162            }
163        };
164    }
165
166    // Color checks
167    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    // Bright color checks
177    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    // Background normal
187    replacement!(on_black, 40);
188    replacement!(on_red, 41);
189    replacement!(on_green, 42);
190    replacement!(on_yellow, 43);
191
192    // Background bright
193    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    // Style checks
199    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    // Reset check
209    #[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        // Make sure its still in there
227        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}