Expand description
§forge-rsx
forge-rsx is a Rust macro library for declarative, JSX-like HTML generation. It allows you to write HTML structures with embedded logic, attributes, nested tags, loops, and more, directly in Rust code with a concise syntax.
§Features
- Declarative HTML macro:
rsx!macro - Supports nested tags, attributes, loops, and embedded expressions
- Indentation-aware formatting
- String literal and identifier attributes
- Flexible syntax for defining complex HTML structures
§Usage
Run the following Cargo command in your project directory:
cargo add forge-rsxOr add forge-rsx as a dependency in your Cargo.toml:
[dependencies]
forge-rsx = "MAJOR.MINOR.PATCH" # Replace with the latest versionIn your Rust code, import the macro:
use forge_rsx::rsx;§Macro Variants
lined: produces HTML without indentation or line breaks (single-line output)btfy0: uses 0 spaces (no indentation, minified output)btfy2: Indentation with 2 spaces per level.btfy4: Indentation with 4 spaces per level.tabed: Indentation with 2 spaces per level. (tabed = btfy2)
§Examples:
use forge_rsx::rsx;
rsx!(btfy0, div { "No indentation" });
rsx!(btfy2, div { "Indented with 2 spaces" });
rsx!(tabed, div { "Indented with 2 spaces" });
rsx!(btfy4, div { "Indented with 4 spaces" });§Examples
§Basic Tag with Content
use forge_rsx::rsx;
// 1. First style: using parentheses => ()
let greeting1 = rsx!(lined, span { "Hello, World!" });
println!("{}", greeting1); // output: <span>Hello, World!</span>
// 2. Second style: using braces => {}
let greeting2 = rsx! { lined, span { "Hello, World!" } };
println!("{}", greeting2); // output: <span>Hello, World!</span>§Nested Tags
use forge_rsx::rsx;
let nested_html = rsx!(lined, div {
header {
span { "Navigation" }
}
section {
p { "Welcome to the forge-rsx template!" }
}
});
println!("{}", nested_html);§Attributes
// This example demonstrates:
// 1. Attribute quoting: Forge-RSX uses double quotes. Use
// single quotes inside JS/Alpine strings to maintain HTML validity.
// 2. Boolean attributes: Attributes assigned `true` render as keys only.
// Attributes assigned `false` are omitted.
// 3. Formatting: The `lined` mode creates compact HTML without extra whitespace.
use forge_rsx::rsx;
fn main () {
let button_html = rsx! { lined,
button {
class: "btn-primary",
"data-toggle": "modal", // Alpine.js
// ❌ INCORRECT: The "User" double quotes will terminate the attribute early.
"hx-on:click": r#"const name="User"; alert(`Hello, ${name}!`)"#, // HTMX: incorrect output
// ✅ CORRECT: Using single quotes 'User' keeps the attribute valid.
"hx-on:click": "const name='User'; alert(`Hello, ${name}!`)", // HTMX: correct output
// "hx-on:click": r#"const name='User'; alert(`Hello, ${name}!`)"# // HTMX: correct output
"Click me!"
}
};
println!("{}", button_html);
// output:
// <button class="btn-primary" data-toggle="modal" hx-on:click="const name="User"; alert(`Hello, ${name}!`)" hx-on:click="const name='User'; alert(`Hello, ${name}!`)">Click me!</button>
let is_loading = true;
let is_admin = false;
let script_html = rsx!(lined,
script {
defer: true,
async: is_loading, // true
src: "https://example.com/app.js",
hidden: is_admin // false (won't show up)
}
);
println!("{}", script_html);
// Output: <script defer async src="https://example.com/app.js"></script>
}§Loop Example
use forge_rsx::rsx;
let users = vec!["Ahmed", "Mohamed", "Montasir"];
let list_html = rsx!(lined, ul {
for user in &users => {
li { { format!("User: {}", user) } }
}
});
println!("{}", list_html);§Code Tag
use forge_rsx::rsx;
fn main () {
// The raw data
let code_source = "fn main() {\n println!(\"Hello\");\n}";
// The specific HTML structure for the code
let code_snippet = rsx!(lined,
pre {
code {
{code_source}
}
}
);
// The final container (lined)
let final_html_lined = rsx!(lined,
div {
{code_snippet}
}
);
println!("{}", final_html_lined);
// (lined lined) output:
// <div><pre><code>fn main() {
// println!("Hello");
// }</code></pre></div>
// Browser result:
// fn main() {
// println!("Hello");
// }
// The final container (btfy0)
let final_html_btfy0 = rsx!(btfy0,
div {
{code_snippet}
}
);
println!("{}", final_html_btfy0);
// (lined btfy0) output:
// <div>
// <pre><code>fn main() {
// println!("Hello");
// }</code></pre>
// </div>
// Browser result:
// fn main() {
// println!("Hello");
// }
// The final container (btfy4)
let final_html_btfy4 = rsx!(btfy4,
div {
{code_snippet}
}
);
println!("{}", final_html_btfy4);
// (lined btfy4) output:
// <div>
// <pre><code>fn main() {
// println!("Hello");
// }</code></pre>
// </div>
// Browser result:
// fn main() {
// println!("Hello");
// }
}§Full Complex Example
use forge_rsx::{rsx, get_char};
fn main() {
// 1. Component defined with 'lined' (minified single line)
let apple = "🍎 Apple";
let apple_component = rsx!(lined, span { {&apple} });
// 2. Full HTML Document using 'doctype_html' and 'btfy4'
let html_doc = rsx! {
btfy4,
doctype_html
html {
head {
meta { charset: "UTF-8" } // No comma needed here
meta { name: "viewport", content: "width=device-width, initial-scale=1.0" }
title { "Forge RSX Demo" }
}
body {
"x-data": "{ open: false }", ":class": "bg-white",
h1 { "Welcome to Forge RSX" }
br {}
div {
class: "container",
"x-show": "open",
"Alpine.js integration demo"
}
"id": "my-id", "style": "color: #4f4f4f; font-size: 2rem;" // No comma needed here
}
}
};
// 3. Formatting Samples: Demonstrating 0 and 2-space indentation styles
let span = rsx!(btfy0, span { "..." });
let empty_p = rsx!(btfy2, p { });
let p = rsx!(btfy2, p {"..."});
// 4. Complex section with 'for' loops and logic
let fruits = vec!["🍇", "mango", "orange"];
let section = rsx!(btfy4, section {
div {
ol {
for fruit in &fruits => {
li {
span {
{
if fruit == &"🍇" {
&format!("{} {}", fruit.to_string(), "Grapes")
} else if fruit == &"mango" {
&format!("{} {}", "🥭", fruit.to_lowercase())
} else {
&fruit.to_uppercase()
}
}
}
}
}
li {
{"<!-- How to join RSX component -->"}
{&apple_component.to_string()}
{
if get_char(&apple, 1).to_string() == "🍎" {
"🍎".to_string()
} else {
apple_component.to_string()
}
}
}
}
}
});
// 5. Printing all results
println!("--- FULL HTML DOCUMENT ---\n{}\n", html_doc);
println!("--- MINIFIED SPAN ---\n{}\n", span);
println!("--- EMPTY P ---\n{}\n", empty_p);
println!("--- P WITH CONTENT ---\n{}\n", p);
println!("--- FRUIT SECTION ---\n{}", section);
// --- FULL HTML DOCUMENT ---
// <!DOCTYPE html>
// <html>
// <head>
// <meta charset="UTF-8">
// <meta name="viewport" content="width=device-width, initial-scale=1.0">
// <title>
// Forge RSX Demo
// </title>
// </head>
// <body x-data="{ open: false }" :class="bg-white" id="my-id" style="color: #4f4f4f; font-size: 2rem;">
// <h1>
// Welcome to Forge RSX
// </h1>
// <br>
// <div class="container" x-show="open">
// Alpine.js integration demo
// </div>
// </body>
// </html>
// --- MINIFIED SPAN ---
// <span>
// ...
// </span>
// --- EMPTY P ---
// <p></p>
// --- P WITH CONTENT ---
// <p>
// ...
// </p>
// --- FRUIT SECTION ---
// <section>
// <div>
// <ol>
// <li>
// <span>
// 🍇 Grapes
// </span>
// </li>
// <li>
// <span>
// 🥭 mango
// </span>
// </li>
// <li>
// <span>
// ORANGE
// </span>
// </li>
// <li>
// <!-- How to join RSX component -->
// <span>🍎 Apple</span>
// 🍎
// </li>
// </ol>
// </div>
// </section>
}§License
MIT License
§Notes
- The macro supports attributes with string literals and identifiers.
- Nested tags are handled with recursive macro calls.
- Looping constructs generate repeated content.
- Content inside braces
{}can contain any Rust expression that implementsDisplay.
Modules§
- rules
- Rules Module
Macros§
- parse_
attr - Parses attribute pattern into a key-value tuple, if applicable.
- rsx
- A macro to generate HTML-like markup with different indentation styles.
- rsx_
muncher - The core macro responsible for generating HTML-like markup with flexible indentation, attribute handling, nested tags, loops, and expressions.
Functions§
- get_
char - Returns the character at the specified 1-based index
nfrom the input strings.