efmt 0.21.1

Erlang code formatter
Documentation
use colored::Colorize as _;
use log::{Level, LevelFilter, Log, Metadata, Record};
use std::io::{IsTerminal as _, Write as _};
use std::sync::{Once, OnceLock};

static LOGGER: OnceLock<SimpleLogger> = 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,
}

impl SimpleLogger {
    fn new(use_color: bool) -> Self {
        Self { use_color }
    }

    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 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 _ = 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");
    }
}