Jinja2 syntax

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 and 14 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 why flow.g passed the sequence test in addition to the mapping 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 line 2 will result in an error. If you can’t be sure of the existence of a variable, using .get("<variable>") or the default 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: