use std::{path::PathBuf, sync::OnceLock};
use logger::Logger;
use styling::*;
pub mod logger;
pub mod prelude;
pub mod styling;
mod tests;
pub static LOGGER: OnceLock<Logger> = OnceLock::new();
pub static DEBUG: OnceLock<bool> = OnceLock::new();
#[inline]
pub fn init_logger<P: Into<PathBuf>>(path: P) -> std::io::Result<()> {
let logger = Logger::new(path)?;
LOGGER.set(logger).unwrap_or(());
Ok(())
}
#[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(
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",
color,
get_borders().top_left,
get_colors().bold,
symbol,
title,
color,
get_borders().horizontal.repeat(total_space),
get_colors().dim,
timestamp_display,
get_colors().reset,
color,
get_borders().top_right
));
for line in message_lines {
let clean_line = strip_ansi_codes(line);
let padding = width - clean_line.len() - 4; result.push_str(&format!(
"{}{} {} {}{}{}\n",
color,
get_borders().vertical,
line,
" ".repeat(padding),
get_borders().vertical,
get_colors().reset,
));
}
result.push_str(&format!(
"{}{}{}{}{}\n",
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_symbols().info,
$title,
&format!($($arg)*),
75
));
let log = make_log!(
$crate::get_colors().debug,
$crate::get_symbols().debug,
$title,
$($arg)*
);
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&log);
if let Err(e) = logger.log(&clean_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_symbols().warn,
$title,
&format!($($arg)*),
75
));
let log = make_log!(
$crate::get_colors().debug,
$crate::get_symbols().debug,
$title,
$($arg)*
);
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&log);
if let Err(e) = logger.log(&clean_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_symbols().error,
$title,
&format!($($arg)*),
75
));
let log = make_log!(
$crate::get_colors().debug,
$crate::get_symbols().debug,
$title,
$($arg)*
);
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&log);
if let Err(e) = logger.log(&clean_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_symbols().success,
$title,
&format!($($arg)*),
75
));
let log = make_log!(
$crate::get_colors().debug,
$crate::get_symbols().debug,
$title,
$($arg)*
);
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&log);
if let Err(e) = logger.log(&clean_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_symbols().debug,
$title,
&format!($($arg)*),
75
));
}
let log = make_log!(
$crate::get_colors().debug,
$crate::get_symbols().debug,
$title,
$($arg)*
);
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&log);
if let Err(e) = logger.log(&clean_log) {
eprintln!("Error logging to file: {e}");
}
}
};
}
#[macro_export]
macro_rules! make_log {
($color:expr, $symbol:expr, $title:expr, $($arg:tt)*) => {{
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,
$title,
$crate::get_colors().reset,
format!($($arg)*)
)
}};
}
#[macro_export]
macro_rules! info {
($title:expr, $($arg:tt)*) => {{
let msg = make_log!(
$crate::get_colors().info,
$crate::get_symbols().info,
$title,
$($arg)*
);
println!("{msg}");
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&msg);
if let Err(e) = logger.log(&clean_log) {
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,
$($arg)*
);
println!("{msg}");
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&msg);
if let Err(e) = logger.log(&clean_log) {
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,
$($arg)*
);
eprintln!("{msg}");
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&msg);
if let Err(e) = logger.log(&clean_log) {
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,
$($arg)*
);
println!("{msg}");
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&msg);
if let Err(e) = logger.log(&clean_log) {
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,
$($arg)*
);
if *$crate::DEBUG.get().unwrap_or(&true) {
println!("{msg}");
}
if let Some(logger) = $crate::LOGGER.get() {
let clean_log = $crate::strip_ansi_codes(&msg);
if let Err(e) = logger.log(&clean_log) {
eprintln!("Error logging to file: {e}");
}
}
}};
}