About
In the following we discuss jinja strings, which are the return entity of an entity in comp component system.
Jinja Strings
The component system of comp is based in jinja2. This means that the components construct strings following jinja2 syntax. We have a specific type Jinja
(which is a subtype of Str
- the string type of typed) whose instances are the so-called jinja strings. These are Python strings preppended with the “jinja
” keyword.
So, more precisely, an instance of Jinja
is a string as follows (see jinja2 to discover the full valid syntax):
1my_jinja_string = """jinja
2[% for i in x %]
3<some html>
4 [% if y is True %]
5 [% set w = "something" %]
6 <more html>
7 ...
8[# my beautiful comment #]
9 [[ z ]]
10 ...
11 </more html>
12 [% endif %]
13</some html>
14[% endfor %]
15"""
Delimiters
You should note that the jinja2 code provided in the previous jinja string uses delimiters differing from the standard ones: while {lib:jinja} uses {%
, {{
and {#
as default for blocks, variables and comments, respectively, we are using [
, [[
and [#
.
The reason is that as we will see in components documentation, in comp the jinja strings are typically returned by functions, and one would like to use variables of the function inside the return jinja string. This implies to consider the jinja strings as f-strings. But in f-strings, variables already use {
as delimiters, causing certain conflict with the default {lib:jinja} delimiters.
If you don’t like using [
as the basic delimiter for jinja strings, you can set one of the following options, or some mix of them:
blocks |
variables |
comments |
---|---|---|
[% … %] |
[[ … ]] |
[# … #] |
(% … %) |
(( … )) |
(# … #) |
<% … %> |
<< … >> |
<# … #> |
This is done by setting the following environment variables:
env |
description |
default value |
---|---|---|
COMP_JINJA_VAR_DELIM |
delimiters for jinja vars |
(“[[”, “]]”) |
COMP_JINJA_BLOCK_DELIM |
delimiters for jinja blocks |
(“[%”, “%]”) |
COMP_JINJA_COMMENT_DELIM |
delimiters for jinja comments |
(“[#”, “#]”) |
Variables
When working with jinja strings we have two kinds of variables:
inner jinja variables, which are defined as part of the jinja string through a
[% set ... %]
block;free jinja variables, which are not defined inside the jinja string.
So, for example, in the above jinja string, x
, y
and z
are all free jinja variables, while w
is an inner jinja var.
The variables of a jinja string are accessible as attributes corresponding to properties of the type Jinja
:
attribute |
meaning |
---|---|
.vars |
dictionary with all vars organized by kind |
.inner_vars |
dict of inner vars and values of the jinja string |
.free_vars |
tuple of free vars of the jinja string |
In the case of the above jinja string one would get:
1from comp import Jinja
2from some.where import my_jinja_string
3
4print(Jinja(my_jinja_string).vars) # {"inner": {"w": "something"}, "free": ("x", "y", z")}
5print(Jinja(my_jinja_string).inner_vars) # {"w": "something"}
6print(Jinja(my_jinja_string).free_vars) # ("x", "y", "z")
Notice that the attributes are accessible only after initialization of the jinja strings in the Jinja
type. However, following the {lib:functions over methods} philosophy of typed, the properties above are actually defined through typed functions:
attribute |
typed function |
---|---|
.vars |
jinja_vars |
.inner_vars |
jinja_inner_vars |
.free_vars |
jinja_free_vars |
Therefore, the same action could be taken directly, i.e, without initialization:
1from comp import jinja_vars, jinja_inner_vars, jinja_free_vars
2from some.where import my_jinja_string
3
4print(jinja_vars(my_jinja_string)) # {"inner": {"w": "something"}, "free": ("x", "y", z")}
5print(jinja_inner_vars(my_jinja_string)) # {"w": "something"}
6print(jinja_free_vars(my_jinja_string)) # ("x", "y", "z")
Rendering
The main objective of using jinja strings is to create “dynamic HTML”, where the dynamic aspects is provided precisely by the existence of {f:free jinja vars}, which are leaved to be fixed a posteriori. The process of transforming jinja strings into raw HTML is called rendering.
Notice that the inner vars of a jinja string already comes equipped with a value, which is set in the moment of its definition. To be rendered we then need to set values to its free jinja vars. This is done by defining a jinja context, which is a dictionary whose keys are strings with the names of the free jinja vars, and the values are the values that will be set to them.
As example of jinja context for the above jinja string would be:
1my_context = {
2 "x": some_value,
3 "y": other_value,
4 "z": another_value
5}
One time defined the context, one renders a jinja string by making use of the .render
method:
1from comp import Jinja
2from some.where import my_jinja_string
3
4Jinja(my_jinja_string).render(**my_context)
Or, without initialization, through the typed function render
:
1from comp import render
2from some.where import my_jinja_string
3
4render(my_jinja_string)
Factories
Inside comp we find some jinja factories. These are type factories constructing subtypes of Jinja
. In other words, are typed functions taking values into SUB(Jinja)
, the type of subtypes of Jinja
.
Highlight
To conclude, let us talk briefly on how to improve syntax highlight while working with jinja strings. We have two points to consider:
jinja strings are used inside files with Python filetype;
jinja strings use delimiters differing from the standard jinja delimiters.
From the first point, to get a nice highlighting one needs to mix both Python and Jinja highlights. This can be done by means of creating a custom filetype or by maintaining the Python filetype and using some regex filtering that includes Jinja highlight inside Python highlighting precisely for jinja strings.
Since in comp the only non-pythonic feature we will need is highlighting, the second option is highly preferable. Indeed, notice that jinja strings are defined as:
The fact that they are prefixed with a special keyword was introduced precisely to allow an easy parsing of the region that will be highlighted with Jinja syntax. Indeed: it is the region in between a """jinja
and a triple quotes """
.
There are multiple solutions to embed syntax of a language inside the syntax of other language by giving certain delimiters. Such solutions depend on the development environment. For vim
you can use vim-SyntaxRange. For neovim
, try nvim-treesitter.
In the case of vim
with SyntaxRange
, to embed Jinja highlight with the given delimiters you can just add the following line to your .vimrc
:
autocmd BufWinEnter,FileType python call SyntaxRange#Include('f"""jinja', '"""', 'jinja', 'NonText')
Concerning the second point above, before embedding Jinja syntax inside Python syntax, one first need to modify the Jinja syntax to highlight the correct delimiters. For vim
users, you can use Vim-Jinja2-Syntax with minor modifications.