https://github.com/santosr2/luma
A language-agnostic, Lua-powered templating engine with clean, directive-based syntax.
https://github.com/santosr2/luma
golang helm html jinja2 json lua nodejs python template template-engine wasm yaml
Last synced: 5 months ago
JSON representation
A language-agnostic, Lua-powered templating engine with clean, directive-based syntax.
- Host: GitHub
- URL: https://github.com/santosr2/luma
- Owner: santosr2
- License: mit
- Created: 2025-12-04T12:17:54.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2026-01-06T21:13:27.000Z (6 months ago)
- Last Synced: 2026-01-08T22:27:35.244Z (6 months ago)
- Topics: golang, helm, html, jinja2, json, lua, nodejs, python, template, template-engine, wasm, yaml
- Language: Lua
- Homepage: https://santosr2.github.io/luma/
- Size: 1.01 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# Luma
[](https://github.com/santosr2/luma/actions)
[](https://codecov.io/gh/santosr2/luma)
[](https://luarocks.org/modules/santosr2/luma)
[](LICENSE)
[](https://www.lua.org)
A language-agnostic, Lua-powered templating engine with clean, directive-based syntax.
Luma is designed as a modern alternative to Jinja2-style templating with:
- Less punctuation noise
- Line-based directives
- Familiar `$variable` interpolation (like shell/JS)
- Pure Lua implementation for maximum portability
## Installation
```bash
# Using LuaRocks (coming soon)
luarocks install luma
# Or clone and use directly
git clone https://github.com/santosr2/luma_templates
```
## Quick Start
```lua
local luma = require("luma")
-- Simple rendering
local result = luma.render("Hello, $name!", { name = "World" })
-- Output: "Hello, World!"
-- Compile for reuse
local template = luma.compile("Hello, $name!")
print(template:render({ name = "Alice" }))
print(template:render({ name = "Bob" }))
```
## Syntax
### Interpolation
```text
$var -- Simple variable
$user.name -- Member access
${expr} -- Expression with full features
${name | upper} -- With filter
${val | default("N/A")} -- With filter and args
$$ -- Escaped $ (literal $)
```
### Directives
Directives start with `@` at the beginning of a line (after optional indentation):
```text
@if condition
Content when true
@elif other_condition
Content for elif
@else
Content when false
@end
@for item in items
- $item
@else
No items found
@end
@let total = price * quantity
@# This is a comment (not rendered)
```
Directives can be indented to match your file structure (great for YAML/configs):
```yaml
spec:
containers:
@for container in containers
- name: $container.name
@if container.ports
ports:
@for port in container.ports
- containerPort: $port
@end
@end
@end
```
### Filters
```lua
-- String filters
${name | upper} -- Uppercase
${name | lower} -- Lowercase
${name | capitalize} -- Capitalize first letter
${name | title} -- Title Case
${text | trim} -- Remove whitespace
-- Collection filters
${items | first} -- First element
${items | last} -- Last element
${items | length} -- Count
${items | join(", ")} -- Join with separator
${items | reverse} -- Reverse order
${items | sort} -- Sort
-- Number filters
${num | abs} -- Absolute value
${num | round(2)} -- Round to precision
${num | floor} -- Floor
${num | ceil} -- Ceiling
-- Default value
${missing | default("fallback")}
```
### Loop Variables
Inside `@for` loops, you have access to `loop` metadata:
```text
@for item in items
${loop.index} -- 1-based index
${loop.index0} -- 0-based index
${loop.first} -- true if first iteration
${loop.last} -- true if last iteration
${loop.length} -- total items
@end
```
## API Reference
### Basic Usage
```lua
local luma = require("luma")
-- Render a template string
local result = luma.render(template_string, context)
-- Compile for reuse
local compiled = luma.compile(template_string)
local result = compiled:render(context)
```
### Custom Environment
```lua
local env = luma.create_environment()
-- Add custom filter
env:add_filter("double", function(s)
return s .. s
end)
-- Add global variable
env:add_global("site_name", "My Site")
-- Render with custom environment
local result = env:render("Welcome to $site_name!", {})
```
### Register Global Filters
```lua
luma.register_filter("exclaim", function(s)
return s .. "!"
end)
-- Now available in all templates
local result = luma.render("${msg | exclaim}", { msg = "Hello" })
-- Output: "Hello!"
```
### Inline Directives
For single-line compact output, use semicolon (`;`) to mark the end of directive expressions:
```lua
-- Inline conditional
local status = luma.render("Status: @if active; ✓ Online @else ✗ Offline @end", {active = true})
-- Output: "Status: ✓ Online"
-- Inline loop
local list = luma.render("Items: @for i in nums; $i @end", {nums = {1,2,3}})
-- Output: "Items: 1 2 3 "
-- Space before @ distinguishes directives from literals
local email = luma.render("Contact: user@example.com", {}) -- @ is literal
local directive = luma.render("Result: @if x; yes @end", {x=true}) -- @ is directive
```
**Rules:**
- Use `;` after directive expressions (e.g., `@if condition;`, `@for x in list;`)
- Directives without expressions don't need `;` (e.g., `@else`, `@end`)
- Require space before `@` for inline directives to avoid ambiguity with emails, etc.
## Whitespace & Indentation
> [!IMPORTANT]
> **Luma automatically preserves indentation in ALL file types** - YAML, HTML, JSON, code, configs, markdown, etc.
>
> Indentation is preserved based on where placeholders and directives appear. You rarely need to think about
> whitespace - Luma handles it intelligently by default.
---
> [!TIP]
> While directives don't *require* indentation, we **strongly recommend** indenting them to match your document
> structure for better readability.
>
> **Inline directives**: Use semicolon (`;`) after expressions for single-line compact output: `@if x; yes @else no @end`
>
> Space required before `@` for inline mode. See [docs/WHITESPACE.md](docs/WHITESPACE.md) for details.
## Example: Kubernetes Deployment
### ✅ Recommended (indented directives)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: $app.name
namespace: ${namespace | default("default")}
spec:
replicas: ${replicas | default(1)}
template:
spec:
containers:
@for container in containers
- name: $container.name
image: ${container.image}:${container.tag | default("latest")}
@if container.ports
ports:
@for port in container.ports
- containerPort: $port
@end
@end
@end
```
#### ⚠️ Works, but harder to read
```yaml
spec:
containers:
@for container in containers
- name: $container.name
@if container.ports
ports:
@for port in container.ports
- containerPort: $port
@end
@end
@end
```
Compare either to equivalent Helm/Go templates — Luma is much cleaner!
---
## More Examples: Universal Smart Indentation
Luma's smart indentation works everywhere, not just YAML:
### HTML Template
```html
-
$item.name
@if item.description
$item.description
@end
@for item in items
@end
```
### JSON Configuration
```json
{
"services": {
@for service in services
"$service.name": {
"port": $service.port,
"enabled": @if service.enabled true@else false@end
}@if not loop.last,@end
@end
}
}
```
### Python Code Generation
```python
class $class_name:
def __init__(self):
@for field in fields
self.$field.name = $field.default
@end
@for method in methods
def $method.name(self):
"""$method.docstring"""
pass
@end
```
**All of these work perfectly without any whitespace control directives!**
See [docs/WHITESPACE.md](docs/WHITESPACE.md) for comprehensive examples and advanced control options.
---
## Running Tests
```bash
# Install busted
luarocks install busted
# Run tests
busted spec/
```
## License
MIT
## Comparison with Jinja2
| Feature | Jinja2 | Luma |
|---------|--------|------|
| Variable | `{{ name }}` | `$name` |
| Expression | `{{ expr }}` | `${expr}` |
| If block | `{% if %}...{% endif %}` | `@if...@end` |
| For loop | `{% for %}...{% endfor %}` | `@for...@end` |
| Comment | `{# comment #}` | `@# comment` |
| Filters | `{{ x \| filter }}` | `${x \| filter}` |
Luma aims for less visual noise while maintaining full expressiveness.