globetrotter 0.0.10

Polyglot, type-safe internationalization
Documentation
use crate::{
    config::v1::{ConfigFile, Configs},
    model::Language,
    target::Target,
};
use colored::Colorize;
use std::path::{Path, PathBuf};

fn strip_color(value: &str) -> String {
    strip_ansi_escapes::strip_str(value)
}

fn safe_length(value: &str) -> usize {
    strip_color(value).len()
}

fn pad_left(value: &str, width: usize, fill: char) -> String {
    let len = safe_length(value);
    let pad_len = width.saturating_sub(len);
    format!(
        "{}{value}",
        std::iter::repeat_n(fill, pad_len).collect::<String>()
    )
}

fn pad_right(value: &str, width: usize, fill: char) -> String {
    let len = safe_length(value);
    let pad_len = width.saturating_sub(len);
    format!(
        "{value}{}",
        std::iter::repeat_n(fill, pad_len).collect::<String>()
    )
}

#[must_use]
pub fn relative_to(base_dir: Option<&Path>, path: &Path) -> PathBuf {
    base_dir
        .as_ref()
        .and_then(|base_dir| pathdiff::diff_paths(path, base_dir))
        .unwrap_or(path.to_path_buf())
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Logger {
    pub use_absolute_paths: bool,
    pub longest_config_name: usize,
    pub longest_target_name: usize,
}

impl Logger {
    #[must_use]
    pub fn new<F>(configs: &Configs<F>) -> Self {
        let longest_config_name = configs
            .iter()
            .filter(|ConfigFile { config, .. }| !config.is_empty())
            .map(|ConfigFile { config, .. }| config.name.len())
            .max()
            .unwrap_or(0)
            + 3;

        let target_names = Target::iter().map(|target| target.to_string());
        let language_names = Language::iter().map(|language| language.to_string());
        let longest_target_name = target_names
            .chain(language_names)
            .map(|name| name.len())
            .max()
            .unwrap_or(0);

        Self {
            use_absolute_paths: false,
            longest_target_name,
            longest_config_name,
        }
    }

    #[must_use]
    pub fn target_log_prefix(&self, name: &str, target: Target) -> String {
        format!(
            "{}{}",
            pad_left(&name.green().to_string(), self.longest_config_name, ' '),
            pad_right(
                &format!("[{}]", target.to_string().to_lowercase().blue()),
                self.longest_target_name + 2,
                ' ',
            ),
        )
    }

    #[must_use]
    pub fn language_log_prefix(&self, name: &str, language: Language) -> String {
        format!(
            "{}{}",
            pad_left(&name.green().to_string(), self.longest_config_name, ' '),
            pad_right(
                &format!("[{}]", language.to_string().to_lowercase().bright_blue()),
                self.longest_target_name + 2,
                ' '
            )
        )
    }

    #[must_use]
    pub fn completed(&self, duration: &std::time::Duration) -> String {
        format!(
            "{} {}",
            std::iter::repeat_n(' ', self.longest_config_name + self.longest_target_name + 2)
                .collect::<String>(),
            format!("completed in {duration:?}").bright_black(),
        )
    }

    #[must_use]
    pub fn dry_run_would_write(&self, path: &Path) -> colored::ColoredString {
        format!("{} would write {}", "DRY RUN:".yellow(), path.display()).bright_black()
    }
}

#[cfg(test)]
mod test {
    use colored::Colorize;
    use similar_asserts::assert_eq as sim_assert_eq;

    #[test]
    fn test_pad_right() {
        sim_assert_eq!(have: super::pad_right("test", 7, ' '), want: "test   ");
        sim_assert_eq!(have: super::pad_right("test", 2, ' '), want: "test");

        let width = 20;
        let colored = format!("-> {} <-", "color".green().bold());
        let want = "-> color <-         ";
        sim_assert_eq!(have: want.len(), want: width);
        sim_assert_eq!(
            have: super::strip_color(&super::pad_right(&colored, width, ' ')),
            want: want
        );
    }

    #[test]
    fn test_pad_left() {
        sim_assert_eq!(have: super::pad_left("test", 7, ' '), want: "   test");
        sim_assert_eq!(have: super::pad_left("test", 2, ' '), want: "test");

        let width = 20;
        let colored = format!("-> {} <-", "color".green().bold());
        let want = "         -> color <-";
        sim_assert_eq!(have: want.len(), want: width);
        sim_assert_eq!(
            have: super::strip_color(&super::pad_left(&colored, width, ' ')),
            want: want
        );
    }
}