Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
ModProg committed May 22, 2024
1 parent 1969d25 commit 513f91a
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 148 deletions.
18 changes: 9 additions & 9 deletions htmx-macros/src/htmx/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,21 @@ pub fn expand_node(node: Node) -> Result {
};
let script = script.into_token_stream();
if let Ok(script) = parse2::<LitStr>(script.clone()) {
quote!(__html.child_expr(#script);)
quote!(::htmx::ToScript::to_script(&#script, &mut __html);)
} else if let Ok(block) =
parse2::<Recoverable<NodeBlock>>(script.clone()).map(Recoverable::inner)
{
quote!(__html.child_expr({#[allow(unused_braces)] #block});)
quote!(::htmx::ToScript::to_script(&{#[allow(unused_braces)] #block}, &mut __html);)
} else {
let script: Script = parse2(script)?;
let script = script.to_java_script();
quote!(__html.child(#script);)
quote!(::htmx::ToScript::to_script(#script, &mut __html);)
}
} else {
expand_nodes(children)?
};
let body = (!children.is_empty()).then(|| quote!(let mut __html = __html.body();));
let main = quote!({let mut __html = #name #(__html.#attributes;)* #body; #children});
let body = (!children.is_empty()).then(|| quote!(let mut __html = __html.body(|mut __html| {#children});));
let main = quote!({let mut __html = #name #(.#attributes)*; #body;});

match close_tag {
Some(CloseTag {
Expand Down Expand Up @@ -139,11 +139,11 @@ pub fn ensure_tag_name(name: String, span: impl ToTokens) -> Result<String, Erro

fn name_to_struct(name: NodeName) -> Result<(TokenStream, bool)> {
match name {
NodeName::Path(path) => Ok((quote!(#path::new(&mut __html);), false)),
NodeName::Path(path) => Ok((quote!(#path::new(&mut __html)), false)),
name @ NodeName::Punctuated(_) => {
let name = ensure_tag_name(name.to_string(), name)?;
Ok((
quote!(::htmx::CustomElement::new_unchecked(&mut __html, #name);),
quote!(::htmx::CustomElement::new_unchecked(&mut __html, #name)),
true,
))
}
Expand All @@ -161,12 +161,12 @@ fn name_to_struct(name: NodeName) -> Result<(TokenStream, bool)> {
{
let name = ensure_tag_name(name.value(), name)?;
Ok((
quote!(::htmx::CustomElement::new_unchecked(&mut __html, #name);),
quote!(::htmx::CustomElement::new_unchecked(&mut __html, #name)),
true,
))
} else {
Ok((
quote!(::htmx::CustomElement::new(&mut __html, #name);),
quote!(::htmx::CustomElement::new(&mut __html, #name)),
true,
))
}
Expand Down
3 changes: 1 addition & 2 deletions htmx-macros/src/htmx/rusty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,12 @@ impl Element {

let attrs = attrs.attrs.into_iter().map(Attr::expand);

let body = children.peek().is_some().then(|| quote!(let mut __html = __html.body()));
let body = children.peek().is_some().then(|| quote!(__html.body(|mut __html| {#(#children)*})));

quote!({
let mut __html = #name
#(__html #attrs;)*
#body;
#(#children)*
})
}
}
Expand Down
166 changes: 93 additions & 73 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,6 @@ impl<T: WriteHtml> WriteHtml for ManuallyDrop<T> {
///
/// The [`html!`] macro uses them for all tags that contain `-` making it
/// possible to use web-components.
#[must_use]
pub struct CustomElement<Html: WriteHtml, S: ElementState> {
html: ManuallyDrop<Html>,
name: ManuallyDrop<Cow<'static, str>>,
Expand Down Expand Up @@ -444,64 +443,37 @@ impl<Html: WriteHtml> CustomElement<Html, Tag> {
/// [`AnyAttributeValue`], without checking for invalid characters.
///
/// Note: This function does contain the check for [invalid attribute names](https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0) only in debug builds, failing to ensure valid keys can lead to broken HTML output.
pub fn custom_attr_unchecked(
&mut self,
key: impl Display,
value: impl ToAttribute<Any>,
) {
pub fn custom_attr_unchecked(&mut self, key: impl Display, value: impl ToAttribute<Any>) {
debug_assert!(!key.to_string().chars().any(|c| c.is_whitespace()
|| c.is_control()
|| matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
write!(self.html, " {key}");
value.write(&mut self.html);
}

pub fn custom_attr_composed(self, key: impl Display) -> CustomElement<Html, CustomAttr> {
assert!(!key.to_string().chars().any(|c| c.is_whitespace()
|| c.is_control()
|| matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
self.custom_attr_composed_unchecked(key)
}

pub fn custom_attr_composed_unchecked(
mut self,
key: impl Display,
) -> CustomElement<Html, CustomAttr> {
debug_assert!(!key.to_string().chars().any(|c| c.is_whitespace()
|| c.is_control()
|| matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
write!(self.html, " {key}=\"");
self.change_state()
}

pub fn body(mut self) -> CustomElement<Html, Body> {
// TODO, use closure like body
// pub fn custom_attr_composed(self, key: impl Display) -> CustomElement<Html,
// CustomAttr> { assert!(!key.to_string().chars().any(|c|
// c.is_whitespace() || c.is_control()
// || matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
// self.custom_attr_composed_unchecked(key)
// }

// pub fn custom_attr_composed_unchecked(
// mut self,
// key: impl Display,
// ) -> CustomElement<Html, CustomAttr> {
// debug_assert!(!key.to_string().chars().any(|c| c.is_whitespace()
// || c.is_control()
// || matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
// write!(self.html, " {key}=\"");
// self.change_state()
// }

pub fn body(mut self, body: impl FnOnce(&mut Html)) {
self.html.write_gt();
self.change_state()
}
}

impl<Html: WriteHtml> WriteHtml for CustomElement<Html, Body> {
fn write_str(&mut self, s: &str) {
self.html.write_str(s);
}

fn write_char(&mut self, c: char) {
self.html.write_char(c);
}

fn write_fmt(&mut self, a: std::fmt::Arguments) {
self.html.write_fmt(a);
}
}

impl<Html: WriteHtml> CustomElement<Html, Body> {
pub fn child_expr(mut self, child: impl ToHtml) -> Self {
child.to_html(&mut self);
self
}

pub fn child<C>(self, child: impl FnOnce(Self) -> C) -> C {
child(self)
body(&mut self.html);
self.change_state::<Body>();
}
}

Expand All @@ -516,14 +488,6 @@ impl<Html: WriteHtml, S: ElementState> CustomElement<Html, S> {
state: PhantomData,
}
}

pub fn close(mut self) -> Html {
S::close_tag(&mut self.html);
self.html.write_close_tag_unchecked(self.name.as_ref());
let html = unsafe { ManuallyDrop::take(&mut self.html) };
std::mem::forget(self);
html
}
}

impl<Html: WriteHtml, S: ElementState> Drop for CustomElement<Html, S> {
Expand All @@ -533,20 +497,6 @@ impl<Html: WriteHtml, S: ElementState> Drop for CustomElement<Html, S> {
}
}

impl<Html: WriteHtml> CustomElement<Html, CustomAttr> {
pub fn attr_value(mut self, value: impl ToAttribute<Any>) -> Self {
if !value.is_unset() {
value.write_inner(&mut self.html);
}
self
}

pub fn close_attr(mut self) -> CustomElement<Html, Tag> {
self.html.write_quote();
self.change_state()
}
}

/// Puts content directly into HTML bypassing HTML-escaping.
///
/// ```
Expand All @@ -567,6 +517,30 @@ impl<'a> RawHtml<'a> {
}
}

pub struct Fragment<F>(pub F);

// TODO reconsider elements implementing WriteHtml, maybe it would be better for them to implement a way to access the underlying `Html`
impl<F> Fragment<F> {
#[allow(non_snake_case)]
pub fn EMPTY(self, html: impl WriteHtml) {}
}

impl<F: FnOnce(Html), Html: WriteHtml> IntoHtml<Html> for Fragment<F> {
fn into_html(self, html: Html) {
self.0(html);
}
}

pub trait IntoHtml<Html> {
fn into_html(self, html: Html);
}

impl<T: ToHtml, Html: WriteHtml> IntoHtml<Html> for T {
fn into_html(self, html: Html) {
self.to_html(html);
}
}

pub trait ToHtml {
fn to_html(&self, html: impl WriteHtml);
}
Expand Down Expand Up @@ -629,3 +603,49 @@ impl ElementState for Body {
pub trait ElementState {
fn close_tag(html: impl WriteHtml);
}

forr! {$type:ty in [&str, String, Cow<'_, str>]$*
impl ToHtml for $type {
fn to_html(&self, mut out: impl WriteHtml) {
write!(out, "{}", html_escape::encode_text(&self));
}
}

impl ToScript for $type {
fn to_script(&self, mut out: impl WriteHtml) {
write!(out, "{}", html_escape::encode_script(&self));
}
}

impl ToStyle for $type {
fn to_style(&self, mut out: impl WriteHtml) {
write!(out, "{}", html_escape::encode_style(&self));
}
}
}

impl ToHtml for char {
fn to_html(&self, mut out: impl WriteHtml) {
write!(out, "{}", html_escape::encode_text(&self.to_string()));
}
}

pub trait ToScript {
fn to_script(&self, out: impl WriteHtml);
}

impl<T: ToScript> ToScript for &T {
fn to_script(&self, out: impl WriteHtml) {
T::to_script(self, out);
}
}

pub trait ToStyle {
fn to_style(&self, out: impl WriteHtml);
}

impl<T: ToStyle> ToStyle for &T {
fn to_style(&self, out: impl WriteHtml) {
T::to_style(self, out);
}
}
55 changes: 6 additions & 49 deletions src/native.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,17 @@
//! Native HTML elements
#![allow(non_camel_case_types, clippy::return_self_not_must_use)]

use std::borrow::Cow;
use std::fmt::Display;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;

use forr::{forr, iff};

use crate::attributes::{Any, DateTime, FlagOrValue, Number, TimeDateTime, ToAttribute};
use crate::{Body, ClassesAttr, CustomAttr, ElementState, StyleAttr, Tag, ToHtml, WriteHtml};

forr! {$type:ty in [&str, String, Cow<'_, str>]$*
impl ToHtml for $type {
fn to_html(&self, mut out: impl WriteHtml) {
write!(out, "{}", html_escape::encode_text(&self));
}
}

impl ToScript for $type {
fn to_script(&self, mut out: impl WriteHtml) {
write!(out, "{}", html_escape::encode_script(&self));
}
}

impl ToStyle for $type {
fn to_style(&self, mut out: impl WriteHtml) {
write!(out, "{}", html_escape::encode_style(&self));
}
}
}

impl ToHtml for char {
fn to_html(&self, mut out: impl WriteHtml) {
write!(out, "{}", html_escape::encode_text(&self.to_string()));
}
}

pub trait ToScript {
fn to_script(&self, out: impl WriteHtml);
}

impl<T: ToScript> ToScript for &T {
fn to_script(&self, out: impl WriteHtml) {
T::to_script(self, out);
}
}

pub trait ToStyle {
fn to_style(&self, out: impl WriteHtml);
}

impl<T: ToStyle> ToStyle for &T {
fn to_style(&self, out: impl WriteHtml) {
T::to_style(self, out);
}
}
use crate::{
Body, ClassesAttr, CustomAttr, ElementState, StyleAttr, Tag, ToHtml, ToScript, ToStyle,
WriteHtml,
};

macro_rules! attribute {
($elem:ident|$name:ident<FlagOrAttributeValue>) => {
Expand Down Expand Up @@ -180,8 +136,9 @@ forr! { $type:ty in [a, abbr, address, area, article, aside, audio, b, base, bdi
}

iff! {!equals_any($type)[(area), (base), (br), (col), (embeded), (hr), (input), (link), (meta), (source), (track), (wbr)] $:
pub fn body(mut self) -> $type<T, Body> {
pub fn body(mut self, body: impl FnOnce(&mut T)) -> $type<T, Body> {
self.html.write_gt();
body(&mut self.html);
self.change_state()
}
}
Expand Down
Loading

0 comments on commit 513f91a

Please sign in to comment.