#![warn(missing_docs)]
mod formatter;
use std::fmt::Display;
use std::thread;
use std::time::Duration;
use std::sync::{ Arc, RwLock };
use std::io::prelude::*;
use std::io;
use chrono::{ Timelike, Utc };
pub use formatter::{ Formatter, LogIcon };
#[allow(missing_docs)]
pub struct Logger {
is_loading: Arc<RwLock<bool>>,
loading_message: String,
loading_handle: Option<thread::JoinHandle<()>>,
with_timestamp: bool,
skip_timestamp: bool,
line_ending: String
}
impl Logger {
pub fn new(include_timestamp: bool) -> Logger {
Logger {
is_loading : Arc::new(RwLock::new(false)),
loading_message : String::from(""),
loading_handle : None,
with_timestamp : include_timestamp,
skip_timestamp : false,
line_ending : String::from("\n")
}
}
pub fn log<T: Display>(&mut self, message: T) -> &mut Logger {
self.stdout(message)
}
pub fn info<T: Display>(&mut self, message: T) -> &mut Logger {
self.stdout(format!("<cyan><info></> {}", message))
}
pub fn success<T: Display>(&mut self, message: T) -> &mut Logger {
self.stdout(format!("<green><tick></> {}", message))
}
pub fn warn<T: Display>(&mut self, message: T) -> &mut Logger {
self.stdout(format!("<yellow><warn></> {}", message))
}
pub fn error<T: Display>(&mut self, message: T) -> &mut Logger {
self.stderr(format!("<red><cross></> {}", message))
}
pub fn newline(&mut self, amount: usize) -> &mut Logger {
self.done();
print!("{}", "\n".repeat(amount));
self
}
pub fn indent(&mut self, amount: usize) -> &mut Logger {
self.done();
print!("{}", "\t".repeat(amount));
self
}
pub fn loading<T: Display>(&mut self, message: T) -> &mut Logger {
let mut status = self.is_loading.write().unwrap();
*status = true;
drop(status);
let status = self.is_loading.clone();
let thread_message = message.to_string();
self.loading_message = 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!(
"\r<cyan>{}</> {}",
frames[i],
&thread_message
);
print!("{}", Formatter::colorize_string(message));
io::stdout().flush().unwrap();
thread::sleep(Duration::from_millis(100));
i += 1;
}
}));
self
}
pub fn done(&mut self) -> &mut Logger {
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");
let clearing_length = self.loading_message.len() + 5;
print!("\r{}\r", " ".repeat(clearing_length));
io::stdout().flush().unwrap();
self
}
pub fn same(&mut self) -> &mut Logger {
self.set_line_ending("");
self.skip_timestamp();
self
}
fn timestamp(&mut self) -> String {
if !self.with_timestamp || self.skip_timestamp {
self.skip_timestamp = false;
return String::from("");
}
let now = Utc::now();
let (is_pm, hour) = now.hour12();
let stamp = format!(
"<dimmed>{:02}:{:02}:{:02}.{:03} {}: </>",
hour,
now.minute(),
now.second(),
now.nanosecond() / 1_000_000,
if is_pm { "PM" } else { "AM" }
);
stamp
}
fn stdout<T>(&mut self, message: T) -> &mut Logger
where T: Display
{
self.done();
let timestamp = self.timestamp();
let message = format!("{}{}{}", timestamp, message, self.get_line_ending());
print!("{}", Formatter::colorize_string(message));
self
}
fn stderr<T>(&mut self, message: T) -> &mut Logger
where T: Display
{
self.done();
let timestamp = self.timestamp();
let message = format!("{}{}{}", timestamp, message, self.get_line_ending());
eprint!("{}", Formatter::colorize_string(message));
self
}
fn skip_timestamp(&mut self) {
self.skip_timestamp = true;
}
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::*;
#[test]
fn timestamp() {
let mut logger = Logger::new(false);
assert_eq!(logger.with_timestamp, false);
logger.info("It doesn't have a timestamp");
let mut logger = Logger::new(true);
assert_eq!(logger.with_timestamp, true);
logger.info("It has a timestamp");
}
#[test]
fn loading() {
let mut logger = Logger::new(false);
logger.loading("Loading in the middle of a test is not good!");
logger.done().success("Done loading!");
logger.info("About to load again");
logger
.loading("Loading something else")
.done()
.error("Done loading instantly lol");
}
#[test]
fn same() {
let mut logger = Logger::new(false);
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(true);
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");
assert_eq!(logger.with_timestamp, true);
}
}