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
}
}
|