summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--modules/morgana_proc/src/lib.rs62
-rw-r--r--modules/site_test/src/main.rs11
-rw-r--r--src/lib.rs2
-rw-r--r--src/render.rs60
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<Either<MorxNode, MorxChild, Literal>>);
+    keyword MorxDocKey = "doctype";
+    struct MorxBlock(Vec<Either<MorxDoctype, MorxNode, MorxChild, Literal>>);
+    struct MorxDoctype(Cons<PunctAny<'!'>, MorxDocKey, Vec<Either<Ident, LiteralString>>, NodeSeparator>);
     struct MorxNode(Cons<Ident, Either<
         NodeSeparator,
         MorxChildren,
@@ -27,7 +29,7 @@ pub fn morx(input: proc_macro::TokenStream) -> 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<unsynn::TokenStream> = 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<unsynn::TokenStream> = 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<Literal, BraceGroup, EndOfStream>) -> 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<Literal, BraceGroup, EndOfStream>) -> 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<Self>) -> 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<RenderNode>
+    children: Vec<RenderNode>,
+    some_prop: String,
 }
 
 impl Component for Child {
     fn render(self: Box<Self>) -> 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<Self>) -> RenderNode;
 }
 
+pub enum DoctypeElem {
+    Word(&'static str),
+    String(&'static str),
+}
+
 pub enum RenderNode {
+    Doctype(Vec<DoctypeElem>),
     Suspense {
         fallback: Box<RenderNode>,
         children: Pin<Box<dyn Future<Output = Vec<RenderNode>>>>
@@ -30,19 +36,29 @@ pub enum RenderNode {
 impl RenderNode {
     pub(crate) fn render_to_string(self) -> Pin<Box<dyn Future<Output = String>>> {
         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::<Vec<_>>().join(" ").clone();
+
+                Box::pin(async move {
+                    format!("<!doctype {}>", 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::<Vec<_>>().join("");
 
-                Box::pin((async move || {
+                Box::pin(async move {
                     let rendered_children = join_all(children.into_iter()
                         .map(|child| child.render_to_string())
                         .collect::<Vec<_>>()).await.join("");
 
                     format!("<{name}{text_attributes}>{rendered_children}</{name}>")
 
-                })())
+                })
             },
             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<RenderNode>;
+}
+
+macro_rules! impl_str {
+    ($t:ty) => {
+        impl IntoRender for $t {
+            fn into_render(self) -> Vec<RenderNode> {
+                vec![RenderNode::TextNode { content: String::from(self) }]
+            }
         }
+    };
+}
+
+impl_str!(String);
+impl_str!(&str);
+impl_str!(std::borrow::Cow<'_,str>);
+
+impl IntoRender for Vec<RenderNode> {
+    fn into_render(self) -> Vec<RenderNode> {
+        self
     }
 }