log4wasm 0.1.3

A stylish rust-generated WebAssembly logging utility.
Documentation
use crate::styled::{Color, Styled};
use std::fmt::{Display, Formatter};
pub(crate) use wasm_bindgen::prelude::wasm_bindgen;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    pub fn log(s: &str);
}

/// The supported log levels available for use by a logger.
#[derive(Debug, Default)]
pub enum Level {
    Trace,
    Debug,
    #[default]
    Info,
    Warn,
    Error,
    Fatal,
    Off,
}

impl Level {
    /// A flag denoting if the log level should be omitted from the written output. Currently, the only rule for exclusion
    /// is if the level is set to [`Level::Off`].
    pub(crate) fn ignore(&self) -> bool {
        matches!(self, Level::Off)
    }

    pub(crate) fn wrap(&self, content: impl AsRef<str>) -> String {
        let styled = self.to_string();
        if let Some(pos) = styled.rfind("]") {
            let (first_half, remaining) = styled.split_at(pos);
            format!("{}@{}{}", first_half, content.as_ref(), remaining)
        } else {
            styled
        }
    }
}

impl Display for Level {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        const EMPTY: &'static str = "";
        match self {
            Self::Trace => write!(
                f,
                "{}",
                Styled::with_content("[TRACE]")
                    .colored(Color::Green)
                    .into_log()
            ),
            Self::Debug => write!(
                f,
                "{}",
                Styled::with_content("[DEBUG]")
                    .colored(Color::Purple)
                    .into_log()
            ),
            Self::Info => write!(
                f,
                "{}",
                Styled::with_content("[INFO]")
                    .colored(Color::Cyan)
                    .into_log()
            ),
            Self::Warn => write!(
                f,
                "{}",
                Styled::with_content("[WARN]")
                    .colored(Color::Yellow)
                    .into_log()
            ),
            Self::Error => write!(
                f,
                "{}",
                Styled::with_content("[ERROR]")
                    .colored(Color::BrightRed)
                    .into_log()
            ),
            Self::Fatal => write!(
                f,
                "{}",
                Styled::with_content("[FATAL]")
                    .colored(Color::Red)
                    .into_log()
            ),
            _ => write!(f, "{}", EMPTY),
        }
    }
}

/// A reusable logger that may be 
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct Logger(String);

impl Logger {
    /// Returns a new [Builder] instance used to construct the logger.
    pub fn new_builder() -> Builder {
        Builder::new()
    }

    /// Writes an unformatted output of the log to the console.
    pub fn print(&self) {
        log(self.as_ref())
    }

    /// Writes an output of the log, appending the passed-in formatted string to the existing contents of the log.
    pub fn print_fmt(&self, fmt: impl Display) {
        let formatted = format!("{}\t{}", self.as_ref(), fmt.to_string());
        log(&formatted)
    }
}

impl AsRef<str> for Logger {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

/// A builder enabling the ability to configure and create a new logger object. A new builder instance may be obtained by calling the [`new_builder`] construct within [Logger].
pub struct Builder {
    level: Level,
    name: Option<String>,
    content: Option<String>,
}

impl Builder {
    pub(crate) fn new() -> Self {
        Self {
            name: None,
            level: Level::Off,
            content: None,
        }
    }

    /// Sets the [Level] type for the logger being built.
    pub fn with_level(mut self, level: Level) -> Self {
        self.level = level;
        self
    }

    /// Sets the name for the logger being built. Names are purely optional therefore not providing one does not influence the
    /// capabilities of the logger or how it outputs a log to the console. In some cases, providing a name for a logger may be useful 
    /// as it adds more context to the output thus making it inherently easier to pinpoint where a log came from in an environment where
    /// multiple loggers are active.
    pub fn named(mut self, name: impl AsRef<str>) -> Self {
        self.name = Some(name.as_ref().into());
        self
    }

    /// Finalizes the builder by consuming its reference and returning a new [Logger] from the values set during the build composition.
    pub fn build(self) -> Logger {
        let Self { name, level, content } = self;
        let mut fmt = String::new();
        if !level.ignore() {
            if let Some(name) = name {
                fmt.push_str(&level.wrap(name));
            } else {
                fmt.push_str(&format!("{}", level));
            }
        }
        if let Some(content) = content {
            fmt.push_str(&format!("{}", content));
        }

        Logger(fmt)
    }
}