beuvy 0.1.0

Facade crate for beuvy-runtime plus optional declarative UI authoring.
Documentation
use super::*;
use crate::parser::action::parse_literal_for_type;

pub(crate) fn parse_conditional(
    node: XmlNode<'_, '_>,
    state_specs: &BTreeMap<String, DeclarativeStateSpec>,
) -> Result<DeclarativeConditional, DeclarativeUiAssetLoadError> {
    if let Some(value) = attr(node, "v-if") {
        return Ok(DeclarativeConditional::If(parse_condition_expr(
            node,
            "v-if",
            value,
            true,
            state_specs,
        )?));
    }
    if let Some(value) = attr(node, "v-else-if") {
        return Ok(DeclarativeConditional::ElseIf(parse_condition_expr(
            node,
            "v-else-if",
            value,
            false,
            state_specs,
        )?));
    }
    if let Some(value) = attr(node, "v-else") {
        if !value.trim().is_empty() {
            return Err(attr_error(
                node,
                "v-else",
                value,
                "v-else does not accept any value",
            ));
        }
        return Ok(DeclarativeConditional::Else);
    }
    Ok(DeclarativeConditional::Always)
}

pub(crate) fn parse_condition_expr(
    node: XmlNode<'_, '_>,
    name: &str,
    raw: &str,
    allow_binding_fallback: bool,
    state_specs: &BTreeMap<String, DeclarativeStateSpec>,
) -> Result<DeclarativeConditionExpr, DeclarativeUiAssetLoadError> {
    if let Some((left, right)) = raw.split_once("===").or_else(|| raw.split_once("==")) {
        let state_name = parse_condition_path(node, name, left.trim())?;
        let value = if let Some(state_spec) = state_specs.get(&state_name).copied() {
            parse_literal_for_type(node, name, right.trim(), state_spec.type_hint)?
        } else {
            parse_literal(node, name, right.trim())?
        };
        return Ok(DeclarativeConditionExpr::Equals {
            name: state_name,
            value,
        });
    }
    if allow_binding_fallback && is_identifier_path(raw.trim()) {
        return Ok(DeclarativeConditionExpr::Binding(raw.trim().to_string()));
    }
    Err(attr_error(node, name, raw, "expected `name === literal`"))
}

fn parse_condition_path(
    node: XmlNode<'_, '_>,
    attr_name: &str,
    raw: &str,
) -> Result<String, DeclarativeUiAssetLoadError> {
    if is_identifier_path(raw) {
        Ok(raw.to_string())
    } else {
        Err(attr_error(node, attr_name, raw, "expected binding path"))
    }
}

pub(super) fn validate_conditional_chain(
    parent: XmlNode<'_, '_>,
    children: &[DeclarativeUiNode],
) -> Result<(), DeclarativeUiAssetLoadError> {
    for (index, child) in children.iter().enumerate() {
        let Some(conditional) = node_conditional(child) else {
            continue;
        };
        if !matches!(
            conditional,
            DeclarativeConditional::Else | DeclarativeConditional::ElseIf(_)
        ) {
            continue;
        }
        if index == 0 {
            return Err(dsl_error(
                parent,
                "else-if/else must follow a sibling with if/else-if",
            ));
        }
        let previous = node_conditional(&children[index - 1]);
        if !matches!(
            previous,
            Some(DeclarativeConditional::If(_)) | Some(DeclarativeConditional::ElseIf(_))
        ) {
            return Err(dsl_error(
                parent,
                "else-if/else must follow a sibling with if/else-if",
            ));
        }
    }
    Ok(())
}

pub(super) fn node_conditional(node: &DeclarativeUiNode) -> Option<&DeclarativeConditional> {
    match node {
        DeclarativeUiNode::Container { conditional, .. }
        | DeclarativeUiNode::Text { conditional, .. }
        | DeclarativeUiNode::Image { conditional, .. }
        | DeclarativeUiNode::Link { conditional, .. }
        | DeclarativeUiNode::Hr { conditional, .. }
        | DeclarativeUiNode::Button { conditional, .. }
        | DeclarativeUiNode::Input { conditional, .. }
        | DeclarativeUiNode::Select { conditional, .. } => Some(conditional),
        DeclarativeUiNode::Template { .. } => None,
    }
}