paris/logger/
mod.rs

1use std::fmt::Display;
2use std::io;
3use std::io::prelude::*;
4use std::sync::{Arc, RwLock};
5use std::thread;
6use std::time::Duration;
7
8use crate::formatter::{colorize_string, Ansi, Formatter};
9use crate::output;
10
11#[allow(missing_docs)]
12pub struct Logger<'a> {
13    is_loading: Arc<RwLock<bool>>,
14    loading_handle: Option<thread::JoinHandle<()>>,
15
16    line_ending: String,
17    formatter: Formatter<'a>,
18}
19
20impl<'a> Default for Logger<'a> {
21    fn default() -> Self {
22        Self {
23            is_loading: Arc::new(RwLock::new(false)),
24            loading_handle: None,
25
26            line_ending: String::from("\n"),
27            formatter: Formatter::new(),
28        }
29    }
30}
31
32impl<'a> Logger<'a> {
33    /// Initializes a new logger
34    ///
35    /// # Example
36    /// ```
37    /// use paris::Logger;
38    /// let logger = Logger::new();
39    /// ```
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Prints to stdout with no bells and whistles. I does however
45    /// add a timestamp if enabled.
46    ///
47    /// # Example
48    /// ```
49    /// # use paris::Logger;
50    /// let mut logger = Logger::new();
51    ///
52    /// logger.log("Basic and boring."); // Basic and boring.
53    /// ```
54    ///
55    /// Equivalent macro: `log!()`
56    pub fn log<T: Display>(&mut self, message: T) -> &mut Self {
57        self.stdout(message)
58    }
59
60    /// Prints to stdout and adds some info flair to the text
61    ///
62    /// # Example
63    /// ```
64    /// # use paris::Logger;
65    /// # let mut logger = Logger::new();
66    /// logger.info("This is some info");
67    /// ```
68    ///
69    /// Equivalent macro: `info!()`
70    pub fn info<T: Display>(&mut self, message: T) -> &mut Self {
71        self.stdout(format!("<cyan><info></> {}", message))
72    }
73
74    /// Prints to stdout and adds some success flair to text
75    ///
76    /// # Example
77    /// ```
78    /// # use paris::Logger;
79    /// # let mut logger = Logger::new();
80    /// logger.success("Everything went great!");
81    /// ```
82    ///
83    /// Equivalent macro: `success!()`
84    pub fn success<T: Display>(&mut self, message: T) -> &mut Self {
85        self.stdout(format!("<green><tick></> {}", message))
86    }
87
88    /// Prints to stdout and adds some warning flare to text
89    ///
90    /// # Example
91    /// ```
92    /// # use paris::Logger;
93    /// # let mut logger = Logger::new();
94    /// logger.warn("This is a warning");
95    /// ```
96    ///
97    /// Equivalent macro: `warn!()`
98    pub fn warn<T: Display>(&mut self, message: T) -> &mut Self {
99        self.stdout(format!("<yellow><warn></> {}", message))
100    }
101
102    /// Prints to stderr and adds some error flare to text
103    ///
104    /// # Example
105    /// ```
106    /// # use paris::Logger;
107    /// # let mut logger = Logger::new();
108    /// logger.error("Something broke, here's the error");
109    /// ```
110    ///
111    /// Equivalent macro: `error!()`
112    pub fn error<T: Display>(&mut self, message: T) -> &mut Self {
113        self.stderr(format!("<red><cross></> {}", message))
114    }
115
116    /// Prints a specified amount of newlines to stdout
117    ///
118    /// # Example
119    /// ```
120    /// # use paris::Logger;
121    /// # let mut logger = Logger::new();
122    /// logger
123    ///     .newline(5)
124    ///     .info("Some newlines before info")
125    ///     .newline(2)
126    ///     .info("And some more in between");
127    /// ```
128    pub fn newline(&mut self, amount: usize) -> &mut Self {
129        self.done();
130        print!("{}", "\n".repeat(amount));
131        self
132    }
133
134    /// Prints a specified amount of tabs to stdout
135    ///
136    /// # Example
137    /// ```
138    /// # use paris::Logger;
139    /// # let mut logger = Logger::new();
140    /// logger
141    ///     .indent(1)
142    ///     .warn("Indented warning eh? Stands out a bit")
143    ///     .newline(5);
144    /// ```
145    pub fn indent(&mut self, amount: usize) -> &mut Self {
146        self.done();
147        print!("{}", "\t".repeat(amount));
148        self
149    }
150
151    /// Starts a loading animation with the given message.
152    ///
153    /// # Example
154    /// ```
155    /// # use paris::Logger;
156    /// let mut logger = Logger::new();
157    /// logger.loading("Counting to 52!");
158    ///
159    /// // counting happens here (somehow)
160    ///
161    /// logger
162    ///     .done()
163    ///     .success("Done counting, only took 1 million years");
164    /// ```
165    ///
166    /// That's one way of doing it, but having to always call `.done()` doesn't
167    /// look that tidy. Well you don't have to, unless you want. All other functions
168    /// (success, info, error, etc.) call `.done()` just in case a loading thread is running
169    /// already. A cleaner example would be:
170    /// ```
171    /// # use paris::Logger;
172    /// let mut logger = Logger::new();
173    /// logger.loading("Counting to 52! again");
174    ///
175    /// // ....
176    ///
177    /// logger.error("I give up, I can't do it again!");
178    /// ```
179    pub fn loading<T: Display>(&mut self, message: T) -> &mut Self {
180        // If already running, stop the currently running thread
181        // and clean it up before adding a new message.
182        self.done();
183
184        let mut status = self.is_loading.write().unwrap();
185        *status = true;
186
187        drop(status); // Release the lock so a mutable can be returned
188
189        let status = self.is_loading.clone();
190        let message = self.formatter.colorize(&message.to_string());
191
192        self.loading_handle = Some(thread::spawn(move || {
193            let frames: [&str; 6] = ["⠦", "⠇", "⠋", "⠙", "⠸", "⠴"];
194            let mut i = 1;
195
196            while *status.read().unwrap() {
197                if i == frames.len() {
198                    i = 0;
199                }
200
201                let message = format!("<cyan>{}</> {}", frames[i], &message);
202                output::stdout(colorize_string(message), "", true);
203                io::stdout().flush().unwrap();
204
205                thread::sleep(Duration::from_millis(100));
206
207                i += 1;
208            }
209        }));
210
211        self
212    }
213
214    /// Stops the loading animation and clears the line so you can print something else
215    /// when loading is done, maybe a success message. All other methods (success, warning, error, etc.)
216    /// call this one automatically when called so you can use one of those directly
217    /// for less clutter.
218    pub fn done(&mut self) -> &mut Self {
219        if !*self.is_loading.read().unwrap() {
220            return self;
221        }
222
223        let mut status = self.is_loading.write().unwrap();
224        *status = false;
225
226        drop(status); // Release the lock so a mutable can be returned
227
228        self.loading_handle
229            .take()
230            .expect("Called stop on a non-existing thread")
231            .join()
232            .expect("Could not join spawned thread");
233
234        Ansi::clear_line();
235        self
236    }
237
238    /// Forces the next statement to not output a newline
239    ///
240    /// # Example
241    /// ```
242    /// # use paris::Logger;
243    /// # let mut logger = Logger::new();
244    ///
245    /// logger
246    ///     .same().log("This is on one line")
247    ///     .indent(4)
248    ///     .log("This is on the same line!");
249    /// ```
250    pub fn same(&mut self) -> &mut Self {
251        self.set_line_ending("");
252
253        self
254    }
255
256    /// Add a custom key to the available list of keys
257    ///
258    /// # Example
259    /// ```
260    /// # use paris::Logger;
261    /// # let mut logger = Logger::new();
262    ///
263    /// logger.add_style("lol", vec!["green", "bold", "on_blue"]);
264    ///
265    /// // '<lol>' can now be used as a key in strings and will contain
266    /// // the defined colors and styles
267    /// logger.info("<lol>much shorter than writing all of them</>");
268    pub fn add_style(&mut self, key: &str, colors: Vec<&'a str>) -> &mut Self {
269        self.formatter.new_style(key, colors);
270        self
271    }
272
273    /// Output to stdout, add timestamps or on the same line
274    fn stdout<T>(&mut self, message: T) -> &mut Self
275    where
276        T: Display,
277    {
278        self.done();
279        let message = message.to_string();
280
281        output::stdout(
282            self.formatter.colorize(&message),
283            &self.get_line_ending(),
284            false,
285        );
286        self
287    }
288
289    /// Output to stderr, add timestamps or write on the same line
290    fn stderr<T>(&mut self, message: T) -> &mut Self
291    where
292        T: Display,
293    {
294        self.done();
295        let message = message.to_string();
296
297        output::stderr(
298            self.formatter.colorize(&message),
299            &self.get_line_ending(),
300            false,
301        );
302        self
303    }
304
305    /// Sets line ending to something specific
306    /// mostly \n for now
307    fn set_line_ending<T: Into<String>>(&mut self, ending: T) {
308        self.line_ending = ending.into();
309    }
310
311    /// Return line ending based on whats already set
312    /// set it back to newline if its not already
313    fn get_line_ending(&mut self) -> String {
314        let newline = String::from("\n");
315        let empty = String::from("");
316
317        if self.line_ending != newline {
318            self.set_line_ending(newline);
319            return empty;
320        }
321
322        newline
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329    use std::{thread, time::Duration};
330
331    #[test]
332    fn loading() {
333        let mut logger = Logger::new();
334        logger.loading("Loading in the middle of a test is not good!");
335        thread::sleep(Duration::from_secs(1));
336        logger.done().success("Done loading!");
337
338        logger.info("About to load again");
339        logger
340            .loading("Loading something else")
341            .done()
342            .error("Done loading instantly lol");
343    }
344
345    #[test]
346    fn multiple_loading() {
347        let mut logger = Logger::new();
348        logger.loading("Loading in the middle of a test is not good!");
349        thread::sleep(Duration::from_secs(1));
350        logger.loading("This will break it?");
351        thread::sleep(Duration::from_secs(1));
352
353        logger.success("Loading done!");
354    }
355
356    #[cfg(feature = "timestamps")]
357    #[test]
358    fn loading_with_timestamps() {
359        let mut logger = Logger::new();
360        logger.loading("Loading in the middle of a test is not good!");
361        thread::sleep(Duration::from_secs(4));
362        logger.loading("Still loading...");
363        thread::sleep(Duration::from_secs(4));
364
365        logger.success("Loading done!");
366    }
367
368    #[test]
369    fn same() {
370        let mut logger = Logger::new();
371        logger
372            .same()
373            .success("This is on one line")
374            .indent(1)
375            .info("This is on the same line!!!")
376            .error("But this one isn't");
377
378        logger.same();
379        assert_eq!(logger.line_ending, String::from(""));
380
381        logger.info("Reset the line");
382        assert_eq!(logger.line_ending, String::from("\n"));
383    }
384
385    #[test]
386    fn it_works() {
387        let mut logger = Logger::new();
388
389        logger
390            .info("Somebody")
391            .error("Once")
392            .warn("Told")
393            .success("Me")
394            .newline(5)
395            .log("A basic log eh")
396            .indent(2)
397            .info("If it didn't crash it's fine");
398    }
399
400    #[test]
401    fn add_style_works() {
402        let mut logger = Logger::new();
403
404        logger.add_style("lmao", vec!["red", "on-green"]);
405    }
406}