From 001a7ba016a19794e752546e8815a5388919d394 Mon Sep 17 00:00:00 2001 From: Bodil Stokke Date: Fri, 16 Nov 2018 22:55:25 +0000 Subject: [PATCH] Parameterise nodes etc on output type, so we can type event handlers based on it. With this, Node only accepts event handlers implemented for String, etc. No danger of trying to stringify functions and vice versa. --- macros/src/declare.rs | 26 +++++++++++---------- rocket/src/main.rs | 27 ++++++++++++++++------ typed-html/src/bin/main.rs | 4 ++-- typed-html/src/dom.rs | 46 ++++++++++++++++++++------------------ typed-html/src/elements.rs | 31 ++++++++++++------------- typed-html/src/events.rs | 45 +++++++++++++++++++++++++------------ typed-html/src/lib.rs | 10 +++++++++ wasm/src/main.rs | 5 +++-- 8 files changed, 120 insertions(+), 74 deletions(-) diff --git a/macros/src/declare.rs b/macros/src/declare.rs index e5a1a5e..d401ee4 100644 --- a/macros/src/declare.rs +++ b/macros/src/declare.rs @@ -91,19 +91,20 @@ impl Declare { let mut body = TokenStream::new(); for (child_name, child_type, _) in self.req_children() { - body.extend(quote!( pub $child_name: Box<$child_type>, )); + body.extend(quote!( pub $child_name: Box<$child_type>, )); } if let Some(child_constraint) = &self.opt_children { let child_constraint = child_constraint.clone(); - body.extend(quote!(pub children: Vec>,)); + body.extend(quote!(pub children: Vec>>,)); } quote!( - pub struct $elem_name { + pub struct $elem_name where T: ::OutputType { + phantom_output: std::marker::PhantomData, pub attrs: $attr_type_name, pub data_attributes: Vec<(&'static str, String)>, - pub events: ::events::Events, + pub events: ::events::Events, $body } ) @@ -115,7 +116,7 @@ impl Declare { let mut args = TokenStream::new(); for (child_name, child_type, _) in self.req_children() { - args.extend(quote!( $child_name: Box<$child_type>, )); + args.extend(quote!( $child_name: Box<$child_type>, )); } let mut attrs = TokenStream::new(); @@ -137,9 +138,10 @@ impl Declare { } quote!( - impl $elem_name { + impl $elem_name where T: ::OutputType { pub fn new($args) -> Self { $elem_name { + phantom_output: std::marker::PhantomData, events: ::events::Events::default(), $body } @@ -194,8 +196,8 @@ impl Declare { let elem_name = self.elem_name(); let vnode = self.impl_vnode(); quote!( - impl ::dom::Node for $elem_name { - fn vnode<'a>(&'a mut self) -> ::dom::VNode<'a> { + impl ::dom::Node for $elem_name where T: ::OutputType { + fn vnode(&'_ mut self) -> ::dom::VNode<'_, T> { $vnode } } @@ -222,7 +224,7 @@ impl Declare { } quote!( - impl ::dom::Element for $elem_name { + impl ::dom::Element for $elem_name where T: ::OutputType { fn name() -> &'static str { $name } @@ -253,7 +255,7 @@ impl Declare { for t in &self.traits { let name = t.clone(); body.extend(quote!( - impl $name for $elem_name {} + impl $name for $elem_name where T: ::OutputType {} )); } body @@ -316,13 +318,13 @@ impl Declare { print_events.extend(quote!( if let Some(ref value) = self.events.$event_name { write!(f, " on{}=\"{}\"", $event_str, - ::htmlescape::encode_attribute(&value.render().unwrap()))?; + ::htmlescape::encode_attribute(value.render().unwrap().as_str()))?; } )); } quote!( - impl std::fmt::Display for $elem_name { + impl std::fmt::Display for $elem_name where T: ::OutputType { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "<{}", $name)?; $print_attrs diff --git a/rocket/src/main.rs b/rocket/src/main.rs index 81ba954..0de5d49 100644 --- a/rocket/src/main.rs +++ b/rocket/src/main.rs @@ -4,16 +4,29 @@ extern crate rocket; extern crate typed_html; extern crate typed_html_macros; -use rocket::http::ContentType; -use rocket::response::Content; -use rocket::{get, routes}; -use typed_html::text; +use rocket::http::{ContentType, Status}; +use rocket::response::{Responder, Result}; +use rocket::{get, routes, Request, Response}; +use std::io::Cursor; use typed_html::types::LinkType; +use typed_html::{dom::Node, text}; use typed_html_macros::html; +struct Html(Box>); + +impl<'r> Responder<'r> for Html { + fn respond_to(self, _request: &Request) -> Result<'r> { + Ok(Response::build() + .status(Status::Ok) + .header(ContentType::HTML) + .sized_body(Cursor::new(self.0.to_string())) + .finalize()) + } +} + #[get("/")] -fn index() -> Content { - Content(ContentType::HTML, html!( +fn index() -> Html { + Html(html!( "Hello Kitty!" @@ -34,7 +47,7 @@ fn index() -> Content { - ).to_string()) + )) } fn main() { diff --git a/typed-html/src/bin/main.rs b/typed-html/src/bin/main.rs index f44948a..2a9523c 100644 --- a/typed-html/src/bin/main.rs +++ b/typed-html/src/bin/main.rs @@ -5,7 +5,7 @@ extern crate typed_html; extern crate typed_html_macros; -use typed_html::dom::Node; +use typed_html::dom::TextNode; use typed_html::types::*; use typed_html_macros::html; @@ -14,7 +14,7 @@ struct Foo { } fn main() { - let the_big_question = text!("How does she eat?"); + let the_big_question: Box> = text!("How does she eat?"); let splain_class = "well-actually"; let wibble = Foo { foo: "welp" }; let doc = html!( diff --git a/typed-html/src/dom.rs b/typed-html/src/dom.rs index 523a54e..8214367 100644 --- a/typed-html/src/dom.rs +++ b/typed-html/src/dom.rs @@ -1,7 +1,9 @@ //! DOM and virtual DOM types. use std::fmt::Display; +use std::marker::PhantomData; +use super::OutputType; use elements::{FlowContent, PhrasingContent}; use events::Events; use htmlescape::encode_minimal; @@ -21,17 +23,17 @@ use htmlescape::encode_minimal; /// ``` /// /// [Node]: trait.Node.html -pub enum VNode<'a> { +pub enum VNode<'a, T: OutputType> { Text(&'a str), - Element(VElement<'a>), + Element(VElement<'a, T>), } /// An untyped representation of an HTML element. -pub struct VElement<'a> { +pub struct VElement<'a, T: OutputType> { pub name: &'static str, pub attributes: Vec<(&'static str, String)>, - pub events: &'a mut Events, - pub children: Vec>, + pub events: &'a mut Events, + pub children: Vec>, } /// Trait for rendering a typed HTML node. @@ -46,11 +48,11 @@ pub struct VElement<'a> { /// [TextNode]: struct.TextNode.html /// [elements]: ../elements/index.html /// [vnode]: #tymethod.vnode -pub trait Node: Display { +pub trait Node: Display { /// Render the node into a [`VNode`][VNode] tree. /// /// [VNode]: enum.VNode.html - fn vnode<'a>(&'a mut self) -> VNode<'a>; + fn vnode<'a>(&'a mut self) -> VNode<'a, T>; } /// Trait for querying a typed HTML element. @@ -58,7 +60,7 @@ pub trait Node: Display { /// All [HTML elements][elements] implement this. /// /// [elements]: ../elements/index.html -pub trait Element: Node { +pub trait Element: Node { /// Get the name of the element. fn name() -> &'static str; /// Get a list of the attribute names for this element. @@ -80,7 +82,7 @@ pub trait Element: Node { } /// An HTML text node. -pub struct TextNode(String); +pub struct TextNode(String, PhantomData); /// Macro for creating text nodes. /// @@ -112,7 +114,7 @@ macro_rules! text { }; } -impl TextNode { +impl TextNode { /// Construct a text node. /// /// The preferred way to construct a text node is with the [`text!()`][text] @@ -120,39 +122,39 @@ impl TextNode { /// /// [text]: ../macro.text.html pub fn new>(s: S) -> Self { - TextNode(s.into()) + TextNode(s.into(), PhantomData) } } -impl Display for TextNode { +impl Display for TextNode { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { f.write_str(&encode_minimal(&self.0)) } } -impl Node for TextNode { - fn vnode<'a>(&'a mut self) -> VNode<'a> { +impl Node for TextNode { + fn vnode(&'_ mut self) -> VNode<'_, T> { VNode::Text(&self.0) } } -impl IntoIterator for TextNode { - type Item = TextNode; - type IntoIter = std::vec::IntoIter; +impl IntoIterator for TextNode { + type Item = TextNode; + type IntoIter = std::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { vec![self].into_iter() } } -impl IntoIterator for Box { - type Item = Box; - type IntoIter = std::vec::IntoIter>; +impl IntoIterator for Box> { + type Item = Box>; + type IntoIter = std::vec::IntoIter>>; fn into_iter(self) -> Self::IntoIter { vec![self].into_iter() } } -impl FlowContent for TextNode {} -impl PhrasingContent for TextNode {} +impl FlowContent for TextNode {} +impl PhrasingContent for TextNode {} diff --git a/typed-html/src/elements.rs b/typed-html/src/elements.rs index cdf9a15..669b8f3 100644 --- a/typed-html/src/elements.rs +++ b/typed-html/src/elements.rs @@ -2,29 +2,30 @@ use typed_html_macros::declare_elements; +use super::OutputType; use dom::{Node, TextNode}; use types::*; // Marker traits for element content groups -pub trait MetadataContent: Node {} -pub trait FlowContent: Node {} -pub trait SectioningContent: Node {} -pub trait HeadingContent: Node {} +pub trait MetadataContent: Node {} +pub trait FlowContent: Node {} +pub trait SectioningContent: Node {} +pub trait HeadingContent: Node {} // Phrasing content seems to be entirely a subclass of FlowContent -pub trait PhrasingContent: FlowContent {} -pub trait EmbeddedContent: Node {} -pub trait InteractiveContent: Node {} -pub trait FormContent: Node {} +pub trait PhrasingContent: FlowContent {} +pub trait EmbeddedContent: Node {} +pub trait InteractiveContent: Node {} +pub trait FormContent: Node {} // Traits for elements that are more picky about their children -pub trait DescriptionListContent: Node {} -pub trait HGroupContent: Node {} -pub trait MapContent: Node {} -pub trait MediaContent: Node {} //