Such work.
This commit is contained in:
parent
858b16cf34
commit
2806bde178
|
@ -15,8 +15,22 @@ pub fn global_attrs(span: Span) -> StringyMap<Ident, TokenStream> {
|
||||||
{
|
{
|
||||||
let mut insert =
|
let mut insert =
|
||||||
|key, value: &str| attrs.insert(Ident::new(key, span), value.parse().unwrap());
|
|key, value: &str| attrs.insert(Ident::new(key, span), value.parse().unwrap());
|
||||||
insert("id", "crate::elements::CssId");
|
|
||||||
insert("class", "crate::elements::CssClass");
|
insert("id", "crate::types::Id");
|
||||||
|
insert("class", "crate::types::ClassList");
|
||||||
|
|
||||||
|
insert("accesskey", "String");
|
||||||
|
insert("autocapitalize", "String");
|
||||||
|
insert("contenteditable", "bool");
|
||||||
|
insert("contextmenu", "crate::types::Id");
|
||||||
|
insert("dir", "String");
|
||||||
|
insert("draggable", "bool");
|
||||||
|
insert("hidden", "bool");
|
||||||
|
insert("is", "String");
|
||||||
|
insert("lang", "crate::types::LanguageTag");
|
||||||
|
insert("style", "String");
|
||||||
|
insert("tabindex", "isize");
|
||||||
|
insert("title", "String");
|
||||||
}
|
}
|
||||||
attrs
|
attrs
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,14 @@ impl Declare {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn attr_type_name(&self) -> TokenTree {
|
||||||
|
Ident::new(
|
||||||
|
&format!("ElementAttrs_{}", self.name.to_string()),
|
||||||
|
self.name.span(),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn attrs(&self) -> impl Iterator<Item = (TokenTree, TokenStream, TokenTree)> + '_ {
|
fn attrs(&self) -> impl Iterator<Item = (TokenTree, TokenStream, TokenTree)> + '_ {
|
||||||
self.attrs.iter().map(|(key, value)| {
|
self.attrs.iter().map(|(key, value)| {
|
||||||
let attr_name: TokenTree =
|
let attr_name: TokenTree =
|
||||||
|
@ -58,6 +66,7 @@ impl Declare {
|
||||||
|
|
||||||
fn into_token_stream(self) -> TokenStream {
|
fn into_token_stream(self) -> TokenStream {
|
||||||
let mut stream = TokenStream::new();
|
let mut stream = TokenStream::new();
|
||||||
|
stream.extend(self.attr_struct());
|
||||||
stream.extend(self.struct_());
|
stream.extend(self.struct_());
|
||||||
stream.extend(self.impl_());
|
stream.extend(self.impl_());
|
||||||
stream.extend(self.impl_node());
|
stream.extend(self.impl_node());
|
||||||
|
@ -67,16 +76,28 @@ impl Declare {
|
||||||
stream
|
stream
|
||||||
}
|
}
|
||||||
|
|
||||||
fn struct_(&self) -> TokenStream {
|
fn attr_struct(&self) -> TokenStream {
|
||||||
let elem_name = self.elem_name();
|
|
||||||
|
|
||||||
let mut body = TokenStream::new();
|
let mut body = TokenStream::new();
|
||||||
|
|
||||||
for (attr_name, attr_type, _) in self.attrs() {
|
for (attr_name, attr_type, _) in self.attrs() {
|
||||||
body.extend(quote!( pub $attr_name: Option<$attr_type>, ));
|
body.extend(quote!( pub $attr_name: Option<$attr_type>, ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let attr_type_name = self.attr_type_name();
|
||||||
|
quote!(
|
||||||
|
pub struct $attr_type_name {
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn struct_(&self) -> TokenStream {
|
||||||
|
let elem_name = self.elem_name();
|
||||||
|
let attr_type_name = self.attr_type_name();
|
||||||
|
|
||||||
|
let mut body = TokenStream::new();
|
||||||
|
|
||||||
body.extend(quote!(
|
body.extend(quote!(
|
||||||
|
pub attrs: $attr_type_name,
|
||||||
pub data_attributes: std::collections::BTreeMap<String, String>,
|
pub data_attributes: std::collections::BTreeMap<String, String>,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -98,16 +119,22 @@ impl Declare {
|
||||||
|
|
||||||
fn impl_(&self) -> TokenStream {
|
fn impl_(&self) -> TokenStream {
|
||||||
let elem_name = self.elem_name();
|
let elem_name = self.elem_name();
|
||||||
|
let attr_type_name = self.attr_type_name();
|
||||||
|
|
||||||
let mut args = TokenStream::new();
|
let mut args = TokenStream::new();
|
||||||
for (child_name, child_type, _) in self.req_children() {
|
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 body = TokenStream::new();
|
let mut attrs = TokenStream::new();
|
||||||
for (attr_name, _, _) in self.attrs() {
|
for (attr_name, _, _) in self.attrs() {
|
||||||
body.extend(quote!( $attr_name: None, ));
|
attrs.extend(quote!( $attr_name: None, ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut body = TokenStream::new();
|
||||||
|
body.extend(quote!(
|
||||||
|
attrs: $attr_type_name { $attrs },
|
||||||
|
));
|
||||||
body.extend(quote!(data_attributes: std::collections::BTreeMap::new(),));
|
body.extend(quote!(data_attributes: std::collections::BTreeMap::new(),));
|
||||||
for (child_name, _, _) in self.req_children() {
|
for (child_name, _, _) in self.req_children() {
|
||||||
body.extend(quote!( $child_name, ));
|
body.extend(quote!( $child_name, ));
|
||||||
|
@ -135,6 +162,7 @@ impl Declare {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_element(&self) -> TokenStream {
|
fn impl_element(&self) -> TokenStream {
|
||||||
|
let name: TokenTree = Literal::string(&self.name.to_string()).into();
|
||||||
let elem_name = self.elem_name();
|
let elem_name = self.elem_name();
|
||||||
|
|
||||||
let attrs: TokenStream = self.attrs().map(|(_, _, name)| quote!( $name, )).collect();
|
let attrs: TokenStream = self.attrs().map(|(_, _, name)| quote!( $name, )).collect();
|
||||||
|
@ -143,15 +171,37 @@ impl Declare {
|
||||||
.map(|(_, _, name)| quote!( $name, ))
|
.map(|(_, _, name)| quote!( $name, ))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let mut push_attrs = TokenStream::new();
|
||||||
|
for (attr_name, _, attr_str) in self.attrs() {
|
||||||
|
push_attrs.extend(quote!(
|
||||||
|
if let Some(ref value) = self.attrs.$attr_name {
|
||||||
|
out.push(($attr_str.to_string(), value.to_string()));
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
quote!(
|
quote!(
|
||||||
impl ::elements::Element for $elem_name {
|
impl ::elements::Element for $elem_name {
|
||||||
fn attributes() -> &'static [&'static str] {
|
fn name() -> &'static str {
|
||||||
|
$name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attribute_names() -> &'static [&'static str] {
|
||||||
&[ $attrs ]
|
&[ $attrs ]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_children() -> &'static [&'static str] {
|
fn required_children() -> &'static [&'static str] {
|
||||||
&[ $reqs ]
|
&[ $reqs ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn attributes(&self) -> Vec<(String, String)> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
$push_attrs
|
||||||
|
for (key, value) in &self.data_attributes {
|
||||||
|
out.push((format!("data-{}", key), value.to_string()));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -211,7 +261,7 @@ impl Declare {
|
||||||
let mut print_attrs = TokenStream::new();
|
let mut print_attrs = TokenStream::new();
|
||||||
for (attr_name, _, attr_str) in self.attrs() {
|
for (attr_name, _, attr_str) in self.attrs() {
|
||||||
print_attrs.extend(quote!(
|
print_attrs.extend(quote!(
|
||||||
if let Some(ref value) = self.$attr_name {
|
if let Some(ref value) = self.attrs.$attr_name {
|
||||||
write!(f, " {}={:?}", $attr_str, value.to_string())?;
|
write!(f, " {}={:?}", $attr_str, value.to_string())?;
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
@ -237,13 +287,14 @@ impl Declare {
|
||||||
fn declare_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<(Ident, TokenStream)>>>
|
fn declare_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Vec<(Ident, TokenStream)>>>
|
||||||
{
|
{
|
||||||
group().map(|group: Group| -> Vec<(Ident, TokenStream)> {
|
group().map(|group: Group| -> Vec<(Ident, TokenStream)> {
|
||||||
let attr = ident() - punct(':') + type_spec();
|
let attr = ident() - punct(':') + type_spec() - punct(',').opt();
|
||||||
let parser = attr.repeat(0..);
|
let parser = attr.repeat(0..);
|
||||||
let input: Vec<TokenTree> = group.stream().into_iter().collect();
|
let input: Vec<TokenTree> = group.stream().into_iter().collect();
|
||||||
// FIXME the borrow checker won't let me use plain &input, it seems like a bug.
|
// FIXME the borrow checker won't let me use plain &input, it seems like a bug.
|
||||||
// It works in Rust 2018, so please get rid of this unsafe block when it stabilises.
|
// It works in Rust 2018, so please get rid of this unsafe block when it stabilises.
|
||||||
let result = parser.parse(unsafe { &*(input.as_slice() as *const _) });
|
parser
|
||||||
result.unwrap()
|
.parse(unsafe { &*(input.as_slice() as *const _) })
|
||||||
|
.unwrap()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use pom::combinator::*;
|
use pom::combinator::*;
|
||||||
use pom::Parser;
|
use pom::Parser;
|
||||||
use proc_macro::{quote, Group, Ident, Literal, TokenStream, TokenTree};
|
use proc_macro::{quote, Delimiter, Group, Ident, Literal, TokenStream, TokenTree};
|
||||||
|
|
||||||
use config::required_children;
|
use config::required_children;
|
||||||
use map::StringyMap;
|
use map::StringyMap;
|
||||||
|
@ -72,6 +72,25 @@ fn extract_data_attrs(attrs: &mut StringyMap<Ident, TokenTree>) -> StringyMap<St
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_value(value: &TokenTree) -> TokenStream {
|
||||||
|
match value {
|
||||||
|
TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => {
|
||||||
|
let content = g.stream();
|
||||||
|
quote!( [ $content ] )
|
||||||
|
}
|
||||||
|
TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis => {
|
||||||
|
let content = g.stream();
|
||||||
|
quote!( ( $content ) )
|
||||||
|
}
|
||||||
|
v => v.clone().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_string_literal(literal: &Literal) -> bool {
|
||||||
|
// This is the worst API
|
||||||
|
literal.to_string().starts_with('"')
|
||||||
|
}
|
||||||
|
|
||||||
impl Element {
|
impl Element {
|
||||||
fn new(name: Ident) -> Self {
|
fn new(name: Ident) -> Self {
|
||||||
Element {
|
Element {
|
||||||
|
@ -97,8 +116,9 @@ impl Element {
|
||||||
let data_attrs = extract_data_attrs(&mut self.attributes);
|
let data_attrs = extract_data_attrs(&mut self.attributes);
|
||||||
let attrs = self.attributes.iter().map(|(key, value)| {
|
let attrs = self.attributes.iter().map(|(key, value)| {
|
||||||
(
|
(
|
||||||
|
key.to_string(),
|
||||||
TokenTree::Ident(Ident::new(&format!("attr_{}", key), key.span())),
|
TokenTree::Ident(Ident::new(&format!("attr_{}", key), key.span())),
|
||||||
value.clone(),
|
value,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let opt_children = self
|
let opt_children = self
|
||||||
|
@ -109,10 +129,35 @@ impl Element {
|
||||||
let req_children = self.children.into_iter().map(Node::into_token_stream);
|
let req_children = self.children.into_iter().map(Node::into_token_stream);
|
||||||
|
|
||||||
let mut body = TokenStream::new();
|
let mut body = TokenStream::new();
|
||||||
for (key, value) in attrs {
|
for (attr_str, key, value) in attrs {
|
||||||
body.extend(quote!(
|
match value {
|
||||||
element.$key = Some($value.into());
|
TokenTree::Literal(l) if is_string_literal(l) => {
|
||||||
));
|
let value = value.clone();
|
||||||
|
let tag_name: TokenTree = Literal::string(&name_str).into();
|
||||||
|
let attr_str: TokenTree = Literal::string(&attr_str).into();
|
||||||
|
let span = value.span();
|
||||||
|
let pos = format!(
|
||||||
|
"{}:{}:{}",
|
||||||
|
span.source_file().path().to_str().unwrap_or("unknown"),
|
||||||
|
span.start().line,
|
||||||
|
span.start().column
|
||||||
|
);
|
||||||
|
let pos_str: TokenTree = Literal::string(&pos).into();
|
||||||
|
body.extend(quote!(
|
||||||
|
element.attrs.$key = Some($value.parse().unwrap_or_else(|err| {
|
||||||
|
eprintln!("ERROR: {}: <{} {}={:?}> attribute value was not accepted: {:?}",
|
||||||
|
$pos_str, $tag_name, $attr_str, $value, err);
|
||||||
|
panic!();
|
||||||
|
}));
|
||||||
|
));
|
||||||
|
}
|
||||||
|
value => {
|
||||||
|
let value = process_value(value);
|
||||||
|
body.extend(quote!(
|
||||||
|
element.attrs.$key = Some(std::convert::TryInto::try_into($value).unwrap());
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (key, value) in data_attrs
|
for (key, value) in data_attrs
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -144,7 +189,7 @@ fn element_start<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Element
|
||||||
}
|
}
|
||||||
|
|
||||||
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) | dotted_ident() | group().map(TokenTree::Group)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attr<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = (Ident, TokenTree)>> {
|
fn attr<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = (Ident, TokenTree)>> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use pom::combinator::*;
|
use pom::combinator::*;
|
||||||
use pom::{Error, Parser};
|
use pom::{Error, Parser};
|
||||||
use proc_macro::{Group, Ident, Literal, Punct, TokenStream, TokenTree};
|
use proc_macro::{Delimiter, Group, Ident, Literal, Punct, TokenStream, TokenTree};
|
||||||
|
|
||||||
pub fn unit<'a, I: 'a, A: Clone>(value: A) -> Combinator<impl Parser<'a, I, Output = A>> {
|
pub fn unit<'a, I: 'a, A: Clone>(value: A) -> Combinator<impl Parser<'a, I, Output = A>> {
|
||||||
comb(move |_, start| Ok((value.clone(), start)))
|
comb(move |_, start| Ok((value.clone(), start)))
|
||||||
|
@ -81,6 +81,20 @@ pub fn type_spec<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = TokenSt
|
||||||
valid.repeat(1..).collect().map(to_stream)
|
valid.repeat(1..).collect().map(to_stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dotted_ident<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = TokenTree>> {
|
||||||
|
(ident()
|
||||||
|
+ ((punct('.') + ident()).discard() | (punct(':').repeat(2) + ident()).discard())
|
||||||
|
.repeat(0..))
|
||||||
|
.collect()
|
||||||
|
.map(|tokens| {
|
||||||
|
if tokens.len() == 1 {
|
||||||
|
tokens[0].clone()
|
||||||
|
} else {
|
||||||
|
Group::new(Delimiter::Brace, to_stream(tokens)).into()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Read a sequence of idents and dashes, and merge them into a single ident
|
/// Read a sequence of idents and dashes, and merge them into a single ident
|
||||||
/// with the dashes replaced by underscores.
|
/// with the dashes replaced by underscores.
|
||||||
pub fn html_ident<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Ident>> {
|
pub fn html_ident<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Ident>> {
|
||||||
|
|
|
@ -5,4 +5,9 @@ authors = ["Bodil Stokke <bodil@bodil.org>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
typed-html-macros = { path = "../macros" }
|
typed-html-macros = { path = "../macros" }
|
||||||
|
strum = "0.11.0"
|
||||||
|
strum_macros = "0.11.0"
|
||||||
|
mime = "0.3.12"
|
||||||
|
language-tags = "0.2.2"
|
||||||
|
enumset = "0.3.12"
|
||||||
http = "0.1.13"
|
http = "0.1.13"
|
||||||
|
|
|
@ -1,23 +1,33 @@
|
||||||
|
#![feature(try_from)]
|
||||||
#![feature(proc_macro_hygiene)]
|
#![feature(proc_macro_hygiene)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate typed_html;
|
extern crate typed_html;
|
||||||
extern crate typed_html_macros;
|
extern crate typed_html_macros;
|
||||||
|
|
||||||
|
use typed_html::types::*;
|
||||||
use typed_html_macros::html;
|
use typed_html_macros::html;
|
||||||
|
|
||||||
|
struct Foo {
|
||||||
|
foo: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let the_big_question = text!("How does she eat?");
|
let the_big_question = text!("How does she eat?");
|
||||||
let splain_class = "well-actually";
|
let splain_class = "well-actually";
|
||||||
|
let wibble = Foo { foo: "welp" };
|
||||||
let doc = html!(
|
let doc = html!(
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>"Hello Kitty!"</title>
|
<title>"Hello Kitty!"</title>
|
||||||
|
<link rel=LinkType::StyleSheet href="lol.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 data-lol="foo">"Hello Kitty!"</h1>
|
<h1 data-lol="omg" data-foo=wibble.foo>"Hello Kitty!"</h1>
|
||||||
<p class=splain_class>"She is not a cat. She is a human girl."</p>
|
<p class=splain_class>
|
||||||
<p class="mind-blown">{the_big_question}</p>
|
"She is not a "<em>"cat"</em>". She is a "<em>"human girl"</em>"."
|
||||||
|
</p>
|
||||||
|
<p class=["foo", "bar"]>{the_big_question}</p>
|
||||||
{
|
{
|
||||||
(1..4).map(|i| {
|
(1..4).map(|i| {
|
||||||
html!(<p>{ text!("{}. Ceci n'est pas une chatte.", i) }</p>)
|
html!(<p>{ text!("{}. Ceci n'est pas une chatte.", i) }</p>)
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use http::Uri;
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use typed_html_macros::declare_element;
|
use typed_html_macros::declare_element;
|
||||||
|
|
||||||
pub type CssId = String;
|
use super::types::*;
|
||||||
pub type CssClass = String;
|
|
||||||
|
|
||||||
pub trait Node: Display {}
|
pub trait Node: Display {}
|
||||||
|
|
||||||
pub trait Element: Node {
|
pub trait Element: Node {
|
||||||
fn attributes() -> &'static [&'static str];
|
fn name() -> &'static str;
|
||||||
|
fn attribute_names() -> &'static [&'static str];
|
||||||
fn required_children() -> &'static [&'static str];
|
fn required_children() -> &'static [&'static str];
|
||||||
|
fn attributes(&self) -> Vec<(String, String)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MetadataContent: Node {}
|
pub trait MetadataContent: Node {}
|
||||||
|
@ -69,8 +69,61 @@ declare_element!(html {
|
||||||
xmlns: Uri,
|
xmlns: Uri,
|
||||||
} [head, body]);
|
} [head, body]);
|
||||||
declare_element!(head {} [title] MetadataContent);
|
declare_element!(head {} [title] MetadataContent);
|
||||||
declare_element!(title {} [] [MetadataContent] TextNode);
|
|
||||||
declare_element!(body {} [] FlowContent);
|
declare_element!(body {} [] FlowContent);
|
||||||
|
|
||||||
|
// Metadata content
|
||||||
|
declare_element!(base {
|
||||||
|
href: Uri,
|
||||||
|
target: String,
|
||||||
|
} [] [MetadataContent]);
|
||||||
|
declare_element!(link {
|
||||||
|
as: Mime,
|
||||||
|
crossorigin: CrossOrigin,
|
||||||
|
href: Uri,
|
||||||
|
hreflang: LanguageTag,
|
||||||
|
media: String, // FIXME media query
|
||||||
|
rel: LinkType,
|
||||||
|
sizes: String, // FIXME
|
||||||
|
title: String, // FIXME
|
||||||
|
type: Mime,
|
||||||
|
} [] [MetadataContent]);
|
||||||
|
declare_element!(meta {
|
||||||
|
charset: String, // FIXME IANA standard names
|
||||||
|
content: String,
|
||||||
|
http_equiv: String, // FIXME string enum
|
||||||
|
name: String, // FIXME string enum
|
||||||
|
} [] [MetadataContent]);
|
||||||
|
declare_element!(style {
|
||||||
|
type: Mime,
|
||||||
|
media: String, // FIXME media query
|
||||||
|
nonce: String, // bigint?
|
||||||
|
title: String, // FIXME
|
||||||
|
} [] [MetadataContent] TextNode);
|
||||||
|
declare_element!(title {} [] [MetadataContent] TextNode);
|
||||||
|
|
||||||
|
// Flow content
|
||||||
|
declare_element!(div {} [] [FlowContent] FlowContent);
|
||||||
declare_element!(p {} [] [FlowContent] PhrasingContent);
|
declare_element!(p {} [] [FlowContent] PhrasingContent);
|
||||||
declare_element!(h1 {} [] [FlowContent] PhrasingContent);
|
declare_element!(h1 {} [] [FlowContent] PhrasingContent);
|
||||||
|
declare_element!(h2 {} [] [FlowContent] PhrasingContent);
|
||||||
|
declare_element!(h3 {} [] [FlowContent] PhrasingContent);
|
||||||
|
declare_element!(h4 {} [] [FlowContent] PhrasingContent);
|
||||||
|
declare_element!(h5 {} [] [FlowContent] PhrasingContent);
|
||||||
|
declare_element!(h6 {} [] [FlowContent] PhrasingContent);
|
||||||
declare_element!(em {} [] [FlowContent, PhrasingContent] PhrasingContent);
|
declare_element!(em {} [] [FlowContent, PhrasingContent] PhrasingContent);
|
||||||
|
|
||||||
|
// Don't @ me
|
||||||
|
declare_element!(blink {} [] [FlowContent, PhrasingContent] PhrasingContent);
|
||||||
|
declare_element!(marquee {
|
||||||
|
behavior: String, // FIXME enum
|
||||||
|
bgcolor: String, // FIXME colour
|
||||||
|
direction: String, // FIXME direction enum
|
||||||
|
height: String, // FIXME size
|
||||||
|
hspace: String, // FIXME size
|
||||||
|
loop: isize,
|
||||||
|
scrollamount: usize,
|
||||||
|
scrolldelay: usize,
|
||||||
|
truespeed: bool,
|
||||||
|
vspace: String, // FIXME size
|
||||||
|
width: String, // FIXME size
|
||||||
|
} [] [FlowContent, PhrasingContent] PhrasingContent);
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
|
#![feature(try_from)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate enumset;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate strum_macros;
|
||||||
|
|
||||||
extern crate http;
|
extern crate http;
|
||||||
|
extern crate language_tags;
|
||||||
|
extern crate mime;
|
||||||
|
extern crate strum;
|
||||||
extern crate typed_html_macros;
|
extern crate typed_html_macros;
|
||||||
|
|
||||||
pub mod elements;
|
pub mod elements;
|
||||||
|
pub mod types;
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt::{Display, Error, Formatter};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use super::Id;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub struct Class(String);
|
||||||
|
|
||||||
|
impl Class {
|
||||||
|
// Construct a new class name from a string.
|
||||||
|
//
|
||||||
|
// Returns `None` if the provided string is invalid.
|
||||||
|
pub fn try_new<S: Into<String>>(id: S) -> Result<Self, &'static str> {
|
||||||
|
let id = id.into();
|
||||||
|
{
|
||||||
|
let mut chars = id.chars();
|
||||||
|
match chars.next() {
|
||||||
|
None => return Err("class name cannot be empty"),
|
||||||
|
Some(c) if !c.is_alphabetic() => {
|
||||||
|
return Err("class name must start with an alphabetic character")
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
for c in chars {
|
||||||
|
if !c.is_alphanumeric() && c != '_' && c != '-' && c != '.' {
|
||||||
|
return Err(
|
||||||
|
"class name can only contain alphanumerics, dash, dot and underscore",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Class(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a new class name from a string.
|
||||||
|
//
|
||||||
|
// Panics if the provided string is invalid.
|
||||||
|
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||||
|
let id = id.into();
|
||||||
|
Self::try_new(id.clone()).unwrap_or_else(|err| {
|
||||||
|
panic!(
|
||||||
|
"typed_html::types::Class: {:?} is not a valid class name: {}",
|
||||||
|
id, err
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Class {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for Class {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Id> for Class {
|
||||||
|
fn from(id: Id) -> Self {
|
||||||
|
Class(id.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Class {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Class {
|
||||||
|
type Target = String;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::fmt::{Debug, Display, Error, Formatter};
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use super::Class;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ClassList(BTreeSet<Class>);
|
||||||
|
|
||||||
|
impl ClassList {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ClassList(BTreeSet::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClassList {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<Class> for ClassList {
|
||||||
|
fn from_iter<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Class>,
|
||||||
|
{
|
||||||
|
ClassList(iter.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromIterator<&'a Class> for ClassList {
|
||||||
|
fn from_iter<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a Class>,
|
||||||
|
{
|
||||||
|
ClassList(iter.into_iter().cloned().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<String> for ClassList {
|
||||||
|
fn from_iter<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = String>,
|
||||||
|
{
|
||||||
|
ClassList(iter.into_iter().map(Class::new).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromIterator<&'a str> for ClassList {
|
||||||
|
fn from_iter<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a str>,
|
||||||
|
{
|
||||||
|
ClassList(iter.into_iter().map(Class::new).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for ClassList {
|
||||||
|
fn from(s: &'a str) -> Self {
|
||||||
|
Self::from_iter(s.split_whitespace().map(Class::new))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ClassList {
|
||||||
|
type Target = BTreeSet<Class>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for ClassList {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ClassList {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
let mut it = self.0.iter().peekable();
|
||||||
|
while let Some(class) = it.next() {
|
||||||
|
Display::fmt(class, f)?;
|
||||||
|
if it.peek().is_some() {
|
||||||
|
Display::fmt(" ", f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for ClassList {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
f.debug_list().entries(self.0.iter()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str)> for ClassList {
|
||||||
|
fn from(s: (&str, &str)) -> Self {
|
||||||
|
let mut list = Self::new();
|
||||||
|
list.insert(Class::new(s.0));
|
||||||
|
list.insert(Class::new(s.1));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str, &str)> for ClassList {
|
||||||
|
fn from(s: (&str, &str, &str)) -> Self {
|
||||||
|
let mut list = Self::new();
|
||||||
|
list.insert(Class::new(s.0));
|
||||||
|
list.insert(Class::new(s.1));
|
||||||
|
list.insert(Class::new(s.2));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str, &str, &str)> for ClassList {
|
||||||
|
fn from(s: (&str, &str, &str, &str)) -> Self {
|
||||||
|
let mut list = Self::new();
|
||||||
|
list.insert(Class::new(s.0));
|
||||||
|
list.insert(Class::new(s.1));
|
||||||
|
list.insert(Class::new(s.2));
|
||||||
|
list.insert(Class::new(s.3));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str, &str, &str, &str)> for ClassList {
|
||||||
|
fn from(s: (&str, &str, &str, &str, &str)) -> Self {
|
||||||
|
let mut list = Self::new();
|
||||||
|
list.insert(Class::new(s.0));
|
||||||
|
list.insert(Class::new(s.1));
|
||||||
|
list.insert(Class::new(s.2));
|
||||||
|
list.insert(Class::new(s.3));
|
||||||
|
list.insert(Class::new(s.4));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str, &str, &str, &str, &str)> for ClassList {
|
||||||
|
fn from(s: (&str, &str, &str, &str, &str, &str)) -> Self {
|
||||||
|
let mut list = Self::new();
|
||||||
|
list.insert(Class::new(s.0));
|
||||||
|
list.insert(Class::new(s.1));
|
||||||
|
list.insert(Class::new(s.2));
|
||||||
|
list.insert(Class::new(s.3));
|
||||||
|
list.insert(Class::new(s.4));
|
||||||
|
list.insert(Class::new(s.5));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str, &str, &str, &str, &str, &str)> for ClassList {
|
||||||
|
fn from(s: (&str, &str, &str, &str, &str, &str, &str)) -> Self {
|
||||||
|
let mut list = Self::new();
|
||||||
|
list.insert(Class::new(s.0));
|
||||||
|
list.insert(Class::new(s.1));
|
||||||
|
list.insert(Class::new(s.2));
|
||||||
|
list.insert(Class::new(s.3));
|
||||||
|
list.insert(Class::new(s.4));
|
||||||
|
list.insert(Class::new(s.5));
|
||||||
|
list.insert(Class::new(s.6));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str, &str, &str, &str, &str, &str, &str)> for ClassList {
|
||||||
|
fn from(s: (&str, &str, &str, &str, &str, &str, &str, &str)) -> Self {
|
||||||
|
let mut list = Self::new();
|
||||||
|
list.insert(Class::new(s.0));
|
||||||
|
list.insert(Class::new(s.1));
|
||||||
|
list.insert(Class::new(s.2));
|
||||||
|
list.insert(Class::new(s.3));
|
||||||
|
list.insert(Class::new(s.4));
|
||||||
|
list.insert(Class::new(s.5));
|
||||||
|
list.insert(Class::new(s.6));
|
||||||
|
list.insert(Class::new(s.7));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! classlist_from_array {
|
||||||
|
($num:tt) => {
|
||||||
|
impl From<[&str; $num]> for ClassList {
|
||||||
|
fn from(s: [&str; $num]) -> Self {
|
||||||
|
Self::from_iter(s.into_iter().map(|s| Class::new(*s)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
classlist_from_array!(1);
|
||||||
|
classlist_from_array!(2);
|
||||||
|
classlist_from_array!(3);
|
||||||
|
classlist_from_array!(4);
|
||||||
|
classlist_from_array!(5);
|
||||||
|
classlist_from_array!(6);
|
||||||
|
classlist_from_array!(7);
|
||||||
|
classlist_from_array!(8);
|
||||||
|
classlist_from_array!(9);
|
||||||
|
classlist_from_array!(10);
|
||||||
|
classlist_from_array!(11);
|
||||||
|
classlist_from_array!(12);
|
||||||
|
classlist_from_array!(13);
|
||||||
|
classlist_from_array!(14);
|
||||||
|
classlist_from_array!(15);
|
||||||
|
classlist_from_array!(16);
|
||||||
|
classlist_from_array!(17);
|
||||||
|
classlist_from_array!(18);
|
||||||
|
classlist_from_array!(19);
|
||||||
|
classlist_from_array!(20);
|
||||||
|
classlist_from_array!(21);
|
||||||
|
classlist_from_array!(22);
|
||||||
|
classlist_from_array!(23);
|
||||||
|
classlist_from_array!(24);
|
||||||
|
classlist_from_array!(25);
|
||||||
|
classlist_from_array!(26);
|
||||||
|
classlist_from_array!(27);
|
||||||
|
classlist_from_array!(28);
|
||||||
|
classlist_from_array!(29);
|
||||||
|
classlist_from_array!(30);
|
||||||
|
classlist_from_array!(31);
|
||||||
|
classlist_from_array!(32);
|
|
@ -0,0 +1,82 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt::{Display, Error, Formatter};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use super::Class;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub struct Id(String);
|
||||||
|
|
||||||
|
impl Id {
|
||||||
|
// Construct a new ID from a string.
|
||||||
|
//
|
||||||
|
// Returns `None` if the provided string is invalid.
|
||||||
|
pub fn try_new<S: Into<String>>(id: S) -> Result<Self, &'static str> {
|
||||||
|
let id = id.into();
|
||||||
|
{
|
||||||
|
let mut chars = id.chars();
|
||||||
|
match chars.next() {
|
||||||
|
None => return Err("ID cannot be empty"),
|
||||||
|
Some(c) if !c.is_alphabetic() => {
|
||||||
|
return Err("ID must start with an alphabetic character")
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
for c in chars {
|
||||||
|
if !c.is_alphanumeric() && c != '_' && c != '-' && c != '.' {
|
||||||
|
return Err("ID can only contain alphanumerics, dash, dot and underscore");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Id(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a new ID from a string.
|
||||||
|
//
|
||||||
|
// Panics if the provided string is invalid.
|
||||||
|
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||||
|
let id = id.into();
|
||||||
|
Self::try_new(id.clone()).unwrap_or_else(|err| {
|
||||||
|
panic!("typed_html::types::Id: {:?} is not a valid ID: {}", id, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Id {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for Id {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Class> for Id {
|
||||||
|
fn from(c: Class) -> Self {
|
||||||
|
Id(c.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a Class> for Id {
|
||||||
|
fn from(c: &'a Class) -> Self {
|
||||||
|
Id(c.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Id {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Id {
|
||||||
|
type Target = String;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
mod class;
|
||||||
|
pub use self::class::Class;
|
||||||
|
|
||||||
|
mod id;
|
||||||
|
pub use self::id::Id;
|
||||||
|
|
||||||
|
mod classlist;
|
||||||
|
pub use self::classlist::ClassList;
|
||||||
|
|
||||||
|
pub use http::Uri;
|
||||||
|
pub use language_tags::LanguageTag;
|
||||||
|
pub use mime::Mime;
|
||||||
|
|
||||||
|
#[derive(EnumString, Display)]
|
||||||
|
pub enum CrossOrigin {
|
||||||
|
#[strum(to_string = "anonymous")]
|
||||||
|
Anonymous,
|
||||||
|
#[strum(to_string = "use-credentials")]
|
||||||
|
UseCredentials,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_set_type! {
|
||||||
|
#[derive(EnumString, Display)]
|
||||||
|
pub enum LinkType {
|
||||||
|
#[strum(to_string = "alternate")]
|
||||||
|
Alternate,
|
||||||
|
#[strum(to_string = "author")]
|
||||||
|
Author,
|
||||||
|
#[strum(to_string = "bookmark")]
|
||||||
|
Bookmark,
|
||||||
|
#[strum(to_string = "canonical")]
|
||||||
|
Canonical,
|
||||||
|
#[strum(to_string = "external")]
|
||||||
|
External,
|
||||||
|
#[strum(to_string = "help")]
|
||||||
|
Help,
|
||||||
|
#[strum(to_string = "icon")]
|
||||||
|
Icon,
|
||||||
|
#[strum(to_string = "license")]
|
||||||
|
License,
|
||||||
|
#[strum(to_string = "manifest")]
|
||||||
|
Manifest,
|
||||||
|
#[strum(to_string = "modulepreload")]
|
||||||
|
ModulePreload,
|
||||||
|
#[strum(to_string = "next")]
|
||||||
|
Next,
|
||||||
|
#[strum(to_string = "nofollow")]
|
||||||
|
NoFollow,
|
||||||
|
#[strum(to_string = "noopener")]
|
||||||
|
NoOpener,
|
||||||
|
#[strum(to_string = "noreferrer")]
|
||||||
|
NoReferrer,
|
||||||
|
#[strum(to_string = "pingback")]
|
||||||
|
PingBack,
|
||||||
|
#[strum(to_string = "prefetch")]
|
||||||
|
Prefetch,
|
||||||
|
#[strum(to_string = "preload")]
|
||||||
|
Preload,
|
||||||
|
#[strum(to_string = "prev")]
|
||||||
|
Prev,
|
||||||
|
#[strum(to_string = "search")]
|
||||||
|
Search,
|
||||||
|
#[strum(to_string = "shortlink")]
|
||||||
|
ShortLink,
|
||||||
|
#[strum(to_string = "stylesheet")]
|
||||||
|
StyleSheet,
|
||||||
|
#[strum(to_string = "tag")]
|
||||||
|
Tag,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue