The nastiest element declaration macro.
This commit is contained in:
parent
3bc444068c
commit
4f8b4e2b20
|
@ -4,10 +4,26 @@ extern crate proc_macro;
|
||||||
|
|
||||||
use pom::combinator::*;
|
use pom::combinator::*;
|
||||||
use pom::{Error, Parser};
|
use pom::{Error, Parser};
|
||||||
use proc_macro2::{Group, Ident, Literal, TokenStream, TokenTree};
|
use proc_macro2::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn required_children(element: &str) -> &[&str] {
|
||||||
|
match element {
|
||||||
|
"html" => &["head", "body"],
|
||||||
|
"head" => &["title"],
|
||||||
|
_ => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn global_attrs(span: Span) -> HashMap<Ident, TokenStream> {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
let mut insert = |key, value: &str| attrs.insert(Ident::new(key, span), value.parse().unwrap());
|
||||||
|
insert("id", "crate::elements::CssId");
|
||||||
|
insert("class", "crate::elements::CssClass");
|
||||||
|
attrs
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Node {
|
enum Node {
|
||||||
Element(Element),
|
Element(Element),
|
||||||
|
@ -19,8 +35,8 @@ impl Node {
|
||||||
fn into_token_stream(self) -> TokenStream {
|
fn into_token_stream(self) -> TokenStream {
|
||||||
match self {
|
match self {
|
||||||
Node::Element(el) => el.into_token_stream(),
|
Node::Element(el) => el.into_token_stream(),
|
||||||
Node::Text(_) => panic!("top level must be an element"),
|
Node::Text(text) => quote!(typed_html::elements::TextNode::new(#text.to_string())),
|
||||||
Node::Block(_) => panic!("top level must be an element"),
|
Node::Block(_) => panic!("cannot have a block in this position"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,16 +45,19 @@ impl Node {
|
||||||
Node::Element(el) => {
|
Node::Element(el) => {
|
||||||
let el = el.into_token_stream();
|
let el = el.into_token_stream();
|
||||||
quote!(
|
quote!(
|
||||||
element.append_child(#el);
|
element.children.push(Box::new(#el));
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tx @ Node::Text(_) => {
|
||||||
|
let tx = tx.into_token_stream();
|
||||||
|
quote!(
|
||||||
|
element.children.push(Box::new(#tx));
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Node::Text(tx) => quote!(
|
|
||||||
element.append_child(typed_html::Node::Text(#tx.to_string()));
|
|
||||||
),
|
|
||||||
Node::Block(group) => quote!({
|
Node::Block(group) => quote!({
|
||||||
let iter = #group.into_iter();
|
let iter = #group.into_iter();
|
||||||
for child in iter {
|
for child in iter {
|
||||||
element.append_child(child);
|
element.children.push(Box::new(child));
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -47,13 +66,13 @@ impl Node {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Element {
|
struct Element {
|
||||||
name: String,
|
name: Ident,
|
||||||
attributes: HashMap<String, TokenTree>,
|
attributes: HashMap<Ident, TokenTree>,
|
||||||
children: Vec<Node>,
|
children: Vec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element {
|
impl Element {
|
||||||
fn new(name: String) -> Self {
|
fn new(name: Ident) -> Self {
|
||||||
Element {
|
Element {
|
||||||
name,
|
name,
|
||||||
attributes: HashMap::new(),
|
attributes: HashMap::new(),
|
||||||
|
@ -61,19 +80,54 @@ impl Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_token_stream(self) -> TokenStream {
|
fn into_token_stream(mut self) -> TokenStream {
|
||||||
let name = self.name;
|
let name = self.name;
|
||||||
let keys: Vec<_> = self.attributes.keys().cloned().collect();
|
let name_str = name.to_string();
|
||||||
|
let typename = Ident::new(&format!("Element_{}", &name_str), name.span());
|
||||||
|
let req_names = required_children(&name_str);
|
||||||
|
if req_names.len() > self.children.len() {
|
||||||
|
panic!(
|
||||||
|
"<{}> requires {} children but found only {}",
|
||||||
|
name_str,
|
||||||
|
req_names.len(),
|
||||||
|
self.children.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let keys: Vec<_> = self
|
||||||
|
.attributes
|
||||||
|
.keys()
|
||||||
|
.map(|key| Ident::new(&format!("attr_{}", key), key.span()))
|
||||||
|
.collect();
|
||||||
let values: Vec<TokenTree> = self.attributes.values().cloned().collect();
|
let values: Vec<TokenTree> = self.attributes.values().cloned().collect();
|
||||||
let children = self.children.into_iter().map(Node::into_child_stream);
|
let opt_children = self
|
||||||
|
.children
|
||||||
|
.split_off(req_names.len())
|
||||||
|
.into_iter()
|
||||||
|
.map(Node::into_child_stream);
|
||||||
|
for (index, child) in self.children.iter().enumerate() {
|
||||||
|
match child {
|
||||||
|
Node::Element(_) => (),
|
||||||
|
_ => panic!(
|
||||||
|
"child #{} of {} must be a {} element",
|
||||||
|
index + 1,
|
||||||
|
&name,
|
||||||
|
req_names[index]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let req_children = self.children.into_iter().map(Node::into_token_stream);
|
||||||
quote!(
|
quote!(
|
||||||
{
|
{
|
||||||
let mut element = typed_html::Element::new(#name);
|
let mut element = typed_html::elements::#typename::new(
|
||||||
|
#({ #req_children }),*
|
||||||
|
);
|
||||||
#(
|
#(
|
||||||
element.set_attr(#keys, #values.to_string());
|
element.#keys = Some(#values.into());
|
||||||
)*
|
)*
|
||||||
#(#children)*
|
#(
|
||||||
typed_html::Node::Element(element)
|
#opt_children
|
||||||
|
)*
|
||||||
|
element
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -83,9 +137,9 @@ fn unit<'a, I: 'a, A: Clone>(value: A) -> Combinator<impl Parser<'a, I, Output =
|
||||||
comb(move |_, start| Ok((value.clone(), start)))
|
comb(move |_, start| Ok((value.clone(), start)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn punct<'a>(punct: char) -> Combinator<impl Parser<'a, TokenTree, Output = ()>> {
|
fn punct<'a>(punct: char) -> Combinator<impl Parser<'a, TokenTree, Output = Punct>> {
|
||||||
comb(move |input: &[TokenTree], start| match input.get(start) {
|
comb(move |input: &[TokenTree], start| match input.get(start) {
|
||||||
Some(TokenTree::Punct(p)) if p.as_char() == punct => Ok(((), start + 1)),
|
Some(TokenTree::Punct(p)) if p.as_char() == punct => Ok((p.clone(), start + 1)),
|
||||||
_ => Err(Error::Mismatch {
|
_ => Err(Error::Mismatch {
|
||||||
message: format!("expected {:?}", punct),
|
message: format!("expected {:?}", punct),
|
||||||
position: start,
|
position: start,
|
||||||
|
@ -143,15 +197,15 @@ fn group<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Group>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_start<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
|
fn element_start<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
|
||||||
(punct('<') * ident()).map(|i| Element::new(i.to_string()))
|
(punct('<') * ident()).map(Element::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attr_value<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = TokenTree>> {
|
fn attr_value<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = TokenTree>> {
|
||||||
literal().map(TokenTree::Literal) | ident().map(TokenTree::Ident)
|
literal().map(TokenTree::Literal) | ident().map(TokenTree::Ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attr<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = (String, TokenTree)>> {
|
fn attr<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = (Ident, TokenTree)>> {
|
||||||
ident().map(|i| i.to_string()) + (punct('=') * attr_value())
|
ident() + (punct('=') * attr_value())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_with_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
|
fn element_with_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
|
||||||
|
@ -171,16 +225,17 @@ fn element_open<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>
|
||||||
element_with_attrs() - punct('>')
|
element_with_attrs() - punct('>')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_close<'a>(name: String) -> Combinator<impl Parser<'a, TokenTree, Output = ()>> {
|
fn element_close<'a>(name: &str) -> Combinator<impl Parser<'a, TokenTree, Output = ()>> {
|
||||||
|
let name = name.to_lowercase();
|
||||||
// TODO make this return an error message containing the tag name
|
// TODO make this return an error message containing the tag name
|
||||||
punct('<') * punct('/') * ident_match(name) * punct('>')
|
punct('<') * punct('/') * ident_match(name) * punct('>').discard()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_with_children<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
|
fn element_with_children<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element>> {
|
||||||
(element_open() + comb(node).repeat(0..)).map(|(mut el, children)| {
|
(element_open() + comb(node).repeat(0..)).map(|(mut el, children)| {
|
||||||
el.children.extend(children.into_iter());
|
el.children.extend(children.into_iter());
|
||||||
el
|
el
|
||||||
}) >> |el: Element| element_close(el.name.clone()).expect("closing tag") * unit(el)
|
}) >> |el: Element| element_close(&el.name.to_string()).expect("closing tag") * unit(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node(input: &[TokenTree], start: usize) -> pom::Result<(Node, usize)> {
|
fn node(input: &[TokenTree], start: usize) -> pom::Result<(Node, usize)> {
|
||||||
|
@ -192,15 +247,214 @@ fn node(input: &[TokenTree], start: usize) -> pom::Result<(Node, usize)> {
|
||||||
.parse(input, start)
|
.parse(input, start)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn macro_expand(input: &[TokenTree]) -> pom::Result<TokenStream> {
|
fn expand_html(input: &[TokenTree]) -> pom::Result<TokenStream> {
|
||||||
comb(node).parse(input).map(|el| el.into_token_stream())
|
comb(node).parse(input).map(|el| el.into_token_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Declare {
|
||||||
|
name: Ident,
|
||||||
|
attrs: HashMap<Ident, TokenStream>,
|
||||||
|
req_children: Vec<Ident>,
|
||||||
|
opt_children: Option<TokenStream>,
|
||||||
|
traits: Vec<TokenStream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Declare {
|
||||||
|
fn new(name: Ident) -> Self {
|
||||||
|
Declare {
|
||||||
|
attrs: global_attrs(name.span()),
|
||||||
|
req_children: Vec::new(),
|
||||||
|
opt_children: None,
|
||||||
|
traits: Vec::new(),
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_token_stream(self) -> TokenStream {
|
||||||
|
let elem_name = Ident::new(
|
||||||
|
&format!("Element_{}", self.name.to_string()),
|
||||||
|
self.name.span(),
|
||||||
|
);
|
||||||
|
let name = self.name.to_string();
|
||||||
|
let attr_name: Vec<Ident> = self
|
||||||
|
.attrs
|
||||||
|
.keys()
|
||||||
|
.map(|k| Ident::new(&format!("attr_{}", k.to_string()), k.span()))
|
||||||
|
.collect();
|
||||||
|
let attr_name_2 = attr_name.clone();
|
||||||
|
let attr_name_3 = attr_name.clone();
|
||||||
|
let attr_name_str = self.attrs.keys().map(|k| k.to_string());
|
||||||
|
let attr_type = self.attrs.values().cloned();
|
||||||
|
let req_child_name: Vec<Ident> = self
|
||||||
|
.req_children
|
||||||
|
.iter()
|
||||||
|
.map(|c| Ident::new(&format!("child_{}", c.to_string()), c.span()))
|
||||||
|
.collect();
|
||||||
|
let req_child_name_2 = req_child_name.clone();
|
||||||
|
let req_child_name_3 = req_child_name.clone();
|
||||||
|
let req_child_name_4 = req_child_name.clone();
|
||||||
|
let req_child_type: Vec<Ident> = self
|
||||||
|
.req_children
|
||||||
|
.iter()
|
||||||
|
.map(|c| Ident::new(&format!("Element_{}", c.to_string()), c.span()))
|
||||||
|
.collect();
|
||||||
|
let req_child_type_2 = req_child_type.clone();
|
||||||
|
let construct_children = match self.opt_children {
|
||||||
|
Some(_) => quote!(children: Vec::new()),
|
||||||
|
None => TokenStream::new(),
|
||||||
|
};
|
||||||
|
let print_opt_children = if self.opt_children.is_some() {
|
||||||
|
quote!(for child in &self.children {
|
||||||
|
child.fmt(f)?;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
TokenStream::new()
|
||||||
|
};
|
||||||
|
let print_children = if req_child_name_2.is_empty() {
|
||||||
|
if self.opt_children.is_some() {
|
||||||
|
quote!(if self.children.is_empty() {
|
||||||
|
write!(f, "/>")
|
||||||
|
} else {
|
||||||
|
write!(f, ">")?;
|
||||||
|
#print_opt_children
|
||||||
|
write!(f, "</{}>", #name)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
quote!(write!(f, "/>"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote!(
|
||||||
|
write!(f, ">")?;
|
||||||
|
#(
|
||||||
|
self.#req_child_name_2.fmt(f)?;
|
||||||
|
)*
|
||||||
|
#print_opt_children
|
||||||
|
write!(f, "</{}>", #name)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let children = match self.opt_children {
|
||||||
|
Some(child_constraint) => quote!(pub children: Vec<Box<#child_constraint>>),
|
||||||
|
None => TokenStream::new(),
|
||||||
|
};
|
||||||
|
let trait_for = std::iter::repeat(elem_name.clone());
|
||||||
|
let trait_name = self.traits.into_iter();
|
||||||
|
|
||||||
|
quote!(
|
||||||
|
pub struct #elem_name {
|
||||||
|
#( pub #attr_name: Option<#attr_type>, )*
|
||||||
|
#( pub #req_child_name: #req_child_type, )*
|
||||||
|
#children
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #elem_name {
|
||||||
|
pub fn new(#(#req_child_name_3: #req_child_type_2),*) -> Self {
|
||||||
|
#elem_name {
|
||||||
|
#( #attr_name_2: None, )*
|
||||||
|
#( #req_child_name_4, )*
|
||||||
|
#construct_children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node for #elem_name {}
|
||||||
|
impl Element for #elem_name {}
|
||||||
|
#(
|
||||||
|
impl #trait_name for #trait_for {}
|
||||||
|
)*
|
||||||
|
|
||||||
|
impl std::fmt::Display for #elem_name {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
write!(f, "<{}", #name);
|
||||||
|
#(
|
||||||
|
if let Some(ref value) = self.#attr_name_3 {
|
||||||
|
write!(f, " {}={:?}", #attr_name_str, value.to_string())?;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
#print_children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_spec<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = TokenStream>> {
|
||||||
|
let valid = ident().map(TokenTree::Ident)
|
||||||
|
| punct(':').map(TokenTree::Punct)
|
||||||
|
| punct('<').map(TokenTree::Punct)
|
||||||
|
| punct('>').map(TokenTree::Punct)
|
||||||
|
| punct('&').map(TokenTree::Punct)
|
||||||
|
| punct('\'').map(TokenTree::Punct);
|
||||||
|
valid.repeat(1..).map(|tokens| {
|
||||||
|
let mut stream = TokenStream::new();
|
||||||
|
stream.extend(tokens);
|
||||||
|
stream
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<(Ident, TokenStream)>>>
|
||||||
|
{
|
||||||
|
group().map(|group: Group| {
|
||||||
|
let attr = ident() - punct(':') + type_spec();
|
||||||
|
let input: Vec<TokenTree> = group.stream().into_iter().collect();
|
||||||
|
let result = attr.repeat(0..).parse(&input);
|
||||||
|
result.unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_children<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<Ident>>> {
|
||||||
|
group().map(|group: Group| {
|
||||||
|
let input: Vec<TokenTree> = group.stream().into_iter().collect();
|
||||||
|
let children = (ident() - punct(',').opt()).repeat(0..);
|
||||||
|
let result = children.parse(&input);
|
||||||
|
result.unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_traits<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<TokenStream>>> {
|
||||||
|
group().map(|group: Group| {
|
||||||
|
let input: Vec<TokenTree> = group.stream().into_iter().collect();
|
||||||
|
let traits = (type_spec() - punct(',').opt()).repeat(0..);
|
||||||
|
let result = traits.parse(&input);
|
||||||
|
result.unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Declare>> {
|
||||||
|
(ident() + declare_attrs() + declare_children() + declare_traits().opt() + type_spec().opt())
|
||||||
|
.map(|((((name, attrs), children), traits), child_type)| {
|
||||||
|
let mut declare = Declare::new(name);
|
||||||
|
for (key, value) in attrs {
|
||||||
|
declare.attrs.insert(key, value);
|
||||||
|
}
|
||||||
|
for child in children {
|
||||||
|
declare.req_children.push(child);
|
||||||
|
}
|
||||||
|
declare.opt_children = child_type;
|
||||||
|
declare.traits = traits.unwrap_or_default();
|
||||||
|
declare
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_declare(input: &[TokenTree]) -> pom::Result<TokenStream> {
|
||||||
|
declare().parse(input).map(|decl| decl.into_token_stream())
|
||||||
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let input: TokenStream = input.into();
|
let input: TokenStream = input.into();
|
||||||
let input: Vec<TokenTree> = input.into_iter().collect();
|
let input: Vec<TokenTree> = input.into_iter().collect();
|
||||||
let result = macro_expand(&input);
|
let result = expand_html(&input);
|
||||||
|
match result {
|
||||||
|
Err(error) => panic!("error: {:?}", error),
|
||||||
|
Ok(ts) => ts.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn declare_element(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input: TokenStream = input.into();
|
||||||
|
let input: Vec<TokenTree> = input.into_iter().collect();
|
||||||
|
let result = expand_declare(&input);
|
||||||
match result {
|
match result {
|
||||||
Err(error) => panic!("error: {:?}", error),
|
Err(error) => panic!("error: {:?}", error),
|
||||||
Ok(ts) => ts.into(),
|
Ok(ts) => ts.into(),
|
||||||
|
|
|
@ -6,3 +6,4 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
typed-html-macros = { path = "../macros" }
|
typed-html-macros = { path = "../macros" }
|
||||||
|
http = "0.1.13"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#![feature(proc_macro_hygiene)]
|
#![feature(proc_macro_hygiene)]
|
||||||
|
|
||||||
use typed_html::Node;
|
use typed_html::elements::TextNode;
|
||||||
use typed_html_macros::html;
|
use typed_html_macros::html;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let the_big_question = Node::text("How does she eat?");
|
let the_big_question = TextNode::new("How does she eat?");
|
||||||
let splain_class = "well-actually";
|
let splain_class = "well-actually";
|
||||||
let doc = html!(
|
let doc = html!(
|
||||||
<html>
|
<html>
|
||||||
|
@ -17,7 +17,7 @@ fn main() {
|
||||||
<p class="mind-blown">{the_big_question}</p>
|
<p class="mind-blown">{the_big_question}</p>
|
||||||
{
|
{
|
||||||
(1..4).map(|i| {
|
(1..4).map(|i| {
|
||||||
html!(<p>{ Node::text(format!("Generated paragraph {}", i)) }</p>)
|
html!(<p>{ TextNode::new(format!("Generated paragraph {}", i)) }</p>)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use http::Uri;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use typed_html_macros::declare_element;
|
||||||
|
|
||||||
|
pub type CssId = String;
|
||||||
|
pub type CssClass = String;
|
||||||
|
|
||||||
|
pub trait Node: Display {}
|
||||||
|
pub trait Element: Node {}
|
||||||
|
pub trait MetadataContent: Node {}
|
||||||
|
pub trait FlowContent: Node {}
|
||||||
|
pub trait PhrasingContent: Node {}
|
||||||
|
|
||||||
|
impl IntoIterator for TextNode {
|
||||||
|
type Item = TextNode;
|
||||||
|
type IntoIter = std::vec::IntoIter<TextNode>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
vec![self].into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextNode(String);
|
||||||
|
|
||||||
|
impl TextNode {
|
||||||
|
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||||
|
TextNode(s.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TextNode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node for TextNode {}
|
||||||
|
impl FlowContent for TextNode {}
|
||||||
|
impl PhrasingContent for TextNode {}
|
||||||
|
|
||||||
|
declare_element!(html {
|
||||||
|
xmlns: Uri,
|
||||||
|
} [head, body]);
|
||||||
|
declare_element!(head {} [title] MetadataContent);
|
||||||
|
declare_element!(title {} [] [MetadataContent] TextNode);
|
||||||
|
declare_element!(body {} [] FlowContent);
|
||||||
|
declare_element!(p {} [] [FlowContent] PhrasingContent);
|
||||||
|
declare_element!(h1 {} [] [FlowContent] PhrasingContent);
|
||||||
|
declare_element!(em {} [] [FlowContent, PhrasingContent] PhrasingContent);
|
|
@ -1,3 +1,5 @@
|
||||||
pub mod node;
|
pub mod node;
|
||||||
|
|
||||||
pub use crate::node::{Element, Node};
|
pub use crate::node::{Element, Node};
|
||||||
|
|
||||||
|
pub mod elements;
|
||||||
|
|
Loading…
Reference in New Issue