Elixir API Reference
View SourceComplete reference for the Mau template engine Elixir API.
Overview
The Mau library provides a clean, functional API for template compilation and rendering. All functions use Elixir's standard error handling with {:ok, result} and {:error, reason} tuples.
Main Module: Mau
The main entry point for all template operations.
Mau.compile/2
Compiles a template string into an Abstract Syntax Tree (AST).
Signature:
def compile(template, opts \\ []) :: {:ok, ast} | {:error, Error.t()}Parameters:
template(string) - Template source code to compileopts(keyword list, optional)::strict_mode- Enable strict error reporting (default: false):max_template_size- Maximum allowed template size in bytes (default: 100,000)
Return Values:
{:ok, ast}- Successfully compiled AST{:error, error}- Compilation error
Examples:
# Simple compilation
{:ok, ast} = Mau.compile("Hello {{ name }}")
# With options
{:ok, ast} = Mau.compile(template, strict_mode: true)
# Error handling
case Mau.compile(invalid_template) do
{:ok, ast} -> IO.inspect(ast)
{:error, error} -> IO.puts("Compilation failed: #{error}")
endSee Also:
- AST Specification - Structure of compiled templates
Mau.render/3
Renders a template string or pre-compiled AST with the given context.
Signature:
def render(template, context, opts \\ []) :: {:ok, result} | {:error, Error.t()}Parameters:
template(string or AST) - Template source or compiled ASTcontext(map) - Data context for variable substitutionopts(keyword list, optional)::preserve_types- Preserve non-string types for single-value templates (default: false):max_template_size- Maximum template size in bytes (default: 100,000):max_loop_iterations- Maximum iterations in loops (default: 10,000)
Return Values:
{:ok, result}- Rendered output (string, or original type withpreserve_types: true){:error, error}- Rendering error
Examples:
# Basic rendering
{:ok, output} = Mau.render("Hello {{ name }}", %{"name" => "World"})
# Output: "Hello World"
# Rendering with pre-compiled AST
{:ok, ast} = Mau.compile(template)
{:ok, output} = Mau.render(ast, context)
# Preserving data types
{:ok, result} = Mau.render("{{ 42 }}", %{}, preserve_types: true)
# Result: 42 (integer, not "42" string)
# Boolean preservation
{:ok, result} = Mau.render("{{ user.active }}",
%{"user" => %{"active" => true}},
preserve_types: true)
# Result: true (boolean)
# Mixed content always returns string
{:ok, output} = Mau.render("Count: {{ items | length }}",
%{"items" => [1, 2, 3]},
preserve_types: true)
# Output: "Count: 3" (string)
# Error handling
case Mau.render(template, context) do
{:ok, output} -> IO.puts(output)
{:error, error} -> IO.puts("Render failed: #{error}")
end
# With options
{:ok, output} = Mau.render(template, context,
preserve_types: true,
max_loop_iterations: 5000)Behavior:
Type Preservation: When
preserve_types: true:- Single-value templates render to their native type (number, boolean, etc.)
- Mixed content (text + expressions) always renders to string
- Undefined variables return nil
Undefined Variables: In strict mode (false by default):
- Undefined variables render as empty strings
- No error is raised
See Also:
- Template Language Reference - Template syntax
- Filters List - Available filters
Mau.render_map/3
Recursively renders template strings in nested map structures with support for transformation directives.
Signature:
def render_map(nested_map, context, opts \\ []) :: {:ok, result} | {:error, Error.t()}Parameters:
nested_map(map) - Nested map structure containing template stringscontext(map) - Data context for variable substitutionopts(keyword list, optional)::preserve_types- Preserve data types in results (default: true):max_template_size- Maximum template size (default: 100,000):max_loop_iterations- Maximum loop iterations (default: 10,000)
Return Values:
{:ok, result}- Rendered map with all template strings processed{:error, error}- Rendering error
Directives:
Map keys starting with # trigger transformation directives:
| Directive | Purpose | Syntax |
|---|---|---|
#map | Iterate over collections | "#map" => [collection, template] |
#filter | Filter collections | "#filter" => [collection, condition] |
#merge | Combine maps | "#merge" => [map1, map2, ...] |
#if | Conditional rendering | "#if" => [condition, true_tmpl, false_tmpl] |
#pick | Extract specific keys | "#pick" => [map, key_list] |
#pipe | Thread through transformations | "#pipe" => [initial, directives] |
Examples:
# Simple map rendering
input = %{
"greeting" => "Hello {{ name }}!",
"count" => "{{ items | length }}"
}
context = %{"name" => "Alice", "items" => [1, 2, 3]}
{:ok, result} = Mau.render_map(input, context)
# Result: %{
# "greeting" => "Hello Alice!",
# "count" => "3"
# }
# Using #map directive
input = %{
"users" => %{
"#map" => [
"{{$users}}",
%{"name" => "{{$loop.item.name}}"}
]
}
}
context = %{
"$users" => [
%{"name" => "Alice"},
%{"name" => "Bob"}
]
}
{:ok, result} = Mau.render_map(input, context)
# Using #filter directive
input = %{
"active_users" => %{
"#filter" => [
"{{$users}}",
"{{$loop.item.active}}"
]
}
}
# Using #merge directive
input = %{
"profile" => %{
"#merge" => [
"{{$user}}",
%{"verified" => true}
]
}
}
# Using #if directive
input = %{
"status" => %{
"#if" => [
"{{$premium}}",
%{"level" => "premium"},
%{"level" => "free"}
]
}
}
# Using #pipe for data transformation
input = %{
"result" => %{
"#pipe" => [
"{{$items}}",
[
%{"#filter" => "{{$loop.item.price > 100}}"},
%{"#map" => %{"name" => "{{$loop.item.name}}"}}
]
]
}
}
# Error handling
case Mau.render_map(nested_data, context) do
{:ok, result} -> IO.inspect(result)
{:error, error} -> IO.puts("Map render failed: #{error}")
endContext Variables in Directives:
Special variables available in template strings within directives:
{{$self}} # The piped value (in #pipe directive)
{{$loop.item}} # Current item in #map or #filter
{{$loop.index}} # Current item index (0-based)
{{$loop.first}} # Is first item? (boolean)
{{$loop.parentloop}} # Parent loop info (in nested loops)See Also:
- Map Directives Reference - Complete directive documentation
Supporting Modules
Mau.Error
Handles error types and messages.
Functions:
Mau.Error.runtime_error/1
Creates a runtime error with a custom message.
Mau.Error.runtime_error("Template error message")Mau.FilterRegistry
Manages available filters (functions).
Functions:
Mau.FilterRegistry.apply/3
Applies a filter to a value.
Signature:
def apply(filter_name, value, args \\ []) ::
{:ok, result} | {:error, :filter_not_found | {:filter_error, reason}}Examples:
# Apply a simple filter
{:ok, result} = Mau.FilterRegistry.apply("upper_case", "hello", [])
# Result: "HELLO"
# Apply filter with arguments
{:ok, result} = Mau.FilterRegistry.apply("truncate", "Hello World", [8])
# Result: "Hello..."
# Error handling
case Mau.FilterRegistry.apply("upper_case", 123, []) do
{:ok, result} -> IO.puts(result)
{:error, :filter_not_found} -> IO.puts("Filter not found")
{:error, {:filter_error, reason}} -> IO.puts("Filter error: #{reason}")
endAvailable Filters:
All filters from the three filter modules:
- String filters: 6 filters
- Collection filters: 18 filters
- Math filters: 10 filters
See Filters List for complete reference.
Mau.FilterRegistry.get/1
Gets a filter function by name.
Signature:
def get(filter_name) :: {:ok, {module, function}} | {:error, :not_found}Examples:
# Get filter function
{:ok, {module, function}} = Mau.FilterRegistry.get("upper_case")
# Error handling
case Mau.FilterRegistry.get("upper_case") do
{:ok, {mod, func}} -> :io.format("Filter: ~w:~w~n", [mod, func])
{:error, :not_found} -> IO.puts("Filter not found")
endMau.MapDirectives
Handles transformation directives in maps.
Functions:
Mau.MapDirectives.match_directive/1
Identifies if a map contains a supported directive.
Signature:
def match_directive(map) :: {directive_type, args} | :noneExamples:
# Check for directive
case Mau.MapDirectives.match_directive(%{"#map" => [collection, template]}) do
{:map, args} -> IO.inspect(args)
:none -> IO.puts("Not a directive")
endMau.MapDirectives.apply_directive/4
Applies a directive to transform data.
Signature:
def apply_directive(directive, context, opts, render_fn) :: resultDirectives:
:map- Iterate over collection:filter- Filter collection items:merge- Merge maps:if- Conditional rendering:pick- Extract keys:pipe- Pipeline transformations
Data Types
AST (Abstract Syntax Tree)
Templates compile to AST represented as tuples.
Node Structure:
{node_type, content, options}Node Types:
:text- Literal text content:expression- Variable interpolation:tag- Control flow (if/for):literal- Constant values:variable- Variable reference:binary_op- Binary operations:logical_op- Logical operations:call- Function/filter calls
Example:
{:ok, ast} = Mau.compile("Hello {{ name | upper_case }}")
# AST structure:
# [
# {:text, ["Hello "], []},
# {:expression,
# {:call, ["upper_case",
# {:variable, ["name"], []}
# ], []},
# []}
# ]See Also:
- AST Specification - Complete AST documentation
Context Map
The context is a standard Elixir map containing variables for template rendering.
Structure:
%{
"variable_name" => value,
"user" => %{
"name" => "Alice",
"email" => "alice@example.com"
},
"$workflow_var" => workflow_value
}Variable Naming:
- Regular variables:
"name","user", etc. - Workflow variables:
"$input","$nodes","$variables","$context" - Both strings and atoms are supported as keys
Examples:
# Simple context
context = %{"name" => "Alice", "age" => 30}
# Nested context
context = %{
"user" => %{
"name" => "Bob",
"profile" => %{
"bio" => "Developer"
}
}
}
# With workflow variables
context = %{
"$input" => %{"email" => "user@example.com"},
"$nodes" => %{
"fetch_user" => %{"output" => user_data}
},
"$variables" => %{"api_key" => "secret"}
}Error Handling
All Mau functions follow Elixir's standard error handling pattern.
Error Structure:
{:error, error_message}Error Types:
- Compilation errors (invalid template syntax)
- Runtime errors (undefined variables, filter errors)
- Type errors (filter applied to wrong type)
- Size errors (template exceeds max_template_size)
- Loop limit errors (exceeds max_loop_iterations)
Example Error Handling:
case Mau.render(template, context) do
{:ok, output} ->
IO.puts("Success: #{output}")
{:error, error} ->
IO.puts("Error: #{error}")
# Log error, send to error tracking, etc.
end
# With rescue for unexpected errors
try do
{:ok, result} = Mau.render(template, context)
result
rescue
e ->
IO.puts("Unexpected error: #{Exception.message(e)}")
raise e
endOptions Reference
Mau.compile/2 Options
| Option | Type | Default | Description |
|---|---|---|---|
strict_mode | boolean | false | Enable strict error reporting |
max_template_size | integer | 100,000 | Maximum template size in bytes |
Mau.render/3 Options
| Option | Type | Default | Description |
|---|---|---|---|
preserve_types | boolean | false | Preserve non-string types for single values |
max_template_size | integer | 100,000 | Maximum template size in bytes |
max_loop_iterations | integer | 10,000 | Maximum iterations in loops |
Mau.render_map/3 Options
| Option | Type | Default | Description |
|---|---|---|---|
preserve_types | boolean | true | Preserve non-string types in results |
max_template_size | integer | 100,000 | Maximum template size in bytes |
max_loop_iterations | integer | 10,000 | Maximum iterations in loops |
Common Patterns
Compile Once, Render Many
For performance, compile templates once and reuse the AST:
# Compile once
{:ok, template_ast} = Mau.compile(template_string)
# Render multiple times with different contexts
contexts = [
%{"name" => "Alice"},
%{"name" => "Bob"},
%{"name" => "Charlie"}
]
results = Enum.map(contexts, fn context ->
case Mau.render(template_ast, context) do
{:ok, output} -> output
{:error, _} -> nil
end
end)Pipeline Processing
Render templates as part of data processing pipelines:
data
|> Enum.map(&preprocess/1)
|> Enum.map(&render_with_context/1)
|> Enum.filter(&valid?/1)
|> Enum.map(&postprocess/1)
defp render_with_context(data) do
case Mau.render(data.template, data.context) do
{:ok, output} -> %{data | output: output}
{:error, error} -> %{data | error: error}
end
endType-Safe Data Transformation
Use preserve_types: true for type-safe transformations:
# Extract numeric values from templates
{:ok, price} = Mau.render("{{ product.price }}",
%{"product" => %{"price" => 29.99}},
preserve_types: true)
# Safely use as number
total = price * quantity # No conversion needed
# Extract booleans
{:ok, is_active} = Mau.render("{{ user.active }}",
context,
preserve_types: true)
if is_active do
# ...
endPerformance Considerations
Template Compilation
- Compile once, render many times - Compilation is the expensive operation
- Reuse AST - Pass compiled AST to render instead of template string
- Cache compiled templates - Store AST in ETS or application state
Filter Performance
- Filters are optimized - Implemented in native Elixir
- Avoid long chains - Chain filters efficiently but readably
- Consider custom filters - For domain-specific high-performance transformations
Loop Performance
- Set
max_loop_iterations- Prevent runaway loops in user input - Use
#pipefor complex transforms - More efficient than nested#mapdirectives - Profile with
:benchee- Measure performance of templates
Version Compatibility
- Elixir: 1.12+
- Erlang: 23+
- OTP: 23+
See Also
- Template Language Reference - Complete syntax documentation
- Filters List - All available filters
- Map Directives - Directive system reference
- AST Specification - AST structure details
- Filters Guide - How to use filters effectively
- Custom Filters - Extend with custom filters