Expression Usage
This page summarizes the main syntax of filters, functions, and control structures available in Pebble templating.
Syntax Reference
There are two primary delimiters used within a Pebble template: {{ ... }}
and {% ... %}
.
{{ ... }}
is used to output the result of an expression. Expressions can be very simple (ex. a variable name) or much more complex.
{% ... %}
is used to change the control flow of the template; it can contain an if-statement, define a parent template, define a new block, etc.
If needed, you can escape those delimiters and avoid Pebble templating applying on a variable thanks to the raw
tag.
You can use the dot (.
) notation to access child attributes. If the attribute contains a special character, you can use the subscript notation ([]
) instead.
{{ foo.bar }} # Attribute 'bar' of 'foo'
{{ foo.bar.baz }} # Attribute 'baz' of attribute 'bar' of 'foo'
{{ foo['foo-bar'] }} # Attribute 'foo-bar' of 'foo'
{{ foo['foo-bar']['foo-baz'] }} # Attribute 'foo-baz' of attribute 'foo-bar' of 'foo'
When using task, variable or output names containing a hyphen symbol -
, make sure to use the subscript notation ([]
) e.g. "{{ outputs.mytask.myoutput['foo-bar'] }}"
because -
is a special character in the Pebble templating engine.
If foo
would be a List, then foo[idx]
can be used to access the elements of index idx
of the foo variable.
Filters
You can use filters in your expressions using a pipe symbol (|
). Multiple filters can be chained, and the output of one filter is applied to the next one.
{{ "When life gives you lemons, make lemonade." | upper | abbreviate(13) }}
The above example will output the following:
WHEN LIFE ...
Functions
Whereas filters are intended to modify existing content/variables, functions are intended to generate new content. Similar to other programming languages, functions are invoked via their name followed by parentheses function_name()
.
{{ max(user.score, highscore) }}
The above example will output the maximum of the two variables user.score
and highscore
.
Control Structure
Pebble provides several tags to control the flow of your template, two of the main ones being the for loop, and the if statement.
{% for article in articles %}
{{ article.title }}
{% else %}
"There are no articles."
{% endfor %}
{% if category == "news" %}
{{ news }}
{% elseif category == "sports" %}
{{ sports }}
{% else %}
"Please select a category"
{% endif %}
Macros
Macros are lightweight and re-usable template fragments. A macro is defined via the macro tag:
{% macro input(type, name) %}
{{ name }} is of type {{ type }}
{% endmacro %}
And the macro will be invoked just like a function:
{{ input("text", "Mitchell") }}
A macro does not have access to the main context; the only variables it can access are its local arguments.
Here is an example flow showing the usage of macro:
id: macro-example
namespace: company.team
tasks:
- id: log_output
type: io.kestra.plugin.core.log.Log
message: |
{% macro input(type, name) %}{{ name }} is of type {{ type }}{% endmacro %}
{{ input("text", "John") }}
{{ input("number", 1) }}
Named Arguments
In filters, functions, or macros, you can use named arguments. Named arguments allow us to be more explicit on which arguments are passed and avoid mandating to pass default values.
{{ stringDate | date(existingFormat="yyyy-MMMM-d", format="yyyy/MMMM/d") }}
Positional arguments can be used in conjunction with named arguments, but all positional arguments must come before any named arguments:
{{ stringDate | date("yyyy/MMMM/d", existingFormat="yyyy-MMMM-d") }}
Macros are a great use case for named arguments because they also allow you to define default values for unused arguments:
{% macro input(type="text", name, value) %}
type is "{{ type }}", name is "{{ name }}", and value is "{{ value }}"
{% endmacro %}
{{ input(name="country") }}
{# will output: type is "text", name is "country", and value is "" #}
Comments
You add comments using the {# ... #}
delimiters. These comments will not appear in the rendered output.
{# THIS IS A COMMENT #}
{% for article in articles %}
{{ article.title }} has content {{ article.content }}
{% endfor %}
Literals
The simplest form of expressions are literals. Literals are representations for Java types such as strings and numbers.
"Hello World"
: Everything between two double or single quotes is a string. You can use a backslash to escape quotation marks within the string."Hello #{who}"
: String interpolation is also possible using#{}
. In this example, if the value of the variablewho
is"world"
, then the expression will be evaluated to"Hello world"
.100 + 10l * 2.5
: Integers, longs and floating point numbers are similar to their Java counterparts.true
/false
: Boolean values equivalent to their Java counterparts.null
: Represents no specific value, similar to its Java counterpart.none
is an alias for null.
Collections
Both lists and maps can be created directly within the template.
["apple", "banana", "pear"]
: A list of strings{"apple":"red", "banana":"yellow", "pear":"green"}
: A map of strings
The collections can contain expressions.
Math
Pebble allows you to calculate values using some basic mathematical operators. The following operators are supported:
+
: Addition-
: Subtraction/
: Division%
: Modulus*
: Multiplication
Logic
You can combine multiple expressions with the following operators:
and
: Returns true if both operands are trueor
: Returns true if either operand is truenot
: Negates an expression(...)
: Groups expressions together
Comparisons
The following comparison operators are supported in any expression: ==
, !=
, <
, >
, >=
, and <=
.
{% if user.age >= 18 %}
...
{% endif %}
Tests
The is
operator performs tests. Tests can be used to test an expression for certain qualities. The right operand is the name of the test:
{% if 3 is odd %}
...
{% endif %}
Tests can be negated by using the is not operator:
{% if name is not null %}
...
{% endif %}
Conditional (Ternary) Operator
The conditional operator is similar to its Java counterpart:
{{ foo ? "yes" : "no" }}
Null-Coalescing Operator
The null-coalescing operator allows to quickly test if a variable is defined (exists) and to use an alternative value if not:
{% set baz = "baz" %}
{{ foo ?? bar ?? baz }}
{# results in: 'baz' #}
{{ foo ?? bar ?? raise }}
{# results: an exception because none of the 3 vars is defined #}
Operator Precedence
In order from highest to lowest precedence:
.
|
%
,/
,*
-
,+
==
,!=
,>
,<
,>=
,<=
is
,is not
and
or
Parent tasks with Flowable tasks
When using nested Flowable Tasks, only the direct parent task is accessible via taskrun.value
. To access a parent task higher up the tree, you can use the parent
and the parents
expressions.
The following flow shows how to access the parent tasks:
id: each_switch
namespace: company.team
tasks:
- id: simple
type: io.kestra.plugin.core.debug.Return
format: "{{ task.id }} > {{ taskrun.startDate }}"
- id: hierarchy_1
type: io.kestra.plugin.core.flow.EachSequential
value: ["a", "b"]
tasks:
- id: hierarchy_2
type: io.kestra.plugin.core.flow.Switch
value: "{{ taskrun.value }}"
cases:
a:
- id: hierarchy_2_a
type: io.kestra.plugin.core.debug.Return
format: "{{ task.id }}"
b:
- id: hierarchy_2_b
type: io.kestra.plugin.core.debug.Return
format: "{{ task.id }}"
- id: hierarchy_2_b_second
type: io.kestra.plugin.core.flow.EachSequential
value: ["1", "2"]
tasks:
- id: switch
type: io.kestra.plugin.core.flow.Switch
value: "{{ taskrun.value }}"
cases:
1:
- id: switch_1
type: io.kestra.plugin.core.debug.Return
format: "{{ parents[0].taskrun.value }}"
2:
- id: switch_2
type: io.kestra.plugin.core.debug.Return
format: "{{ parents[0].taskrun.value}} {{ parents[1].taskrun.value }}"
- id: simple_again
type: io.kestra.plugin.core.debug.Return
format: "{{ task.id }} > {{ taskrun.startDate }}"
The parent
variable gives direct access to the first parent, while the parents[INDEX]
gives you access to the parent higher up the tree.
Was this page helpful?