Implement IntoIterator for all marker traits.

Further closes #12.
This commit is contained in:
Bodil Stokke 2018-11-29 16:35:35 +00:00
parent 1823e5ecb9
commit 270e3b52e1
3 changed files with 111 additions and 68 deletions

View File

@ -10,7 +10,8 @@ use rocket::response::{Responder, Result};
use rocket::{get, routes, Request, Response}; use rocket::{get, routes, Request, Response};
use std::io::Cursor; use std::io::Cursor;
use typed_html::types::LinkType; use typed_html::types::LinkType;
use typed_html::{dom::DOMTree, html, text}; use typed_html::elements::FlowContent;
use typed_html::{dom::DOMTree, html, text, OutputType};
struct Html(DOMTree<String>); struct Html(DOMTree<String>);
@ -24,32 +25,46 @@ impl<'r> Responder<'r> for Html {
} }
} }
#[get("/")] // Function that wraps a DOM node in an HTML document, to demonstrate how you'd
fn index() -> Html { // do this sort of templating.
let a = false; //
Html(html!( // It's a bit more complicated than you'd hope because you need to take an input
// argument of the type that the element that you're inserting it into expects,
// which in the case of `<body>` is `FlowContent`, not just `Node`, so you can't
// pass it a `DOMTree<T>` or you'll get a type error.
fn doc<T: OutputType + 'static>(tree: Box<dyn FlowContent<T>>) -> DOMTree<T> {
html!(
<html> <html>
<head> <head>
<title>"Hello Kitty!"</title> <title>"Hello Kitty!"</title>
<link rel=LinkType::StyleSheet href="lol.css"/> <link rel=LinkType::StyleSheet href="lol.css"/>
</head> </head>
<body> <body>
<div></div> { tree }
<h1 data-lol="omg">"Hello Kitty!"</h1>
<p class="official-position-of-sanrio-ltd emphasis">
"She is not a "<em><a href="https://en.wikipedia.org/wiki/Cat">"cat"</a></em>". She is a "<em>"human girl"</em>"."
</p>
<p class=["urgent", "question"]>"But how does she eat?"</p>
{
(1..4).map(|i| {
html!(<p>{ text!("{}. Ceci n'est pas une chatte.", i) }</p>)
})
}
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
<button disabled=a onclick="alert('She is not a cat.')">"Click me!"</button>
</body> </body>
</html> </html>
)) )
}
#[get("/")]
fn index() -> Html {
let a = false;
Html(doc(html!(
<div>
<h1 data-lol="omg">"Hello Kitty!"</h1>
<p class="official-position-of-sanrio-ltd emphasis">
"She is not a "<em><a href="https://en.wikipedia.org/wiki/Cat">"cat"</a></em>". She is a "<em>"human girl"</em>"."
</p>
<p class=["urgent", "question"]>"But how does she eat?"</p>
{
(1..4).map(|i| {
html!(<p>{ text!("{}. Ceci n'est pas une chatte.", i) }</p>)
})
}
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
<button disabled=a onclick="alert('She is not a cat.')">"Click me!"</button>
</div>
)))
} }
fn main() { fn main() {

View File

@ -73,6 +73,15 @@ pub trait Node<T: OutputType>: Display {
fn vnode(&mut self) -> VNode<T>; fn vnode(&mut self) -> VNode<T>;
} }
impl<T> IntoIterator for Box<dyn Node<T>> where T: OutputType {
type Item = Box<dyn Node<T>>;
type IntoIter = std::vec::IntoIter<Box<dyn Node<T>>>;
fn into_iter(self) -> Self::IntoIter {
vec![self].into_iter()
}
}
/// Trait for querying a typed HTML element. /// Trait for querying a typed HTML element.
/// ///
/// All [HTML elements][elements] implement this. /// All [HTML elements][elements] implement this.

View File

@ -8,24 +8,43 @@ use types::*;
// Marker traits for element content groups // Marker traits for element content groups
pub trait MetadataContent<T: OutputType>: Node<T> {} macro_rules! marker_trait {
pub trait FlowContent<T: OutputType>: Node<T> {} ($trait:ident) => {
pub trait SectioningContent<T: OutputType>: Node<T> {} marker_trait!($trait, Node);
pub trait HeadingContent<T: OutputType>: Node<T> {} };
($trait:ident, $parent:ident) => {
pub trait $trait<T: OutputType>: $parent<T> {}
impl<T> IntoIterator for Box<dyn $trait<T>> where T: OutputType {
type Item = Box<dyn $trait<T>>;
type IntoIter = std::vec::IntoIter<Box<dyn $trait<T>>>;
fn into_iter(self) -> Self::IntoIter {
vec![self].into_iter()
}
}
};
}
marker_trait!(MetadataContent);
marker_trait!(FlowContent);
marker_trait!(SectioningContent);
marker_trait!(HeadingContent);
// Phrasing content seems to be entirely a subclass of FlowContent // Phrasing content seems to be entirely a subclass of FlowContent
pub trait PhrasingContent<T: OutputType>: FlowContent<T> {} marker_trait!(PhrasingContent, FlowContent);
pub trait EmbeddedContent<T: OutputType>: Node<T> {} marker_trait!(EmbeddedContent);
pub trait InteractiveContent<T: OutputType>: Node<T> {} marker_trait!(InteractiveContent);
pub trait FormContent<T: OutputType>: Node<T> {} marker_trait!(FormContent);
// Traits for elements that are more picky about their children // Traits for elements that are more picky about their children
pub trait DescriptionListContent<T: OutputType>: Node<T> {} marker_trait!(DescriptionListContent);
pub trait HGroupContent<T: OutputType>: Node<T> {} marker_trait!(HGroupContent);
pub trait MapContent<T: OutputType>: Node<T> {} marker_trait!(MapContent);
pub trait MediaContent<T: OutputType>: Node<T> {} // <audio> and <video> marker_trait!(MediaContent); // <audio> and <video>
pub trait SelectContent<T: OutputType>: Node<T> {} marker_trait!(SelectContent);
pub trait TableContent<T: OutputType>: Node<T> {} marker_trait!(TableContent);
pub trait TableColumnContent<T: OutputType>: Node<T> {} marker_trait!(TableColumnContent);
declare_elements!{ declare_elements!{
html { html {
@ -79,11 +98,11 @@ declare_elements!{
article in [FlowContent, SectioningContent] with FlowContent; article in [FlowContent, SectioningContent] with FlowContent;
aside in [FlowContent, SectioningContent] with FlowContent; aside in [FlowContent, SectioningContent] with FlowContent;
audio { audio {
autoplay: Bool, autoplay: bool,
controls: Bool, controls: bool,
crossorigin: CrossOrigin, crossorigin: CrossOrigin,
loop: Bool, loop: bool,
muted: Bool, muted: bool,
preload: Preload, preload: Preload,
src: Uri, src: Uri,
} in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent; } in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent;
@ -95,13 +114,13 @@ declare_elements!{
} in [FlowContent] with FlowContent; } in [FlowContent] with FlowContent;
br in [FlowContent, PhrasingContent]; br in [FlowContent, PhrasingContent];
button { button {
autofocus: Bool, autofocus: bool,
disabled: Bool, disabled: bool,
form: Id, form: Id,
formaction: Uri, formaction: Uri,
formenctype: FormEncodingType, formenctype: FormEncodingType,
formmethod: FormMethod, formmethod: FormMethod,
formnovalidate: Bool, formnovalidate: bool,
formtarget: Target, formtarget: Target,
name: Id, name: Id,
type: ButtonType, type: ButtonType,
@ -122,7 +141,7 @@ declare_elements!{
datetime: Datetime, datetime: Datetime,
} in [FlowContent, PhrasingContent] with FlowContent; } in [FlowContent, PhrasingContent] with FlowContent;
details { details {
open: Bool, open: bool,
} in [FlowContent, SectioningContent, InteractiveContent] with [summary] FlowContent; } in [FlowContent, SectioningContent, InteractiveContent] with [summary] FlowContent;
dfn in [FlowContent, PhrasingContent] with PhrasingContent; dfn in [FlowContent, PhrasingContent] with PhrasingContent;
div in [FlowContent] with FlowContent; div in [FlowContent] with FlowContent;
@ -146,7 +165,7 @@ declare_elements!{
enctype: FormEncodingType, enctype: FormEncodingType,
method: FormMethod, method: FormMethod,
name: Id, name: Id,
novalidate: Bool, novalidate: bool,
target: Target, target: Target,
} in [FlowContent] with FlowContent; } in [FlowContent] with FlowContent;
h1 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent; h1 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent;
@ -161,8 +180,8 @@ declare_elements!{
i in [FlowContent, PhrasingContent] with PhrasingContent; i in [FlowContent, PhrasingContent] with PhrasingContent;
iframe { iframe {
allow: FeaturePolicy, allow: FeaturePolicy,
allowfullscreen: Bool, allowfullscreen: bool,
allowpaymentrequest: Bool, allowpaymentrequest: bool,
height: usize, height: usize,
name: Id, name: Id,
referrerpolicy: ReferrerPolicy, referrerpolicy: ReferrerPolicy,
@ -176,7 +195,7 @@ declare_elements!{
crossorigin: CrossOrigin, crossorigin: CrossOrigin,
decoding: ImageDecoding, decoding: ImageDecoding,
height: usize, height: usize,
ismap: Bool, ismap: bool,
sizes: SpacedList<String>, // FIXME it's not really just a string sizes: SpacedList<String>, // FIXME it's not really just a string
src: Uri, src: Uri,
srcset: String, // FIXME this is much more complicated srcset: String, // FIXME this is much more complicated
@ -185,12 +204,12 @@ declare_elements!{
} in [FlowContent, PhrasingContent, EmbeddedContent]; } in [FlowContent, PhrasingContent, EmbeddedContent];
input { input {
autocomplete: String, autocomplete: String,
autofocus: Bool, autofocus: bool,
disabled: Bool, disabled: bool,
form: Id, form: Id,
list: Id, list: Id,
name: Id, name: Id,
required: Bool, required: bool,
tabindex: usize, tabindex: usize,
type: InputType, type: InputType,
value: String, value: String,
@ -227,12 +246,12 @@ declare_elements!{
height: usize, height: usize,
name: Id, name: Id,
type: Mime, type: Mime,
typemustmatch: Bool, typemustmatch: bool,
usemap: String, // TODO should be a fragment starting with '#' usemap: String, // TODO should be a fragment starting with '#'
width: usize, width: usize,
} in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent, FormContent] with param; } in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent, FormContent] with param;
ol { ol {
reversed: Bool, reversed: bool,
start: isize, start: isize,
type: OrderedListType, type: OrderedListType,
} in [FlowContent] with li; } in [FlowContent] with li;
@ -254,11 +273,11 @@ declare_elements!{
s in [FlowContent, PhrasingContent] with PhrasingContent; s in [FlowContent, PhrasingContent] with PhrasingContent;
samp in [FlowContent, PhrasingContent] with PhrasingContent; samp in [FlowContent, PhrasingContent] with PhrasingContent;
script { script {
async: Bool, async: bool,
crossorigin: CrossOrigin, crossorigin: CrossOrigin,
defer: Bool, defer: bool,
integrity: Integrity, integrity: Integrity,
nomodule: Bool, nomodule: bool,
nonce: Nonce, nonce: Nonce,
src: Uri, src: Uri,
text: String, text: String,
@ -267,12 +286,12 @@ declare_elements!{
section in [FlowContent, SectioningContent] with FlowContent; section in [FlowContent, SectioningContent] with FlowContent;
select { select {
autocomplete: String, autocomplete: String,
autofocus: Bool, autofocus: bool,
disabled: Bool, disabled: bool,
form: Id, form: Id,
multiple: Bool, multiple: bool,
name: Id, name: Id,
required: Bool, required: bool,
size: usize, size: usize,
} in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with SelectContent; } in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with SelectContent;
small in [FlowContent, PhrasingContent] with PhrasingContent; small in [FlowContent, PhrasingContent] with PhrasingContent;
@ -284,16 +303,16 @@ declare_elements!{
template in [MetadataContent, FlowContent, PhrasingContent, TableColumnContent] with Node; template in [MetadataContent, FlowContent, PhrasingContent, TableColumnContent] with Node;
textarea { textarea {
autocomplete: OnOff, autocomplete: OnOff,
autofocus: Bool, autofocus: bool,
cols: usize, cols: usize,
disabled: Bool, disabled: bool,
form: Id, form: Id,
maxlength: usize, maxlength: usize,
minlength: usize, minlength: usize,
name: Id, name: Id,
placeholder: String, placeholder: String,
readonly: Bool, readonly: bool,
required: Bool, required: bool,
rows: usize, rows: usize,
spellcheck: BoolOrDefault, spellcheck: BoolOrDefault,
wrap: Wrap, wrap: Wrap,
@ -310,7 +329,7 @@ declare_elements!{
area { area {
alt: String, alt: String,
coords: String, // TODO could perhaps be validated coords: String, // TODO could perhaps be validated
download: Bool, download: bool,
href: Uri, href: Uri,
hreflang: LanguageTag, hreflang: LanguageTag,
ping: SpacedList<Uri>, ping: SpacedList<Uri>,
@ -333,13 +352,13 @@ declare_elements!{
value: isize, value: isize,
} with FlowContent; } with FlowContent;
option { option {
disabled: Bool, disabled: bool,
label: String, label: String,
selected: Bool, selected: bool,
value: String, value: String,
} in [SelectContent] with TextNode; } in [SelectContent] with TextNode;
optgroup { optgroup {
disabled: Bool, disabled: bool,
label: String, label: String,
} in [SelectContent] with option; } in [SelectContent] with option;
param { param {
@ -368,7 +387,7 @@ declare_elements!{
thead in [TableContent] with tr; thead in [TableContent] with tr;
tr in [TableContent] with TableColumnContent; tr in [TableContent] with TableColumnContent;
track { track {
default: Bool, default: bool,
kind: VideoKind, kind: VideoKind,
label: String, label: String,
src: Uri, src: Uri,
@ -386,7 +405,7 @@ declare_elements!{
loop: isize, loop: isize,
scrollamount: usize, scrollamount: usize,
scrolldelay: usize, scrolldelay: usize,
truespeed: Bool, truespeed: bool,
vspace: String, // FIXME size vspace: String, // FIXME size
width: String, // FIXME size width: String, // FIXME size
} in [FlowContent, PhrasingContent] with PhrasingContent; } in [FlowContent, PhrasingContent] with PhrasingContent;