Playing with Jinja
Objectives
In this section we will learn how to create new ways of calculation.
Up to now, our only way of dynamically (that is, during the runtime) calculating a value is to point on another variable’s value. But this is not the only way.
We will learn how to insert Jinja templating language into our structure files. Please note that we do not intend to template our YAML files. That is a completely different activity. We will simply insert YAML code to calculate the value of a variable, a property, or a parameter.
Prerequisites
We assume that Rougail’s library is installed on your computer.
It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step by checking out the corresponding tag of the
rougail-tutorialsgit repository. Each tag corresponds to a stage of progress in the tutorial. Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding rougail-tutorials git repository, this workshop page corresponds to the tags v1.1_070 to v1.1_073 in the repository.
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_070
preliminary thoughts about calculation
Let’s reason on the previous HTTPS proxy configuration’s manual mode settings:
use_for_https:
default: true
https_proxy:
type: family
description: HTTPS Proxy
hidden:
variable: _.use_for_https
We see here that the https_proxy family is hidden
depending on the value of another variable. This is what we saw previously,
how to hide a variable.
We will code the same behavior in a somehow different way:
https_proxy':
description: HTTPS Proxy
hidden:
jinja: |-
{% if _.use_for_https %}
HTTPS is same has HTTP
{% endif %}
This code has the same result and yes, it’s done in a more complicated way.
We have replaced this simple hidden property variable parameter by
a Jinja parameter. The hidden process is done by a calculation.
The fact is that it has same result, but it opens more possibilities, this is what we are going to work on in this section.
- calculation
A calculation is the act of calculating the value of a variable or a property of a variable. This calculation can be a redirection of the value of an existing variable (or property), or it can be a custom calculation using the Jinja templating language.
Why the Jinja templating engine ?
We are going to embed some Jinja templating code in our structure file.
- Jinja
Jinja is a template engine. we are using Jinja in a classical way, that is, Jinja allows us to handle different cases, for example with the
ifstatement.
What about a Jinja calculation?
The Jinja templating language enables some complex calculation. For example we can code more complex behavior for hiding or disabling a variable, that is not only depending on the value of another variable as we saw before.
Jinja with a description
For those who follow the tutorial with the help of the git repository
Now you need to checkout the v1.1_071 version:
git switch --detach v1.1_071
It is preferable to include a description related to the Jinja calculation. That allows the calculation performed by the Jinja code to be understood rapidely without having to read the code itself. This is useful for the integrators who review these structure files.
firefox/20-manual.yml structure file with a description added to the Jinja code%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
'{{ identifier }}_proxy':
description: '{{ identifier }} Proxy'
hidden:
jinja: |-
{% if _.use_for_https %}
HTTPS is same has HTTP
{% endif %}
description: in HTTPS case if "_.use_for_https" is set to "true"
dynamic:
- HTTPS
- SOCKS
address:
description: '{{ identifier }} address'
default:
variable: __.http_proxy.address
port:
description: '{{ identifier }} port'
default:
variable: __.http_proxy.port
version:
description: SOCKS host version used by proxy
choices:
- v4
- v5
default: v5
disabled:
type: identifier
when: HTTPS
...
This description will appear in the documentation output for the family
With this rougail command:
rougail -m firefox -o doc --doc.output_format html > readme.html
It outputs an HTML report, here is a part of it about the https_proxy with the hidden property:
Hidden: in HTTPS case if "manual.use_for_https" is set to "true"
Identifiers:
- HTTPS
We can see that the hidden explanation for the manual.https_proxy family
is the hidden property’s description.
Jinja with a parameter
For those who follow the tutorial with the help of the git repository
Now you need to checkout the v1.1_072 version:
git switch --detach v1.1_072
Regarding dynamic families as in the use case we are dealing with, here is an interesting Rougail feature: it is possible to retrieve the family identifiers and declare them as parameters in the Jinja calculation.
firefox/20-manual.yml structure file with an identifier type parameter in the hidden property%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
'{{ identifier }}_proxy':
description: '{{ identifier }} Proxy'
hidden:
jinja: |-
{% if my_identifier == 'HTTPS' and _.use_for_https %}
HTTPS is same has HTTP
{% endif %}
description: in HTTPS case if "_.use_for_https" is set to "true"
params:
my_identifier:
type: identifier
dynamic:
- HTTPS
- SOCKS
address:
description: '{{ identifier }} address'
default:
variable: __.http_proxy.address
port:
description: '{{ identifier }} port'
default:
variable: __.http_proxy.port
version:
description: SOCKS host version used by proxy
choices:
- v4
- v5
default: v5
disabled:
type: identifier
when: HTTPS
...
We have added an identifier type parameter to the hidden property:
hidden:
params:
my_identifier:
type: identifier
Thus we can refine our Jinja code and hide the https_proxy dynamic family,
that is only in the case where the identifier is HTTPS.
hidden:
jinja: |-
{% if my_identifier == 'HTTPS' and _.use_for_https %}
HTTPS is same has HTTP
{% endif %}
In this Jinja code, we can understand that only the https_proxy dynamic family
will be hidden when the _.use_for_https variable is set to true.
Let’s set the _.use_for_https variable to true.
We have a warning:
🔔 Warning
┗━━ Manual proxy configuration
┗━━ HTTPS Proxy
┗━━ HTTPS address: 🔔 family "HTTPS Proxy" has property hidden,
so cannot access to "HTTPS address", it will be ignored
when loading from the YAML file "config/01/config.yml"
It’s helpful to run this command in read/write mode:
rougail -m firefox/ --cli.root manual.https_proxy -u yaml \
-yf config/01/config.yml --cli.read_write
because at that moment we get an error:
ERROR: cannot access to optiondescription "HTTPS Proxy"
because has property "hidden" (HTTPS is same has HTTP)
So the Jinja code worked well since the https_proxy family is hidden only when
the use_for_https variable is set to true and the identifier is HTTPS
(the socks_proxy family, however, is not hidden).
Jinja could returns a boolean
For those who follow the tutorial with the help of the git repository
Now you need to checkout the v1.1_073 version:
git switch --detach v1.1_073
So far, we have used a Jinja expression that could have a true value,
otherwise it returned nothing (i.e., None, that is a false value):
{% if my_identifier == 'HTTPS' and _.use_for_https %}
HTTPS is same has HTTP
{% endif %}
We have implicitly used the truthiness capabilities of Jinja, because the
HTTPS is same has HTTP expression is interpreted by Jinja as the True value and
the "" (empty string) expression is interpreted by Jinja as the False value.
This behavior is known as implicit boolean coercion (or truthiness).
The truthiness of python or Jinja
In Python or in Jinja, every value has an inherent Boolean interpretation—it
can be treated as either True or False in contexts that expect a Boolean
(like if, while, or logical operators).
This is often referred to as “truthiness.”
However, not every value is an instance of the `bool` type;
the bool type itself has only two instances: True and False.
Truthiness Rules
A value is considered falsy if it evaluates to
Falsein a Boolean context.Otherwise, it is truthy.
Common Falsy Values
NoneFalseZero of any numeric type:
0,0.0,0j,Decimal(0),Fraction(0, 1)Empty sequences/collections:
'',[],(),{},set(),range(0)Objects that define
__bool__()returningFalse, or__len__()returning0
Everything else is truthy, including:
Non‑zero numbers
Non‑empty strings, lists, tuples, dictionaries, etc.
Most user‑defined class instances (unless they override
__bool__or__len__)
Using truthiness in Code
You can directly use any value in a conditional:
if some_value:
print("Truthy")
else:
print("Falsy")
Jinja calcluation without truthiness
This is truthiness capability is entirely possible with Rougail but, in programming language theory, it is a form of implicit type conversion where a value of any type is automatically interpreted as a boolean in contexts that expect one (e.g., conditionals).
We like things to be declared explicitly. It is a better practice to simply avoid this implicit coercion and instead:
return a boolean value as the result of the Jinja expression
explicitly declare that this expression shall return a boolean type
Here is a Jinja code that return a boolean value:
{{ my_identifier == 'HTTPS' and _.use_for_https }}
And in order to declare explicitly the use of this explicit non-truthiness pratice,
we add a return_type parameter in the hiden property:
firefox/20-manual.yml structure file with an explicit boolean declaration%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
'{{ identifier }}_proxy':
description: '{{ identifier }} Proxy'
hidden:
jinja: |-
{{ my_identifier == 'HTTPS' and _.use_for_https }}
return_type: boolean
description: in HTTPS case if "_.use_for_https" is set to "true"
params:
my_identifier:
type: identifier
dynamic:
- HTTPS
- SOCKS
address:
description: '{{ identifier }} address'
default:
variable: __.http_proxy.address
port:
description: '{{ identifier }} port'
default:
variable: __.http_proxy.port
version:
description: SOCKS host version used by proxy
choices:
- v4
- v5
default: v5
disabled:
type: identifier
when: HTTPS
...
Key points
We have seen that the disabled or hidden properties could be calculated.
The default values can be calculated too. We’ll see a bunch of examples later on.
progress
calculation with a Jinja type calculation: we can hide a family
we know how to describe a calculation
we know how to output some documentation (markdown HTML) of the configuration
we can calculate with a dynamic family identifer
we can set an explicit boolean Jinja expression in a Jinja calculation
keywords
Jinja type calculation for a hidden property
Jinja calculation’s description
identifier type parameter
Here we have come to the possibility of making any kind of calculations based on the state of the configuration. This is an important feature to manage the stateful aspect of a configuration.