From 6a105fe1ee4f45439ae3cfc8a4e9f6cbac663982 Mon Sep 17 00:00:00 2001 From: Ash Guy Date: Sun, 24 May 2020 11:11:39 +1000 Subject: [PATCH 01/11] Fix --- render_macros/src/function_component.rs | 6 ++- render_tests/src/lib.rs | 49 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/render_macros/src/function_component.rs b/render_macros/src/function_component.rs index 261528b..b4d3511 100644 --- a/render_macros/src/function_component.rs +++ b/render_macros/src/function_component.rs @@ -10,7 +10,11 @@ pub fn create_function_component(f: syn::ItemFn) -> TokenStream { let vis = f.vis; let inputs_block = if inputs.len() > 0 { - quote!({ #inputs }) + let input_names: Vec<_> = inputs + .iter() + .collect(); + + quote!({ #(#vis #input_names),* }) } else { quote!(;) }; diff --git a/render_tests/src/lib.rs b/render_tests/src/lib.rs index 63070e0..f65366c 100644 --- a/render_tests/src/lib.rs +++ b/render_tests/src/lib.rs @@ -67,4 +67,53 @@ mod kaki { ); assert_eq!(actual, expected); } + + #[test] + fn externals_test() { + use pretty_assertions::assert_eq; + use crate::other::ExternalPage; + + let actual = render::html! { + + {format!("Welcome, {}", "Gal")} + + }; + + let expected = concat!( + "", + "", + "Home", + "", + "

Foo

", + "Welcome, Gal", + "", + "" + ); + assert_eq!(actual, expected); + } } + +/// ## Other +/// +/// Module for testing component visibility when imported from other modules. + +mod other { + use render::html::HTML5Doctype; + use render::{ component, rsx, Render }; + + #[component] + pub fn ExternalPage<'title, 'subtitle, Children: Render>(title: &'title str, subtitle: &'subtitle str, children: Children) { + rsx! { + <> + + + {title} + +

{subtitle}

+ {children} + + + + } + } +} \ No newline at end of file From 5320327ab07dcc1f49db5d1345da070773e6914d Mon Sep 17 00:00:00 2001 From: Ash Guy Date: Sun, 24 May 2020 11:18:47 +1000 Subject: [PATCH 02/11] Added a bit of documentation --- README.md | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8069ce7..e1bc605 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ XML rendering, but can work with other usages as well, like ReasonML's [`Pastel` A renderable component is a struct that implements the `Render` trait. There are multiple macros that provide a better experience implementing Renderable: -* `#[component]` for defining components using a function -* `rsx!` for composing elements with JSX ergonomics -* `html!` for composing elements and render them to a string +- `#[component]` for defining components using a function +- `rsx!` for composing elements with JSX ergonomics +- `html!` for composing elements and render them to a string ## Why is this different from... @@ -113,10 +113,37 @@ assert_eq!(rendered_html, r#"

Hello world!

"#); If you pay close attention, you see that the function `Heading` is: -* declared with an uppercase. Underneath, it generates a struct with the same name, and -implements the `Render` trait on it. -* does not have a return type. This is because everything is written to a writer, for -performance reasons. +- declared with an uppercase. Underneath, it generates a struct with the same name, and + implements the `Render` trait on it. +- does not have a return type. This is because everything is written to a writer, for + performance reasons. + +### Visibility & Component Libraries + +Often you're going to want to store your components somewhere else in your +project tree other than the module you're working on (if not in a different +module entirely!). In these cases, the visibility applied top the function that +defines your component will flow down into all elements of that struct. + +For example, if we add "pub" to the front of our Heading component above: + +```rust +#[component] +pub fn Heading<'title>(title: &'title str) { + rsx! {

{title}

} +} +``` + +...the struct that is generated would look something like... + +```rust +pub struct Heading { + pub title: &'title str +} +``` + +This is important to understand from a safety point of view when structuring +your libraries. #### Full example From 9bcd0b2fcecd07009c22b37399454233313b59b4 Mon Sep 17 00:00:00 2001 From: Ash Guy Date: Sun, 24 May 2020 11:34:27 +1000 Subject: [PATCH 03/11] fixed terminology (elements > fields) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1bc605..d6cf677 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ If you pay close attention, you see that the function `Heading` is: Often you're going to want to store your components somewhere else in your project tree other than the module you're working on (if not in a different module entirely!). In these cases, the visibility applied top the function that -defines your component will flow down into all elements of that struct. +defines your component will flow down into all fields of that struct. For example, if we add "pub" to the front of our Heading component above: From d159b2a287a5a0a235c0ecba7e7f134d56fa06fb Mon Sep 17 00:00:00 2001 From: Ash Guy Date: Sun, 24 May 2020 15:03:04 +1000 Subject: [PATCH 04/11] Added failing test for the element ordering --- .gitignore | 1 + render_tests/src/lib.rs | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 84a323e..ea56ff2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target **/*.rs.bk +.DS_Store \ No newline at end of file diff --git a/render_tests/src/lib.rs b/render_tests/src/lib.rs index f65366c..994fe3d 100644 --- a/render_tests/src/lib.rs +++ b/render_tests/src/lib.rs @@ -20,6 +20,22 @@ pub fn works_with_raw() { assert_eq!(actual, "
"); } +#[test] +pub fn element_ordering() { + use pretty_assertions::assert_eq; + use render::{html, raw}; + + let actual = html! { +
    +
  • {"1"}
  • +
  • {"2"}
  • +
  • {"3"}
  • +
+ }; + + assert_eq!(actual, "
  • 1
  • 2
  • 3
"); +} + mod kaki { // A simple HTML 5 doctype declaration use render::html::HTML5Doctype; @@ -102,7 +118,11 @@ mod other { use render::{ component, rsx, Render }; #[component] - pub fn ExternalPage<'title, 'subtitle, Children: Render>(title: &'title str, subtitle: &'subtitle str, children: Children) { + pub fn ExternalPage<'title, 'subtitle, Children: Render>( + title: &'title str, + subtitle: &'subtitle str, + children: Children + ) { rsx! { <> From 1d5f88a5183fce4ccb8b90fdfd63d5f82cf412a4 Mon Sep 17 00:00:00 2001 From: Ash Guy Date: Sun, 24 May 2020 15:28:38 +1000 Subject: [PATCH 05/11] Fixed ordering of lists of things --- render_macros/src/children.rs | 7 +++++-- render_tests/src/lib.rs | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/render_macros/src/children.rs b/render_macros/src/children.rs index 8d9feeb..7de0a43 100644 --- a/render_macros/src/children.rs +++ b/render_macros/src/children.rs @@ -24,16 +24,19 @@ impl Children { quote! { #child } }) .collect(); + match children_quotes.len() { 0 => quote! { Option::<()>::None }, - 1 => quote! { Some(#(#children_quotes)*) }, + 1 => quote! { Some(#(#children_quotes),*) }, _ => { let mut iter = children_quotes.iter(); + let first = iter.next().unwrap(); let second = iter.next().unwrap(); + let tuple_of_tuples = iter.fold( quote!((#first, #second)), - |renderable, current| quote!((#current, #renderable)), + |renderable, current| quote!((#renderable, #current)), ); quote! { Some(#tuple_of_tuples) } diff --git a/render_tests/src/lib.rs b/render_tests/src/lib.rs index 994fe3d..bf1ced5 100644 --- a/render_tests/src/lib.rs +++ b/render_tests/src/lib.rs @@ -26,14 +26,28 @@ pub fn element_ordering() { use render::{html, raw}; let actual = html! { +
    +
  • {"1"}
  • +
  • {"2"}
  • +
  • {"3"}
  • +
+ }; + + assert_eq!(actual, "
  • 1
  • 2
  • 3
"); + + let deep = html! { +
+

{"A list"}

+
  • {"1"}
  • {"2"}
  • {"3"}
+
}; - assert_eq!(actual, "
  • 1
  • 2
  • 3
"); + assert_eq!(deep, "

A list


  • 1
  • 2
  • 3
"); } mod kaki { From 5de20c176e068dbdd97031811d99d22d4736f1cf Mon Sep 17 00:00:00 2001 From: Deploy Bot Date: Wed, 26 May 2021 19:22:39 +1000 Subject: [PATCH 06/11] Added ability to have weird attribute names (eg, svg or alpine.js) --- render/src/simple_element.rs | 2 +- render_macros/src/element_attribute.rs | 33 +- render_macros/src/element_attributes.rs | 15 +- render_tests/src/lib.rs | 437 ++++++++++++------------ 4 files changed, 256 insertions(+), 231 deletions(-) diff --git a/render/src/simple_element.rs b/render/src/simple_element.rs index 4cc0e22..908fbee 100644 --- a/render/src/simple_element.rs +++ b/render/src/simple_element.rs @@ -21,7 +21,7 @@ fn write_attributes<'a, W: Write>(maybe_attributes: Attributes<'a>, writer: &mut Some(mut attributes) => { for (key, value) in attributes.drain() { write!(writer, " {}=\"", key)?; - escape_html(&value, writer)?; + write!(writer, "{}", value)?; write!(writer, "\"")?; } Ok(()) diff --git a/render_macros/src/element_attribute.rs b/render_macros/src/element_attribute.rs index abf38ad..7d43cbd 100644 --- a/render_macros/src/element_attribute.rs +++ b/render_macros/src/element_attribute.rs @@ -4,7 +4,7 @@ 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; pub enum ElementAttribute { Punned(AttributeKey), @@ -94,14 +94,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..8a118bb 100644 --- a/render_macros/src/element_attributes.rs +++ b/render_macros/src/element_attributes.rs @@ -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_tests/src/lib.rs b/render_tests/src/lib.rs index 5653975..5529857 100644 --- a/render_tests/src/lib.rs +++ b/render_tests/src/lib.rs @@ -1,303 +1,294 @@ #[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! {