Prana Template Language Reference
View SourceThis document defines the syntax, data types, operators, and semantics of the Prana template language.
Overview
Prana templates combine literal text with dynamic expressions and control flow. The language is designed for workflow automation contexts where data flows between nodes in a graph-based execution model.
Template Syntax
Basic Structure
Literal text content
{{ expression }}
{% tag %}...{% endtag %}Delimiters
Expression Blocks:
{{ expression }}- Output expression result{{- expression }}- Trim whitespace before{{ expression -}}- Trim whitespace after{{- expression -}}- Trim whitespace both sides
Tag Blocks:
{% tag %}- Control flow and logic tags{%- tag %}- Trim whitespace before{% tag -%}- Trim whitespace after{%- tag -%}- Trim whitespace both sides
Comments:
{# comment #}- Template comments (not rendered)
Data Types
Primitive Types
String
{{ "hello world" }}
{{ 'single quotes' }}
{{ "escape \"quotes\" with backslash" }}Number
{{ 42 }}
{{ 3.14159 }}
{{ -17 }}
{{ 1.5e-10 }}Boolean
{{ true }}
{{ false }}Null
{{ nil }}Note: Both nil and null are supported and equivalent.
Complex Types
Array/List
{{ items[0] }} <!-- First element -->
{{ items[-1] }} <!-- Last element (Not Yet Implemented) -->
{{ items[1:3] }} <!-- Slice (Not Yet Implemented) -->Object/Map
{{ user.name }} <!-- Property access -->
{{ user["email"] }} <!-- Bracket access -->
{{ user.address.city }} <!-- Nested access -->Variable Access
Simple Variables
{{ name }}
{{ age }}
{{ active }}
{{ $user }}Object Property Access
{{ user.name }}
{{ user.profile.email }}
{{ $settings.api.timeout }}Array Access
{{ users[0] }} <!-- Index access -->
{{ users[index] }} <!-- Dynamic index -->
{{ $nested[0].items[1] }} <!-- Nested arrays -->Workflow Context Access
<!-- Input data from workflow trigger -->
{{ $input.email }}
{{ $input.user.preferences.theme }}
<!-- Results from previous nodes -->
{{ $nodes.fetch_user.output.user_id }}
{{ $nodes.api_call.response.data }}
<!-- Workflow variables -->
{{ $variables.api_key }}
{{ $variables.base_url }}
<!-- Execution context -->
{{ $context.execution_id }}
{{ $context.timestamp }}Operators
Comparison Operators
Equality
{{ user.role == "admin" }}
{{ status != "pending" }}Relational
{{ age >= 18 }}
{{ score > 90 }}
{{ count <= 10 }}
{{ priority < 5 }}Arithmetic Operators
Basic Math
{{ price + tax }}
{{ total - discount }}
{{ quantity * price }}
{{ total / count }}
{{ number % 2 }}String Concatenation
{{ first_name + " " + last_name }}
{{ "Hello " + user.name }}Logical Operators
Mau supports two styles of logical operators with different behavior:
Word-based Operators: and / or / not
Returns boolean values (true or false) using Liquid-style truthiness:
{{ user.active and user.verified }} # Returns: true or false
{{ role == "admin" or role == "moderator" }} # Returns: true or false
{{ not user.blocked }} # Returns: true or falseTruthiness (Liquid-style):
- Falsy values:
nil,false,0,""(empty string),[](empty list),{}(empty map) - Everything else is truthy
Examples:
{{ 0 and "hello" }} # false (0 is falsy)
{{ "" or "world" }} # true (returns boolean, not the value)
{{ "hello" and "world" }} # true (returns boolean)Symbol-based Operators: && / || / !
Returns actual values (not booleans) using Elixir-style truthiness:
{{ user.active && user.verified }} # Returns: right value or false/nil
{{ role == "admin" || role == "moderator" }} # Returns: first truthy value
{{ !user.blocked }} # Returns: true or falseTruthiness (Elixir-style):
- Falsy values: only
nilandfalse - Everything else is truthy (including
0,"",[],{})
Examples:
{{ 0 && "hello" }} # "hello" (0 is truthy, returns right value)
{{ "" || "world" }} # "" (empty string is truthy, returns left value)
{{ "hello" && "world" }} # "world" (returns right value)
{{ false || 42 }} # 42 (returns first truthy value)
{{ nil && "test" }} # nil (short-circuits, returns nil)Choosing Between Operators
Use and / or / not when:
- You need boolean results for conditions
- You want Liquid-like behavior where
0and""are falsy - You're writing templates familiar to Liquid users
Use && / || / ! when:
- You need the actual values (for default value patterns)
- You want Elixir-like behavior where only
nilandfalseare falsy - You're using
||for default values:{{ user.name || "Guest" }}
Complex Conditions:
{{ (age >= 18 and age <= 65) and (role == "user" or role == "premium") }}
{{ user.active && (user.plan == "pro" || user.credits > 0) }}Short-circuit Evaluation: Both operator styles support short-circuit evaluation:
{{ false && expensive_operation }} # expensive_operation not evaluated
{{ true || expensive_operation }} # expensive_operation not evaluatedOperator Precedence
Highest to Lowest:
- Property access:
.,[] - Unary:
not/!,- - Multiplicative:
*,/,% - Additive:
+,- - Relational:
<,<=,>,>= - Equality:
==,!= - Logical AND:
and/&& - Logical OR:
or/||
Parentheses can override precedence:
{{ (total + tax) * quantity }}
{{ user.active and (role == "admin" or permissions.elevated) }}Control Flow Tags
Conditional Statements
If/Else
{% if user.active %}
Welcome back, {{ user.name }}!
{% else %}
Your account is inactive.
{% endif %}If/Elsif/Else
{% if user.role == "admin" %}
Administrator Dashboard
{% elsif user.role == "moderator" %}
Moderator Panel
{% elsif user.role == "user" %}
User Profile
{% else %}
Access Denied
{% endif %}Loops
For Loops
{% for user in users %}
Name: {{ user.name }}
Email: {{ user.email }}
{% endfor %}For with Conditions (Not Yet Implemented)
{% for item in items limit: 5 %}
{{ item.name }}
{% endfor %}
{% for user in users offset: 10 limit: 20 %}
{{ user.name }}
{% endfor %}For with Filtering (Not Yet Implemented)
{% for user in users where user.active %}
{{ user.name }}
{% endfor %}Loop Variables
{% for item in items %}
{{ forloop.index }}: {{ item.name }}
{% if forloop.first %}First item!{% endif %}
{% if forloop.last %}Last item!{% endif %}
{% endfor %}Case/Switch (Not Yet Implemented)
{% case user.role %}
{% when "admin" %}
Administrator
{% when "moderator" %}
Moderator
{% when "user" %}
User
{% else %}
Unknown role
{% endcase %}Utility Tags
Assignment
{% assign full_name = user.first_name + " " + user.last_name %}
{% assign total_price = quantity * price + tax %}
Hello {{ full_name }}!
Total: ${{ total_price }}Comments
{# Inline comment - not rendered #}Filters
Filters transform expression values using the pipe (|) operator or function call syntax.
Pipe Syntax:
{{ user.name | capitalize }}
{{ text | upper_case | strip }}Function Call Syntax:
{{ capitalize(user.name) }}
{{ upper_case(text) }}Multiple Filters (Fully Supported)
Filters can be chained together for complex transformations:
{{ user.bio | strip | truncate(100) | capitalize }}
{{ price | multiply(quantity) | format_currency("USD") }}
{{ items | slice(0, 3) | join(", ") | upper_case }}
{{ numbers | sum | power(2) | sqrt }}Available Filters
Mau includes 40+ built-in filters organized into categories:
- String Filters - Text manipulation and formatting
- Collection Filters - Array and list operations
- Math Filters - Numeric calculations and rounding
- Number Filters - Number formatting
For a complete list of all available filters with detailed documentation, see Filters List Reference.
Whitespace Control
Tag-Level Control
Trim Before
Text {%- if true %}Content{% endif %}
<!-- Result: "TextContent" -->Trim After
{% if true -%} Content {% endif %}
<!-- Result: "Content " -->Trim Both
Text {%- if true -%} Content {%- endif -%} More
<!-- Result: "TextContentMore" -->Global Options
trim_blocks (Not Yet Implemented) - Remove newlines after tag blocks With trim_blocks: true
Start
{% if true %}
Content
{% endif %}
End- Result: "Start\nContent\nEnd"
lstrip_blocks (Not Yet Implemented) - Remove leading whitespace before tag blocks
- With lstrip_blocks: true
Start {% if true %}Content{% endif %} End - Result: "Start\n{% if true %}Content{% endif %}\nEnd"
Data Type Preservation
By default, all template output is converted to strings. However, Mau supports preserving original data types for single-value templates using the preserve_types option.
Basic Usage
# Default behavior (all strings)
Mau.render("{{ 42 }}", %{}) #=> {:ok, "42"}
Mau.render("{{ true }}", %{}) #=> {:ok, "true"}
# With data type preservation
Mau.render("{{ 42 }}", %{}, preserve_types: true) #=> {:ok, 42}
Mau.render("{{ true }}", %{}, preserve_types: true) #=> {:ok, true}Supported Data Types
Primitive Types
{{ 42 }} # Integer -> 42
{{ 3.14 }} # Float -> 3.14
{{ true }} # Boolean -> true
{{ false }} # Boolean -> false
{{ nil }} # Nil -> nil
{{ "hello" }} # String -> "hello"Collection Types
{{ [1, 2, 3] }} # List -> [1, 2, 3]
{{ %{"key" => "value"} }} # Map -> %{"key" => "value"} # not yet supportExpressions and Operations
{{ 5 + 3 }} # Arithmetic -> 8
{{ 5 > 3 }} # Comparison -> true
{{ true and false }} # Logic -> falseFilter Results
{{ items | length }} # Numeric filter -> 3
{{ items | reverse }} # List filter -> [3, 2, 1]
{{ items | join(",") }} # String filter -> "1,2,3"Mixed Content Behavior
Mixed content (text + expressions) always returns strings, even with preserve_types: true:
# These always return strings
Mau.render("Count: {{ 42 }}", %{}, preserve_types: true) #=> {:ok, "Count: 42"}
Mau.render("{{ name }}: {{ age }}", context, preserve_types: true) #=> {:ok, "Alice: 25"}Single Value Detection
Data type preservation only applies to templates that contain exactly one expression and no surrounding text:
# Single value - types preserved
{{ 42 }} # -> 42 (Integer)
{{ user.active }} # -> true (Boolean)
{{ items | length }} # -> 3 (Integer)
# Multiple expressions - returns string
{{ 42 }}{{ true }} # -> "42true" (String)
# Mixed content - returns string
Value: {{ 42 }} # -> "Value: 42" (String)Error Handling
Strict Mode
strict_mode: true - Return errors for incomplete expressions and undefined variables
- Incomplete expression:
{{ variable→ Error - Undefined variable:
{{ unknown_var }}→ Error
strict_mode: false (default) - Graceful degradation
- Incomplete expression:
{{ variable→ Rendered as-is: "{{ variable" - Undefined variable:
{{ unknown_var }}→ Treated as nil/empty
Missing Variables
Default Behavior (strict_mode: false)
{{ undefined_variable }} <!-- Renders as empty string -->
{{ user.missing_property }} <!-- Renders as empty string -->Type Coercion
Boolean Context
{% if "non_empty_string" %} <!-- true -->
{% if "" %} <!-- false -->
{% if 0 %} <!-- false -->
{% if null %} <!-- false -->Workflow Integration
Execution Context
Available Variables
$input- Data passed to the workflow$variables- Workflow-level variables$nodes- Results from executed nodes$context- Execution metadata$env- Environment variable for project (Not Yet Implemented)
Future Extensions
Planned Features
Enhanced Control Flow (Not Yet Implemented)
{% break %}- Break out of loops{% case %}- Switch statements
Advanced Expressions (Not Yet Implemented)
- Ternary operator:
condition ? true_value : false_value
Implementation Status
✅ Fully Implemented Features
Core Template Syntax
- All expression blocks with whitespace control:
{{ }},{{- }},{{ -}},{{- -}} - All tag blocks with whitespace control:
{% %},{%- %},{% -%},{%- -%} - Template comments:
{# comment #} - Data type preservation:
preserve_types: trueoption
Data Types & Variables
- All primitive types: strings, numbers, booleans, null/nil
- Complex types: arrays with indexing (positive only), objects/maps with property access
- Workflow variables:
$input,$variables,$nodes,$context
Operators & Expressions
- All comparison operators:
==,!=,>,>=,<,<= - All arithmetic operators:
+,-,*,/,% - All logical operators:
and,or,not - Proper operator precedence and parentheses support
Control Flow
- If/else/elsif conditionals with full nesting support
- For loops with collection iteration
- Loop variables:
forloop.index,forloop.first,forloop.last - Assignment tags:
{% assign var = value %}
Filters (40+ implemented)
- String filters:
upper_case,lower_case,capitalize,strip,truncate,default - Collection filters:
length,first,last,join,sort,reverse,uniq,slice,contains,compact,flatten,sum,keys,values,group_by,map,filter,reject,dump - Math filters:
abs,ceil,floor,round,max,min,power,sqrt,mod,clamp - Number filters:
format_currency Full filter chaining support:
{{ value | filter1 | filter2 | filter3 }}- Function call syntax:
{{ filter_name(value) }}
❌ Not Yet Implemented
Advanced Loop Features
- Loop conditions:
limit:,offset:parameters - Loop filtering:
whereconditions
Control Flow Extensions
- Case/switch statements:
{% case %}{% when %}{% endcase %} - Break statements:
{% break %}
Filter Limitations
Complex filter expressions in conditionals (e.g.,
{% if name | upper_case == "ADMIN" %})
Global Options
trim_blocksandlstrip_blocksglobal whitespace control- Environment variables:
$envworkflow variable
Advanced Features
- Array slicing:
{{ items[1:3] }} - Negative array indexing:
{{ items[-1] }} - Ternary operator:
condition ? true_value : false_value
Implementation Coverage
~95% of documented features are fully implemented, with comprehensive support for core template functionality, full filter chaining, loop variables, data type preservation, and an extensive filter library.
Syntax Summary
<!-- Basic output -->
{{ variable }}
{{ $variable_with_dollar_sign }}
{{ object.property }}
{{ array[index] }}
<!-- Expressions -->
{{ a + b }}
{{ name == "John" }}
{{ active and verified }}
<!-- Control flow -->
{% if condition %}...{% endif %}
{% for item in items %}...{% endfor %}
<!-- Utility -->
{% assign var = value %}
<!-- Filters (full chaining support) -->
{{ text | upper_case | truncate(50) }}
{{ items | slice(0, 3) | join(", ") | upper_case }}
{{ price | format_currency("USD") }}
{{ format_currency(price, "USD") }}
<!-- Whitespace control -->
{{- variable -}}
{%- tag -%}...{%- endtag -%}