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}