From 864e058f009eb0416d86e9461726d3f97e956df9 Mon Sep 17 00:00:00 2001 From: Ashelyn Rose Date: Sat, 26 Apr 2025 21:55:30 -0600 Subject: allow doctype --- modules/morgana_proc/src/lib.rs | 62 +++++++++++++++++++++++++++++++---------- modules/site_test/src/main.rs | 11 ++++++-- src/lib.rs | 2 +- src/render.rs | 60 ++++++++++++++++++++++++++++++++------- 4 files changed, 106 insertions(+), 29 deletions(-) diff --git a/modules/morgana_proc/src/lib.rs b/modules/morgana_proc/src/lib.rs index 8061b1b..7730f6b 100644 --- a/modules/morgana_proc/src/lib.rs +++ b/modules/morgana_proc/src/lib.rs @@ -4,7 +4,9 @@ use unsynn::*; use quote::quote; unsynn! { - struct MorxBlock(Vec>); + keyword MorxDocKey = "doctype"; + struct MorxBlock(Vec>); + struct MorxDoctype(Cons, MorxDocKey, Vec>, NodeSeparator>); struct MorxNode(Cons proc_macro::TokenStream { let child_vec_expr = generate_block(&ast); quote! { - RenderNode::Fragment { + ::morgana::RenderNode::Fragment { children: #child_vec_expr } }.into() @@ -36,10 +38,10 @@ pub fn morx(input: proc_macro::TokenStream) -> proc_macro::TokenStream { fn generate_block(ast: &MorxBlock) -> unsynn::TokenStream { let children: Vec = ast.0.iter().map(|child| { match child { - Either::First(node) => generate_node(node), - Either::Second(child_expression) => generate_expr_node(&child_expression.0.second), - Either::Third(literal) => generate_literal(literal), - _ => unreachable!("Invalid morx block AST"), + Either::First(doctype) => generate_doctype(doctype), + Either::Second(node) => generate_node(node), + Either::Third(child_expression) => generate_expr_node(&child_expression.0.second), + Either::Fourth(literal) => generate_literal(literal), } }).collect(); @@ -50,6 +52,28 @@ fn generate_block(ast: &MorxBlock) -> unsynn::TokenStream { }.into() } +fn generate_doctype(ast: &MorxDoctype) -> unsynn::TokenStream { + let elem_expressions: Vec = ast.0.third.iter().map(|elem| { + match elem { + Either::First(ident) => { + let ident_string = Literal::string(&ident.to_string()).into_token_stream(); + quote! {::morgana::DoctypeElem::Word( #ident_string )} + }, + Either::Second(literal) => { + let literal = literal.clone().into_token_stream(); + quote! {::morgana::DoctypeElem::String( #literal )} + }, + _ => unreachable!("Invalid doctype AST"), + } + }).collect(); + + quote! { + ::morgana::RenderNode::Doctype(vec![ + #(#elem_expressions),* + ]) + } +} + fn generate_node(ast: &MorxNode) -> unsynn::TokenStream { let elem_name = &ast.0.first; let attrs_or_props = match &ast.0.second { @@ -72,7 +96,10 @@ fn generate_node(ast: &MorxNode) -> unsynn::TokenStream { let value = match &attr.1 { Some(Either::First(literal)) => quote!{ format!("{}", #literal) }, Some(Either::Second(ident)) => quote!{ format!("{}", #ident) }, - Some(Either::Third(bracegroup)) => bracegroup.0.stream(), + Some(Either::Third(bracegroup)) => { + let inner = bracegroup.0.stream(); + quote!{ {#inner}.into() } + }, Some(Either::Fourth(_invalid)) => unreachable!("Invalid element attribute type"), None => quote!{ String::new() }, }; @@ -83,9 +110,12 @@ fn generate_node(ast: &MorxNode) -> unsynn::TokenStream { let elem_props = attrs_or_props.iter().map(|attr| { let name = &attr.0; let value = match &attr.1 { - Some(Either::First(literal)) => quote!{ format!("{}", #literal) }, - Some(Either::Second(ident)) => quote!{ #ident }, - Some(Either::Third(bracegroup)) => bracegroup.0.stream(), + Some(Either::First(literal)) => quote!{ {String::from(#literal).into()} }, + Some(Either::Second(ident)) => quote!{ {String::from(#ident).into()} }, + Some(Either::Third(bracegroup)) => { + let inner = bracegroup.0.stream(); + quote!{ {#inner}.into() } + }, Some(Either::Fourth(_invalid)) => unreachable!("Invalid element attribute type"), None => quote!{ true }, }; @@ -107,17 +137,17 @@ fn generate_node(ast: &MorxNode) -> unsynn::TokenStream { if is_component_node { quote! { - RenderNode::Component( + ::morgana::RenderNode::Component( Box::new({ #elem_name { + children: #children, #(#elem_props),* - children: #children } }) ) }.into() } else { let elem_name = Literal::string(&elem_name.to_string()).into_token_stream(); quote! { - RenderNode::Element { + ::morgana::RenderNode::Element { name: #elem_name.to_string(), children: #children, attributes: std::collections::HashMap::from([#(#elem_attrs),*]) @@ -133,7 +163,9 @@ fn generate_expr_node(ast: &Either) -> unsynn: Either::Second(brace_expr) => { let children = brace_expr.0.stream(); quote! { - RenderNode::Fragment { children: #children } + ::morgana::RenderNode::Fragment { + children: ::morgana::IntoRender::into_render(#children) + } } }, _ => unreachable!("Invalid expr node"), @@ -142,6 +174,6 @@ fn generate_expr_node(ast: &Either) -> unsynn: fn generate_literal(ast: &Literal) -> unsynn::TokenStream { quote! { - RenderNode::TextNode { content: format!("{}", #ast) } + ::morgana::RenderNode::TextNode { content: format!("{}", #ast) } } } diff --git a/modules/site_test/src/main.rs b/modules/site_test/src/main.rs index 9201c8a..b0fe9f3 100644 --- a/modules/site_test/src/main.rs +++ b/modules/site_test/src/main.rs @@ -3,7 +3,7 @@ use morgana::{morx, Component, RenderNode}; pub fn main() { let parent = morx! { ParentLayout { - Child { + Child some_prop={"something".to_string()} { "Hello world!" } } @@ -19,10 +19,13 @@ struct ParentLayout { impl Component for ParentLayout { fn render(self: Box) -> RenderNode { + let string = "test string"; + morx!{ + !doctype html; html lang = "en-US" { head { - title = "test thing" + title ={string} } body = {self.children} } @@ -31,13 +34,15 @@ impl Component for ParentLayout { } struct Child { - children: Vec + children: Vec, + some_prop: String, } impl Component for Child { fn render(self: Box) -> RenderNode { morx! { p= {self.children} + p= {self.some_prop} } } } diff --git a/src/lib.rs b/src/lib.rs index c760338..5b3a634 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ mod render; -pub use render::{RenderNode, Component}; +pub use render::{RenderNode, Component, IntoRender, DoctypeElem}; pub use morgana_proc::morx; pub async fn render_tree(parent_node: RenderNode) -> String { diff --git a/src/render.rs b/src/render.rs index 8addbdb..6ae948b 100644 --- a/src/render.rs +++ b/src/render.rs @@ -7,7 +7,13 @@ pub trait Component { fn render(self: Box) -> RenderNode; } +pub enum DoctypeElem { + Word(&'static str), + String(&'static str), +} + pub enum RenderNode { + Doctype(Vec), Suspense { fallback: Box, children: Pin>>> @@ -30,19 +36,29 @@ pub enum RenderNode { impl RenderNode { pub(crate) fn render_to_string(self) -> Pin>> { match self { + RenderNode::Doctype(elements) => { + let strings = elements.iter().map(|elem| match elem { + DoctypeElem::Word(word) => word.to_string(), + DoctypeElem::String(string) => format!("\"{}\"", string), + }).collect::>().join(" ").clone(); + + Box::pin(async move { + format!("", strings) + }) + }, RenderNode::Component(component) => { let result_root = component.render(); - Box::pin((async move || { + Box::pin(async move { result_root.render_to_string().await - })()) + }) }, RenderNode::Suspense {fallback: _, children} => { - Box::pin((async move || { + Box::pin(async move { join_all(children.await.into_iter() .map(|child| child.render_to_string())).await .join("") - })()) + }) }, RenderNode::Element { name, attributes, children } => { @@ -50,25 +66,49 @@ impl RenderNode { .map(|(key, value)| format!(" {key}=\"{value}\"")) .collect::>().join(""); - Box::pin((async move || { + Box::pin(async move { let rendered_children = join_all(children.into_iter() .map(|child| child.render_to_string()) .collect::>()).await.join(""); format!("<{name}{text_attributes}>{rendered_children}") - })()) + }) }, RenderNode::Fragment { children } => { - Box::pin((async move || { + Box::pin(async move { join_all(children.into_iter() .map(|child| child.render_to_string())).await .join("") - })()) + }) } - RenderNode::TextNode { content } => Box::pin((async move || content)()), - RenderNode::Null => Box::pin((async move || "".to_string())()), + RenderNode::TextNode { content } => Box::pin(async move { content }), + RenderNode::Null => Box::pin(async move { "".to_string() }), + } + } +} + +pub trait IntoRender { + fn into_render(self) -> Vec; +} + +macro_rules! impl_str { + ($t:ty) => { + impl IntoRender for $t { + fn into_render(self) -> Vec { + vec![RenderNode::TextNode { content: String::from(self) }] + } } + }; +} + +impl_str!(String); +impl_str!(&str); +impl_str!(std::borrow::Cow<'_,str>); + +impl IntoRender for Vec { + fn into_render(self) -> Vec { + self } } -- cgit 1.4.1