Skip to content
2 changes: 1 addition & 1 deletion render/src/fragment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::fmt::{Result, Write};
/// };
/// assert_eq!(result, "<a/><b/>");
/// ```
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Fragment<T: Render> {
pub children: T,
}
Expand Down
7 changes: 5 additions & 2 deletions render/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<W: Write>(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();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stuff makes the Render trait more portable-- we still need sized as its a dependency of the writer structure we're doing, but we can at least scope this requirement to the render method so that it'll only complain about being passed around when used directly in the html! macro... 🤞

buf
Expand Down
15 changes: 11 additions & 4 deletions render/src/simple_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::fmt::{Result, Write};
type Attributes<'a> = Option<HashMap<&'a str, Cow<'a, str>>>;

/// 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,
Expand All @@ -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('.') == '!' {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not my favorite addition as its a bit of a hack-- definitely want a better way to do this in the future. This interprets b! at the start of an attribute as a boolean attribute in HTML, which will cause it to optionally render the key based on a value of "true".

Mostly because we need some way to do this today as things like selected='false' in an option isn't actually valid html.

if(value == "true") {
write!(writer, " {}", key.replace("b!", ""))?;
}
}
else {
write!(writer, " {}=\"", key)?;
write!(writer, "{}", value)?;
write!(writer, "\"")?;
}
}
Ok(())
}
Expand Down
15 changes: 11 additions & 4 deletions render/src/text_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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<W: Write>(self, writer: &mut W) -> Result {
write!(writer, "{}", self.0)
}
Expand Down
1 change: 1 addition & 0 deletions render_macros/src/child.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
3 changes: 1 addition & 2 deletions render_macros/src/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Child>,
}
Expand Down Expand Up @@ -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();

Expand Down
1 change: 1 addition & 0 deletions render_macros/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
34 changes: 31 additions & 3 deletions render_macros/src/element_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Result};
use syn::spanned::Spanned;

pub type AttributeKey = syn::punctuated::Punctuated<syn::Ident, syn::Token![-]>;
pub type AttributeKey = syn::punctuated::Punctuated<proc_macro2::Ident, proc_macro2::Punct>;

#[derive(Clone)]
pub enum ElementAttribute {
Punned(AttributeKey),
WithValue(AttributeKey, syn::Block),
Expand Down Expand Up @@ -94,14 +95,41 @@ impl Hash for ElementAttribute {

impl Parse for ElementAttribute {
fn parse(input: ParseStream) -> Result<Self> {
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<proc_macro2::Ident, proc_macro2::Punct> =
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);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop is similar to the implementation in parse_separated_nonempty_with. Key thing I was looking to do here was to give us the ability to modify how and when this accrues / breaks so we can better handle the edge cases. I'm not sure I ended up going very far with this but think it probably makes sense to hold onto this here.


// 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![=]);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there are multiple sets of punctuation that can occur in a row we look forward to see if any of the next 3 Punctuated's have an equals in it. This may ultimately become a limitation of the library that it can only handle 2 distinct piece of punctuation before the equals though that would be a vast improvement.


if !not_punned {
return Ok(Self::Punned(name));
}

// Parse equals
input.parse::<syn::Token![=]>()?;

// Parse body
let value = input.parse::<syn::Block>()?;

Ok(Self::WithValue(name, value))
Expand Down
17 changes: 12 additions & 5 deletions render_macros/src/element_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use syn::spanned::Spanned;

pub type Attributes = HashSet<ElementAttribute>;

#[derive(Default)]
#[derive(Default, Clone)]
pub struct ElementAttributes {
pub attributes: Attributes,
}
Expand Down Expand Up @@ -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(),
}
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This properly collapses our punctuation and ident fields back into a proper string when there are multiple.

});
let value = attribute.value_tokens();

Expand Down
2 changes: 1 addition & 1 deletion render_macros/src/function_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading