oxiforge 0.4.0

YAML-to-Rust code generator for oxivgl LVGL UIs
Documentation
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
    error::OxiforgeError,
    model::{LvglConfig, ToastDef, ToastPreset, UiDoc},
};

/// Extract SPDX-License-Identifier from leading YAML comment, if present.
fn extract_spdx(yaml: &str) -> Option<String> {
    for line in yaml.lines() {
        let trimmed = line.trim();
        if trimmed.is_empty() {
            continue;
        }
        if !trimmed.starts_with('#') {
            break;
        }
        if let Some(rest) = trimmed.strip_prefix("# SPDX-License-Identifier:") {
            return Some(rest.trim().to_string());
        }
    }
    None
}

/// Parse YAML string into UiDoc.
pub fn parse_str(yaml: &str) -> Result<UiDoc, OxiforgeError> {
    let spdx = extract_spdx(yaml);
    let mut doc: UiDoc = serde_yaml::from_str(yaml)?;
    doc.lvgl.spdx_license = spdx;
    expand_toast_presets(&mut doc.lvgl);
    Ok(doc)
}

/// Expand `toast_presets:` into concrete parameterized toasts appended to
/// `toasts:`, so validation and codegen treat them like any other toast.
fn expand_toast_presets(lvgl: &mut LvglConfig) {
    let Some(presets) = lvgl.toast_presets.take() else { return };
    let toasts = lvgl.toasts.get_or_insert_with(Vec::new);
    for preset in presets {
        toasts.push(preset_toast_def(preset));
    }
}

/// Build the [`ToastDef`] for a preset: a colored bar with a single `msg`
/// param bound to a centered white label. Authored as a YAML template so it
/// goes through exactly the same deserialization as a hand-written toast.
fn preset_toast_def(preset: ToastPreset) -> ToastDef {
    let yaml = format!(
        "id: {id}\n\
         duration_ms: 3000\n\
         params: [msg]\n\
         bg_color: 0x{bg:06x}\n\
         bg_opa: 255\n\
         radius: 6\n\
         pad_all: 10\n\
         widgets:\n\
        \x20 - label:\n\
        \x20     bind_text: msg\n\
        \x20     text_color: 0xffffff\n\
        \x20     align: center\n",
        id = preset.id(),
        bg = preset.bg(),
    );
    serde_yaml::from_str(&yaml).expect("preset toast template must be valid")
}

/// Parse YAML file into UiDoc.
pub fn parse_file(path: &std::path::Path) -> Result<UiDoc, OxiforgeError> {
    let content = std::fs::read_to_string(path)?;
    parse_str(&content)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn extract_spdx_found() {
        let yaml = "# SPDX-License-Identifier: GPL-3.0-only\nlvgl:\n  pages: []\n";
        assert_eq!(extract_spdx(yaml), Some("GPL-3.0-only".into()));
    }

    #[test]
    fn extract_spdx_none() {
        let yaml = "lvgl:\n  pages: []\n";
        assert_eq!(extract_spdx(yaml), None);
    }

    #[test]
    fn extract_spdx_after_blank_comment() {
        let yaml = "# some comment\n# SPDX-License-Identifier: MIT\nlvgl:\n  pages: []\n";
        assert_eq!(extract_spdx(yaml), Some("MIT".into()));
    }
}