diff --git a/.cocorc b/.cocorc index 64a35cc..c6e2d9b 100644 --- a/.cocorc +++ b/.cocorc @@ -5,3 +5,4 @@ useEmoji: true scopes: - "" - "cli" + - "logger" diff --git a/Cargo.lock b/Cargo.lock index 3419622..18bcd03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,41 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dtt" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b2dd9ee2d76888dc4c17d6da74629fa11b3cb1e8094fdc159b7f8ff259fc88" +dependencies = [ + "regex", + "serde", + "time", +] + [[package]] name = "eyre" version = "0.6.12" @@ -24,16 +53,145 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + [[package]] name = "lool" version = "0.0.1" dependencies = [ "bitflags", + "dtt", "eyre", + "log", ] +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml index cc8173f..7c5a3be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,6 @@ cli-stylize = ["dep:bitflags"] [dependencies] bitflags = { version = "2.5.0", optional = true} +dtt = "0.0.5" eyre = { version = "0.6.12", default-features = false } +log = "0.4.21" diff --git a/examples/logger.rs b/examples/logger.rs new file mode 100644 index 0000000..b4bf79d --- /dev/null +++ b/examples/logger.rs @@ -0,0 +1,12 @@ +use log::Level; +use lool::logger::ConsoleLogger; + + +fn main() { + ConsoleLogger::default_setup(Level::Trace, "test").unwrap(); + log::info!("log line"); + log::warn!("log line"); + log::error!("log line"); + log::debug!("log line"); + log::trace!("log line"); +} \ No newline at end of file diff --git a/examples/stylizer.rs b/examples/stylizer.rs index ca068ec..0339c2f 100644 --- a/examples/stylizer.rs +++ b/examples/stylizer.rs @@ -17,6 +17,7 @@ fn main() -> Result<()> { let red_on_blue = stylize("[white on blue]", "white on blue"); let rgb = stylize("[#3a95ef]", "#3a95ef"); let rgb_on_rgb = stylize("[#3a95ef on #c174dd]", "#3a95ef on #c174dd"); + let rgb_dim = stylize("[#3a95ef+dimmed]", "#3a95ef+dimmed"); println!("pre {} post", red_bold); println!("pre {} post", alt_red_bold); @@ -26,6 +27,7 @@ fn main() -> Result<()> { println!("pre {} post", red_on_blue); println!("pre {} post", rgb); println!("pre {} post", rgb_on_rgb); + println!("pre {} post", rgb_dim); println!("pre {} post", "[green]".stl("green").stl("+bold")); println!("pre {} post", "[green+bold]".stl("green+bold")); @@ -34,5 +36,8 @@ fn main() -> Result<()> { println!("pre {} post", "[.blue().bold()]".blue().bold()); println!("pre {} post", "[.blue().on_red().bold()]".blue().on_red().bold()); + println!("pre {} post", "[.dim()]".dim()); + println!("pre {} post", "[.blue().dim()]".blue().dim()); + Ok(()) } \ No newline at end of file diff --git a/lib/cli/stylize/stylizer.rs b/lib/cli/stylize/stylizer.rs index 1652aff..0747fb3 100644 --- a/lib/cli/stylize/stylizer.rs +++ b/lib/cli/stylize/stylizer.rs @@ -126,7 +126,7 @@ pub trait Stylize { fn on_bright_white(&self) -> String { self.stl("on bright-white") } fn on_rgb(&self, r: u8, g: u8, b: u8) -> String { self.stl(&format!("on #{:02X}{:02X}{:02X}", r, g, b)) } fn bold(&self) -> String { self.stl("+bold") } - fn dim(&self) -> String { self.stl("+dim") } + fn dim(&self) -> String { self.stl("+dimmed") } fn italic(&self) -> String { self.stl("+italic") } fn underline(&self) -> String { self.stl("+underline") } fn blink(&self) -> String { self.stl("+blink") } diff --git a/lib/lib.rs b/lib/lib.rs index 5d863fb..5c673f6 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -1 +1,2 @@ -pub mod cli; \ No newline at end of file +pub mod cli; +pub mod logger; \ No newline at end of file diff --git a/lib/logger/datetime.rs b/lib/logger/datetime.rs new file mode 100644 index 0000000..b0d2346 --- /dev/null +++ b/lib/logger/datetime.rs @@ -0,0 +1,60 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +pub fn noop_datetime() -> String { + "".to_string() +} + +/// Get the current time in the format yyyy-mm-dd hh:mm:ss +pub fn utc_current_time() -> String { + // Get the current system time + let now = SystemTime::now(); + + // Convert the adjusted time to a formatted string + match now.duration_since(UNIX_EPOCH) { + Ok(duration) => { + let secs = duration.as_secs(); + let days = secs / (24 * 3600); + let mut years = 1970; + let mut days_left = days; + + // Adjusting for leap years + loop { + let days_in_year = if is_leap_year(years) { 366 } else { 365 }; + if days_left < days_in_year { + break; + } + days_left -= days_in_year; + years += 1; + } + + let (month, day) = days_to_date(days_left, is_leap_year(years)); + + let hours = (secs / 3600) % 24; + let minutes = (secs / 60) % 60; + let seconds = secs % 60; + + format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}", years, month + 1, day, hours, minutes, seconds) + } + Err(_) => "0".to_string(), + } +} + +// Check if a year is a leap year +fn is_leap_year(year: u64) -> bool { + (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) +} + +// Calculate the month and day from the number of days since UNIX epoch +fn days_to_date(days: u64, leap: bool) -> (u64, u64) { + let days_in_month = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let mut days_left = days; + let mut month = 0; + + while days_left >= days_in_month[month as usize] { + days_left -= days_in_month[month as usize]; + month += 1; + } + + (month, days_left + 1) // Adding 1 to day to make it 1-based +} + diff --git a/lib/logger/mod.rs b/lib/logger/mod.rs new file mode 100644 index 0000000..6d470dd --- /dev/null +++ b/lib/logger/mod.rs @@ -0,0 +1,97 @@ +pub mod datetime; + +use eyre::{eyre, Result}; +use log::{Level, Metadata, Record}; + +const RESET: &str = "\x1b[0m"; + +struct StyledRecord { + time: String, + level: String, + message: String, + file: String, + line: String, +} + +impl StyledRecord { + fn from(record: &Record, get_time: fn() -> String) -> Self { + let ansi_style_level = match record.level() { + Level::Error => "\x1b[31m", // red + Level::Warn => "\x1b[33m", // yellow + Level::Info => "\x1b[32m", // green + Level::Debug => "\x1b[95m", // magenta + Level::Trace => "\x1b[34m", // blue + }; + + let file_ansi_color = "\x1b[34m"; // blue + let line_ansi_color = "\x1b[34;1m"; // bold blue + let mut time = get_time(); + + if !time.is_empty() { + time = format!("\x1b[2m{}\x1b[0m", time); // dim + } + + Self { + level: format!("{}{:<5}{}", ansi_style_level, record.level(), RESET), + message: format!("{}", record.args()), + file: format!("{}{}{}", file_ansi_color, record.file().unwrap_or("unknown"), RESET), + line: format!("{}{}{}", line_ansi_color, record.line().unwrap_or(0), RESET), + time, + } + } +} + +pub struct ConsoleLogger<'a>{ + context: &'a str, + time_fn: fn() -> String, +} + +impl<'a> ConsoleLogger<'a> { + pub fn default_setup(max_level: Level, context: &'static str) -> Result<()> { + let logger = Box::new(ConsoleLogger { context, time_fn: datetime::utc_current_time}); + log::set_logger(Box::leak(logger) as &'static ConsoleLogger) + .map(|()| log::set_max_level(max_level.to_level_filter())) + .map_err(|err| eyre!("failed to set logger: {}", err)) + } + + pub fn setup(max_level: Level, context: &'static str, time_fn: fn() -> String) -> Result<()> { + let logger = Box::new(ConsoleLogger { context, time_fn}); + log::set_logger(Box::leak(logger) as &'static ConsoleLogger) + .map(|()| log::set_max_level(max_level.to_level_filter())) + .map_err(|err| eyre!("failed to set logger: {}", err)) + } + + pub fn set_context(&mut self, context: &'a str) { + self.context = context; + } +} + +/// simple implementation of a themed logger +impl<'a> log::Log for ConsoleLogger<'a> { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Info + } + + fn log(&self, record: &Record) { + let styled_record = StyledRecord::from(record, self.time_fn); + + let time = if styled_record.time.is_empty() { + " ".to_string() + } else { + format!(" {} ", styled_record.time) + }; + + // print to stdout + println!( + "[{}]{}| {} | {}:{} - {}", + self.context, + time, + styled_record.level, + styled_record.file, + styled_record.line, + styled_record.message + ); + } + + fn flush(&self) {} +}