syn_rsx/
node.rs

1//! Tree of nodes.
2
3use std::{convert::TryFrom, fmt, ops::Deref};
4
5use proc_macro2::{Punct, TokenStream};
6use quote::ToTokens;
7use syn::{
8    punctuated::{Pair, Punctuated},
9    Expr, ExprBlock, ExprLit, ExprPath, Ident, Lit,
10};
11
12use crate::Error;
13
14/// Node types.
15#[derive(Debug, PartialEq, Eq)]
16pub enum NodeType {
17    Element,
18    Attribute,
19    Text,
20    Comment,
21    Doctype,
22    Block,
23    Fragment,
24}
25
26impl fmt::Display for NodeType {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(
29            f,
30            "{}",
31            match self {
32                Self::Element => "NodeType::Element",
33                Self::Attribute => "NodeType::Attribute",
34                Self::Text => "NodeType::Text",
35                Self::Comment => "NodeType::Comment",
36                Self::Doctype => "NodeType::Doctype",
37                Self::Block => "NodeType::Block",
38                Self::Fragment => "NodeType::Fragment",
39            }
40        )
41    }
42}
43
44/// Node in the tree.
45#[derive(Debug)]
46pub enum Node {
47    Element(NodeElement),
48    Attribute(NodeAttribute),
49    Text(NodeText),
50    Comment(NodeComment),
51    Doctype(NodeDoctype),
52    Block(NodeBlock),
53    Fragment(NodeFragment),
54}
55
56impl Node {
57    /// Get the type of the node.
58    pub fn r#type(&self) -> NodeType {
59        match &self {
60            Self::Element(_) => NodeType::Element,
61            Self::Attribute(_) => NodeType::Attribute,
62            Self::Text(_) => NodeType::Text,
63            Self::Comment(_) => NodeType::Comment,
64            Self::Doctype(_) => NodeType::Element,
65            Self::Block(_) => NodeType::Block,
66            Self::Fragment(_) => NodeType::Fragment,
67        }
68    }
69
70    /// Get node children.
71    pub fn children(&self) -> Option<&Vec<Node>> {
72        match self {
73            Self::Fragment(NodeFragment { children })
74            | Self::Element(NodeElement { children, .. }) => Some(children),
75            _ => None,
76        }
77    }
78
79    /// Get mutable node children.
80    pub fn children_mut(&mut self) -> Option<&mut Vec<Node>> {
81        match self {
82            Self::Fragment(NodeFragment { children })
83            | Self::Element(NodeElement { children, .. }) => Some(children),
84            _ => None,
85        }
86    }
87}
88
89impl fmt::Display for Node {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        write!(
92            f,
93            "{}",
94            match self {
95                Self::Element(_) => "Node::Element",
96                Self::Attribute(_) => "Node::Attribute",
97                Self::Text(_) => "Node::Text",
98                Self::Comment(_) => "Node::Comment",
99                Self::Doctype(_) => "Node::Doctype",
100                Self::Block(_) => "Node::Block",
101                Self::Fragment(_) => "Node::Fragment",
102            }
103        )
104    }
105}
106
107/// Element node.
108///
109/// A HTMLElement tag, with optional children and attributes.
110/// Potentially selfclosing. Any tag name is valid.
111#[derive(Debug)]
112pub struct NodeElement {
113    /// Name of the element.
114    pub name: NodeName,
115    /// Attributes of the element node.
116    pub attributes: Vec<Node>,
117    /// Children of the element node.
118    pub children: Vec<Node>,
119}
120
121impl fmt::Display for NodeElement {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        write!(f, "NodeElement")
124    }
125}
126
127/// Attribute node.
128///
129/// Attributes of opening tags. Every attribute is itself a node.
130#[derive(Debug)]
131pub struct NodeAttribute {
132    /// Key of the element attribute.
133    pub key: NodeName,
134    /// Value of the element attribute.
135    pub value: Option<NodeValueExpr>,
136}
137
138impl fmt::Display for NodeAttribute {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        write!(f, "NodeAttribute")
141    }
142}
143
144/// Text node.
145///
146/// Quoted text. It's [planned to support unquoted text] as well
147/// using span start and end, but that currently only works
148/// with nightly rust.
149///
150/// [planned to support unquoted text]: https://github.com/stoically/syn-rsx/issues/2
151#[derive(Debug)]
152pub struct NodeText {
153    /// The text value.
154    pub value: NodeValueExpr,
155}
156
157impl fmt::Display for NodeText {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(f, "NodeText")
160    }
161}
162
163/// Comment node.
164///
165/// Comment: `<!-- "comment" -->`, currently has the same restrictions as
166/// `Text` (comment needs to be quoted).
167#[derive(Debug)]
168pub struct NodeComment {
169    /// The comment value.
170    pub value: NodeValueExpr,
171}
172
173impl fmt::Display for NodeComment {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        write!(f, "NodeComment")
176    }
177}
178
179/// Doctype node.
180///
181/// Doctype declaration: `<!DOCTYPE html>` (case insensitive), `html` is the
182/// node value in this case.
183#[derive(Debug)]
184pub struct NodeDoctype {
185    /// The doctype value.
186    pub value: NodeValueExpr,
187}
188
189impl fmt::Display for NodeDoctype {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        write!(f, "NodeDoctype")
192    }
193}
194
195/// Fragement node.
196///
197/// Fragment: `<></>`
198#[derive(Debug)]
199pub struct NodeFragment {
200    /// Children of the fragment node.
201    pub children: Vec<Node>,
202}
203
204impl fmt::Display for NodeFragment {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(f, "NodeFragment")
207    }
208}
209
210/// Block node.
211///
212/// Arbitrary rust code in braced `{}` blocks.
213#[derive(Debug)]
214pub struct NodeBlock {
215    /// The block value..
216    pub value: NodeValueExpr,
217}
218
219impl fmt::Display for NodeBlock {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        write!(f, "NodeBlock")
222    }
223}
224
225/// Name of the node.
226#[derive(Debug)]
227pub enum NodeName {
228    /// A plain identifier like `div` is a path of length 1, e.g. `<div />`. Can
229    /// be separated by double colons, e.g. `<foo::bar />`.
230    Path(ExprPath),
231
232    /// Name separated by punctuation, e.g. `<div data-foo="bar" />` or `<div
233    /// data:foo="bar" />`.
234    Punctuated(Punctuated<Ident, Punct>),
235
236    /// Arbitrary rust code in braced `{}` blocks.
237    Block(Expr),
238}
239
240impl TryFrom<&NodeName> for ExprBlock {
241    type Error = Error;
242
243    fn try_from(node: &NodeName) -> Result<Self, Self::Error> {
244        match node {
245            NodeName::Block(Expr::Block(expr)) => Ok(expr.to_owned()),
246            _ => Err(Error::TryFrom(
247                "NodeName does not match NodeName::Block(Expr::Block(_))".into(),
248            )),
249        }
250    }
251}
252
253impl PartialEq for NodeName {
254    fn eq(&self, other: &NodeName) -> bool {
255        match self {
256            Self::Path(this) => match other {
257                Self::Path(other) => this == other,
258                _ => false,
259            },
260            // can't be derived automatically because `Punct` doesn't impl `PartialEq`
261            Self::Punctuated(this) => match other {
262                Self::Punctuated(other) => {
263                    this.pairs()
264                        .zip(other.pairs())
265                        .all(|(this, other)| match (this, other) {
266                            (
267                                Pair::Punctuated(this_ident, this_punct),
268                                Pair::Punctuated(other_ident, other_punct),
269                            ) => {
270                                this_ident == other_ident
271                                    && this_punct.as_char() == other_punct.as_char()
272                            }
273                            (Pair::End(this), Pair::End(other)) => this == other,
274                            _ => false,
275                        })
276                }
277                _ => false,
278            },
279            Self::Block(this) => match other {
280                Self::Block(other) => this == other,
281                _ => false,
282            },
283        }
284    }
285}
286
287impl ToTokens for NodeName {
288    fn to_tokens(&self, tokens: &mut TokenStream) {
289        match self {
290            NodeName::Path(name) => name.to_tokens(tokens),
291            NodeName::Punctuated(name) => name.to_tokens(tokens),
292            NodeName::Block(name) => name.to_tokens(tokens),
293        }
294    }
295}
296
297impl fmt::Display for NodeName {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        write!(
300            f,
301            "{}",
302            match self {
303                NodeName::Path(expr) => path_to_string(expr),
304                NodeName::Punctuated(name) => {
305                    name.pairs()
306                        .flat_map(|pair| match pair {
307                            Pair::Punctuated(ident, punct) => {
308                                [ident.to_string(), punct.to_string()]
309                            }
310                            Pair::End(ident) => [ident.to_string(), "".to_string()],
311                        })
312                        .collect::<String>()
313                }
314                NodeName::Block(_) => String::from("{}"),
315            }
316        )
317    }
318}
319
320/// Smart pointer to `syn::Expr`.
321#[derive(Debug)]
322pub struct NodeValueExpr {
323    expr: Expr,
324}
325
326impl NodeValueExpr {
327    /// Create a `NodeValueExpr`.
328    pub fn new(expr: Expr) -> Self {
329        Self { expr }
330    }
331}
332
333impl AsRef<Expr> for NodeValueExpr {
334    fn as_ref(&self) -> &Expr {
335        &self.expr
336    }
337}
338
339impl Deref for NodeValueExpr {
340    type Target = Expr;
341
342    fn deref(&self) -> &Self::Target {
343        &self.expr
344    }
345}
346
347impl From<Expr> for NodeValueExpr {
348    fn from(expr: Expr) -> Self {
349        Self { expr }
350    }
351}
352
353impl From<ExprLit> for NodeValueExpr {
354    fn from(expr: ExprLit) -> Self {
355        Self { expr: expr.into() }
356    }
357}
358
359impl From<ExprBlock> for NodeValueExpr {
360    fn from(expr: ExprBlock) -> Self {
361        Self { expr: expr.into() }
362    }
363}
364
365impl From<NodeValueExpr> for Expr {
366    fn from(value: NodeValueExpr) -> Self {
367        value.expr
368    }
369}
370
371impl<'a> From<&'a NodeValueExpr> for &'a Expr {
372    fn from(value: &'a NodeValueExpr) -> Self {
373        &value.expr
374    }
375}
376
377impl TryFrom<NodeValueExpr> for ExprBlock {
378    type Error = Error;
379
380    fn try_from(value: NodeValueExpr) -> Result<Self, Self::Error> {
381        if let Expr::Block(block) = value.expr {
382            Ok(block)
383        } else {
384            Err(Error::TryFrom(
385                "NodeValueExpr does not match Expr::Block(_)".into(),
386            ))
387        }
388    }
389}
390
391impl TryFrom<NodeValueExpr> for ExprLit {
392    type Error = Error;
393
394    fn try_from(value: NodeValueExpr) -> Result<Self, Self::Error> {
395        if let Expr::Lit(lit) = value.expr {
396            Ok(lit)
397        } else {
398            Err(Error::TryFrom(
399                "NodeValueExpr does not match Expr::Lit(_)".into(),
400            ))
401        }
402    }
403}
404
405impl TryFrom<&NodeValueExpr> for String {
406    type Error = Error;
407
408    fn try_from(value: &NodeValueExpr) -> Result<Self, Self::Error> {
409        match &value.expr {
410            Expr::Lit(expr) => match &expr.lit {
411                Lit::Str(lit_str) => Some(lit_str.value()),
412                _ => None,
413            },
414            Expr::Path(expr) => Some(path_to_string(&expr)),
415            _ => None,
416        }
417        .ok_or_else(|| {
418            Error::TryFrom(
419                "NodeValueExpr does not match Expr::Lit(Lit::Str(_)) or Expr::Path(_)".into(),
420            )
421        })
422    }
423}
424
425fn path_to_string(expr: &ExprPath) -> String {
426    expr.path
427        .segments
428        .iter()
429        .map(|segment| segment.ident.to_string())
430        .collect::<Vec<String>>()
431        .join("::")
432}