diff --git a/examples/dodrio/counter/Cargo.toml b/examples/dodrio/counter/Cargo.toml index c9a2870..5a16a25 100644 --- a/examples/dodrio/counter/Cargo.toml +++ b/examples/dodrio/counter/Cargo.toml @@ -2,7 +2,7 @@ name = "dodrio-counter" version = "0.1.0" authors = ["Nick Fitzgerald "] -edition = "2018" +edition = "2021" [lib] crate-type = ["cdylib"] diff --git a/examples/dodrio/todomvc/Cargo.toml b/examples/dodrio/todomvc/Cargo.toml index eae203a..12536fb 100644 --- a/examples/dodrio/todomvc/Cargo.toml +++ b/examples/dodrio/todomvc/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Nick Fitzgerald "] -edition = "2018" +edition = "2021" name = "dodrio-todomvc" version = "0.1.0" diff --git a/examples/iron/Cargo.toml b/examples/iron/Cargo.toml index 04f80a7..6a8fadb 100644 --- a/examples/iron/Cargo.toml +++ b/examples/iron/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "typed-html-iron-test" version = "0.1.0" -edition = "2018" +edition = "2021" authors = ["Bodil Stokke "] [dependencies] diff --git a/examples/stdweb/Cargo.toml b/examples/stdweb/Cargo.toml index 57a9843..8eb27fa 100644 --- a/examples/stdweb/Cargo.toml +++ b/examples/stdweb/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "typed-html-stdweb-test" version = "0.1.0" -edition = "2018" +edition = "2021" authors = ["Bodil Stokke "] [dependencies] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 806cb98..5b04056 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "typed-html-macros" version = "0.2.2" -edition = "2018" +edition = "2021" authors = ["Bodil Stokke "] build = "build.rs" license = "MPL-2.0+" diff --git a/macros/src/config.rs b/macros/src/config.rs index d486414..8a86b9c 100644 --- a/macros/src/config.rs +++ b/macros/src/config.rs @@ -21,12 +21,15 @@ pub fn global_attrs(span: Span) -> StringyMap { insert("accesskey", "String"); insert("autocapitalize", "String"); - insert("contenteditable", "crate::types::Bool"); + insert("contenteditable", "crate::types::EnumeratedBool"); insert("contextmenu", "crate::types::Id"); insert("dir", "crate::types::TextDirection"); - insert("draggable", "crate::types::Bool"); + insert("draggable", "crate::types::EnumeratedBool"); insert("hidden", "crate::types::Bool"); insert("is", "String"); + insert("itemprop", "String"); + insert("itemscope", "crate::types::Bool"); + insert("itemtype", "String"); insert("lang", "crate::types::LanguageTag"); insert("role", "crate::types::Role"); insert("style", "String"); @@ -39,20 +42,6 @@ pub fn global_attrs(span: Span) -> StringyMap { } pub static SELF_CLOSING: &[&str] = &[ - "area", - "base", - "br", - "col", - "command", - "embed", - "hr", - "img", - "input", - "keygen", - "link", - "meta", - "param", - "source", - "track", - "wbr", + "area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", + "meta", "param", "source", "track", "wbr", ]; diff --git a/macros/src/declare.rs b/macros/src/declare.rs index 819b039..d7aaf81 100644 --- a/macros/src/declare.rs +++ b/macros/src/declare.rs @@ -15,6 +15,7 @@ pub struct Declare { pub attrs: StringyMap, pub req_children: Vec, pub opt_children: Option, + pub opt_children_dyn: bool, pub traits: Vec, } @@ -24,6 +25,7 @@ impl Declare { attrs: global_attrs(name.span()), req_children: Vec::new(), opt_children: None, + opt_children_dyn: false, traits: Vec::new(), name, } @@ -34,11 +36,7 @@ impl Declare { } fn attr_type_name(&self) -> TokenTree { - Ident::new( - &format!("Attrs_{}", self.name), - self.name.span(), - ) - .into() + Ident::new(&format!("Attrs_{}", self.name), self.name.span()).into() } fn attrs(&self) -> impl Iterator + '_ { @@ -99,7 +97,12 @@ impl Declare { if let Some(child_constraint) = &self.opt_children { let child_constraint = child_constraint.clone(); - body.extend(quote!(pub children: Vec>>,)); + let child_dyn = if self.opt_children_dyn { + quote!(dyn) + } else { + quote!() + }; + body.extend(quote!(pub children: Vec>>,)); } quote!( @@ -170,7 +173,7 @@ impl Declare { for (attr_name, _, attr_str) in self.attrs() { push_attrs.extend(quote!( if let Some(ref value) = self.attrs.#attr_name { - attributes.push((#attr_str, value.to_string())); + attributes.push((#attr_str, value.to_string_value())); } )); } @@ -219,7 +222,7 @@ impl Declare { 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, value.to_string())); + out.push((#attr_str, value.to_string_value())); } )); } @@ -303,26 +306,14 @@ impl Declare { } let print_children = if self.req_children.is_empty() { - if self.opt_children.is_some() { - if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) { - quote!( - write!(f, ">")?; - #print_opt_children - write!(f, "", #name) - ) - } else { - quote!(if self.children.is_empty() { - write!(f, " />") - } else { - write!(f, ">")?; - #print_opt_children - write!(f, "", #name) - }) - } - } else if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) { - quote!(write!(f, ">", #name)) + if SELF_CLOSING.contains(&elem_name.to_string().as_str()) { + quote!(write!(f, ">")) } else { - quote!(write!(f, "/>")) + quote!( + write!(f, ">")?; + #print_opt_children + write!(f, "", #name) + ) } } else { quote!( @@ -337,10 +328,7 @@ impl Declare { for (attr_name, _, attr_str) in self.attrs() { print_attrs.extend(quote!( if let Some(ref value) = self.attrs.#attr_name { - let value = crate::escape_html_attribute(value.to_string()); - if !value.is_empty() { - write!(f, " {}=\"{}\"", #attr_str, value)?; - } + value.write_attribute(#attr_str, f)?; } )); } diff --git a/macros/src/grammar.lalrpop b/macros/src/grammar.lalrpop index 172313e..983c059 100644 --- a/macros/src/grammar.lalrpop +++ b/macros/src/grammar.lalrpop @@ -272,8 +272,8 @@ IdentList = "[" > "]"; Groups = "in" ; -Children: (Vec, Option>) = "with" => { - (req.unwrap_or_else(|| Vec::new()), opt) +Children: (Vec, bool, Option>) = "with" => { + (req.unwrap_or_else(|| Vec::new()), has_dyn.is_some(), opt) }; Declaration: Declare = ";" => { @@ -288,8 +288,9 @@ Declaration: Declare = Token::GroupClose(Delimiter::Bracket, _), "in" => Token::Keyword(lexer::Keyword::In, _), "with" => Token::Keyword(lexer::Keyword::With, _), + "dyn" => Token::Keyword(lexer::Keyword::Dyn, _), IdentToken => Token::Ident(_), LiteralToken => Token::Literal(_), ParenGroupToken => Token::Group(Delimiter::Parenthesis, _), diff --git a/macros/src/html.rs b/macros/src/html.rs index eefa817..d97b251 100644 --- a/macros/src/html.rs +++ b/macros/src/html.rs @@ -379,7 +379,7 @@ impl Element { let key_str = TokenTree::from(Literal::string(&key_str)); builder.extend(quote!( let attr_value = dodrio::bumpalo::format!( - in &#bump, "{}", element.attrs.#key.unwrap()); + in &#bump, "{}", typed_html::types::DisplayAttribute::to_string_value(&element.attrs.#key.unwrap())); if !attr_value.is_empty() { attr_list.push(dodrio::builder::attr(#key_str, attr_value.into_bump_str())); } diff --git a/macros/src/lexer.rs b/macros/src/lexer.rs index 4a2a412..f27ac18 100644 --- a/macros/src/lexer.rs +++ b/macros/src/lexer.rs @@ -82,6 +82,7 @@ impl From for Token { pub enum Keyword { In, With, + Dyn, } pub fn keywordise(tokens: Vec) -> Vec { @@ -94,6 +95,8 @@ pub fn keywordise(tokens: Vec) -> Vec { Token::Keyword(Keyword::In, ident) } else if name == "with" { Token::Keyword(Keyword::With, ident) + } else if name == "dyn" { + Token::Keyword(Keyword::Dyn, ident) } else { Token::Ident(ident) } diff --git a/typed-html/Cargo.toml b/typed-html/Cargo.toml index 15ecd06..d79cddd 100644 --- a/typed-html/Cargo.toml +++ b/typed-html/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "typed-html" version = "0.2.2" -edition = "2018" +edition = "2021" authors = ["Bodil Stokke "] license = "MPL-2.0+" description = "Type checked JSX for Rust" diff --git a/typed-html/src/elements.rs b/typed-html/src/elements.rs index 42369bf..682a213 100644 --- a/typed-html/src/elements.rs +++ b/typed-html/src/elements.rs @@ -54,8 +54,8 @@ declare_elements! { html { xmlns: Uri, } with [head, body]; - head with [title] MetadataContent; - body with FlowContent; + head with [title] dyn MetadataContent; + body with dyn FlowContent; // Metadata base { @@ -78,6 +78,7 @@ declare_elements! { content: String, http_equiv: HTTPEquiv, name: Metadata, + property: String, // This is non-standard, but used by the Open Graph protocol } in [MetadataContent]; style { type: Mime, @@ -96,11 +97,11 @@ declare_elements! { rel: SpacedList, target: Target, type: Mime, - } in [FlowContent, PhrasingContent, InteractiveContent] with FlowContent; - abbr in [FlowContent, PhrasingContent] with PhrasingContent; - address in [FlowContent] with FlowContent; - article in [FlowContent, SectioningContent] with FlowContent; - aside in [FlowContent, SectioningContent] with FlowContent; + } in [FlowContent, PhrasingContent, InteractiveContent] with dyn FlowContent; + abbr in [FlowContent, PhrasingContent] with dyn PhrasingContent; + address in [FlowContent] with dyn FlowContent; + article in [FlowContent, SectioningContent] with dyn FlowContent; + aside in [FlowContent, SectioningContent] with dyn FlowContent; audio { autoplay: Bool, controls: Bool, @@ -109,13 +110,13 @@ declare_elements! { muted: Bool, preload: Preload, src: Uri, - } in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent; - b in [FlowContent, PhrasingContent] with PhrasingContent; - bdo in [FlowContent, PhrasingContent] with PhrasingContent; - bdi in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent, EmbeddedContent] with dyn MediaContent; + b in [FlowContent, PhrasingContent] with dyn PhrasingContent; + bdo in [FlowContent, PhrasingContent] with dyn PhrasingContent; + bdi in [FlowContent, PhrasingContent] with dyn PhrasingContent; blockquote { cite: Uri, - } in [FlowContent] with FlowContent; + } in [FlowContent] with dyn FlowContent; br in [FlowContent, PhrasingContent]; button { autofocus: Bool, @@ -129,28 +130,28 @@ declare_elements! { name: Id, type: ButtonType, value: String, - } in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with PhrasingContent; + } in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with dyn PhrasingContent; canvas { height: usize, width: usize, - } in [FlowContent, PhrasingContent, EmbeddedContent] with FlowContent; - cite in [FlowContent, PhrasingContent] with PhrasingContent; - code in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent, EmbeddedContent] with dyn FlowContent; + cite in [FlowContent, PhrasingContent] with dyn PhrasingContent; + code in [FlowContent, PhrasingContent] with dyn PhrasingContent; data { value: String, - } in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent] with dyn PhrasingContent; datalist in [FlowContent, PhrasingContent] with option; del { cite: Uri, datetime: Datetime, - } in [FlowContent, PhrasingContent] with FlowContent; + } in [FlowContent, PhrasingContent] with dyn FlowContent; details { open: Bool, - } in [FlowContent, SectioningContent, InteractiveContent] with [summary] FlowContent; - dfn in [FlowContent, PhrasingContent] with PhrasingContent; - div in [FlowContent] with FlowContent; - dl in [FlowContent] with DescriptionListContent; - em in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, SectioningContent, InteractiveContent] with [summary] dyn FlowContent; + dfn in [FlowContent, PhrasingContent] with dyn PhrasingContent; + div in [FlowContent] with dyn FlowContent; + dl in [FlowContent] with dyn DescriptionListContent; + em in [FlowContent, PhrasingContent] with dyn PhrasingContent; embed { height: usize, src: Uri, @@ -158,10 +159,10 @@ declare_elements! { width: usize, } in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent]; // FIXME the legend attribute should be optional - fieldset in [FlowContent, SectioningContent, FormContent] with [legend] FlowContent; + fieldset in [FlowContent, SectioningContent, FormContent] with [legend] dyn FlowContent; // FIXME the figcaption attribute should be optional - figure in [FlowContent, SectioningContent] with [figcaption] FlowContent; - footer in [FlowContent] with FlowContent; + figure in [FlowContent, SectioningContent] with [figcaption] dyn FlowContent; + footer in [FlowContent] with dyn FlowContent; form { accept-charset: SpacedList, action: Uri, @@ -171,17 +172,17 @@ declare_elements! { name: Id, novalidate: Bool, target: Target, - } in [FlowContent] with FlowContent; - h1 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent; - h2 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent; - h3 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent; - h4 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent; - h5 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent; - h6 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent; - header in [FlowContent] with FlowContent; - hgroup in [FlowContent, HeadingContent] with HGroupContent; + } in [FlowContent] with dyn FlowContent; + h1 in [FlowContent, HeadingContent, HGroupContent] with dyn PhrasingContent; + h2 in [FlowContent, HeadingContent, HGroupContent] with dyn PhrasingContent; + h3 in [FlowContent, HeadingContent, HGroupContent] with dyn PhrasingContent; + h4 in [FlowContent, HeadingContent, HGroupContent] with dyn PhrasingContent; + h5 in [FlowContent, HeadingContent, HGroupContent] with dyn PhrasingContent; + h6 in [FlowContent, HeadingContent, HGroupContent] with dyn PhrasingContent; + header in [FlowContent] with dyn FlowContent; + hgroup in [FlowContent, HeadingContent] with dyn HGroupContent; hr in [FlowContent]; - i in [FlowContent, PhrasingContent] with PhrasingContent; + i in [FlowContent, PhrasingContent] with dyn PhrasingContent; iframe { allow: FeaturePolicy, allowfullscreen: Bool, @@ -193,7 +194,7 @@ declare_elements! { src: Uri, srcdoc: Uri, width: usize, - } in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent] with FlowContent; + } in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent] with dyn FlowContent; img { alt: String, crossorigin: CrossOrigin, @@ -244,17 +245,17 @@ declare_elements! { ins { cite: Uri, datetime: Datetime, - } in [FlowContent, PhrasingContent] with FlowContent; - kbd in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent] with dyn FlowContent; + kbd in [FlowContent, PhrasingContent] with dyn PhrasingContent; label { for: Id, form: Id, - } in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with PhrasingContent; - main in [FlowContent] with FlowContent; + } in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with dyn PhrasingContent; + main in [FlowContent] with dyn FlowContent; map { name: Id, - } in [FlowContent, PhrasingContent] with MapContent; - mark in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent] with dyn MapContent; + mark in [FlowContent, PhrasingContent] with dyn PhrasingContent; // TODO the element meter { value: isize, @@ -264,9 +265,9 @@ declare_elements! { high: isize, optimum: isize, form: Id, - } in [FlowContent, PhrasingContent] with PhrasingContent; - nav in [FlowContent, SectioningContent] with FlowContent; - noscript in [MetadataContent, FlowContent, PhrasingContent] with Node; + } in [FlowContent, PhrasingContent] with dyn PhrasingContent; + nav in [FlowContent, SectioningContent] with dyn FlowContent; + noscript in [MetadataContent, FlowContent, PhrasingContent] with dyn Node; object { data: Uri, form: Id, @@ -286,19 +287,19 @@ declare_elements! { for: SpacedSet, form: Id, name: Id, - } in [FlowContent, PhrasingContent, FormContent] with PhrasingContent; - p in [FlowContent] with PhrasingContent; - pre in [FlowContent] with PhrasingContent; + } in [FlowContent, PhrasingContent, FormContent] with dyn PhrasingContent; + p in [FlowContent] with dyn PhrasingContent; + pre in [FlowContent] with dyn PhrasingContent; progress { max: f64, value: f64, - } in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent] with dyn PhrasingContent; q { cite: Uri, - } in [FlowContent, PhrasingContent] with PhrasingContent; - ruby in [FlowContent, PhrasingContent] with PhrasingContent; - s in [FlowContent, PhrasingContent] with PhrasingContent; - samp in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent] with dyn PhrasingContent; + ruby in [FlowContent, PhrasingContent] with dyn PhrasingContent; + s in [FlowContent, PhrasingContent] with dyn PhrasingContent; + samp in [FlowContent, PhrasingContent] with dyn PhrasingContent; script { async: Bool, crossorigin: CrossOrigin, @@ -310,7 +311,7 @@ declare_elements! { text: String, type: String, // TODO could be an enum } in [MetadataContent, FlowContent, PhrasingContent, TableColumnContent] with TextNode; - section in [FlowContent, SectioningContent] with FlowContent; + section in [FlowContent, SectioningContent] with dyn FlowContent; select { autocomplete: String, autofocus: Bool, @@ -320,14 +321,14 @@ declare_elements! { name: Id, required: Bool, size: usize, - } in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with SelectContent; - small in [FlowContent, PhrasingContent] with PhrasingContent; - span in [FlowContent, PhrasingContent] with PhrasingContent; - strong in [FlowContent, PhrasingContent] with PhrasingContent; - sub in [FlowContent, PhrasingContent] with PhrasingContent; - sup in [FlowContent, PhrasingContent] with PhrasingContent; - table in [FlowContent] with TableContent; - template in [MetadataContent, FlowContent, PhrasingContent, TableColumnContent] with Node; + } in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with dyn SelectContent; + small in [FlowContent, PhrasingContent] with dyn PhrasingContent; + span in [FlowContent, PhrasingContent] with dyn PhrasingContent; + strong in [FlowContent, PhrasingContent] with dyn PhrasingContent; + sub in [FlowContent, PhrasingContent] with dyn PhrasingContent; + sup in [FlowContent, PhrasingContent] with dyn PhrasingContent; + table in [FlowContent] with dyn TableContent; + template in [MetadataContent, FlowContent, PhrasingContent, TableColumnContent] with dyn Node; textarea { autocomplete: OnOff, autofocus: Bool, @@ -346,9 +347,9 @@ declare_elements! { } in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with TextNode; time { datetime: Datetime, - } in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent] with dyn PhrasingContent; ul in [FlowContent] with li; - var in [FlowContent, PhrasingContent] with PhrasingContent; + var in [FlowContent, PhrasingContent] with dyn PhrasingContent; video { autoplay: Bool, controls: Bool, @@ -361,7 +362,7 @@ declare_elements! { poster: Uri, src: Uri, width: usize, - } in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent; + } in [FlowContent, PhrasingContent, EmbeddedContent] with dyn MediaContent; wbr in [FlowContent, PhrasingContent]; // Non-group elements @@ -376,20 +377,20 @@ declare_elements! { shape: AreaShape, target: Target, } in [MapContent]; - caption in [TableContent] with FlowContent; + caption in [TableContent] with dyn FlowContent; col { span: usize, }; colgroup { span: usize, } in [TableContent] with col; - dd in [DescriptionListContent] with FlowContent; - dt in [DescriptionListContent] with FlowContent; - figcaption with FlowContent; - legend with PhrasingContent; + dd in [DescriptionListContent] with dyn FlowContent; + dt in [DescriptionListContent] with dyn FlowContent; + figcaption with dyn FlowContent; + legend with dyn PhrasingContent; li { value: isize, - } with FlowContent; + } with dyn FlowContent; option { disabled: Bool, label: String, @@ -408,13 +409,13 @@ declare_elements! { src: Uri, type: Mime, } in [MediaContent]; - summary with PhrasingContent; + summary with dyn PhrasingContent; tbody in [TableContent] with tr; td { colspan: usize, headers: SpacedSet, rowspan: usize, - } in [TableColumnContent] with FlowContent; + } in [TableColumnContent] with dyn FlowContent; tfoot in [TableContent] with tr; th { abbr: String, @@ -422,9 +423,9 @@ declare_elements! { headers: SpacedSet, rowspan: usize, scope: TableHeaderScope, - } in [TableColumnContent] with FlowContent; + } in [TableColumnContent] with dyn FlowContent; thead in [TableContent] with tr; - tr in [TableContent] with TableColumnContent; + tr in [TableContent] with dyn TableColumnContent; track { default: Bool, kind: VideoKind, @@ -434,7 +435,7 @@ declare_elements! { } in [MediaContent]; // Don't @ me - blink in [FlowContent, PhrasingContent] with PhrasingContent; + blink in [FlowContent, PhrasingContent] with dyn PhrasingContent; marquee { behavior: String, // FIXME enum bgcolor: String, // FIXME colour @@ -447,7 +448,7 @@ declare_elements! { truespeed: Bool, vspace: String, // FIXME size width: String, // FIXME size - } in [FlowContent, PhrasingContent] with PhrasingContent; + } in [FlowContent, PhrasingContent] with dyn PhrasingContent; } #[test] diff --git a/typed-html/src/types/mod.rs b/typed-html/src/types/mod.rs index 0fb5881..a92d7c7 100644 --- a/typed-html/src/types/mod.rs +++ b/typed-html/src/types/mod.rs @@ -28,6 +28,34 @@ pub type Integrity = String; pub type Nonce = String; pub type Target = String; +pub trait DisplayAttribute { + fn write_attribute( + &self, + attr_str: &str, + f: &mut std::fmt::Formatter<'_>, + ) -> std::result::Result<(), std::fmt::Error>; + + fn to_string_value(&self) -> String; +} + +impl DisplayAttribute for Attribute { + fn write_attribute( + &self, + attr_str: &str, + f: &mut std::fmt::Formatter<'_>, + ) -> std::result::Result<(), std::fmt::Error> { + let value = crate::escape_html_attribute(self.to_string()); + if !value.is_empty() { + write!(f, " {}=\"{}\"", attr_str, value)?; + } + Ok(()) + } + + fn to_string_value(&self) -> String { + self.to_string() + } +} + #[derive(EnumString, Display, PartialEq, Eq, PartialOrd, Ord, AsRefStr, IntoStaticStr)] pub enum AreaShape { #[strum(to_string = "rect")] @@ -60,11 +88,9 @@ pub enum ButtonType { Button, } -#[derive(EnumString, Display, PartialEq, Eq, PartialOrd, Ord, AsRefStr, IntoStaticStr)] +#[derive(EnumString, PartialEq, Eq, PartialOrd, Ord, AsRefStr, IntoStaticStr)] pub enum Bool { - #[strum(to_string = "true")] True, - #[strum(to_string = "")] False, } @@ -78,6 +104,44 @@ impl From for Bool { } } +impl DisplayAttribute for Bool { + fn write_attribute( + &self, + attr_str: &str, + f: &mut std::fmt::Formatter<'_>, + ) -> std::result::Result<(), std::fmt::Error> { + match self { + Bool::True => write!(f, " {}", attr_str), + Bool::False => Ok(()), + } + } + + fn to_string_value(&self) -> String { + match self { + Bool::True => "true".to_string(), + Bool::False => "false".to_string(), + } + } +} + +#[derive(EnumString, Display, PartialEq, Eq, PartialOrd, Ord, AsRefStr, IntoStaticStr)] +pub enum EnumeratedBool { + #[strum(to_string = "true")] + True, + #[strum(to_string = "false")] + False, +} + +impl From for EnumeratedBool { + fn from(v: bool) -> Self { + if v { + EnumeratedBool::True + } else { + EnumeratedBool::False + } + } +} + #[derive(EnumString, Display, PartialEq, Eq, PartialOrd, Ord, AsRefStr, IntoStaticStr)] pub enum CrossOrigin { #[strum(to_string = "anonymous")] diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 84eb5c8..0c70069 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "typed-html-tests" version = "0.0.0" -edition = "2018" +edition = "2021" authors = ["Bodil Stokke "] publish = false