diff --git a/render/src/fragment.rs b/render/src/fragment.rs index 6b653aa..27797b0 100644 --- a/render/src/fragment.rs +++ b/render/src/fragment.rs @@ -17,7 +17,7 @@ use std::fmt::{Result, Write}; /// }; /// assert_eq!(result, ""); /// ``` -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Fragment { pub children: T, } diff --git a/render/src/render.rs b/render/src/render.rs index ae9a880..642e2ec 100644 --- a/render/src/render.rs +++ b/render/src/render.rs @@ -3,13 +3,16 @@ use std::fmt::{Result, Write}; /// Render a component /// /// This is the underlying mechanism of the `#[component]` macro -pub trait Render: Sized { +pub trait Render { /// Render the component to a writer. /// Make sure you escape html correctly using the `render::html_escaping` module fn render_into(self, writer: &mut W) -> Result; /// Render the component to string - fn render(self) -> String { + fn render(self) -> String + where + Self: Sized, + { let mut buf = String::new(); self.render_into(&mut buf).unwrap(); buf diff --git a/render/src/simple_element.rs b/render/src/simple_element.rs index 4cc0e22..18ddbd7 100644 --- a/render/src/simple_element.rs +++ b/render/src/simple_element.rs @@ -7,7 +7,7 @@ use std::fmt::{Result, Write}; type Attributes<'a> = Option>>; /// Simple HTML element tag -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SimpleElement<'a, T: Render> { /// the HTML tag name, like `html`, `head`, `body`, `link`... pub tag_name: &'a str, @@ -20,9 +20,16 @@ fn write_attributes<'a, W: Write>(maybe_attributes: Attributes<'a>, writer: &mut None => Ok(()), Some(mut attributes) => { for (key, value) in attributes.drain() { - write!(writer, " {}=\"", key)?; - escape_html(&value, writer)?; - write!(writer, "\"")?; + if key.chars().nth(0).unwrap_or('.') == 'b' && key.chars().nth(1).unwrap_or('.') == '!' { + if(value == "true") { + write!(writer, " {}", key.replace("b!", ""))?; + } + } + else { + write!(writer, " {}=\"", key)?; + write!(writer, "{}", value)?; + write!(writer, "\"")?; + } } Ok(()) } diff --git a/render/src/text_element.rs b/render/src/text_element.rs index 7ce591c..d49a74a 100644 --- a/render/src/text_element.rs +++ b/render/src/text_element.rs @@ -22,16 +22,23 @@ impl Render for std::borrow::Cow<'_, str> { /// A raw (unencoded) html string #[derive(Debug)] -pub struct Raw<'s>(&'s str); +pub struct Raw(String); -impl<'s> From<&'s str> for Raw<'s> { - fn from(s: &'s str) -> Self { +impl From<&str> for Raw { + fn from(s: &str) -> Self { + Raw(s.to_string()) + } +} + + +impl From for Raw { + fn from(s: String) -> Self { Raw(s) } } /// A raw (unencoded) html string -impl<'s> Render for Raw<'s> { +impl Render for Raw { fn render_into(self, writer: &mut W) -> Result { write!(writer, "{}", self.0) } diff --git a/render_macros/src/child.rs b/render_macros/src/child.rs index 63ec9dd..3519f03 100644 --- a/render_macros/src/child.rs +++ b/render_macros/src/child.rs @@ -2,6 +2,7 @@ use crate::element::Element; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream, Result}; +#[derive(Clone)] pub enum Child { Element(Element), RawBlock(syn::Block), diff --git a/render_macros/src/children.rs b/render_macros/src/children.rs index 2b33d5a..3536798 100644 --- a/render_macros/src/children.rs +++ b/render_macros/src/children.rs @@ -2,7 +2,7 @@ use crate::child::Child; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream, Result}; -#[derive(Default)] +#[derive(Default, Clone)] pub struct Children { pub nodes: Vec, } @@ -30,7 +30,6 @@ impl Children { 1 => quote! { Some(#(#children_quotes),*) }, _ => { let mut iter = children_quotes.iter(); - let first = iter.next().unwrap(); let second = iter.next().unwrap(); diff --git a/render_macros/src/element.rs b/render_macros/src/element.rs index 161919b..46304fd 100644 --- a/render_macros/src/element.rs +++ b/render_macros/src/element.rs @@ -4,6 +4,7 @@ use crate::tags::{ClosingTag, OpenTag}; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream, Result}; +#[derive(Clone)] pub struct Element { name: syn::Path, attributes: ElementAttributes, diff --git a/render_macros/src/element_attribute.rs b/render_macros/src/element_attribute.rs index abf38ad..a7b495c 100644 --- a/render_macros/src/element_attribute.rs +++ b/render_macros/src/element_attribute.rs @@ -4,8 +4,9 @@ use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Result}; use syn::spanned::Spanned; -pub type AttributeKey = syn::punctuated::Punctuated; +pub type AttributeKey = syn::punctuated::Punctuated; +#[derive(Clone)] pub enum ElementAttribute { Punned(AttributeKey), WithValue(AttributeKey, syn::Block), @@ -94,14 +95,41 @@ impl Hash for ElementAttribute { impl Parse for ElementAttribute { fn parse(input: ParseStream) -> Result { - let name = AttributeKey::parse_separated_nonempty_with(input, syn::Ident::parse_any)?; - let not_punned = input.peek(syn::Token![=]); + let mut name: syn::punctuated::Punctuated = + syn::punctuated::Punctuated::new(); + + // Parse the input up to the space + loop { + let value = syn::Ident::parse_any(&input).unwrap(); + name.push_value(value); + + if input.peek(syn::Token![=]) { + break; + } + + let punct = input.parse().unwrap(); + name.push_punct(punct); + } + + // Peak for incoming equals to check if its punned + let mut not_punned = input.peek(syn::Token![=]); + + if !not_punned { + not_punned = input.peek2(syn::Token![=]); + } + + if !not_punned { + not_punned = input.peek3(syn::Token![=]); + } if !not_punned { return Ok(Self::Punned(name)); } + // Parse equals input.parse::()?; + + // Parse body let value = input.parse::()?; Ok(Self::WithValue(name, value)) diff --git a/render_macros/src/element_attributes.rs b/render_macros/src/element_attributes.rs index ace3fb0..5c6ecf0 100644 --- a/render_macros/src/element_attributes.rs +++ b/render_macros/src/element_attributes.rs @@ -9,7 +9,7 @@ use syn::spanned::Spanned; pub type Attributes = HashSet; -#[derive(Default)] +#[derive(Default, Clone)] pub struct ElementAttributes { pub attributes: Attributes, } @@ -123,10 +123,17 @@ impl<'a> ToTokens for SimpleElementAttributes<'a> { .attributes .iter() .map(|attribute| { - let mut iter = attribute.ident().iter(); - let first_word = iter.next().unwrap().unraw(); - let ident = iter.fold(first_word.to_string(), |acc, curr| { - format!("{}-{}", acc, curr.unraw()) + let mut iter = attribute.ident().pairs(); + let ident = iter.fold("".to_string(), |acc, curr| { + format!( + "{}{}{}", + acc, + curr.value(), + match curr.punct() { + Some(p) => p.as_char().to_string(), + None => "".to_string(), + } + ) }); let value = attribute.value_tokens(); diff --git a/render_macros/src/function_component.rs b/render_macros/src/function_component.rs index 884aad0..a4816b1 100644 --- a/render_macros/src/function_component.rs +++ b/render_macros/src/function_component.rs @@ -41,7 +41,7 @@ pub fn create_function_component(f: syn::ItemFn) -> TokenStream { }; TokenStream::from(quote! { - #[derive(Debug)] + #[derive(Debug, Clone)] #vis struct #struct_name#impl_generics #inputs_block impl#impl_generics ::render::Render for #struct_name #ty_generics #where_clause { diff --git a/render_tests/src/lib.rs b/render_tests/src/lib.rs index 873e576..5529857 100644 --- a/render_tests/src/lib.rs +++ b/render_tests/src/lib.rs @@ -1,247 +1,267 @@ #[test] fn ui() { - let t = trybuild::TestCases::new(); - t.compile_fail("ui/fail/*.rs"); + let t = trybuild::TestCases::new(); + t.compile_fail("ui/fail/*.rs"); } #[test] fn works_with_dashes() { - use pretty_assertions::assert_eq; + use pretty_assertions::assert_eq; - let value = render::html! {
}; - assert_eq!(value, r#"
"#); + let value = render::html! {
}; + assert_eq!(value, r#"
"#); } #[test] fn works_with_raw() { - use pretty_assertions::assert_eq; - use render::{html, raw}; + use pretty_assertions::assert_eq; + use render::{html, raw}; - let actual = html! { -
{raw!("")}
- }; + let actual = html! { +
{raw!("")}
+ }; - assert_eq!(actual, "
"); + assert_eq!(actual, "
"); } #[test] fn works_with_raw_ident() { - use pretty_assertions::assert_eq; + use pretty_assertions::assert_eq; - let actual = render::html! { - - }; + let actual = render::html! { + + }; - assert_eq!(actual, r#""#); + assert_eq!(actual, r#""#); } #[test] fn works_with_keywords() { - use pretty_assertions::assert_eq; - use render::html; + use pretty_assertions::assert_eq; + use render::html; - assert_eq!(html! { }, r#""#); - assert_eq!(html! {