fmi-rs 0.1.0

Functional Mock-up Interface (FMI) for Rust
use std::{
    cell::RefCell,
    io::{IsTerminal, Write},
    path::Path,
};

use colored::Colorize;

use crate::fmi3::types::fmi3Status;

pub trait Logger {
    fn log_call(&self, status: fmi3Status, message: &str);
    fn log_message(&self, status: fmi3Status, category: &str, message: &str);
}

pub struct DefaultLogger {
    pub stream: Box<RefCell<dyn Write>>,
    pub is_terminal: bool,
}

impl DefaultLogger {
    pub fn new<S>(stream: S) -> Self
    where
        S: Write + IsTerminal + 'static,
    {
        let is_terminal = stream.is_terminal();
        DefaultLogger {
            stream: Box::new(RefCell::new(stream)),
            is_terminal,
        }
    }

    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
        let file = std::fs::File::create(path)?;
        Ok(Self::new(file))
    }
}

impl Default for DefaultLogger {
    fn default() -> Self {
        DefaultLogger::new(std::io::stderr())
    }
}

impl Logger for DefaultLogger {
    fn log_call(&self, _status: fmi3Status, message: &str) {
        let prefix = if self.is_terminal {
            "[FMI]".bright_black()
        } else {
            "[FMI]".normal()
        };
        writeln!(self.stream.borrow_mut(), "{prefix} {message}")
            .unwrap_or_else(|e| eprintln!("Failed to write log message: {e}"));
    }

    fn log_message(&self, status: fmi3Status, category: &str, message: &str) {
        let message = message.trim_end();

        if self.is_terminal {
            let prefix = match status {
                fmi3Status::Ok => "[INFO]".bright_blue(),
                fmi3Status::Warning => "[WARNING]".yellow(),
                fmi3Status::Error => "[ERROR]".bright_red(),
                fmi3Status::Discard => "[DISCARD]".bright_red(),
                fmi3Status::Fatal => "[FATAL]".bright_red(),
            };
            writeln!(self.stream.borrow_mut(), "{prefix} [{category}] {message}")
                .unwrap_or_else(|e| eprintln!("Failed to write log message: {e}"));
        } else {
            let prefix = match status {
                fmi3Status::Ok => "[INFO]",
                fmi3Status::Warning => "[WARNING]",
                fmi3Status::Error => "[ERROR]",
                fmi3Status::Discard => "[DISCARD]",
                fmi3Status::Fatal => "[FATAL]",
            };
            writeln!(self.stream.borrow_mut(), "{prefix} [{category}] {message}")
                .unwrap_or_else(|e| eprintln!("Failed to write log message: {e}"));
        };
    }
}