Skip to content

Commit 513f91a

Browse files
committed
wip
1 parent 1969d25 commit 513f91a

File tree

6 files changed

+212
-148
lines changed

6 files changed

+212
-148
lines changed

htmx-macros/src/htmx/html.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,21 @@ pub fn expand_node(node: Node) -> Result {
8787
};
8888
let script = script.into_token_stream();
8989
if let Ok(script) = parse2::<LitStr>(script.clone()) {
90-
quote!(__html.child_expr(#script);)
90+
quote!(::htmx::ToScript::to_script(&#script, &mut __html);)
9191
} else if let Ok(block) =
9292
parse2::<Recoverable<NodeBlock>>(script.clone()).map(Recoverable::inner)
9393
{
94-
quote!(__html.child_expr({#[allow(unused_braces)] #block});)
94+
quote!(::htmx::ToScript::to_script(&{#[allow(unused_braces)] #block}, &mut __html);)
9595
} else {
9696
let script: Script = parse2(script)?;
9797
let script = script.to_java_script();
98-
quote!(__html.child(#script);)
98+
quote!(::htmx::ToScript::to_script(#script, &mut __html);)
9999
}
100100
} else {
101101
expand_nodes(children)?
102102
};
103-
let body = (!children.is_empty()).then(|| quote!(let mut __html = __html.body();));
104-
let main = quote!({let mut __html = #name #(__html.#attributes;)* #body; #children});
103+
let body = (!children.is_empty()).then(|| quote!(let mut __html = __html.body(|mut __html| {#children});));
104+
let main = quote!({let mut __html = #name #(.#attributes)*; #body;});
105105

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

140140
fn name_to_struct(name: NodeName) -> Result<(TokenStream, bool)> {
141141
match name {
142-
NodeName::Path(path) => Ok((quote!(#path::new(&mut __html);), false)),
142+
NodeName::Path(path) => Ok((quote!(#path::new(&mut __html)), false)),
143143
name @ NodeName::Punctuated(_) => {
144144
let name = ensure_tag_name(name.to_string(), name)?;
145145
Ok((
146-
quote!(::htmx::CustomElement::new_unchecked(&mut __html, #name);),
146+
quote!(::htmx::CustomElement::new_unchecked(&mut __html, #name)),
147147
true,
148148
))
149149
}
@@ -161,12 +161,12 @@ fn name_to_struct(name: NodeName) -> Result<(TokenStream, bool)> {
161161
{
162162
let name = ensure_tag_name(name.value(), name)?;
163163
Ok((
164-
quote!(::htmx::CustomElement::new_unchecked(&mut __html, #name);),
164+
quote!(::htmx::CustomElement::new_unchecked(&mut __html, #name)),
165165
true,
166166
))
167167
} else {
168168
Ok((
169-
quote!(::htmx::CustomElement::new(&mut __html, #name);),
169+
quote!(::htmx::CustomElement::new(&mut __html, #name)),
170170
true,
171171
))
172172
}

htmx-macros/src/htmx/rusty.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,12 @@ impl Element {
410410

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

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

415415
quote!({
416416
let mut __html = #name
417417
#(__html #attrs;)*
418418
#body;
419-
#(#children)*
420419
})
421420
}
422421
}

src/lib.rs

+93-73
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,6 @@ impl<T: WriteHtml> WriteHtml for ManuallyDrop<T> {
389389
///
390390
/// The [`html!`] macro uses them for all tags that contain `-` making it
391391
/// possible to use web-components.
392-
#[must_use]
393392
pub struct CustomElement<Html: WriteHtml, S: ElementState> {
394393
html: ManuallyDrop<Html>,
395394
name: ManuallyDrop<Cow<'static, str>>,
@@ -444,64 +443,37 @@ impl<Html: WriteHtml> CustomElement<Html, Tag> {
444443
/// [`AnyAttributeValue`], without checking for invalid characters.
445444
///
446445
/// 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.
447-
pub fn custom_attr_unchecked(
448-
&mut self,
449-
key: impl Display,
450-
value: impl ToAttribute<Any>,
451-
) {
446+
pub fn custom_attr_unchecked(&mut self, key: impl Display, value: impl ToAttribute<Any>) {
452447
debug_assert!(!key.to_string().chars().any(|c| c.is_whitespace()
453448
|| c.is_control()
454449
|| matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
455450
write!(self.html, " {key}");
456451
value.write(&mut self.html);
457452
}
458453

459-
pub fn custom_attr_composed(self, key: impl Display) -> CustomElement<Html, CustomAttr> {
460-
assert!(!key.to_string().chars().any(|c| c.is_whitespace()
461-
|| c.is_control()
462-
|| matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
463-
self.custom_attr_composed_unchecked(key)
464-
}
465-
466-
pub fn custom_attr_composed_unchecked(
467-
mut self,
468-
key: impl Display,
469-
) -> CustomElement<Html, CustomAttr> {
470-
debug_assert!(!key.to_string().chars().any(|c| c.is_whitespace()
471-
|| c.is_control()
472-
|| matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
473-
write!(self.html, " {key}=\"");
474-
self.change_state()
475-
}
476-
477-
pub fn body(mut self) -> CustomElement<Html, Body> {
454+
// TODO, use closure like body
455+
// pub fn custom_attr_composed(self, key: impl Display) -> CustomElement<Html,
456+
// CustomAttr> { assert!(!key.to_string().chars().any(|c|
457+
// c.is_whitespace() || c.is_control()
458+
// || matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
459+
// self.custom_attr_composed_unchecked(key)
460+
// }
461+
462+
// pub fn custom_attr_composed_unchecked(
463+
// mut self,
464+
// key: impl Display,
465+
// ) -> CustomElement<Html, CustomAttr> {
466+
// debug_assert!(!key.to_string().chars().any(|c| c.is_whitespace()
467+
// || c.is_control()
468+
// || matches!(c, '\0' | '"' | '\'' | '>' | '/' | '=')), "invalid key `{key}`, https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#attributes-0");
469+
// write!(self.html, " {key}=\"");
470+
// self.change_state()
471+
// }
472+
473+
pub fn body(mut self, body: impl FnOnce(&mut Html)) {
478474
self.html.write_gt();
479-
self.change_state()
480-
}
481-
}
482-
483-
impl<Html: WriteHtml> WriteHtml for CustomElement<Html, Body> {
484-
fn write_str(&mut self, s: &str) {
485-
self.html.write_str(s);
486-
}
487-
488-
fn write_char(&mut self, c: char) {
489-
self.html.write_char(c);
490-
}
491-
492-
fn write_fmt(&mut self, a: std::fmt::Arguments) {
493-
self.html.write_fmt(a);
494-
}
495-
}
496-
497-
impl<Html: WriteHtml> CustomElement<Html, Body> {
498-
pub fn child_expr(mut self, child: impl ToHtml) -> Self {
499-
child.to_html(&mut self);
500-
self
501-
}
502-
503-
pub fn child<C>(self, child: impl FnOnce(Self) -> C) -> C {
504-
child(self)
475+
body(&mut self.html);
476+
self.change_state::<Body>();
505477
}
506478
}
507479

@@ -516,14 +488,6 @@ impl<Html: WriteHtml, S: ElementState> CustomElement<Html, S> {
516488
state: PhantomData,
517489
}
518490
}
519-
520-
pub fn close(mut self) -> Html {
521-
S::close_tag(&mut self.html);
522-
self.html.write_close_tag_unchecked(self.name.as_ref());
523-
let html = unsafe { ManuallyDrop::take(&mut self.html) };
524-
std::mem::forget(self);
525-
html
526-
}
527491
}
528492

529493
impl<Html: WriteHtml, S: ElementState> Drop for CustomElement<Html, S> {
@@ -533,20 +497,6 @@ impl<Html: WriteHtml, S: ElementState> Drop for CustomElement<Html, S> {
533497
}
534498
}
535499

536-
impl<Html: WriteHtml> CustomElement<Html, CustomAttr> {
537-
pub fn attr_value(mut self, value: impl ToAttribute<Any>) -> Self {
538-
if !value.is_unset() {
539-
value.write_inner(&mut self.html);
540-
}
541-
self
542-
}
543-
544-
pub fn close_attr(mut self) -> CustomElement<Html, Tag> {
545-
self.html.write_quote();
546-
self.change_state()
547-
}
548-
}
549-
550500
/// Puts content directly into HTML bypassing HTML-escaping.
551501
///
552502
/// ```
@@ -567,6 +517,30 @@ impl<'a> RawHtml<'a> {
567517
}
568518
}
569519

520+
pub struct Fragment<F>(pub F);
521+
522+
// TODO reconsider elements implementing WriteHtml, maybe it would be better for them to implement a way to access the underlying `Html`
523+
impl<F> Fragment<F> {
524+
#[allow(non_snake_case)]
525+
pub fn EMPTY(self, html: impl WriteHtml) {}
526+
}
527+
528+
impl<F: FnOnce(Html), Html: WriteHtml> IntoHtml<Html> for Fragment<F> {
529+
fn into_html(self, html: Html) {
530+
self.0(html);
531+
}
532+
}
533+
534+
pub trait IntoHtml<Html> {
535+
fn into_html(self, html: Html);
536+
}
537+
538+
impl<T: ToHtml, Html: WriteHtml> IntoHtml<Html> for T {
539+
fn into_html(self, html: Html) {
540+
self.to_html(html);
541+
}
542+
}
543+
570544
pub trait ToHtml {
571545
fn to_html(&self, html: impl WriteHtml);
572546
}
@@ -629,3 +603,49 @@ impl ElementState for Body {
629603
pub trait ElementState {
630604
fn close_tag(html: impl WriteHtml);
631605
}
606+
607+
forr! {$type:ty in [&str, String, Cow<'_, str>]$*
608+
impl ToHtml for $type {
609+
fn to_html(&self, mut out: impl WriteHtml) {
610+
write!(out, "{}", html_escape::encode_text(&self));
611+
}
612+
}
613+
614+
impl ToScript for $type {
615+
fn to_script(&self, mut out: impl WriteHtml) {
616+
write!(out, "{}", html_escape::encode_script(&self));
617+
}
618+
}
619+
620+
impl ToStyle for $type {
621+
fn to_style(&self, mut out: impl WriteHtml) {
622+
write!(out, "{}", html_escape::encode_style(&self));
623+
}
624+
}
625+
}
626+
627+
impl ToHtml for char {
628+
fn to_html(&self, mut out: impl WriteHtml) {
629+
write!(out, "{}", html_escape::encode_text(&self.to_string()));
630+
}
631+
}
632+
633+
pub trait ToScript {
634+
fn to_script(&self, out: impl WriteHtml);
635+
}
636+
637+
impl<T: ToScript> ToScript for &T {
638+
fn to_script(&self, out: impl WriteHtml) {
639+
T::to_script(self, out);
640+
}
641+
}
642+
643+
pub trait ToStyle {
644+
fn to_style(&self, out: impl WriteHtml);
645+
}
646+
647+
impl<T: ToStyle> ToStyle for &T {
648+
fn to_style(&self, out: impl WriteHtml) {
649+
T::to_style(self, out);
650+
}
651+
}

src/native.rs

+6-49
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,17 @@
11
//! Native HTML elements
22
#![allow(non_camel_case_types, clippy::return_self_not_must_use)]
33

4-
use std::borrow::Cow;
54
use std::fmt::Display;
65
use std::marker::PhantomData;
76
use std::mem::ManuallyDrop;
87

98
use forr::{forr, iff};
109

1110
use crate::attributes::{Any, DateTime, FlagOrValue, Number, TimeDateTime, ToAttribute};
12-
use crate::{Body, ClassesAttr, CustomAttr, ElementState, StyleAttr, Tag, ToHtml, WriteHtml};
13-
14-
forr! {$type:ty in [&str, String, Cow<'_, str>]$*
15-
impl ToHtml for $type {
16-
fn to_html(&self, mut out: impl WriteHtml) {
17-
write!(out, "{}", html_escape::encode_text(&self));
18-
}
19-
}
20-
21-
impl ToScript for $type {
22-
fn to_script(&self, mut out: impl WriteHtml) {
23-
write!(out, "{}", html_escape::encode_script(&self));
24-
}
25-
}
26-
27-
impl ToStyle for $type {
28-
fn to_style(&self, mut out: impl WriteHtml) {
29-
write!(out, "{}", html_escape::encode_style(&self));
30-
}
31-
}
32-
}
33-
34-
impl ToHtml for char {
35-
fn to_html(&self, mut out: impl WriteHtml) {
36-
write!(out, "{}", html_escape::encode_text(&self.to_string()));
37-
}
38-
}
39-
40-
pub trait ToScript {
41-
fn to_script(&self, out: impl WriteHtml);
42-
}
43-
44-
impl<T: ToScript> ToScript for &T {
45-
fn to_script(&self, out: impl WriteHtml) {
46-
T::to_script(self, out);
47-
}
48-
}
49-
50-
pub trait ToStyle {
51-
fn to_style(&self, out: impl WriteHtml);
52-
}
53-
54-
impl<T: ToStyle> ToStyle for &T {
55-
fn to_style(&self, out: impl WriteHtml) {
56-
T::to_style(self, out);
57-
}
58-
}
11+
use crate::{
12+
Body, ClassesAttr, CustomAttr, ElementState, StyleAttr, Tag, ToHtml, ToScript, ToStyle,
13+
WriteHtml,
14+
};
5915

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

182138
iff! {!equals_any($type)[(area), (base), (br), (col), (embeded), (hr), (input), (link), (meta), (source), (track), (wbr)] $:
183-
pub fn body(mut self) -> $type<T, Body> {
139+
pub fn body(mut self, body: impl FnOnce(&mut T)) -> $type<T, Body> {
184140
self.html.write_gt();
141+
body(&mut self.html);
185142
self.change_state()
186143
}
187144
}

0 commit comments

Comments
 (0)