rivet-logger 0.1.0

Rivet framework crates and adapters.
Documentation
use std::collections::BTreeMap;
use std::process::Command;
use std::sync::OnceLock;

use crate::logger::{BoxError, Context, Level, LogRecord, LogValue, Processor};

static HG_INFO_CACHE: OnceLock<Context> = OnceLock::new();

pub struct Mercurial {
    min_level: Level,
}

impl Mercurial {
    pub fn new(min_level: Level) -> Self {
        Self { min_level }
    }
}

impl Default for Mercurial {
    fn default() -> Self {
        Self::new(Level::Debug)
    }
}

impl Processor for Mercurial {
    fn process(&self, mut record: LogRecord) -> Result<LogRecord, BoxError> {
        if record.level < self.min_level {
            return Ok(record);
        }

        let info = HG_INFO_CACHE.get_or_init(mercurial_info).clone();
        record.extra.insert("hg".to_string(), LogValue::Map(info));

        Ok(record)
    }
}

fn mercurial_info() -> Context {
    let output = Command::new("hg").args(["id", "-nb"]).output();
    let Ok(output) = output else {
        return BTreeMap::new();
    };
    if !output.status.success() {
        return BTreeMap::new();
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    parse_hg_output(stdout.trim()).unwrap_or_default()
}

fn parse_hg_output(output: &str) -> Option<Context> {
    let parts: Vec<&str> = output.split_whitespace().collect();
    match parts.as_slice() {
        [revision, branch] => {
            let mut info = BTreeMap::new();
            info.insert(
                "branch".to_string(),
                LogValue::String((*branch).to_string()),
            );
            info.insert(
                "revision".to_string(),
                LogValue::String((*revision).to_string()),
            );
            Some(info)
        }
        [_, branch, revision, ..] => {
            let mut info = BTreeMap::new();
            info.insert(
                "branch".to_string(),
                LogValue::String((*branch).to_string()),
            );
            info.insert(
                "revision".to_string(),
                LogValue::String((*revision).to_string()),
            );
            Some(info)
        }
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use crate::logger::LogValue;

    use super::*;

    #[test]
    fn parses_two_token_hg_output() {
        let parsed = parse_hg_output("abc123 default").expect("output should parse");
        assert!(matches!(
            parsed.get("branch"),
            Some(LogValue::String(value)) if value == "default"
        ));
        assert!(matches!(
            parsed.get("revision"),
            Some(LogValue::String(value)) if value == "abc123"
        ));
    }

    #[test]
    fn parses_three_token_hg_output() {
        let parsed = parse_hg_output("123 default 456").expect("output should parse");
        assert!(matches!(
            parsed.get("revision"),
            Some(LogValue::String(value)) if value == "456"
        ));
    }
}