From 16f0be33a482455691a3ab15727b5d0ebb867685 Mon Sep 17 00:00:00 2001 From: Bodil Stokke Date: Mon, 12 Nov 2018 23:20:15 +0000 Subject: [PATCH] I'm halfway through the HTML spec. --- macros/src/config.rs | 2 +- typed-html/src/bin/main.rs | 2 +- typed-html/src/elements.rs | 164 +++++++++++++++++++-- typed-html/src/types/class.rs | 8 + typed-html/src/types/classlist.rs | 221 ---------------------------- typed-html/src/types/id.rs | 8 + typed-html/src/types/mod.rs | 199 ++++++++++++++++++++++++- typed-html/src/types/spacedlist.rs | 228 ++++++++++++++++++++++++++++ typed-html/src/types/spacedset.rs | 229 +++++++++++++++++++++++++++++ 9 files changed, 821 insertions(+), 240 deletions(-) delete mode 100644 typed-html/src/types/classlist.rs create mode 100644 typed-html/src/types/spacedlist.rs create mode 100644 typed-html/src/types/spacedset.rs diff --git a/macros/src/config.rs b/macros/src/config.rs index 93bcbb7..8957c1b 100644 --- a/macros/src/config.rs +++ b/macros/src/config.rs @@ -23,7 +23,7 @@ pub fn global_attrs(span: Span) -> StringyMap { insert("autocapitalize", "String"); insert("contenteditable", "bool"); insert("contextmenu", "crate::types::Id"); - insert("dir", "String"); + insert("dir", "crate::types::TextDirection"); insert("draggable", "bool"); insert("hidden", "bool"); insert("is", "String"); diff --git a/typed-html/src/bin/main.rs b/typed-html/src/bin/main.rs index df52532..2a1a235 100644 --- a/typed-html/src/bin/main.rs +++ b/typed-html/src/bin/main.rs @@ -26,7 +26,7 @@ fn main() {

"Hello Kitty!"

- "She is not a ""cat"". She is a ""human girl""." + "She is not a ""cat"". She is a ""human girl""."

{the_big_question}

{ diff --git a/typed-html/src/elements.rs b/typed-html/src/elements.rs index 47f4f3c..542da74 100644 --- a/typed-html/src/elements.rs +++ b/typed-html/src/elements.rs @@ -32,7 +32,17 @@ pub trait Element: Node { pub trait MetadataContent: Node {} pub trait FlowContent: Node {} -pub trait PhrasingContent: Node {} +pub trait SectioningContent: Node {} +pub trait HeadingContent: Node {} +// Phrasing content seems to be entirely a subclass of FlowContent +pub trait PhrasingContent: FlowContent {} +pub trait EmbeddedContent: Node {} +pub trait InteractiveContent: Node {} +pub trait FormContent: Node {} + +// Traits for elements that are more picky about their children +pub trait DescriptionListContent: Node {} +pub trait HGroupContent: Node {} impl IntoIterator for TextNode { type Item = TextNode; @@ -93,7 +103,7 @@ declare_element!(body {} [] FlowContent); // Metadata content declare_element!(base { href: Uri, - target: String, + target: Target, } [] [MetadataContent]); declare_element!(link { as: Mime, @@ -121,15 +131,151 @@ declare_element!(style { declare_element!(title {} [] [MetadataContent] TextNode); // Flow content +declare_element!(a { + download: String, + href: Uri, + hreflang: LanguageTag, + ping: SpacedList, + rel: SpacedList, + target: Target, + type: Mime, +} [] [FlowContent, PhrasingContent, InteractiveContent] FlowContent); +declare_element!(abbr {} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(address {} [] [FlowContent] FlowContent); // FIXME it has additional constraints on FlowContent +declare_element!(article {} [] [FlowContent, SectioningContent] FlowContent); +declare_element!(aside {} [] [FlowContent, SectioningContent] FlowContent); +declare_element!(audio { + autoplay: bool, + controls: bool, + crossorigin: CrossOrigin, + loop: bool, + muted: bool, + preload: Preload, + src: Uri, +} [] [FlowContent, PhrasingContent]); +declare_element!(b {} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(bdo {} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(bdi {} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(blockquote { + cite: Uri, +} [] [FlowContent] FlowContent); +declare_element!(br {} [] [FlowContent, PhrasingContent]); +declare_element!(button { + autofocus: bool, + disabled: bool, + form: Id, + formaction: Uri, + formenctype: FormEncodingType, + formmethod: FormMethod, + formnovalidate: bool, + formtarget: Target, + name: Id, + type: ButtonType, + value: String, +} [] [FlowContent, PhrasingContent, InteractiveContent, FormContent] PhrasingContent); +declare_element!(canvas { + height: usize, + width: usize, +} [] [FlowContent] FlowContent); // FIXME has additional child constraints +declare_element!(cite {} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(code {} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(data { + value: String, +} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(datalist {} [] [FlowContent, PhrasingContent] Element_option); +declare_element!(del { + cite: Uri, + datetime: String, // FIXME should be "a valid date string with an optional time", + // but I have other hells to live in right now. +} [] [FlowContent, PhrasingContent] FlowContent); +declare_element!(details { + open: bool, +} [summary] [FlowContent, SectioningContent, InteractiveContent] FlowContent); +declare_element!(dfn {} [] [FlowContent, PhrasingContent] PhrasingContent); 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!(dl {} [] [FlowContent] DescriptionListContent); declare_element!(em {} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(embed { + height: usize, + src: Uri, + type: Mime, + width: usize, +} [] [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent]); +// FIXME the legend attribute should be optional +declare_element!(fieldset {} [legend] [FlowContent, SectioningContent, FormContent] FlowContent); +// FIXME the figcaption attribute should be optional +declare_element!(figure {} [figcaption] [FlowContent, SectioningContent] FlowContent); +declare_element!(footer {} [] [FlowContent] FlowContent); +declare_element!(form { + accept-charset: SpacedList, + action: Uri, + autocomplete: OnOff, + enctype: FormEncodingType, + method: FormMethod, + name: Id, + novalidate: bool, + target: Target, +} [] [FlowContent] FlowContent); +declare_element!(h1 {} [] [FlowContent, HeadingContent, HGroupContent] PhrasingContent); +declare_element!(h2 {} [] [FlowContent, HeadingContent, HGroupContent] PhrasingContent); +declare_element!(h3 {} [] [FlowContent, HeadingContent, HGroupContent] PhrasingContent); +declare_element!(h4 {} [] [FlowContent, HeadingContent, HGroupContent] PhrasingContent); +declare_element!(h5 {} [] [FlowContent, HeadingContent, HGroupContent] PhrasingContent); +declare_element!(h6 {} [] [FlowContent, HeadingContent, HGroupContent] PhrasingContent); +declare_element!(header {} [] [FlowContent] FlowContent); +declare_element!(hgroup {} [] [FlowContent, HeadingContent] HGroupContent); +declare_element!(hr {} [] [FlowContent]); +declare_element!(i {} [] [FlowContent, PhrasingContent] PhrasingContent); +declare_element!(iframe { + allow: FeaturePolicy, + allowfullscreen: bool, + allowpaymentrequest: bool, + height: usize, + name: Id, + referrerpolicy: ReferrerPolicy, + sandbox: SpacedSet, + src: Uri, + srcdoc: Uri, + width: usize, +} [] [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent] FlowContent); +declare_element!(img { + alt: String, + crossorigin: CrossOrigin, + decoding: ImageDecoding, + height: usize, + ismap: bool, + sizes: SpacedList, // FIXME it's not really just a string + src: Uri, + srcset: String, // FIXME this is much more complicated + usemap: String, // FIXME should be a fragment starting with '#' + width: usize, +} [] [FlowContent, PhrasingContent, EmbeddedContent]); +declare_element!(input { + autocomplete: String, + autofocus: bool, + disabled: bool, + form: Id, + list: Id, + name: Id, + required: bool, + tabindex: usize, + type: InputType, + value: String, +} [] [FlowContent, FormContent, PhrasingContent]); +declare_element!(p {} [] [FlowContent] PhrasingContent); + +// Non-content elements +declare_element!(dd {} [] [DescriptionListContent] FlowContent); +declare_element!(dt {} [] [DescriptionListContent] FlowContent); +declare_element!(figcaption {} [] [] FlowContent); +declare_element!(legend {} [] [] PhrasingContent); +declare_element!(option { + disabled: bool, + label: String, + selected: bool, + value: String, +} [] [] TextNode); +declare_element!(summary {} [] [] PhrasingContent); // Don't @ me declare_element!(blink {} [] [FlowContent, PhrasingContent] PhrasingContent); diff --git a/typed-html/src/types/class.rs b/typed-html/src/types/class.rs index 5de4c98..060e04a 100644 --- a/typed-html/src/types/class.rs +++ b/typed-html/src/types/class.rs @@ -1,6 +1,7 @@ use std::convert::TryFrom; use std::fmt::{Display, Error, Formatter}; use std::ops::Deref; +use std::str::FromStr; use super::Id; @@ -47,6 +48,13 @@ impl Class { } } +impl FromStr for Class { + type Err = &'static str; + fn from_str(s: &str) -> Result { + Class::try_new(s) + } +} + impl TryFrom for Class { type Error = &'static str; fn try_from(s: String) -> Result { diff --git a/typed-html/src/types/classlist.rs b/typed-html/src/types/classlist.rs deleted file mode 100644 index b944187..0000000 --- a/typed-html/src/types/classlist.rs +++ /dev/null @@ -1,221 +0,0 @@ -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); - -impl ClassList { - pub fn new() -> Self { - ClassList(BTreeSet::new()) - } -} - -impl Default for ClassList { - fn default() -> Self { - Self::new() - } -} - -impl FromIterator for ClassList { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - ClassList(iter.into_iter().collect()) - } -} - -impl<'a> FromIterator<&'a Class> for ClassList { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - ClassList(iter.into_iter().cloned().collect()) - } -} - -impl FromIterator for ClassList { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - ClassList(iter.into_iter().map(Class::new).collect()) - } -} - -impl<'a> FromIterator<&'a str> for ClassList { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - 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; - 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); diff --git a/typed-html/src/types/id.rs b/typed-html/src/types/id.rs index e3e5e86..82ad0bb 100644 --- a/typed-html/src/types/id.rs +++ b/typed-html/src/types/id.rs @@ -1,6 +1,7 @@ use std::convert::TryFrom; use std::fmt::{Display, Error, Formatter}; use std::ops::Deref; +use std::str::FromStr; use super::Class; @@ -42,6 +43,13 @@ impl Id { } } +impl FromStr for Id { + type Err = &'static str; + fn from_str(s: &str) -> Result { + Id::try_new(s) + } +} + impl TryFrom for Id { type Error = &'static str; fn try_from(s: String) -> Result { diff --git a/typed-html/src/types/mod.rs b/typed-html/src/types/mod.rs index 4c64ff7..eed78eb 100644 --- a/typed-html/src/types/mod.rs +++ b/typed-html/src/types/mod.rs @@ -4,19 +4,126 @@ pub use self::class::Class; mod id; pub use self::id::Id; -mod classlist; -pub use self::classlist::ClassList; +mod spacedlist; +pub use self::spacedlist::SpacedList; + +mod spacedset; +pub use self::spacedset::SpacedSet; + +pub type ClassList = SpacedSet; 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, +pub type Target = String; +pub type CharacterEncoding = String; +pub type FeaturePolicy = String; + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum ButtonType { + #[strum(to_string = "submit")] + Submit, + #[strum(to_string = "reset")] + Reset, + #[strum(to_string = "button")] + Button, + } +} + +enum_set_type! { + #[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 FormEncodingType { + #[strum(to_string = "application/x-www-form-urlencoded")] + UrlEncoded, + #[strum(to_string = "multipart/form-data")] + FormData, + #[strum(to_string = "text/plain")] + Text, + } +} + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum FormMethod { + #[strum(to_string = "post")] + Post, + #[strum(to_string = "get")] + Get, + } +} + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum ImageDecoding { + #[strum(to_string = "sync")] + Sync, + #[strum(to_string = "async")] + Async, + #[strum(to_string = "auto")] + Auto, + } +} + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum InputType { + #[strum(to_string = "button")] + Button, + #[strum(to_string = "checkbox")] + Checkbox, + #[strum(to_string = "color")] + Color, + #[strum(to_string = "date")] + Date, + #[strum(to_string = "datetime-local")] + DatetimeLocal, + #[strum(to_string = "email")] + Email, + #[strum(to_string = "file")] + File, + #[strum(to_string = "hidden")] + Hidden, + #[strum(to_string = "image")] + Image, + #[strum(to_string = "month")] + Month, + #[strum(to_string = "number")] + Number, + #[strum(to_string = "password")] + Password, + #[strum(to_string = "radio")] + Radio, + #[strum(to_string = "range")] + Range, + #[strum(to_string = "reset")] + Reset, + #[strum(to_string = "search")] + Search, + #[strum(to_string = "submit")] + Submit, + #[strum(to_string = "tel")] + Tel, + #[strum(to_string = "text")] + Text, + #[strum(to_string = "time")] + Time, + #[strum(to_string = "url")] + Url, + #[strum(to_string = "week")] + Week, + } } enum_set_type! { @@ -68,3 +175,79 @@ enum_set_type! { Tag, } } + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum OnOff { + #[strum(to_string = "on")] + On, + #[strum(to_string = "off")] + Off, + } +} + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum Preload { + #[strum(to_string = "none")] + None, + #[strum(to_string = "metadata")] + Metadata, + #[strum(to_string = "auto")] + Auto, + } +} + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum ReferrerPolicy { + #[strum(to_string = "no-referrer")] + NoReferrer, + #[strum(to_string = "no-referrer-when-downgrade")] + NoReferrerWhenDowngrade, + #[strum(to_string = "origin")] + Origin, + #[strum(to_string = "origin-when-cross-origin")] + OriginWhenCrossOrigin, + #[strum(to_string = "unsafe-url")] + UnsafeUrl, + } +} + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum Sandbox { + #[strum(to_string = "allow-forms")] + AllowForms, + #[strum(to_string = "allow-modals")] + AllowModals, + #[strum(to_string = "allow-orientation-lock")] + AllowOrientationLock, + #[strum(to_string = "allow-pointer-lock")] + AllowPointerLock, + #[strum(to_string = "allow-popups")] + AllowPopups, + #[strum(to_string = "allow-popups-to-escape-sandbox")] + AllowPopupsToEscapeSandbox, + #[strum(to_string = "allow-presentation")] + AllowPresentation, + #[strum(to_string = "allow-same-origin")] + AllowSameOrigin, + #[strum(to_string = "allow-scripts")] + AllowScripts, + #[strum(to_string = "allow-top-navigation")] + AllowTopNavigation, + #[strum(to_string = "allow-top-navigation-by-user-navigation")] + AllowTopNavigationByUserNavigation, + } +} + +enum_set_type! { + #[derive(EnumString, Display)] + pub enum TextDirection { + #[strum(to_string = "ltr")] + LeftToRight, + #[strum(to_string = "rtl")] + RightToLeft, + } +} diff --git a/typed-html/src/types/spacedlist.rs b/typed-html/src/types/spacedlist.rs new file mode 100644 index 0000000..53a3904 --- /dev/null +++ b/typed-html/src/types/spacedlist.rs @@ -0,0 +1,228 @@ +use std::fmt::{Debug, Display, Error, Formatter}; +use std::iter::FromIterator; +use std::ops::{Deref, DerefMut}; +use std::str::FromStr; + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SpacedList(Vec); + +impl SpacedList { + pub fn new() -> Self { + SpacedList(Vec::new()) + } +} + +impl Default for SpacedList { + fn default() -> Self { + Self::new() + } +} + +impl FromIterator for SpacedList { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + SpacedList(iter.into_iter().collect()) + } +} + +impl<'a, A: 'a + Clone> FromIterator<&'a A> for SpacedList { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + SpacedList(iter.into_iter().cloned().collect()) + } +} + +impl<'a, A: FromStr> From<&'a str> for SpacedList +where + ::Err: Debug, +{ + fn from(s: &'a str) -> Self { + Self::from_iter(s.split_whitespace().map(|s| FromStr::from_str(s).unwrap())) + } +} + +impl Deref for SpacedList { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SpacedList { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Display for SpacedList { + 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 SpacedList { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + f.debug_list().entries(self.0.iter()).finish() + } +} + +impl From<(&str, &str)> for SpacedList +where + ::Err: Debug, +{ + fn from(s: (&str, &str)) -> Self { + let mut list = Self::new(); + list.push(FromStr::from_str(s.0).unwrap()); + list.push(FromStr::from_str(s.1).unwrap()); + list + } +} + +impl From<(&str, &str, &str)> for SpacedList +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str)) -> Self { + let mut list = Self::new(); + list.push(FromStr::from_str(s.0).unwrap()); + list.push(FromStr::from_str(s.1).unwrap()); + list.push(FromStr::from_str(s.2).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str)> for SpacedList +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.push(FromStr::from_str(s.0).unwrap()); + list.push(FromStr::from_str(s.1).unwrap()); + list.push(FromStr::from_str(s.2).unwrap()); + list.push(FromStr::from_str(s.3).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str, &str)> for SpacedList +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.push(FromStr::from_str(s.0).unwrap()); + list.push(FromStr::from_str(s.1).unwrap()); + list.push(FromStr::from_str(s.2).unwrap()); + list.push(FromStr::from_str(s.3).unwrap()); + list.push(FromStr::from_str(s.4).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str, &str, &str)> for SpacedList +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.push(FromStr::from_str(s.0).unwrap()); + list.push(FromStr::from_str(s.1).unwrap()); + list.push(FromStr::from_str(s.2).unwrap()); + list.push(FromStr::from_str(s.3).unwrap()); + list.push(FromStr::from_str(s.4).unwrap()); + list.push(FromStr::from_str(s.5).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str, &str, &str, &str)> for SpacedList +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.push(FromStr::from_str(s.0).unwrap()); + list.push(FromStr::from_str(s.1).unwrap()); + list.push(FromStr::from_str(s.2).unwrap()); + list.push(FromStr::from_str(s.3).unwrap()); + list.push(FromStr::from_str(s.4).unwrap()); + list.push(FromStr::from_str(s.5).unwrap()); + list.push(FromStr::from_str(s.6).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str, &str, &str, &str, &str)> for SpacedList +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str, &str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.push(FromStr::from_str(s.0).unwrap()); + list.push(FromStr::from_str(s.1).unwrap()); + list.push(FromStr::from_str(s.2).unwrap()); + list.push(FromStr::from_str(s.3).unwrap()); + list.push(FromStr::from_str(s.4).unwrap()); + list.push(FromStr::from_str(s.5).unwrap()); + list.push(FromStr::from_str(s.6).unwrap()); + list.push(FromStr::from_str(s.7).unwrap()); + list + } +} + +macro_rules! spacedlist_from_array { + ($num:tt) => { + impl From<[&str; $num]> for SpacedList + where + ::Err: Debug, + { + fn from(s: [&str; $num]) -> Self { + Self::from_iter(s.into_iter().map(|s| FromStr::from_str(*s).unwrap())) + } + } + }; +} +spacedlist_from_array!(1); +spacedlist_from_array!(2); +spacedlist_from_array!(3); +spacedlist_from_array!(4); +spacedlist_from_array!(5); +spacedlist_from_array!(6); +spacedlist_from_array!(7); +spacedlist_from_array!(8); +spacedlist_from_array!(9); +spacedlist_from_array!(10); +spacedlist_from_array!(11); +spacedlist_from_array!(12); +spacedlist_from_array!(13); +spacedlist_from_array!(14); +spacedlist_from_array!(15); +spacedlist_from_array!(16); +spacedlist_from_array!(17); +spacedlist_from_array!(18); +spacedlist_from_array!(19); +spacedlist_from_array!(20); +spacedlist_from_array!(21); +spacedlist_from_array!(22); +spacedlist_from_array!(23); +spacedlist_from_array!(24); +spacedlist_from_array!(25); +spacedlist_from_array!(26); +spacedlist_from_array!(27); +spacedlist_from_array!(28); +spacedlist_from_array!(29); +spacedlist_from_array!(30); +spacedlist_from_array!(31); +spacedlist_from_array!(32); diff --git a/typed-html/src/types/spacedset.rs b/typed-html/src/types/spacedset.rs new file mode 100644 index 0000000..6ec6c5f --- /dev/null +++ b/typed-html/src/types/spacedset.rs @@ -0,0 +1,229 @@ +use std::collections::BTreeSet; +use std::fmt::{Debug, Display, Error, Formatter}; +use std::iter::FromIterator; +use std::ops::{Deref, DerefMut}; +use std::str::FromStr; + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SpacedSet(BTreeSet); + +impl SpacedSet { + pub fn new() -> Self { + SpacedSet(BTreeSet::new()) + } +} + +impl Default for SpacedSet { + fn default() -> Self { + Self::new() + } +} + +impl FromIterator for SpacedSet { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + SpacedSet(iter.into_iter().collect()) + } +} + +impl<'a, A: 'a + Ord + Clone> FromIterator<&'a A> for SpacedSet { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + SpacedSet(iter.into_iter().cloned().collect()) + } +} + +impl<'a, A: Ord + FromStr> From<&'a str> for SpacedSet +where + ::Err: Debug, +{ + fn from(s: &'a str) -> Self { + Self::from_iter(s.split_whitespace().map(|s| FromStr::from_str(s).unwrap())) + } +} + +impl Deref for SpacedSet { + type Target = BTreeSet; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SpacedSet { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Display for SpacedSet { + 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 SpacedSet { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + f.debug_list().entries(self.0.iter()).finish() + } +} + +impl From<(&str, &str)> for SpacedSet +where + ::Err: Debug, +{ + fn from(s: (&str, &str)) -> Self { + let mut list = Self::new(); + list.insert(FromStr::from_str(s.0).unwrap()); + list.insert(FromStr::from_str(s.1).unwrap()); + list + } +} + +impl From<(&str, &str, &str)> for SpacedSet +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str)) -> Self { + let mut list = Self::new(); + list.insert(FromStr::from_str(s.0).unwrap()); + list.insert(FromStr::from_str(s.1).unwrap()); + list.insert(FromStr::from_str(s.2).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str)> for SpacedSet +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.insert(FromStr::from_str(s.0).unwrap()); + list.insert(FromStr::from_str(s.1).unwrap()); + list.insert(FromStr::from_str(s.2).unwrap()); + list.insert(FromStr::from_str(s.3).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str, &str)> for SpacedSet +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.insert(FromStr::from_str(s.0).unwrap()); + list.insert(FromStr::from_str(s.1).unwrap()); + list.insert(FromStr::from_str(s.2).unwrap()); + list.insert(FromStr::from_str(s.3).unwrap()); + list.insert(FromStr::from_str(s.4).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str, &str, &str)> for SpacedSet +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.insert(FromStr::from_str(s.0).unwrap()); + list.insert(FromStr::from_str(s.1).unwrap()); + list.insert(FromStr::from_str(s.2).unwrap()); + list.insert(FromStr::from_str(s.3).unwrap()); + list.insert(FromStr::from_str(s.4).unwrap()); + list.insert(FromStr::from_str(s.5).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str, &str, &str, &str)> for SpacedSet +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.insert(FromStr::from_str(s.0).unwrap()); + list.insert(FromStr::from_str(s.1).unwrap()); + list.insert(FromStr::from_str(s.2).unwrap()); + list.insert(FromStr::from_str(s.3).unwrap()); + list.insert(FromStr::from_str(s.4).unwrap()); + list.insert(FromStr::from_str(s.5).unwrap()); + list.insert(FromStr::from_str(s.6).unwrap()); + list + } +} + +impl From<(&str, &str, &str, &str, &str, &str, &str, &str)> for SpacedSet +where + ::Err: Debug, +{ + fn from(s: (&str, &str, &str, &str, &str, &str, &str, &str)) -> Self { + let mut list = Self::new(); + list.insert(FromStr::from_str(s.0).unwrap()); + list.insert(FromStr::from_str(s.1).unwrap()); + list.insert(FromStr::from_str(s.2).unwrap()); + list.insert(FromStr::from_str(s.3).unwrap()); + list.insert(FromStr::from_str(s.4).unwrap()); + list.insert(FromStr::from_str(s.5).unwrap()); + list.insert(FromStr::from_str(s.6).unwrap()); + list.insert(FromStr::from_str(s.7).unwrap()); + list + } +} + +macro_rules! spacedlist_from_array { + ($num:tt) => { + impl From<[&str; $num]> for SpacedSet + where + ::Err: Debug, + { + fn from(s: [&str; $num]) -> Self { + Self::from_iter(s.into_iter().map(|s| FromStr::from_str(*s).unwrap())) + } + } + }; +} +spacedlist_from_array!(1); +spacedlist_from_array!(2); +spacedlist_from_array!(3); +spacedlist_from_array!(4); +spacedlist_from_array!(5); +spacedlist_from_array!(6); +spacedlist_from_array!(7); +spacedlist_from_array!(8); +spacedlist_from_array!(9); +spacedlist_from_array!(10); +spacedlist_from_array!(11); +spacedlist_from_array!(12); +spacedlist_from_array!(13); +spacedlist_from_array!(14); +spacedlist_from_array!(15); +spacedlist_from_array!(16); +spacedlist_from_array!(17); +spacedlist_from_array!(18); +spacedlist_from_array!(19); +spacedlist_from_array!(20); +spacedlist_from_array!(21); +spacedlist_from_array!(22); +spacedlist_from_array!(23); +spacedlist_from_array!(24); +spacedlist_from_array!(25); +spacedlist_from_array!(26); +spacedlist_from_array!(27); +spacedlist_from_array!(28); +spacedlist_from_array!(29); +spacedlist_from_array!(30); +spacedlist_from_array!(31); +spacedlist_from_array!(32);