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 =
|
||||
|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
|
||||
}
|
||||
|
|
|
@ -35,6 +35,14 @@ impl Declare {
|
|||
.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)> + '_ {
|
||||
self.attrs.iter().map(|(key, value)| {
|
||||
let attr_name: TokenTree =
|
||||
|
@ -58,6 +66,7 @@ impl Declare {
|
|||
|
||||
fn into_token_stream(self) -> TokenStream {
|
||||
let mut stream = TokenStream::new();
|
||||
stream.extend(self.attr_struct());
|
||||
stream.extend(self.struct_());
|
||||
stream.extend(self.impl_());
|
||||
stream.extend(self.impl_node());
|
||||
|
@ -67,16 +76,28 @@ impl Declare {
|
|||
stream
|
||||
}
|
||||
|
||||
fn struct_(&self) -> TokenStream {
|
||||
let elem_name = self.elem_name();
|
||||
|
||||
fn attr_struct(&self) -> TokenStream {
|
||||
let mut body = TokenStream::new();
|
||||
|
||||
for (attr_name, attr_type, _) in self.attrs() {
|
||||
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!(
|
||||
pub attrs: $attr_type_name,
|
||||
pub data_attributes: std::collections::BTreeMap<String, String>,
|
||||
));
|
||||
|
||||
|
@ -98,16 +119,22 @@ impl Declare {
|
|||
|
||||
fn impl_(&self) -> TokenStream {
|
||||
let elem_name = self.elem_name();
|
||||
let attr_type_name = self.attr_type_name();
|
||||
|
||||
let mut args = TokenStream::new();
|
||||
for (child_name, child_type, _) in self.req_children() {
|
||||
args.extend(quote!( $child_name: Box<$child_type>, ));
|
||||
}
|
||||
|
||||
let mut body = TokenStream::new();
|
||||
let mut attrs = TokenStream::new();
|
||||
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(),));
|
||||
for (child_name, _, _) in self.req_children() {
|
||||
body.extend(quote!( $child_name, ));
|
||||
|
@ -135,6 +162,7 @@ impl Declare {
|
|||
}
|
||||
|
||||
fn impl_element(&self) -> TokenStream {
|
||||
let name: TokenTree = Literal::string(&self.name.to_string()).into();
|
||||
let elem_name = self.elem_name();
|
||||
|
||||
let attrs: TokenStream = self.attrs().map(|(_, _, name)| quote!( $name, )).collect();
|
||||
|
@ -143,15 +171,37 @@ impl Declare {
|
|||
.map(|(_, _, name)| quote!( $name, ))
|
||||
.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!(
|
||||
impl ::elements::Element for $elem_name {
|
||||
fn attributes() -> &'static [&'static str] {
|
||||
fn name() -> &'static str {
|
||||
$name
|
||||
}
|
||||
|
||||
fn attribute_names() -> &'static [&'static str] {
|
||||
&[ $attrs ]
|
||||
}
|
||||
|
||||
fn required_children() -> &'static [&'static str] {
|
||||
&[ $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();
|
||||
for (attr_name, _, attr_str) in self.attrs() {
|
||||
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())?;
|
||||
}
|
||||
));
|
||||
|
@ -237,13 +287,14 @@ impl Declare {
|
|||
fn declare_attrs<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = 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 input: Vec<TokenTree> = group.stream().into_iter().collect();
|
||||
// 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.
|
||||
let result = parser.parse(unsafe { &*(input.as_slice() as *const _) });
|
||||
result.unwrap()
|
||||
parser
|
||||
.parse(unsafe { &*(input.as_slice() as *const _) })
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use pom::combinator::*;
|
||||
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 map::StringyMap;
|
||||
|
@ -72,6 +72,25 @@ fn extract_data_attrs(attrs: &mut StringyMap<Ident, TokenTree>) -> StringyMap<St
|
|||
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 {
|
||||
fn new(name: Ident) -> Self {
|
||||
Element {
|
||||
|
@ -97,8 +116,9 @@ impl Element {
|
|||
let data_attrs = extract_data_attrs(&mut self.attributes);
|
||||
let attrs = self.attributes.iter().map(|(key, value)| {
|
||||
(
|
||||
key.to_string(),
|
||||
TokenTree::Ident(Ident::new(&format!("attr_{}", key), key.span())),
|
||||
value.clone(),
|
||||
value,
|
||||
)
|
||||
});
|
||||
let opt_children = self
|
||||
|
@ -109,10 +129,35 @@ impl Element {
|
|||
let req_children = self.children.into_iter().map(Node::into_token_stream);
|
||||
|
||||
let mut body = TokenStream::new();
|
||||
for (key, value) in attrs {
|
||||
body.extend(quote!(
|
||||
element.$key = Some($value.into());
|
||||
));
|
||||
for (attr_str, key, value) in attrs {
|
||||
match value {
|
||||
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
|
||||
.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>> {
|
||||
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)>> {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use pom::combinator::*;
|
||||
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>> {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
/// with the dashes replaced by underscores.
|
||||
pub fn html_ident<'a>() -> Combinator<impl Parser<'a, TokenTree, Output = Ident>> {
|
||||
|
|
|
@ -5,4 +5,9 @@ authors = ["Bodil Stokke <bodil@bodil.org>"]
|
|||
|
||||
[dependencies]
|
||||
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"
|
||||
|
|
|
@ -1,23 +1,33 @@
|
|||
#![feature(try_from)]
|
||||
#![feature(proc_macro_hygiene)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate typed_html;
|
||||
extern crate typed_html_macros;
|
||||
|
||||
use typed_html::types::*;
|
||||
use typed_html_macros::html;
|
||||
|
||||
struct Foo {
|
||||
foo: &'static str,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let the_big_question = text!("How does she eat?");
|
||||
let splain_class = "well-actually";
|
||||
let wibble = Foo { foo: "welp" };
|
||||
let doc = html!(
|
||||
<html>
|
||||
<head>
|
||||
<title>"Hello Kitty!"</title>
|
||||
<link rel=LinkType::StyleSheet href="lol.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1 data-lol="foo">"Hello Kitty!"</h1>
|
||||
<p class=splain_class>"She is not a cat. She is a human girl."</p>
|
||||
<p class="mind-blown">{the_big_question}</p>
|
||||
<h1 data-lol="omg" data-foo=wibble.foo>"Hello Kitty!"</h1>
|
||||
<p class=splain_class>
|
||||
"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| {
|
||||
html!(<p>{ text!("{}. Ceci n'est pas une chatte.", i) }</p>)
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
#![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;
|
||||
use super::types::*;
|
||||
|
||||
pub trait Node: Display {}
|
||||
|
||||
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 attributes(&self) -> Vec<(String, String)>;
|
||||
}
|
||||
|
||||
pub trait MetadataContent: Node {}
|
||||
|
@ -69,8 +69,61 @@ declare_element!(html {
|
|||
xmlns: Uri,
|
||||
} [head, body]);
|
||||
declare_element!(head {} [title] MetadataContent);
|
||||
declare_element!(title {} [] [MetadataContent] TextNode);
|
||||
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!(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);
|
||||
|
||||
// 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 language_tags;
|
||||
extern crate mime;
|
||||
extern crate strum;
|
||||
extern crate typed_html_macros;
|
||||
|
||||
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