Using Jinja on StackSpot
In this section, you will find information about using the Jinja language.
Jinja
The StackSpot engine uses the Jinja language to allow Plugins to generate files based on user input parameters when creating an app or applying a Plugin.
In addition to file generation, Jinja expressions can be used in Plugin configuration files, allowing great flexibility in defining settings based on user input.
For security reasons, Jinja runs separately from your system in Sandbox mode. Depending on your usage, some expressions may not behave as expected. For more information about restrictions, see the official Jinja documentation.
Plugins rendering cycle
Before using Jinja, it's essential to understand how the Plugin rendering cycle works in StackSpot. Whenever a Plugin is applied to a project, either by the stk create app
or stk apply plugin
commands, the following sequence of steps occurs:
- The configuration file (
plugin.yaml
) is loaded; - Hooks with trigger
before-input
are executed; - The inputs defined in the configuration are queried for the user;
- The
computed-inputs
are calculated; - Hooks with trigger
before-rendering
are executed; - Jinja templates contained in the Plugin's
templates
folder are rendered; - Hooks with trigger
after-render
are executed; - Merge files if they exist in the target.
The input parameters entered by the user in step 3 are used as variables, they can be used in Jinja expressions both in the templates used for file generation and at some points in the Plugins configuration.
Jinja's basic concepts
The Jinja language, like most template languages, allows, through specific tags in a text, a definition of expressions that are interpolated by the engine to render the final result.
Jinja markup in text can be:
{{ ... }}
, for expressions that will be rendered;{% ... %}
, for control structures;{# ... #}
, for comments.
Check out a simple example of a Jinja template below:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
{# print a Hello message for each name in names list #}
{% for name in names %}
<h1>Hello {{ name }}!</h1>
{% endfor %}
</body>
When the variable name
is given the values ['John', 'Mary']
, the template rendering should return the following result below:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
<h1>Hello João!</h1>
<h1>Hello Maria!</h1>
</body>
In short, when evaluated by StackSpot's template engine, Jinja's expressions and control structures are evaluated to produce the final result, either when generating files or configuring commands and Hooks in the Plugins' configuration files.
Jinja expressions
Expressions must be enclosed within {{ ... }}
, this indicates something that should be rendered by Jinja. Expressions can contain literal values, variables, calculations and complex expressions.
To learn more about all the Jinja expressions, check the Jinja documentation.
Check out the following for a summary of Jinja's most relevant points for a content creator at StackSpot.
Variables
The most common use of Jinja expressions in StackSpot is to use the value of a variable in the rendered files. The variables present in the Jinja context come from the inputs
in the Plugins configuration files asked of the user when applied to a project.
For example, imagine that a Plugin has the following input
defined in it's Yaml file:
inputs:
- label: Enter your name
type: text
name: name
The expression to render the name entered by the user in the input will be:
{{ name }}
When applying the Plugin in question, in the applied project, the input will ask the user for their name. Generated files that contain the expression {{ name }}
, will have the name typed in by the user rendered instead of the expression.
If the Jinja variable has a hyphen, for example, my-var, it can be an input variable, and it is called {{inputs["my-var"]}}. If it doesn't have a hyphen, it will be {{myvar}} or {{inputs.myvar}}.
Filters
Variables can be transformed using filters. The syntax for applying a filter to a variable is:
{{ variable | filter }}
It is possible to apply more than one filter by chaining together several pipes ( |
), as in the example below:
{{ variable | filter1 | filter2 }}
In the above example the variable value is modified by filter1
and then modified by filter2
.
Check the example of the default capitalize
filter:
This filter converts the entered name to the format where, the first letter is uppercase and the others are lowercase.
{{ name | capitalize }}
For more information on Jinja's default filters, check the Jinja documentation.
In addition to Jinja's standard filters, StackSpot provides some useful filters for Stacks creators:
pascalcase
: Converts strings to PascalCase format.camelcase
: Converts strings to camelCase format.kebabcase
: Converts strings to kebab-case format.cobolcase
: Converts strings to COBOL-CASE format.snakecase
: Converts strings to snake_case format.macrocase
: Converts strings to MACRO_CASE format.group_id_folder
: Converts strings by replacing "." with "/". This filter is useful for assembling folder names from Java names and packages.regex_replace
: Return a copy of the value with all occurrences of a substring replaced with a new one. The first argument is the substring that should be replaced, the second is the replacement string.
You can provide an optional third argument count
, only the first occurrences of count will be replaced:
# {{ "string value" |regex_replace("substring pattern", "replacement string") }}
{{ "Hello World"|regex_replace("Hello", "Goodbye") }}
-> Goodbye World
# {{ "string value" |regex_replace("substring pattern", "replacement string", count) }}
{{ "aaathree"|replace("a", "two", 2) }}
-> two, two, athree
You can use variables that contain the value to be replaced:
Consider that the variable hello_word
has the value "dogs, cats, birds"
;
# {{ variable|regelex_replace('substring pattern', 'replacement string')}}
{{ hello_world|regex_replace('birds', 'humans') }}
-> docs, cats, humans
The variable hello_word
now has the value "dogs, cats, humans"
.
It is possible to use the regular expression groups in the replacement string in the replacement field:
Consider that the variable hello_word
has the value 'burger'
;
# {{ string value or variable | regex_replace(define group pattern, replacement string group)}}
{{ hello_world|regex_replace('((\w+\s*)+)', 'cheese\1y') }}
-> cheeseburger
The variable hello_word
now has the value 'cheeseburger'
.
from_json
: Converts a string in JSON format into a python dict.
Consider the JSON string:
{
"input1": "value1"
}
To use the Python dict in JINJA, you must access it as follows:
{% set my_dict = json_input | from_json %}
{{my_dict.input1}}
Control structures
The StackSpot portal does not support using Jinja control structures. Therefore, it is not guaranteed that all Jinja templates will be successfully executed in the browser.
The control structures present in Jinja are theif/elif/else/endif
and for/else/endfor
.
Use the if/elif/else/endif
structure
Commands of the if/elif/else/endif
structure allow you to define blocks that will be rendered according to a condition. The example below illustrates the use of an if
block to control the printing of part of a template:
{% if age >= 18 %}
Would you like a drink?
{% elif >= 5 %}
Would you like a soft drink?
{% else %}
Would you like a juice?
{% endif %}
In the above example, logic is implemented to determine the message that will be printed according to the value of the variable age
.
The comparison operators available in Jinja are:
==
: Compares the two operands, returningtrue
if they are equal.!=
: Compares the two operands, returningtrue
if they are different.>
:true
if the operand on the left is larger than the operand on the right.>=
:true
if the operand on the left is greater than or equal to the operand on the right.<
:true
if the operand on the left is smaller than the operand on the right.<=
:true
if the operand on the left is less than or equal to the operand on the right.
The following logical operators can be used in expressions:
-
and
: Returnstrue
if both left and right expressions are true. -
or
: Returnstrue
if one of the expressions is true. -
not
: Denies the result of an expression. -
(expr)
: Group logical expressions.
The
elif
andelse
blocks are optional, and you can use more than oneelif
block if you need to.
Use the for/else/endfor
structure
The for/else/endfor
commands allow you to define a repeating block for each element in a list as in the example below:
{% for name in names %}
Hello {{ name }}!
{% else %}
No names given!
{% endfor %}
In the example above, for each name in the names
list a line with a salutation for the name entered will be rendered.
If the list of names is empty the contents of the block after the else
will be rendered.
The
else
block is optional and can be omitted if not needed.
Using Jinja on StackSpot
Generation of files in Plugins
Jinja templates are text files containing expressions and variables interpolated by the engine to generate the final result. When creating a Plugin, a templates
folder contains the Jinja templates that will be used to generate the files when the Plugin is used.
The Plugin rendering click is executed when a user applies a Plugin or creates a new app. The Jinja templates present in each of the Plugins being applied are interpolated using the inputs entered by the user as variables that can be used in Jinja expressions in those files.
Jinja expressions can also be used to define dynamic file and folder names based on user inputs.
To illustrate the use of Jinja in file generation, consider a Plugin with the following structure:
example-plugin/
├── plugin.yaml
└── templates
├── file.txt
├── example_folder
| └── nested_file.txt
└── {{folder_name}}
└── {{file_name}}.txt
The Plugin configuration in the plugin.yaml
file contains the definition of the inputs that will be asked of the user:
schema-version: v3
kind: plugin
metadata:
name: example-plugin
display-name: example-plugin
description: Example plugin
version: 0.0.1
spec:
type: app
compatibility:
- python
docs:
pt-br: docs/pt-br/docs.md
en-us: docs/en-us/docs.md
technologies:
- Api
inputs:
- label: Folder name
type: text
name: folder_name
- label: File name
type: text
name: file_name
- label: Content
type: text
name: content
In the example above, you are asked for three inputs: folder_name
, file_name
, and content
, which are strings.
Notice in the Plugin structure that there is a folder called {folder_name}}
and a {file_name}}.txt
file that exemplifies how to use Jinja expressions to define folder and file names based on user inputs dynamically.
The contents of the {file_name}}.txt
file will be:
Content: {{content}}
The file.txt
and nested_file.txt
files are empty and only illustrate that the entire file structure contained in the templates
folder is generated after the Plugin is applied and can have as many files and folders as required.
When applying the Plugin to an empty app
folder, the STK CLI will ask for inputs as per the example below:
$ cd app
$ stk apply plugin -p ../example-plugin
? The current folder doesn't seem to be the part of a StackSpot project. Do you still want to proceed applying the plugin? Yes
? Folder name my-folder
? File name my-file
? Content my-content
- Plugin example-plugin applied.
And the file structure generated will be:
app
├── example_folder
│ └── nested_file.txt
├── example.txt
├── file.txt
├── my-folder
│ └── my-file.txt
└── stk.yaml
Note that the folder name and files containing Jinja expressions were interpolated with the values entered by the user in the inputs.
The contents of the my-file.txt file after rendering will be:
Content: my-content
Note that the contents of the file have been interpolated by the Jinja engine by replacing the expression {{content}}
with the contents that were typed in by the user my-content
.
Jinja expressions in configuration files
In addition to file generation, Jinja expressions can be used in the configuration yaml of Plugins.
Jinja expressions on computed inputs and global computed inputs
Jinja expressions can be used to calculate the computed-inputs and global-computed-inputs of Plugins, as per the example below:
schema-version: v3
kind: plugin
metadata:
name: example-plugin
display-name: example-plugin
description: Example plugin
version: 0.0.1
spec:
type: app
compatibility:
- python
docs:
pt-br: docs/pt-br/docs.md
en-us: docs/en-us/docs.md
technologies:
- Api
inputs:
- label: Nome
type: text
name: nome
computed-inputs:
nome_uppercase: "{{nome | upper}}"
global-computed-inputs:
nome_lowercase: "{{nome | lower}}"
In the example above, the variables name_uppercase
and name_lowercase
are created using Jinja expressions that take the input name
and apply filters to convert the typed name to uppercase and lowercase.
Jinja Expressions in Declarative Hooks
Jinja expressions can be used in some Declarative Hooks configurations and in the file snippets used by Hooks. The following are the attributes that accept Jinja expressions for each Hook type.
- Declarative Hook of type
run
: Jinja expressions can be used in bothcommand
andworkdir
as per the example below:
hooks:
- type: run
trigger: before-render
workdir: {{folder_name}}/{{nested_folder_name}}
command: echo "Hello {{name}}!"
- Declarative Hook of type
edit
: Jinja expressions can be used on thepath
,changes.search.string
,changes.search.pattern
attributes, at all points wherevalue
is used, and on thenot-exists
within awhen
, as per the example below:
- type: edit
trigger: after-render
path: src/{{some_input}}.txt
changes:
- insert:
line: 0
value: "{{another_input}}"
when:
not-exists: "{{another_input}}"
- search:
string: "{{example}}"
insert-before:
value: "using {{value}}"
- search:
pattern: {{pattern_to_search}}
replace-by:
value: bla
- Declarative hook of type
edit-xml
: Jinja expressions can be used in thepath
and anyvalue
, as shown in the example below:
- type: edit-xml
trigger: after-render
path: {{some_variable}}.xml
changes:
- xpath: .//dependency
append:
value: |
<a>{{some_variable}}</a>
- Declarative Hook of type
edit-json
: Jinja expressions can be used onpath
and anyvalue
, as per the example below:
- type: edit-json
trigger: after-render
path: using-template.json
encoding: ascii
changes:
- jsonpath: "$.scripts"
update:
value: |
{
"{{ json_tag }}": "{{ json_value }}"
}
- Declarative Hook of type
edit-yaml
: Jinja expressions can be used onpath
and anyvalue
, as per the example below:
- type: edit-yaml
trigger: after-render
path: using-template.yaml
encoding: ascii
changes:
- yamlpath: "$.scripts"
update:
value: |
{{ json_tag }}: {{ json_body }}
Referencing Connection Interface in Terraform
V.1
requires:
connection-interface:
- connection-interface-one
In Terraform, attributes are referenced with the Connection Interface, according to the model:
{{connection_interface_one_CONNECTOR}}
{{connection_interface_one_CONNECTOR_engine}}
{{connection_interface_one_CONNECTOR_is_stackspot_fun}}
In V1, this model continues to work, but it is not recommended to use it in V2.
V2:
Now it is possible to access the attributes of the Connection Interfaces using a connection
. You can also include an alias
in the syntax. There are two ways to access the attributes:
- Kebab case: access using
[ ]
and a string, similar to a dictionary. Check the example:
{{connections["connection-interface-one"].engine}}
- Snake case: access the attributes by converting the Kebab to Snake case:
{{connections.connection_interface_one.is_stackspot_fun}}
An extra option is to access via Camel Case. See the example:
{{connections["connectionInterfaceOne"].engine}}
Next step
Check out the section on using Metadata and learn where and how to use metadata when creating your Plugins.