A templating library for Rust that allows creating compile-time safe templates that compile to native Rust code, providing type safety and powerful features.
It uses rust syntax for templates, allowing you to write pure Rust code inside your templates. This enables full access to Rust's type system and features and allows you to integrate seamlessly with Rust's ecosystem.
Disclaimer: This project is a work in progress and is not yet production-ready. It needs further testing, documentation, benchmarks, and refinement before being considered stable for production use.
- Magik πͺ
- Compile-time compilation: Templates are compiled at compile time, detecting errors before execution
- Familiar syntax: Uses
{{ }}syntax to insert Rust code - Renderable trait: Automatic implementation for common types
- Choosable trait: Enables elegant conditional logic in templates
- Procedural macros: Facilitates template component creation
All examples are located inside the examples/ folder of the magik_macro crate.
To run a specific example, use the following command from the root of the repository:
cargo run -p magik-macro --example hello_magikExpected output:
Hello from Magik!
Add this to your Cargo.toml:
[dependencies]
magik = { git = "https://github.com/darilrt/magik", package = "magik" }
magik-macro = { git = "https://github.com/darilrt/magik", package = "magik_macro" }use magik_macro::template;
#[template(path = "templates/greeting.tmp")]
pub struct GreetingPage {
name: &'static str,
is_greeting: bool,
}templates/greeting.tmp:
{{ props.is_greeting.choose("Hello", "Goodbye") }}, {{ props.name }}!
Welcome to our application.
use magik::Renderable;
fn main() {
let page = GreetingPage {
name: "World",
is_greeting: true,
};
println!("{}", page.render());
// Output: Hello, World!
// Welcome to our application.
}Templates use the {{ }} syntax to embed pure Rust code. The behavior depends on whether the code returns a value:
- Expressions that return a value: The returned value is converted to a string (via the
Renderabletrait) and inserted into the template - Statements that don't return a value: These are executed in the global scope of the template file, allowing you to declare variables, import modules, or perform setup logic
Example demonstrating scope sharing:
# Configuration Report
{{ use std::collections::HashMap; }}
{{
let mut config = HashMap::new();
config.insert("theme", "dark");
config.insert("lang", "en");
config.insert("debug", "true");
}}
Application Theme: {{ config.get("theme").unwrap_or(&"light") }}
Language: {{ config.get("lang").unwrap_or(&"unknown") }}
Debug Mode: {{ config.get("debug").unwrap_or(&"false") }}
Total settings loaded: {{ config.len() }}
User: {{ props.name }}
Age: {{ props.age }}
Items count: {{ props.items.len() }}
Formatted: {{ format!("User: {}", props.username) }}
{{ use std::collections::HashMap; }}
{{
let user_count = props.users.len();
let is_empty = user_count == 0;
}}
Total users: {{ user_count }}
Status: {{ is_empty.choose("No users found", "Users available") }}
Status: {{ props.is_active.choose("Active", "Inactive") }}
Content: {{ props.show_content.choose_with(|| "Visible content", || "Hidden content") }}
User Type: {{
if props.age >= 18 {
format!("Adult ({})", props.age)
} else {
format!("Minor ({})", props.age)
}
}}
Connection Status: {{
match props.status {
"active" => "π’ Online",
"away" => "π‘ Away",
_ => "π΄ Offline"
}
}}
{{ use crate::components::Button; }}
Actions:
{{ Button { text: "Submit", disabled: false } }}
{{ Button { text: "Cancel", disabled: true } }}
Analyzes templates and separates static text from Rust code:
use magik::Parser;
let mut parser = Parser::new("Hello, {{ name }}! Welcome to {{ app }}.");
while let Some(data) = parser.next() {
println!("{:?}", data);
}Enum that represents different types of content in a template:
pub enum TemplateData {
String(String), // Static text
Code(String), // Rust code to evaluate
}Converts types to strings for display in templates:
pub trait Renderable {
fn render(self) -> String;
}
// Automatically implemented for:
// - String, &str
// - i32, f64, bool
// - Option<T: Renderable>
// - Vec<T: Renderable>
// - ()Enables elegant conditional logic based on boolean values:
pub trait Choosable<T> {
fn choose(&self, if_true: T, if_false: T) -> T;
fn choose_with<F>(&self, if_true: F, if_false: F) -> T
where F: FnOnce() -> T;
}
// Usage example
let is_admin = true;
let message = is_admin.choose("Admin Panel", "User Panel");Applies a template from an external file:
#[template(path = "templates/user.tmp")]
pub struct UserPage {
username: String,
email: String,
}Uses an inline template:
#[template(source = "Hello, {{ props.name }}! Welcome to {{ props.app_name }}.")]
pub struct InlineGreeting<'a> {
name: &'a str,
app_name: &'a str,
}
#[template_str("Hello, {{ props.name }}! Welcome to {{ props.app_name }}.")]
pub struct InlineGreetingStr<'a> {
name: &'a str,
app_name: &'a str,
}- Compile-time safety: Syntax errors detected before execution
- Performance: Templates compile to native Rust code
- Perfect integration: Uses Rust's type system without compromises
- No runtime dependencies: Pure Rust implementation with no external libraries
- Rust integration: Templates are pure Rust code, allowing full access to the language's features
- Requires templates to be available at compile time
- Types must implement the
Renderabletrait - Syntax is limited to valid Rust expressions
To automatically recompile your project when template files change, it's highly recommended to use a build.rs script. This ensures that any changes to your .tmp template files will trigger a rebuild.
You can create a build.rs file or copy the file from the repository build.rs and place it in your project root. This script will watch for changes in the templates/ directory (or any specified directory) and trigger a recompilation when necessary.
Add to your Cargo.toml:
[package]
name = "your-project"
version = "0.1.0"
edition = "2021"
build = "build.rs" # Enable the build script
...With this setup, Cargo will automatically recompile your project whenever you modify any template file in the templates/ directory.
magik/
βββ magik/ # Main library
β βββ src/
β β βββ lib.rs
β β βββ parser.rs # Template parser
β β βββ template.rs # Type definitions
β β βββ renderable.rs # Renderable trait
β β βββ choosable.rs # Choosable trait
β βββ Cargo.toml
βββ magik_macro/ # Procedural macros
β βββ examples/* # Examples
β βββ src/
β β βββ lib.rs
β β βββ utils.rs # Compilation utilities
β β βββ check_return.rs # Return analysis
β βββ Cargo.toml
βββ build.rs # Example build script for automatic recompilation
βββ Cargo.toml
Magik makes text generation in Rust magical β¨