Template syntax
Meya BFML uses Jinja2 as its template rendering engine.
Jinja2 is a templating engine with a simple syntax that allows you to embed filters, tests, variables, and more. Meya’s BFML supports Jinja2 with a couple modifications. In this guide you’ll learn how to take advantage of this powerful syntax in your app code.
Delimiters
Typically with Jinja2, expressions and statements are wrapped in special characters, like this:
{{ <expression> }}
{% <statement> %}
In BFML, you need to use a modified version to make sure it’s compatible with YAML (a syntax which BFML is based on). It looks like this—notice the bracket change {
to (
and the addition of @
:
(@ <expression> )
(% <statement> %)
Displaying variables
To access a variable, use this syntax:
(@ <scope>.<variable> )
(@ <scope>["<variable>"] ) # Alternate syntax
The dot syntax is what you’ll see used throughout our documentation.
Example 1
steps:
- ask: What is your name?
- user_set: name
- say: Hello, (@ user.name )!
- say: Hello, (@ user["name"] )! # Alternate syntax
To access an element in a list, use the element’s position in the list, starting from 0. See the example below.
Example 2
In this example, we’ll print the second element in the list.
steps:
- user_set:
my_list:
- foo
- bar
- 123
- say: (@ user.my_list.1 ) # Prints "bar"
- say: (@ user["my_list"][1] ) # Alternate syntax
Data types
The example below demonstrates the different data types supported by Jinja2, and how to define them in BFML.
steps:
- flow_set:
a: # None
b: Hi! # String
c: 1 # Integer
d: 1.5 # Float
e: True # Boolean
f: # List
- a
- b
- c
g: # Dictionary
x: 1
y: 2
z: 3
Checking the type of a variable
Continuing from the above code example, you can check the type of a variable using Jinja2 tests.
- say: >
(% if flow.a is none %)
a is None
(% endif %)
- say: >
(% if flow.b is string %)
b is a string
(% endif %)
- say: >
(% if flow.c is number %)
c is a number
(% endif %)
- say: >
(% if flow.d is number %)
d is a number
(% endif %)
- say: >
(% if flow.f is sequence %)
f is a sequence
(% endif %)
- say: >
(% if flow.g is mapping %)
g is a mapping
(% endif %)
(% if flow.g is sequence %)
...and a sequence
(% endif %)
The output:
Notice that on lines
10
and14
we can only check if the variable is a number. Testing if the variable is a float or an integer is not supported in Jinja 2.10.1. Testing if a variable is boolean is not supported in Jinja 2.10.1, although we can test the truthiness of a variable.Also, notice that we can’t tell for sure if a variable is a list. Instead, we can only test if it is a
sequence
, meaning it is iterable. Dictionaries are also iterable which is whyflow.g
passed thesequence
test in addition to themapping
test.
The say component
Although Jinja2 supports multiple data types, note that the say
component can only print strings. Other data types need to be converted to strings.
For example, this code will result in an error:
steps:
- flow_set:
foo: bar
bat: baz
count: 1
- say: (@ flow ) # A dictionary
- say: (@ flow.count ) # A number
The issue is that flow
is a dictionary, not a string. The second say
will also generate an error since flow.count
is a number.
Converting data types to strings using Jinja2 filters:
steps:
- flow_set:
foo: bar
bat: baz
count: 1
- say: (@ flow | string )
- say: (@ flow.count | string )
Filters
Variables can be modified using filters. Filters are separated from the variable by a pipe symbol | and may have optional arguments in parentheses. Multiple filters can be chained together. The output of one filter is applied to the next.
View the full list of filters here: Jinja2 Built-in Filters. Note that Meya uses Jinja 2.10.1, so filters added in later versions are not available.
Example 1
steps:
- user_set:
my_list:
- foo
- bar
- 1
- say: (@ user.my_list.1 | replace('b', 'c' ) | upper )
The output:
Example 2
In this example, we print a list using the join filter:
steps:
- flow_set:
my_list:
- a
- b
- c
- say: (@ flow.my_list | join(' & ') )
The output:
Example 3
In this final example, we print flow.name, but specify a default value in case it doesn’t exist:
steps:
- say: Hi, (@ flow.name | default('friend') )!
The output:
Operators
Your code can use a variety of comparison and logical operators to create complex Jinja2 expressions.
For the full list of Jinja2 operators, check out these links: Comparisons, Logic, Other operators.
Example 1
The most common way Jinja2 is used in a Meya app is to evaluate the truthiness of a variable:
steps:
- if: (@ flow.foo )
then:
say: A
else:
say: B
If
flow.foo
has not been defined, the test on line2
will result in an error. If you can’t be sure of the existence of a variable, using.get("<variable>")
or thedefault
filter is safer:steps: - if: (@ flow.get("foo") ) then: say: A else: say: B - if: (@ flow.foo | default(false) ) then: say: A else: say: B
Example 2
Another very common scenario is when you want to test if a variable exists:
steps:
- if: (@ flow.foo is defined )
then:
say: A
else:
say: Z
Example 3
This code will print even numbers up to 10:
steps:
- flow_set:
count: 1
- (test)
- if: (@ flow.count % 2 == 0 )
then: next
else:
jump: increment
- (print)
- say: (@ flow.count ) is even.
- (increment)
- flow_set:
count: (@ flow.count + 1 )
- if: (@ flow.count <= 10 )
then:
jump: test
else: next
- say: Done.
Here is the output:
Statements
Jinja2 statements can be embedded in BFML to enable advanced flow control.
If statement
In this example, a Jinja2 if statement determines what text is printed to the user.
steps:
- flow_set:
name: Susan
- say: >
(% if flow.name %)
Hi, (@ flow.name )!
(% else %)
I don't know you!
(% endif %)
The output:
The pure BFML equivalent looks like this:
steps:
- flow_set:
name: Susan
- if: (@ flow.name )
then:
say: Hi, (@ flow.name )!
else:
say: I don't know you!
For loop
The Jinja2 for loop comes with a special variable called loop that provides access to detailed information about the loop.
In BFML, the for loop can only be used to concatenate strings. It also must be contained within a single step.
You can read more about Jinja2 for loops here.
Example 1
steps:
- flow_set:
numbers:
- 123
- 42
- -12
- 500
- say: >
(% for n in flow.numbers if n % 2 == 0 %)
(@ n ) is even.
(% endfor %)
Example 2
In this example, we loop through some numbers and print whether each one is higher or lower than the previous number.
Notice the
-
at the beginning of each statement. This removes whitespace from that line. Read more about whitespace control here.
steps:
- flow_set:
numbers:
- 123
- 42
- -12
- 500
- say: >
(%- for n in flow.numbers %)
(%- if loop.previtem is defined %)
(@ n ) is (% if loop.previtem < n %) higher (% else %) lower (% endif %) than (@ loop.previtem ).
(%- endif %)
(%- endfor %)
The output:
Tests
Jinja2 has built-in tests you can use in your control statements. Tests always return true
or false
.
You can find the full list of Jinja2 tests here. Note that Meya uses Jinja 2.10.1, so tests added in later versions are not available.
Example 1
In this example, we use the number
test to check the data type of each item in an array.
steps:
- flow_set:
arr:
- a
- b
- -12
- c
- say: >
(%- for item in flow.arr %)
(%- if item is number %)
(@ item ) is a number!
(%- else %)
(@ item ) is not a number.
(%- endif %)
(%- endfor %)
The output:
Functions
Jinja2 also has built-in functions you can use in your app.
View the full list of Jinja2 functions here. Note that Meya uses Jinja 2.10.1, so functions added in later versions are not available.
Example 1
In this example, we calculate the length of flow.my_list
using the count
Jinja2 filter, then use the Jinja2 range
function to generate a list of numbers based on the count. Finally, the for
loop prints each number.
steps:
- flow_set:
my_list:
- foo
- bar
- bat
- baz
- say:
(%- for n in range(flow.my_list | count) %)
(@ n )
(%- endfor %)
The output:
Updated almost 2 years ago