beuvy 0.1.0

Facade crate for beuvy-runtime plus optional declarative UI authoring.
Documentation
use crate::ast::DeclarativeUiNode;
use crate::error::DeclarativeUiAssetLoadError;
use crate::parser::{
    DeclarativeStateSpec, attr, attr_error, bound_attr, parse_binding_path_expr,
    parse_class_bindings, parse_conditional, parse_node_style, parse_node_style_binding,
    parse_ref_binding, parse_show_attr, parse_state_visual_styles, parse_visual_style,
    reject_legacy_attrs, reject_legacy_bind_attrs, reject_style_attrs_except,
};
use roxmltree::Node as XmlNode;
use std::collections::BTreeMap;

pub(crate) fn parse_declarative_image_node(
    node: XmlNode<'_, '_>,
    state_specs: &BTreeMap<String, DeclarativeStateSpec>,
) -> Result<DeclarativeUiNode, DeclarativeUiAssetLoadError> {
    reject_legacy_attrs(node, &["visible"])?;
    reject_legacy_bind_attrs(node)?;
    reject_style_attrs_except(node, &["style"])?;

    let src = attr(node, "src").unwrap_or_default().to_string();
    let src_binding = bound_attr(node, "src")
        .map(|expr| parse_binding_path_expr(node, ":src", expr))
        .transpose()?;
    if src_binding.is_none() && src.trim().is_empty() {
        return Err(attr_error(node, "src", "", "<img> requires src or :src"));
    }
    let alt = attr(node, "alt").unwrap_or_default().to_string();
    let alt_binding = bound_attr(node, "alt")
        .map(|expr| parse_binding_path_expr(node, ":alt", expr))
        .transpose()?;

    Ok(DeclarativeUiNode::Image {
        node_id: String::new(),
        class: attr(node, "class").unwrap_or_default().to_string(),
        class_bindings: parse_class_bindings(node, state_specs)?,
        conditional: parse_conditional(node, state_specs)?,
        show_expr: parse_show_attr(node, state_specs)?,
        ref_binding: parse_ref_binding(node)?,
        style_binding: parse_node_style_binding(node)?,
        src,
        src_binding,
        alt,
        alt_binding,
        node_override: Some(parse_node_style(node)?),
        visual_style: parse_visual_style(node)?,
        state_visual_styles: parse_state_visual_styles(node)?,
    })
}

#[cfg(test)]
mod tests {
    use crate::{DeclarativeUiNode, parse_declarative_ui_asset};

    #[test]
    fn image_node_parses_static_and_bound_attrs() {
        let asset = parse_declarative_ui_asset(
            r#"<template><img src="icons/help.png" :alt="hero.name" /></template>"#,
        )
        .expect("image should parse");

        let DeclarativeUiNode::Image {
            src,
            src_binding,
            alt,
            alt_binding,
            ..
        } = asset.root
        else {
            panic!("expected image node");
        };

        assert_eq!(src, "icons/help.png");
        assert_eq!(src_binding, None);
        assert_eq!(alt, "");
        assert_eq!(alt_binding.as_deref(), Some("hero.name"));
    }
}