Skip to main content

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>&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</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}