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_event_bindings, parse_node_style_binding,
    parse_ref_binding, parse_show_attr, parse_state_visual_styles, parse_text_content,
    parse_text_style, 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_link_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 href = attr(node, "href").unwrap_or_default().to_string();
    let href_binding = bound_attr(node, "href")
        .map(|expr| parse_binding_path_expr(node, ":href", expr))
        .transpose()?;
    if href_binding.is_none() && href.trim().is_empty() {
        return Err(attr_error(node, "href", "", "<a> requires href or :href"));
    }

    Ok(DeclarativeUiNode::Link {
        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)?,
        event_bindings: parse_event_bindings(node)?,
        href,
        href_binding,
        content: parse_text_content(node)?,
        text_style: parse_text_style(node, "a")?,
        visual_style: parse_visual_style(node)?,
        state_visual_styles: parse_state_visual_styles(node)?,
    })
}

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

    #[test]
    fn link_node_parses_href_and_text() {
        let asset = parse_declarative_ui_asset(
            r#"<template><a href="/docs/getting-started">Docs</a></template>"#,
        )
        .expect("link should parse");

        let DeclarativeUiNode::Link { href, content, .. } = asset.root else {
            panic!("expected link node");
        };

        assert_eq!(href, "/docs/getting-started");
        assert!(matches!(
            content,
            DeclarativeUiTextContent::Static { text } if text == "Docs"
        ));
    }
}