use std::{path::PathBuf, sync::OnceLock};
use logger::{LogInterval, LogLevel, Logger};
use styling::*;
pub mod logger;
pub mod prelude;
pub mod styling;
mod tests;
pub static mut LOGGER: OnceLock<Logger> = OnceLock::new();
pub static DEBUG: OnceLock<bool> = OnceLock::new();
#[inline]
pub fn init_logger<P: Into<PathBuf>>(path: P, log_interval: LogInterval) -> std::io::Result<()> {
unsafe {
let logger = Logger::new(path, log_interval)?;
LOGGER.set(logger).unwrap_or(());
}
Ok(())
}
#[inline]
pub fn clear_log_levels() {
unsafe {
_ = LOGGER.get_mut().unwrap().clear_log_levels();
}
}
#[macro_export]
macro_rules! add_log_levels {
($($level:expr),+ $(,)?) => {
unsafe {
$(
_ = LOGGER.get_mut().unwrap().add_log_level($level);
)+
}
};
}
#[inline]
pub fn rgb_to_ansi(r: u8, g: u8, b: u8) -> String {
format!("\x1b[38;2;{};{};{}m", r, g, b)
}
#[inline]
pub fn rgb_to_ansi_bg(r: u8, g: u8, b: u8) -> String {
format!("\x1b[48;2;{};{};{}m", r, g, b)
}
#[macro_export]
macro_rules! ansi_rgb {
($r:expr, $g:expr, $b:expr) => {
concat!("\x1b[38;2;", $r, ";", $g, ";", $b, "m")
};
}
#[macro_export]
macro_rules! ansi_rgb_bg {
($r:expr, $g:expr, $b:expr) => {
concat!("\x1b[48;2;", $r, ";", $g, ";", $b, "m")
};
}
#[inline]
pub fn set_debug(debug: bool) {
DEBUG.set(debug).unwrap_or(());
}
#[inline]
pub fn get_timestamp() -> String {
chrono::Local::now().format("%H:%M:%S%.3f").to_string()
}
#[inline]
pub fn customize_colors(colors: Colors) {
_ = COLORS.set(colors);
}
#[inline]
pub fn customize_symbols(symbols: Symbols) {
_ = SYMBOLS.set(symbols);
}
#[inline]
pub fn customize_borders(borders: Borders) {
_ = BORDERS.set(borders);
}
#[inline]
pub fn get_colors() -> &'static Colors {
COLORS.get_or_init(|| Colors::default())
}
#[inline]
pub fn get_symbols() -> &'static Symbols {
SYMBOLS.get_or_init(|| Symbols::default())
}
#[inline]
pub fn get_borders() -> &'static Borders {
BORDERS.get_or_init(|| Borders::default())
}
pub fn create_styled_box(
box_color: &str,
text_color: &str,
symbol: &str,
title: &str,
message: &str,
width: usize,
) -> String {
let message_lines: Vec<&str> = message.lines().collect();
let mut result = String::new();
let timestamp = chrono::Local::now().format("%H:%M:%S").to_string();
let timestamp_display = format!("⏳ {}", timestamp);
let total_space = width - title.len() - timestamp_display.len() - symbol.len() - 1;
result.push_str(&format!(
"{}{}{}─{} {}{}{}{}{}{}{}{}{}\n",
box_color,
get_borders().top_left,
get_colors().bold,
symbol,
title,
box_color,
get_borders().horizontal.repeat(total_space),
get_colors().dim,
timestamp_display,
get_colors().reset,
box_color,
get_borders().top_right,
get_colors().reset
));
for line in message_lines {
let processed_line = style_text!(line, text_color);
let clean_processed = strip_ansi_codes(&processed_line);
let padding = width - clean_processed.len() - 3;
result.push_str(&format!(
"{}{} {}{}{}{}{}{}\n",
box_color,
get_borders().vertical,
processed_line,
get_colors().reset,
box_color, " ".repeat(padding),
get_borders().vertical,
get_colors().reset,
));
}
result.push_str(&format!(
"{}{}{}{}{}\n",
box_color,
get_borders().bottom_left,
get_borders().horizontal.repeat(width - 2),
get_borders().bottom_right,
get_colors().reset
));
result
}
pub fn strip_ansi_codes(s: &str) -> String {
let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap();
re.replace_all(s, "").to_string()
}
#[macro_export]
macro_rules! info_box {
($title:expr, $($arg:tt)*) => {
print!("{}", $crate::create_styled_box(
$crate::get_colors().info,
$crate::get_colors().info_text,
$crate::get_symbols().info,
$title,
&format!($($arg)*),
75
));
let log = make_log!(
$crate::get_colors().info,
$crate::get_symbols().info,
$title,
$crate::get_colors().info_text,
$($arg)*
);
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&log) {
eprintln!("Error logging to file: {e}");
}
}
}
};
}
#[macro_export]
macro_rules! warn_box {
($title:expr, $($arg:tt)*) => {
print!("{}", $crate::create_styled_box(
$crate::get_colors().warn,
$crate::get_colors().warn_text,
$crate::get_symbols().warn,
$title,
&format!($($arg)*),
75
));
let log = make_log!(
$crate::get_colors().warn,
$crate::get_symbols().warn,
$title,
$crate::get_colors().warn_text,
$($arg)*
);
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&log) {
eprintln!("Error logging to file: {e}");
}
}
}
};
}
#[macro_export]
macro_rules! error_box {
($title:expr, $($arg:tt)*) => {
eprint!("{}", $crate::create_styled_box(
$crate::get_colors().error,
$crate::get_colors().error_text,
$crate::get_symbols().error,
$title,
&format!($($arg)*),
75
));
let log = make_log!(
$crate::get_colors().error,
$crate::get_symbols().error,
$title,
$crate::get_colors().error_text,
$($arg)*
);
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&log) {
eprintln!("Error logging to file: {e}");
}
}
}
};
}
#[macro_export]
macro_rules! success_box {
($title:expr, $($arg:tt)*) => {
print!("{}", $crate::create_styled_box(
$crate::get_colors().success,
$crate::get_colors().success_text,
$crate::get_symbols().success,
$title,
&format!($($arg)*),
75
));
let log = make_log!(
$crate::get_colors().success,
$crate::get_symbols().success,
$title,
$crate::get_colors().success,
$($arg)*
);
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&log) {
eprintln!("Error logging to file: {e}");
}
}
}
};
}
#[macro_export]
macro_rules! debug_box {
($title:expr, $($arg:tt)*) => {
if *$crate::DEBUG.get().unwrap_or(&true) {
print!("{}", $crate::create_styled_box(
$crate::get_colors().debug,
$crate::get_colors().debug_text,
$crate::get_symbols().debug,
$title,
&format!($($arg)*),
75
));
}
let log = make_log!(
$crate::get_colors().debug,
$crate::get_symbols().debug,
$title,
$crate::get_colors().debug_text,
$($arg)*
);
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&log) {
eprintln!("Error logging to file: {e}");
}
}
}
};
}
#[macro_export]
macro_rules! make_log {
($color:expr, $symbol:expr, $title:expr, $textcolor:expr, $($arg:tt)*) => {{
let message = format!($($arg)*);
let processed_message = style_text!(message, $textcolor);
format!(
"{}{} {} {}{}{}{} {} {}{}{}{}{} {}",
$color,
$symbol,
$crate::get_colors().reset,
$crate::get_colors().dim,
$crate::get_timestamp(),
$crate::get_colors().reset,
$crate::get_colors().dim,
$crate::get_symbols().separator,
$crate::get_colors().reset,
$crate::get_colors().bold,
$color,
$title,
$crate::get_colors().reset,
processed_message
)
}};
}
#[macro_export]
macro_rules! style_text {
($text:expr, $color:expr) => {{
let text = $text.to_string();
let mut result = text.clone();
while let Some(start) = result.find("**") {
if let Some(end) = result[start + 2..].find("**") {
let before = &result[..start];
let content = &result[start + 2..start + 2 + end];
let after = &result[start + 2 + end + 2..];
result = format!("{}{}\x1b[1m{}\x1b[22m{}", before, $color, content, after);
} else {
break;
}
}
let styles = [
("*", "\x1b[3m", "\x1b[23m"), ("_", "\x1b[4m", "\x1b[24m"), ("~", "\x1b[9m", "\x1b[29m"), ("@", "\x1b[2m", "\x1b[22m"), ];
for (marker, start_code, end_code) in styles {
while let Some(start) = result.find(marker) {
if let Some(end) = result[start + 1..].find(marker) {
let before = &result[..start];
let content = &result[start + 1..start + 1 + end];
let after = &result[start + 1 + end + 1..];
result = format!(
"{}{}{}{}{}{}",
before, $color, start_code, content, end_code, after
);
} else {
break;
}
}
}
format!("{}{}{}", $color, result, get_colors().reset)
}};
}
#[macro_export]
macro_rules! info {
($title:expr, $($arg:tt)*) => {{
let msg = make_log!(
$crate::get_colors().info,
$crate::get_symbols().info,
$title,
$crate::get_colors().info_text,
$($arg)*
);
println!("{msg}");
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&msg) {
eprintln!("Error logging to file: {e}");
}
}
}
}};
}
#[macro_export]
macro_rules! warn {
($title:expr, $($arg:tt)*) => {{
let msg = make_log!(
$crate::get_colors().warn,
$crate::get_symbols().warn,
$title,
$crate::get_colors().warn_text,
$($arg)*
);
println!("{msg}");
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&msg) {
eprintln!("Error logging to file: {e}");
}
}
}
}};
}
#[macro_export]
macro_rules! error {
($title:expr, $($arg:tt)*) => {{
let msg = make_log!(
$crate::get_colors().error,
$crate::get_symbols().error,
$title,
$crate::get_colors().error_text,
$($arg)*
);
eprintln!("{msg}");
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&msg) {
eprintln!("Error logging to file: {e}");
}
}
}
}};
}
#[macro_export]
macro_rules! success {
($title:expr, $($arg:tt)*) => {{
let msg = make_log!(
$crate::get_colors().success,
$crate::get_symbols().success,
$title,
$crate::get_colors().success_text,
$($arg)*
);
println!("{msg}");
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&msg) {
eprintln!("Error logging to file: {e}");
}
}
}
}};
}
#[macro_export]
macro_rules! debug {
($title:expr, $($arg:tt)*) => {{
let msg = make_log!(
$crate::get_colors().debug,
$crate::get_symbols().debug,
$title,
$crate::get_colors().debug_text,
$($arg)*
);
if *$crate::DEBUG.get().unwrap_or(&true) {
println!("{msg}");
}
unsafe {
if let Some(logger) = $crate::LOGGER.get() {
if let Err(e) = logger.log(&msg) {
eprintln!("Error logging to file: {e}");
}
}
}
}};
}