//! Contains parsing an RSX node into various data types. use std::convert::TryFrom; use proc_macro2::Span; use quote::quote; use syn::{ parse::Parse, punctuated::Punctuated, token, Error, Expr, Ident, LitStr, Token, }; use syn_rsx::{Node, NodeType}; fn under_to_dash(s: impl AsRef) -> String { s.as_ref().trim_matches('_').replace("_", "-") } #[derive(Clone, Debug)] /// An enumeration of all supported attribute types. pub enum AttributeToken { PostBuild(syn::Expr), CaptureView(syn::Expr), CaptureForEach(syn::Expr), Xmlns(syn::Expr), Style(syn::Expr), StyleSingle(String, syn::Expr), On(String, syn::Expr), Window(String, syn::Expr), Document(String, syn::Expr), BooleanSingle(String, syn::Expr), BooleanTrue(String), PatchChildren(syn::Expr), Attrib(String, syn::Expr), } impl TryFrom for AttributeToken { type Error = syn::Error; fn try_from(node: syn_rsx::Node) -> Result { let span = node.name_span().unwrap_or(Span::call_site()); if let Some(key) = node.name_as_string() { if let Some(expr) = node.value { let keys = key.split(':').collect::>(); Ok(AttributeToken::from_keys_expr_pair(&keys, expr)) } else { let name = under_to_dash(&key); Ok(AttributeToken::BooleanTrue(name)) } } else { Err(Error::new(span, "dom attribute is missing a name")) } } } impl Parse for AttributeToken { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut keys: Vec = vec![]; while !input.lookahead1().peek(Token![=]) && !input.is_empty() { let key_segment = match input.parse::() { Ok(ident) => Ok(format!("{}", ident)), Err(e1) => { if input.parse::().is_ok() { Ok("type".to_string()) } else { Err(e1) } } }?; let _ = input.parse::>()?; keys.push(key_segment); } if input.parse::().is_ok() { let expr = input.parse::()?; Ok(AttributeToken::from_keys_expr_pair(&keys, expr)) } else { let key = under_to_dash(keys.join(":")); Ok(AttributeToken::BooleanTrue(key)) } } } impl AttributeToken { pub fn from_keys_expr_pair(keys: &[impl AsRef], expr: Expr) -> Self { let ks = keys.iter().map(|s| s.as_ref()).collect::>(); match ks.as_slice() { ["post", "build"] => AttributeToken::PostBuild(expr), ["capture", "view"] => AttributeToken::CaptureView(expr), ["capture", "for_each"] => AttributeToken::CaptureForEach(expr), ["xmlns"] => AttributeToken::Xmlns(expr), ["style"] => AttributeToken::Style(expr), ["style", name] => { let name = under_to_dash(name); AttributeToken::StyleSingle(name, expr) } ["on", event] => AttributeToken::On(event.to_string(), expr), ["window", event] => AttributeToken::Window(event.to_string(), expr), ["document", event] => AttributeToken::Document(event.to_string(), expr), ["boolean", name] => { let name = under_to_dash(name); AttributeToken::BooleanSingle(name, expr) } ["patch", "children"] => AttributeToken::PatchChildren(expr), [attribute_name] => { let name = under_to_dash(attribute_name); AttributeToken::Attrib(name, expr) } keys => { let name = under_to_dash(&keys.join(":")); AttributeToken::Attrib(name, expr) } } } /// Attempt to create a token stream representing one link in a `ViewBuilder` chain. pub fn try_builder_token_stream( self: &AttributeToken, ) -> Result { use AttributeToken::*; match self { PostBuild(expr) => Ok(quote! { .with_post_build(#expr) }), CaptureView(expr) => Ok(quote! { .with_capture_view(#expr) }), CaptureForEach(expr) => Ok(quote! { .with_capture_for_each(#expr) }), Xmlns(_) => Ok(quote!{}),// handled by a preprocessor Style(expr) => Ok(quote! { .with_style_stream(#expr) }), StyleSingle(name, expr) => Ok(quote! { .with_single_style_stream(#name, #expr) }), On(name, expr) => Ok(quote! { .with_event(#name, "myself", #expr) }), Window(name, expr) => Ok(quote! { .with_event(#name, "window", #expr) }), Document(name, expr) => Ok(quote! { .with_event(#name, "document", #expr) }), BooleanSingle(name, expr) => Ok(quote! { .with_single_bool_attrib_stream(#name, #expr) }), PatchChildren(expr) => Ok(quote! { .with_child_stream(#expr) }), Attrib(name, expr) => Ok(quote! { .with_single_attrib_stream(#name, #expr) }), BooleanTrue(expr) => Ok(quote! { .with_single_bool_attrib_stream(#expr, true) }), } } } #[derive(Clone, Debug)] /// An enumeration of all supported nodes types. pub enum ViewToken { Element { name: String, name_span: proc_macro2::Span, attributes: Vec, children: Vec, }, Text(syn::Expr), Block(syn::Expr), } impl Parse for ViewToken { fn parse(input: syn::parse::ParseStream) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(token::Brace) { Ok(ViewToken::Block(input.parse::()?)) } else if lookahead.peek(LitStr) { Ok(ViewToken::Text(input.parse::()?)) } else { let tag: Ident = input.parse()?; let attributes = if input.lookahead1().peek(token::Paren) { let paren_content; let _paren_token: token::Paren = syn::parenthesized!(paren_content in input); let attrs: Punctuated = paren_content.parse_terminated(AttributeToken::parse)?; attrs.into_iter().collect::>() } else { vec![] }; let brace_content; let _brace: token::Brace = syn::braced!(brace_content in input); let children: ViewTokens = brace_content.parse()?; Ok(ViewToken::Element { name: format!("{}", tag), name_span: tag.span(), attributes, children: children.views, }) } } } impl TryFrom for ViewToken { type Error = Error; fn try_from(node: Node) -> Result { let name_span = node.name_span().unwrap_or(Span::call_site()); match &node.node_type { NodeType::Element => match node.name_as_string() { Some(tag) => { let name = tag; let mut attributes = vec![]; for attribute in node.attributes.into_iter() { let token = AttributeToken::try_from(attribute)?; attributes.push(token); } let mut children = vec![]; for child in node.children.into_iter() { let token = ViewToken::try_from(child)?; children.push(token); } Ok(ViewToken::Element { name, name_span, attributes, children, }) } None => Err(Error::new( node.name_span().unwrap_or_else(|| Span::call_site()), "View node is missing a name.", )), }, NodeType::Text => { if let Some(val) = node.value { Ok(ViewToken::Text(val)) } else { Err(Error::new( node.name_span().unwrap_or(Span::call_site()), "Text node is missing a value.", )) } } NodeType::Block => { if let Some(val) = node.value { Ok(ViewToken::Block(val)) } else { Err(Error::new( node.name_span().unwrap_or(Span::call_site()), "Block node is missing a value.", )) } } _ => Err(Error::new( node.name_span().unwrap_or_else(|| Span::call_site()), "View node is missing a name.", )), } } } /// A list of view tokens #[derive(Default)] pub struct ViewTokens { pub views: Vec, } impl Parse for ViewTokens { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut tokens: ViewTokens = ViewTokens::default(); while !input.is_empty() { tokens.views.push(input.parse::()?); } Ok(tokens) } }