use std::fmt::Display;
use std::io;
use std::io::prelude::*;
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
use crate::formatter::{colorize_string, Ansi, Formatter};
use crate::output;
#[allow(missing_docs)]
pub struct Logger<'a> {
is_loading: Arc<RwLock<bool>>,
loading_handle: Option<thread::JoinHandle<()>>,
line_ending: String,
formatter: Formatter<'a>,
}
impl<'a> Default for Logger<'a> {
fn default() -> Self {
Self {
is_loading: Arc::new(RwLock::new(false)),
loading_handle: None,
line_ending: String::from("\n"),
formatter: Formatter::new(),
}
}
}
impl<'a> Logger<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn log<T: Display>(&mut self, message: T) -> &mut Self {
self.stdout(message)
}
pub fn info<T: Display>(&mut self, message: T) -> &mut Self {
self.stdout(format!("<cyan><info></> {}", message))
}
pub fn success<T: Display>(&mut self, message: T) -> &mut Self {
self.stdout(format!("<green><tick></> {}", message))
}
pub fn warn<T: Display>(&mut self, message: T) -> &mut Self {
self.stdout(format!("<yellow><warn></> {}", message))
}
pub fn error<T: Display>(&mut self, message: T) -> &mut Self {
self.stderr(format!("<red><cross></> {}", message))
}
pub fn newline(&mut self, amount: usize) -> &mut Self {
self.done();
print!("{}", "\n".repeat(amount));
self
}
pub fn indent(&mut self, amount: usize) -> &mut Self {
self.done();
print!("{}", "\t".repeat(amount));
self
}
pub fn loading<T: Display>(&mut self, message: T) -> &mut Self {
self.done();
let mut status = self.is_loading.write().unwrap();
*status = true;
drop(status);
let status = self.is_loading.clone();
let message = self.formatter.colorize(&message.to_string());
self.loading_handle = Some(thread::spawn(move || {
let frames: [&str; 6] = ["â ¦", "â ‡", "â ‹", "â ™", "â ¸", "â ´"];
let mut i = 1;
while *status.read().unwrap() {
if i == frames.len() {
i = 0;
}
let message = format!("<cyan>{}</> {}", frames[i], &message);
output::stdout(colorize_string(message), "", true);
io::stdout().flush().unwrap();
thread::sleep(Duration::from_millis(100));
i += 1;
}
}));
self
}
pub fn done(&mut self) -> &mut Self {
if !*self.is_loading.read().unwrap() {
return self;
}
let mut status = self.is_loading.write().unwrap();
*status = false;
drop(status);
self.loading_handle
.take()
.expect("Called stop on a non-existing thread")
.join()
.expect("Could not join spawned thread");
Ansi::clear_line();
self
}
pub fn same(&mut self) -> &mut Self {
self.set_line_ending("");
self
}
pub fn add_style(&mut self, key: &str, colors: Vec<&'a str>) -> &mut Self {
self.formatter.new_style(key, colors);
self
}
fn stdout<T>(&mut self, message: T) -> &mut Self
where
T: Display,
{
self.done();
let message = message.to_string();
output::stdout(
self.formatter.colorize(&message),
&self.get_line_ending(),
false,
);
self
}
fn stderr<T>(&mut self, message: T) -> &mut Self
where
T: Display,
{
self.done();
let message = message.to_string();
output::stderr(
self.formatter.colorize(&message),
&self.get_line_ending(),
false,
);
self
}
fn set_line_ending<T: Into<String>>(&mut self, ending: T) {
self.line_ending = ending.into();
}
fn get_line_ending(&mut self) -> String {
let newline = String::from("\n");
let empty = String::from("");
if self.line_ending != newline {
self.set_line_ending(newline);
return empty;
}
newline
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{thread, time::Duration};
#[test]
fn loading() {
let mut logger = Logger::new();
logger.loading("Loading in the middle of a test is not good!");
thread::sleep(Duration::from_secs(1));
logger.done().success("Done loading!");
logger.info("About to load again");
logger
.loading("Loading something else")
.done()
.error("Done loading instantly lol");
}
#[test]
fn multiple_loading() {
let mut logger = Logger::new();
logger.loading("Loading in the middle of a test is not good!");
thread::sleep(Duration::from_secs(1));
logger.loading("This will break it?");
thread::sleep(Duration::from_secs(1));
logger.success("Loading done!");
}
#[cfg(feature = "timestamps")]
#[test]
fn loading_with_timestamps() {
let mut logger = Logger::new();
logger.loading("Loading in the middle of a test is not good!");
thread::sleep(Duration::from_secs(4));
logger.loading("Still loading...");
thread::sleep(Duration::from_secs(4));
logger.success("Loading done!");
}
#[test]
fn same() {
let mut logger = Logger::new();
logger
.same()
.success("This is on one line")
.indent(1)
.info("This is on the same line!!!")
.error("But this one isn't");
logger.same();
assert_eq!(logger.line_ending, String::from(""));
logger.info("Reset the line");
assert_eq!(logger.line_ending, String::from("\n"));
}
#[test]
fn it_works() {
let mut logger = Logger::new();
logger
.info("Somebody")
.error("Once")
.warn("Told")
.success("Me")
.newline(5)
.log("A basic log eh")
.indent(2)
.info("If it didn't crash it's fine");
}
#[test]
fn add_style_works() {
let mut logger = Logger::new();
logger.add_style("lmao", vec!["red", "on-green"]);
}
}