Atom Engine v5

A high-performance, component-oriented template engine for Rust built on Tera.
Features
- Built on Tera - Leverages Tera's robust parsing, caching, and expression evaluation
- Component System - Register reusable components with props, slots, and validation
- Provide/Inject Context - React-style context for passing data to descendants
- Stack System - Push content and render stacks
- 50+ Built-in Filters - String, collection, number, date, HTML, conditional filters
- Helper Macros/Directives - @map, @filter, @each, @reduce, @flatten, @partition
- Layout Inheritance - Full support for Tera's
extends/block system
- Macros - Define reusable template fragments
- Async Support - Render templates asynchronously with tokio
- Component Caching - Cache rendered components for performance
- Parallel Rendering - Render multiple templates in parallel with Rayon
- Memory Pool - Optional memory pooling for allocations
Installation
[dependencies]
atom-engine = "5"
Optional Features
[dependencies]
atom-engine = { version = "5", features = ["parallel", "async", "pool-alloc"] }
| Feature |
Description |
parallel |
Enable parallel rendering with Rayon |
async |
Enable async template rendering with Tokio |
pool-alloc |
Enable memory pooling for allocations |
Quick Start
use atom_engine::Atom;
use serde_json::json;
fn main() {
let mut engine = Atom::new();
engine.add_template("hello.html", "Hello, {{ name }}!").unwrap();
let result = engine.render("hello.html", &json!({"name": "World"})).unwrap();
println!("{}", result); }
Template Syntax
Variables
{{ user.name }}
{{ items[0] }}
{{ company.address.city }}
{{ items | length }}
Comments
{# This is a comment #}
Filters
Filters transform values. Chain multiple filters:
{{ name | upper | capitalize }}
{{ text | truncate(length=50, end="...") }}
{{ items | sort | reverse | first }}
Control Flow
Conditionals
{% if user.is_active %}
Welcome, {{ user.name }}!
{% elif user.is_pending %}
Please verify your email.
{% else %}
<a href="/login">Login</a>
{% endif %}
Boolean Operators
{% if user.is_active and user.is_verified %}
Verified user
{% endif %}
{% if is_admin or is_editor %}
Can edit content
{% endif %}
{% if not user.is_banned %}
Not banned
{% endif %}
Comparison Operators
{% if count > 10 %}
Many items
{% endif %}
{% if status == "active" %}
Active
{% endif %}
{% if price >= 100 %}
Premium
{% endif %}
Loops
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
{% for item in items %}
{{ loop.index }}: {{ item }}
{% endfor %}
{% for item in items %}
{% if loop.first %}First: {% endif %}{{ item }}
{% if loop.last %} (Last){% endif %}
{% endfor %}
{% for item in empty_array %}
{{ item }}
{% empty %}
No items found
{% endfor %}
Template Inheritance
Base Template (base.html)
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
{% block extra_head %}{% endblock %}
</head>
<body>
<header>{% block header %}Header{% endblock %}</header>
<main>{% block content %}{% endblock %}</main>
<footer>{% block footer %}Footer{% endblock %}</footer>
</body>
</html>
Child Template
{% extends "base.html" %}
{% block title %}{{ page_title }}{% endblock %}
{% block content %}
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>
{% endblock %}
Includes
{% include "header.html" %}
{% includeIf "optional.html" %}
{% includeFirst ["primary.html", "fallback.html"] %}
Macros
{% macro button(text, type="primary") %}
<button class="btn btn-{{ type }}">{{ text }}</button>
{% endmacro %}
{% macro input(name, type="text", placeholder="") %}
<input type="{{ type }}" name="{{ name }}" placeholder="{{ placeholder }}">
{% endmacro %}
{{ button("Submit", type="success") }}
{{ input("email", type="email") }}
Components
Registering Components
engine.register_component(
"button",
r#"<button class="btn btn-{{ type }}">{{ text }}</button>"#
).unwrap();
Component with Props
engine.register_component(
"card",
r#"<div class="card">
<h3>{{ title }}</h3>
<div class="content">{{ $slots.default() }}</div>
</div>"#
).unwrap();
Using Components
{% call button(text="Click Me", type="primary") %}
{% call card(title="My Card") %}
Card content here
{% endcall %}
Props Validation
engine.register_component(
"user-card",
r#"{%-- atom: @props(name:String, age:Number, active:Boolean=true) --%}
<div class="user">
<h3>{{ name }}</h3>
<p>Age: {{ age }}</p>
</div>"#
).unwrap();
Scoped Slots
{# In component #}
{% for item in items %}
{{ $slots.item(item=item) }}
{% endfor %}
{# Usage #}
{% call list(items=users) %}
{% fill item(item=user) %}{{ user.name }}{% endfill %}
{% endcall %}
Context (Provide/Inject)
Providing Values
engine.provide("theme", json!("dark"));
engine.provide("user", json!({"name": "Alice", "role": "admin"}));
Using in Templates
{# Access provided values #}
Theme: {{ __context.theme }}
User: {{ __context.user.name }}
Stack System
Push to Stack
{% call push(name="scripts", content="<script>alert('hi')</script>") %}
{% call push(name="scripts", content="<script>console.log('test')</script>") %}
Prepend to Stack
{% call prepend(name="header", content="<li>First</li>") %}
Render Stack
{{ "scripts" | stack }}
Filters Reference
String Filters
| Filter |
Description |
Example |
upper |
Uppercase |
{{ "hello" | upper }} → HELLO |
lower |
Lowercase |
{{ "HELLO" | lower }} → hello |
capitalize |
Capitalize first |
{{ "hello" | capitalize }} → Hello |
title |
Title case |
{{ "hello world" | title }} → Hello World |
camel_case |
camelCase |
{{ "hello_world" | camel_case }} → helloWorld |
pascal_case |
PascalCase |
{{ "hello_world" | pascal_case }} → HelloWorld |
snake_case |
snake_case |
{{ "helloWorld" | snake_case }} → hello_world |
kebab_case |
kebab-case |
{{ "helloWorld" | kebab_case }} → hello-world |
truncate(length, end?) |
Truncate |
{{ "Hello World" | truncate(length=5) }} → Hello... |
slugify |
URL slug |
{{ "Hello World!" | slugify }} → hello-world |
pluralize(n, singular?, plural?) |
Pluralize |
{{ 1 | pluralize }} → `` |
replace(old, new) |
Replace |
{{ "Hi" | replace(old="Hi", new="Hello") }} → Hello |
remove(string) |
Remove |
{{ "Hello" | remove(string="l") }} → Heo |
prepend(string) |
Prepend |
{{ "World" | prepend(string="Hello ") }} → Hello World |
append(string) |
Append |
{{ "Hello" | append(string=" World") }} → Hello World |
strip |
Trim whitespace |
{{ " hi " | strip }} → hi |
nl2br |
Newlines to <br> |
{{ "a\nb" | nl2br | safe }} → a<br>b |
word_count |
Count words |
{{ "hello world" | word_count }} → 2 |
char_count |
Count chars |
{{ "hello" | char_count }} → 5 |
starts_with(prefix) |
Check prefix |
{{ "hello" | starts_with(prefix="hel") }} → true |
ends_with(suffix) |
Check suffix |
{{ "hello" | ends_with(suffix="llo") }} → true |
contains(substring) |
Check contains |
{{ "hello" | contains(substring="ell") }} → true |
Collection Filters
| Filter |
Description |
Example |
first |
First element |
{{ [1,2,3] | first }} → 1 |
last |
Last element |
{{ [1,2,3] | last }} → 3 |
length |
Count elements |
{{ [1,2,3] | length }} → 3 |
reverse |
Reverse array |
{{ [1,2,3] | reverse | join(",") }} → 3,2,1 |
sort |
Sort array |
{{ [3,1,2] | sort | join(",") }} → 1,2,3 |
group_by(attribute) |
Group by field |
See docs |
where(attribute, value?) |
Filter array |
See docs |
pluck(attribute) |
Extract field |
{{ users | pluck(attribute="name") }} |
join(separator) |
Join array |
{{ ["a","b"] | join("-") }} → a-b |
slice(start, length) |
Array slice |
{{ [1,2,3,4] | slice(start=1, length=2) }} → [2,3] |
uniq |
Unique elements |
{{ [1,2,1] | uniq }} → [1,2] |
shuffle |
Random shuffle |
{{ [1,2,3] | shuffle }} |
map(prop, transform?) |
Transform elements |
See helper directives |
filter(key, value, op?) |
Filter elements |
See helper directives |
each(index?) |
Iterate with index |
See helper directives |
reduce(initial, prop?) |
Reduce to value |
See helper directives |
flatten |
Flatten nested arrays |
{{ [[1,2],[3]] | flatten }} → [1,2,3] |
partition(key) |
Split array |
See helper directives |
Number Filters
| Filter |
Description |
Example |
round(precision?) |
Round number |
{{ 3.7 | round }} → 4 |
abs |
Absolute value |
{{ -5 | abs }} → 5 |
format(format?) |
Format number |
{{ 1000000 | format }} → 1,000,000 |
min |
Minimum |
{{ [3,1,2] | min }} → 1 |
max |
Maximum |
{{ [3,1,2] | max }} → 3 |
sum |
Sum |
{{ [1,2,3] | sum }} → 6 |
avg |
Average |
{{ [1,2,3] | avg }} → 2 |
ceil |
Ceiling |
{{ 3.1 | ceil }} → 4 |
floor |
Floor |
{{ 3.9 | floor }} → 3 |
Date Filters
| Filter |
Description |
Example |
date(format?) |
Format date |
{{ now() | date }} → 2024-01-15 |
HTML Filters
| Filter |
Description |
Example |
escape_html |
Escape HTML |
{{ "<>" | escape_html }} → <> |
safe |
Mark as safe |
{{ "<b>" | safe }} → <b> |
strip_tags |
Remove HTML tags |
{{ "<p>hi</p>" | strip_tags }} → hi |
Encoding Filters
| Filter |
Description |
Example |
json_decode |
Parse JSON |
{{ '{"a":1}' | json_decode | safe }} |
urlescape |
URL encode |
{{ "a b" | urlescape }} → a%20b |
urlunescape |
URL decode |
{{ "a%20b" | urlunescape }} → a b |
base64_encode |
Base64 encode |
{{ "hi" | base64_encode }} → aGk= |
base64_decode |
Base64 decode |
{{ "aGk=" | base64_decode }} → hi |
Conditional Filters
| Filter |
Description |
Example |
default(value) |
Default value |
{{ null | default(value="x") }} → x |
when(then, else) |
Ternary |
{{ true | when(then="yes", else="no") }} → yes |
coalesce(other) |
First non-null |
{{ null | coalesce(other="x") }} → x |
defined |
Check defined |
See docs |
undefined |
Check undefined |
See docs |
empty |
Check empty |
{{ [] | empty }} → true |
not_empty |
Check not empty |
{{ [1] | not_empty }} → true |
Helper Directives
@map - Transform Array Elements
{# Extract property #}
{{ users | map(prop="name") | join(", ") }}
{# With transform #}
{{ users | map(prop="name", transform="upper") | join(", ") }}
@filter - Filter Array Elements
{# By value #}
{{ users | filter(key="active", value=true) | map(prop="name") }}
{# By operator #}
{{ users | filter(key="age", value=18, op="gte") }}
Operators: eq, ne, gt, gte, lt, lte, contains, exists
@each - Iterate with Index
{% for item in items | each(index=true) %}
{{ item.index }}: {{ item.value }}
{% endfor %}
@reduce - Reduce to Single Value
{{ numbers | reduce(initial=0) }}
{{ users | reduce(initial=0, prop="age") }}
@flatten - Flatten Nested Arrays
{{ [[1,2], [3,4]] | flatten | join(", ") }}
{# Output: 1,2,3,4 #}
@partition - Split Array
{% set partitioned = users | partition(key="active") %}
Matched: {{ partitioned.matched | length }}
Rest: {{ partitioned.rest | length }}
Global Functions
| Function |
Description |
dump(...) |
Debug output to stderr |
log(...) |
Log to stderr |
range(start?, end, step_by?) |
Generate range array |
now(utc?, timestamp?) |
Current datetime |
uuid() |
Generate UUID |
random(min, max) |
Random number |
choice(array) |
Random array element |
file_exists(path) |
Check if file exists |
env(key) |
Get environment variable |
md5(string) |
MD5 hash |
sha256(string) |
SHA256 hash |
repeat(content, count, separator?) |
Repeat string |
times(times, start?, step?) |
Generate sequence |
loop(from, to, step?, inclusive?) |
Loop range |
iterate(array, limit?, skip?) |
Iterate with metadata |
object(keys, values) |
Create object from arrays |
merge(array1, array2) |
Merge arrays |
chunk(array, size) |
Chunk array |
zip(arrays) |
Zip arrays |
compact(array) |
Remove nulls |
Advanced Features
Component Caching
engine.enable_component_cache(true);
let result = engine.render("page.html", &ctx).unwrap();
println!("Cache size: {}", engine.component_cache_len());
engine.clear_component_cache();
Async Rendering
#[cfg(feature = "async")]
async fn render() {
let engine = Atom::new();
let result = engine.render_async("template.html", &json!({})).await.unwrap();
}
Parallel Rendering
#[cfg(feature = "parallel")]
let results = engine.render_many(&[
("t1.html", &json!({})),
("t2.html", &json!({})),
("t3.html", &json!({})),
]).unwrap();
Memory Pool
use atom_engine::{MemoryPool, StringPool, PooledString};
let pool = MemoryPool::new(4096, 16);
let s = pool.allocate("Hello");
Examples
Run the examples:
cargo run --example basic
cargo run --example components
cargo run --example layouts
cargo run --example filters
cargo run --example comprehensive_test
Testing
cargo test
cargo test -- --nocapture
cargo test test_filters
License
MIT OR Apache-2.0