1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Tree of nodes

use syn::{punctuated::Punctuated, token::Colon, Expr, ExprPath, Ident, Lit};

use crate::punctuation::Dash;

/// Node in the tree
#[derive(Debug)]
pub struct Node {
    /// Name according to the `NodeType`:
    ///
    /// - `Element`: Name of the element
    /// - `Attribute`: Key of the element attribute
    /// - `Text`: `None`
    /// - `Block`: `None`
    pub name: Option<NodeName>,

    /// Type of the nodes
    pub node_type: NodeType,

    /// Value according to the `NodeType`:
    ///
    /// - `Element`: `None`
    /// - `Attribute`: Any valid `syn::Expr`
    /// - `Text`: `syn::Expr::Lit`
    /// - `Block`: `syn::Expr::Block`
    pub value: Option<Expr>,

    /// Has nodes if `NodeType::Element`. Every attribute is
    /// `NodeType::Attribute`
    pub attributes: Vec<Node>,

    /// Has nodes if `NodeType::Element`
    pub children: Vec<Node>,
}

impl Node {
    /// Returns `name` as `String` if it's `Some`
    pub fn name_as_string(&self) -> Option<String> {
        match self.name.as_ref() {
            Some(NodeName::Path(expr)) => Some(
                expr.path
                    .segments
                    .iter()
                    .map(|segment| segment.ident.to_string())
                    .collect::<Vec<String>>()
                    .join("::"),
            ),
            Some(NodeName::Dash(name)) => Some(
                name.iter()
                    .map(|ident| ident.to_string())
                    .collect::<Vec<String>>()
                    .join("-"),
            ),
            Some(NodeName::Colon(name)) => Some(
                name.iter()
                    .map(|ident| ident.to_string())
                    .collect::<Vec<String>>()
                    .join(":"),
            ),
            None => None,
        }
    }

    /// Returns `value` as `String` if it's a `Lit::Str` expression
    pub fn value_as_string(&self) -> Option<String> {
        match self.value.as_ref() {
            Some(Expr::Lit(expr)) => match &expr.lit {
                Lit::Str(lit_str) => Some(lit_str.value()),
                _ => None,
            },
            _ => None,
        }
    }
}

/// Type of the node
#[derive(Debug)]
pub enum NodeType {
    /// A HTMLElement tag, with optional children and attributes.
    /// Potentially selfclosing. Any tag name is valid.
    Element,

    /// Attributes of opening tags. Every attribute is itself a node.
    Attribute,

    /// Quoted text. It's planned to support unquoted text as well
    /// using span start and end, but that currently only works
    /// with nightly rust
    Text,

    /// Arbitrary rust code in braced `{}` blocks
    Block,
}

/// Name of the node
#[derive(Debug)]
pub enum NodeName {
    /// [Mod style path] containing no path arguments on any of its segments. A
    /// plain identifier like `x` is a path of length 1.
    ///
    /// [Mod style path]:
    /// https://docs.rs/syn/1.0.30/syn/struct.Path.html#method.parse_mod_style
    Path(ExprPath),

    /// Name separated by dashes, e.g. `<div data-foo="bar" />`
    Dash(Punctuated<Ident, Dash>),

    /// Name separated by colons, e.g. `<div on:click={foo} />`
    Colon(Punctuated<Ident, Colon>),
}

impl PartialEq for NodeName {
    fn eq(&self, other: &NodeName) -> bool {
        match self {
            Self::Path(this) => match other {
                Self::Path(other) => this == other,
                _ => false,
            },
            Self::Dash(this) => match other {
                Self::Dash(other) => this == other,
                _ => false,
            },
            Self::Colon(this) => match other {
                Self::Colon(other) => this == other,
                _ => false,
            },
        }
    }
}