plait/lib.rs
1//! A lightweight, type-safe HTML templating library for Rust.
2//!
3//! Plait provides a macro-based DSL for writing HTML directly in Rust code with compile-time validation and automatic
4//! escaping. It's designed for building server-side rendered HTML with minimal runtime overhead.
5//!
6//! # Quick Start
7//!
8//! ```rust
9//! use plait::{html, render};
10//!
11//! let name = "World";
12//! let html = render(html! {
13//! div(class: "greeting") {
14//! h1 { "Hello, " (name) "!" }
15//! }
16//! });
17//!
18//! assert_eq!(html, "<div class=\"greeting\"><h1>Hello, World!</h1></div>");
19//! ```
20//!
21//! # The `html!` Macro
22//!
23//! The [`html!`] macro provides a concise syntax for writing HTML:
24//!
25//! ```rust
26//! use plait::{html, render};
27//!
28//! let html = render(html! {
29//! // Elements with attributes
30//! div(id: "main", class: "container") {
31//! // Nested elements
32//! h1 { "Title" }
33//!
34//! // Self-closing void elements
35//! br;
36//! input(type: "text", name: "query");
37//!
38//! // Text content and expressions (escaped by default)
39//! p { "Static text and " (2 + 2) " dynamic values" }
40//! }
41//! });
42//! ```
43//!
44//! ## Expressions
45//!
46//! Use parentheses to embed expressions. Content is automatically HTML-escaped:
47//!
48//! ```rust
49//! use plait::{html, render};
50//!
51//! let user_input = "<script>alert('xss')</script>";
52//! let html = render(html! { p { (user_input) } });
53//!
54//! assert_eq!(html, "<p><script>alert('xss')</script></p>");
55//! ```
56//!
57//! For raw, unescaped content, prefix with `#`:
58//!
59//! ```rust
60//! use plait::{html, render};
61//!
62//! let trusted_html = "<strong>Bold</strong>";
63//! let html = render(html! { div { #(trusted_html) } });
64//!
65//! assert_eq!(html, "<div><strong>Bold</strong></div>");
66//! ```
67//!
68//! ## Attributes
69//!
70//! Attributes support several forms:
71//!
72//! ```rust
73//! use plait::{html, render};
74//!
75//! let class = Some("active");
76//! let disabled = true;
77//!
78//! let html = render(html! {
79//! // Boolean attribute (no value)
80//! button(checked) { "Checked" }
81//!
82//! // Optional attribute with `?:` - included when Some or true
83//! div(class?: class) { "Has class" }
84//! button(disabled?: disabled) { "Disabled" }
85//!
86//! // Attribute names with underscores become hyphens
87//! div(hx_target: "body") {} // renders as hx-target="body"
88//!
89//! // String attribute names for special characters
90//! div("@click": "handler()") {}
91//! });
92//! ```
93//!
94//! ## Control Flow
95//!
96//! Standard Rust control flow works naturally:
97//!
98//! ```rust
99//! use plait::{html, render};
100//!
101//! let show = true;
102//! let items = vec!["a", "b", "c"];
103//! let variant = "primary";
104//! let user = Some("Alice");
105//!
106//! let html = render(html! {
107//! // Conditionals
108//! if show {
109//! p { "Visible" }
110//! } else {
111//! p { "Hidden" }
112//! }
113//!
114//! // if let with else
115//! if let Some(name) = user {
116//! p { "Welcome, " (name) "!" }
117//! } else {
118//! p { "Please log in" }
119//! }
120//!
121//! // Loops
122//! ul {
123//! for item in &items {
124//! li { (item) }
125//! }
126//! }
127//!
128//! // Pattern matching
129//! match variant {
130//! "primary" => button(class: "btn-primary") { "Primary" },
131//! "secondary" => button(class: "btn-secondary") { "Secondary" },
132//! _ => button { "Default" }
133//! }
134//! });
135//! ```
136//!
137//! # Components
138//!
139//! Create reusable components using the [`component!`] macro:
140//!
141//! ```rust
142//! use plait::{component, html, render};
143//!
144//! component! {
145//! fn Button<'a>(class: &'a str) {
146//! button(class: format_args!("btn {class}"), #attrs) {
147//! #children
148//! }
149//! }
150//! }
151//!
152//! let html = render(html! {
153//! // Component props before `;`, HTML attributes after
154//! @Button(class: "primary"; id: "submit-btn", disabled?: false) {
155//! "Click me"
156//! }
157//! });
158//!
159//! assert_eq!(html, "<button class=\"btn primary\" id=\"submit-btn\">Click me</button>");
160//! ```
161//!
162//! Inside components, `#attrs` spreads additional HTML attributes and `#children` renders the component's children.
163//!
164//! # URL Safety
165//!
166//! URL attributes (`href`, `src`, `action`, etc.) are automatically validated. Dangerous schemes like `javascript:`
167//! are stripped:
168//!
169//! ```rust
170//! use plait::{html, render};
171//!
172//! let html = render(html! {
173//! a(href: "javascript:alert('xss')") { "Click" }
174//! });
175//!
176//! assert_eq!(html, "<a>Click</a>"); // href removed
177//! ```
178//!
179//! Use `#(...)` for raw URLs when you trust the source.
180//!
181//! # Performance
182//!
183//! For better performance when output size is predictable, use [`render_with_capacity`] to pre-allocate the buffer:
184//!
185//! ```rust
186//! use plait::{html, render_with_capacity};
187//!
188//! let html = render_with_capacity(4096, html! {
189//! div { "Content" }
190//! });
191//! ```
192mod component;
193mod formatter;
194mod html;
195mod maybe_attr;
196mod url;
197
198pub use plait_macros::{component, html};
199
200pub use self::{component::Component, formatter::HtmlFormatter, html::Html, maybe_attr::MaybeAttr};
201
202/// Renders HTML content using the provided closure.
203///
204/// This function creates an [`Html`] buffer and passes an [`HtmlFormatter`] to the closure, which can be used to
205/// write HTML content. Typically used with the [`html!`] macro.
206///
207/// # Examples
208///
209/// ```rust
210/// use plait::{html, render};
211///
212/// let html = render(html! {
213/// div(class: "container") {
214/// "Hello, World!"
215/// }
216/// });
217///
218/// assert_eq!(html, "<div class=\"container\">Hello, World!</div>");
219/// ```
220pub fn render(content: impl FnOnce(&mut HtmlFormatter<'_>)) -> Html {
221 let mut output = Html::new();
222 let mut f = HtmlFormatter::new(&mut output);
223 content(&mut f);
224
225 output
226}
227
228/// Renders HTML content with a pre-allocated buffer capacity.
229///
230/// This function is similar to [`render`], but pre-allocates the internal string buffer with the specified capacity.
231/// Use this when you know the approximate size of the output to avoid reallocations and improve performance.
232///
233/// # Examples
234///
235/// ```rust
236/// use plait::{html, render_with_capacity};
237///
238/// let html = render_with_capacity(1024, html! {
239/// div(class: "card") {
240/// h1 { "Title" }
241/// p { "Content goes here..." }
242/// }
243/// });
244///
245/// assert_eq!(html, "<div class=\"card\"><h1>Title</h1><p>Content goes here...</p></div>");
246/// ```
247pub fn render_with_capacity(capacity: usize, content: impl FnOnce(&mut HtmlFormatter<'_>)) -> Html {
248 let mut output = Html::with_capacity(capacity);
249 let mut f = HtmlFormatter::new(&mut output);
250 content(&mut f);
251
252 output
253}