diff --git a/Cargo.lock b/Cargo.lock index 08a3bdf..531b88c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,56 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - [[package]] name = "bstr" version = "1.12.1" @@ -72,12 +22,6 @@ dependencies = [ "serde", ] -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - [[package]] name = "colored" version = "3.1.1" @@ -130,7 +74,6 @@ version = "0.21.0" dependencies = [ "colored", "efmt_core", - "env_logger", "erl_tokenize", "ignore", "log", @@ -181,29 +124,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "env_filter" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "erl_tokenize" version = "0.10.0" @@ -248,36 +168,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "jiff" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-static" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "libc" version = "0.2.181" @@ -308,27 +198,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "portable-atomic-util" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" -dependencies = [ - "portable-atomic", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -489,12 +358,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index d41714d..a2ee3d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ exclude = ["/rebar3_efmt/", "efmt_wasm.wasm", "/vscode/"] [dependencies] erl_tokenize = "0.10.0" efmt_core = { path = "efmt_core", version = "0.7.0" } -env_logger = "0.11" log = "0.4" rayon = "1" similar = { version= "2", features = ["inline"] } diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..f86ef65 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,145 @@ +use colored::Colorize as _; +use log::{Level, LevelFilter, Log, Metadata, Record}; +use std::io::{IsTerminal as _, Write as _}; +use std::sync::{Mutex, Once, OnceLock}; + +static LOGGER: OnceLock = OnceLock::new(); +static INSTALL: Once = Once::new(); + +pub(crate) fn init(verbose: bool) { + let max_level = if verbose { + LevelFilter::Debug + } else { + LevelFilter::Info + }; + + INSTALL.call_once(|| { + let use_color = std::io::stderr().is_terminal(); + let logger = LOGGER.get_or_init(|| SimpleLogger::new(use_color)); + let _ = log::set_logger(logger); + }); + log::set_max_level(max_level); +} + +struct SimpleLogger { + use_color: bool, + write_lock: Mutex<()>, +} + +impl SimpleLogger { + fn new(use_color: bool) -> Self { + Self { + use_color, + write_lock: Mutex::new(()), + } + } + + fn level_field(&self, level: Level) -> String { + let label = level.as_str(); + let padding = " ".repeat(5usize.saturating_sub(label.len())); + if !self.use_color { + return format!("{label}{padding}"); + } + + let colored = match level { + Level::Error => label.red().bold(), + Level::Warn => label.yellow().bold(), + Level::Info => label.green(), + Level::Debug => label.blue(), + Level::Trace => label.magenta(), + }; + format!("{colored}{padding}") + } +} + +impl Log for SimpleLogger { + fn enabled(&self, metadata: &Metadata<'_>) -> bool { + metadata.level() <= log::max_level() + } + + fn log(&self, record: &Record<'_>) { + if !self.enabled(record.metadata()) { + return; + } + + let _guard = self.write_lock.lock().unwrap_or_else(|e| e.into_inner()); + let mut stderr = std::io::stderr().lock(); + let _ = writeln!( + stderr, + "[{} {} {}] {}", + format_utc_timestamp_now(), + self.level_field(record.level()), + record.target(), + record.args(), + ); + } + + fn flush(&self) { + let _guard = self.write_lock.lock().unwrap_or_else(|e| e.into_inner()); + let _ = std::io::stderr().lock().flush(); + } +} + +fn format_utc_timestamp_now() -> String { + let unix_seconds = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { + Ok(duration) => duration.as_secs().min(i64::MAX as u64) as i64, + Err(error) => { + let duration = error.duration(); + let seconds = duration.as_secs().min(i64::MAX as u64) as i64; + let seconds = if duration.subsec_nanos() == 0 { + seconds + } else { + seconds.saturating_add(1) + }; + -seconds + } + }; + format_utc_timestamp(unix_seconds) +} + +fn format_utc_timestamp(unix_seconds: i64) -> String { + let days = unix_seconds.div_euclid(86_400); + let seconds_of_day = unix_seconds.rem_euclid(86_400); + + let (year, month, day) = civil_from_days(days); + let hour = seconds_of_day / 3_600; + let minute = (seconds_of_day % 3_600) / 60; + let second = seconds_of_day % 60; + + format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z") +} + +fn civil_from_days(days_since_unix_epoch: i64) -> (i64, u32, u32) { + let z = days_since_unix_epoch + 719_468; + let era = if z >= 0 { z } else { z - 146_096 } / 146_097; + let day_of_era = z - era * 146_097; + let year_of_era = + (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365; + let year = year_of_era + era * 400; + let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100); + let month_prime = (5 * day_of_year + 2) / 153; + let day = day_of_year - (153 * month_prime + 2) / 5 + 1; + let month = month_prime + if month_prime < 10 { 3 } else { -9 }; + let year = year + if month <= 2 { 1 } else { 0 }; + (year, month as u32, day as u32) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn timestamp_format_works() { + assert_eq!(format_utc_timestamp(0), "1970-01-01T00:00:00Z"); + assert_eq!(format_utc_timestamp(-1), "1969-12-31T23:59:59Z"); + assert_eq!(format_utc_timestamp(1_704_067_200), "2024-01-01T00:00:00Z"); + } + + #[test] + fn level_field_alignment_works() { + let logger = SimpleLogger::new(false); + assert_eq!(logger.level_field(Level::Info), "INFO "); + assert_eq!(logger.level_field(Level::Warn), "WARN "); + assert_eq!(logger.level_field(Level::Debug), "DEBUG"); + } +} diff --git a/src/main.rs b/src/main.rs index 176da12..143c601 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use efmt::files::RebarConfigValue; use efmt_core::items::ModuleOrConfig; -use env_logger::Env; use rayon::iter::{IntoParallelIterator as _, ParallelIterator}; use regex::Regex; use std::io::Read as _; @@ -9,6 +8,8 @@ use unicode_width::UnicodeWidthStr; type Result = efmt::Result; +mod logger; + struct Opt { check: bool, write: bool, @@ -42,8 +43,7 @@ impl Opt { .take(&mut args) .is_present(); if !help_mode { - let loglevel = if verbose { "debug" } else { "info" }; - env_logger::Builder::from_env(Env::default().default_filter_or(loglevel)).init(); + logger::init(verbose); } let check = noargs::flag("check")