std-mel 0.10.3

Mélodium standard library
Documentation
use crate::engine::*;
use melodium_core::common::executive::Level as LogLevel;
use melodium_core::DataTrait;
use melodium_macro::{mel_data, mel_function, mel_treatment};

/// Formats a `Value` through its actual runtime `Display` implementation
/// (`DataTrait::display`), rather than `Value`'s own `core::fmt::Display`,
/// which renders `Data` values as a `/* TypeName */` placeholder meant for
/// regenerating Mélodium source, not for user-facing logging.
///
/// `DataTrait::display`'s own fallback for non-`Data` variants used to
/// silently resolve to the derived `Debug::fmt` instead of `Display::fmt`
/// (only `Debug` was imported where it is implemented), printing e.g.
/// `I64(42)` instead of `42` for every plain value, not just `Data` ones.
/// Fixed at the source in `melodium-common`'s `impl DataTrait for Value`.
struct RuntimeDisplay<'a>(&'a Value);

impl std::fmt::Display for RuntimeDisplay<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        DataTrait::display(self.0, f)
    }
}

/// Forward a stream of strings to the engine log at the given `level` under `label`.
///
/// Each received string is logged as a separate entry. The treatment continues until the stream closes.
#[mel_treatment(
    model engine Engine
    input messages Stream<string>
)]
pub async fn log_stream(level: Level, label: string) {
    let engine = EngineModel::into(engine);

    while let Ok(msgs) = messages.recv_many_as::<string>().await {
        for msg in msgs {
            engine
                .world()
                .log(level.level, label.clone(), msg, Some(track_id))
                .await;
        }
    }
}

/// Like `log_stream` but reads `label` as a block input rather than a constant parameter.
///
/// Waits for `label` to arrive first, then logs each string in `messages` at `level` under that label.
#[mel_treatment(
    model engine Engine
    input label Block<string>
    input messages Stream<string>
)]
pub async fn log_stream_label(level: Level) {
    let engine = EngineModel::into(engine);

    if let Ok(label) = label.recv_one_as::<String>().await {
        while let Ok(msgs) = messages.recv_many_as::<string>().await {
            for msg in msgs {
                engine
                    .world()
                    .log(level.level, label.clone(), msg, Some(track_id))
                    .await;
            }
        }
    }
}

/// Forward a single string block to the engine log at the given `level` under `label`.
#[mel_treatment(
    model engine Engine
    input message Block<string>
)]
pub async fn log_block(level: Level, label: string) {
    let engine = EngineModel::into(engine);

    if let Ok(msg) = message.recv_one_as::<string>().await {
        engine
            .world()
            .log(level.level, label, msg, Some(track_id))
            .await;
    }
}

/// Like `log_block` but reads both `label` and `message` as block inputs.
///
/// Waits for `label` first, then logs `message` at `level` under that label.
#[mel_treatment(
    model engine Engine
    input label Block<string>
    input message Block<string>
)]
pub async fn log_block_label(level: Level) {
    let engine = EngineModel::into(engine);

    if let Ok(label) = label.recv_one_as::<String>().await {
        if let Ok(msg) = message.recv_one_as::<string>().await {
            engine
                .world()
                .log(level.level, label, msg, Some(track_id))
                .await;
        }
    }
}

/// Convert each item in a `Display` stream to its string representation and log it at `level` under `label`.
#[mel_treatment(
    model engine Engine
    input display Stream<D>
    generic D (Display)
)]
pub async fn log_data_stream(level: Level, label: string) {
    let engine = EngineModel::into(engine);

    while let Ok(values) = display
        .recv_many()
        .await
        .map(|values| Into::<VecDeque<Value>>::into(values))
    {
        for val in values {
            engine
                .world()
                .log(
                    level.level,
                    label.clone(),
                    format!("{}", RuntimeDisplay(&val)),
                    Some(track_id),
                )
                .await;
        }
    }
}

/// Like `log_data_stream` but reads `label` as a block input.
///
/// Waits for `label` first, then converts and logs each item in `display` at `level`.
#[mel_treatment(
    model engine Engine
    input label Block<string>
    input display Stream<D>
    generic D (Display)
)]
pub async fn log_data_stream_label(level: Level) {
    let engine = EngineModel::into(engine);

    if let Ok(label) = label.recv_one_as::<String>().await {
        while let Ok(values) = display
            .recv_many()
            .await
            .map(|values| Into::<VecDeque<Value>>::into(values))
        {
            for val in values {
                engine
                    .world()
                    .log(
                        level.level,
                        label.clone(),
                        format!("{}", RuntimeDisplay(&val)),
                        Some(track_id),
                    )
                    .await;
            }
        }
    }
}

/// Convert a single `Display` block to its string representation and log it at `level` under `label`.
#[mel_treatment(
    model engine Engine
    input display Block<D>
    generic D (Display)
)]
pub async fn log_data_block(level: Level, label: string) {
    let engine = EngineModel::into(engine);

    if let Ok(val) = display.recv_one().await {
        engine
            .world()
            .log(
                level.level,
                label,
                format!("{}", RuntimeDisplay(&val)),
                Some(track_id),
            )
            .await;
    }
}

/// Like `log_data_block` but reads both `label` and `display` as block inputs.
///
/// Waits for `label` first, then converts and logs `display` at `level`.
#[mel_treatment(
    model engine Engine
    input label Block<string>
    input display Block<D>
    generic D (Display)
)]
pub async fn log_data_block_label(level: Level) {
    let engine = EngineModel::into(engine);

    if let Ok(label) = label.recv_one_as::<String>().await {
        if let Ok(val) = display.recv_one().await {
            engine
                .world()
                .log(
                    level.level,
                    label,
                    format!("{}", RuntimeDisplay(&val)),
                    Some(track_id),
                )
                .await;
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// Log severity level.
///
/// Ordered from lowest to highest verbosity: `trace` < `debug` < `info` < `warning` < `error`.
/// Use the constructor functions `|trace()`, `|debug()`, `|info()`, `|warning()`, `|error()` to obtain a value.
#[mel_data(traits(Serialize Deserialize Bounded PartialEquality Equality PartialOrder Order))]
pub struct Level {
    pub level: LogLevel,
}

fn level_bounded_min() -> Level {
    Level {
        level: LogLevel::Trace,
    }
}

fn level_bounded_max() -> Level {
    Level {
        level: LogLevel::Error,
    }
}

/// Return the error log level.
#[mel_function]
pub fn error() -> Level {
    Level {
        level: LogLevel::Error,
    }
}

/// Return the warning log level.
#[mel_function]
pub fn warning() -> Level {
    Level {
        level: LogLevel::Warning,
    }
}

/// Return the info log level.
#[mel_function]
pub fn info() -> Level {
    Level {
        level: LogLevel::Info,
    }
}

/// Return the debug log level.
#[mel_function]
pub fn debug() -> Level {
    Level {
        level: LogLevel::Debug,
    }
}

/// Return the trace log level (most verbose).
#[mel_function]
pub fn trace() -> Level {
    Level {
        level: LogLevel::Trace,
    }
}