Jinja
StackSpot's engine uses Jinja to allow Templates and Plugins in the Stacks to generate files based on the input parameters the users add when creating an app or applying a Plugin.
In addition to file generation, Jinja expressions can be used in the configuration files of Templates, Plugins and Tasks allowing great flexibility in defining settings based on user input.
Template and Plugin Rendering Cycle
Before using Jinja, it is important to understand how the cycle of rendering Templates and Plugins in StackSpot works. Whenever a Template or Plugin is applied to a project, either by the stk-legacy create app
or stk-legacy apply plugin
commands, the following sequence of steps occurs:
- The configuration file (
template.yaml
orplugin.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
templates
folder of the Plugin/Template are rendered. - Hooks with trigger
after-render
are executed. - Merge files if they exist at the destination.
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 generating files, and at some points in the configuration of the Templates and Plugins.
Basic concepts of Jinja
The Jinja language, like most template languages, allows through specific markup in a text, the definition of expressions that are interpolated by the engine to render the final result.
Jinja markup in a text can be as follows:
{{ ... }}
, 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 John!</h1>
<h1>Hello Mary!</h1>
</body>
In short, when evaluated by the StackSpot template engine, Jinja's expressions and control structures are evaluated to produce the final result, either in the generation of files or in the configuration of commands and Declarative Hooks present in the configuration files of a Stack's Templates and Plugins.
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.
For more information about Jinja expressions, see Jinja's documentation.
Check out the following for a summary of Jinja's most relevant points for a stack 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
configuration files of Templates, Plugins and Tasks, asked to the user when they are applied to a project.
For example, suppose a Plugin or Template has the following input
defined in its 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:
{{ nome }}
When applying the Plugin or Template 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 entered by the user rendered instead of the expression.
Filters
Variables can be transformed using filters. The syntax for applying a filter to a variable is:
{{ variavel | filtro }}
You can apply more than one filter by chaining together several pipes ( |
), as in the example below:
{{ variavel | filtro1 | filtro2 }}
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.
{{ nome | capitalize }}
For more information about Jinja's standard filters, see Jinja's documentation.
In addition to the standard Jinja filters, StackSpot provides some useful filters for Stacks creators:
pascalcase
: Converts strings to the PascalCase format.camelcase
: Converts strings to the 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.
Control Structures
The control structures present in Jinja are the if/elif/else/endif
and for/else/endfor
.
Use the structure if/elif/else/endif
The if/elif/else/endif
structure commands 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 idade >= 18 %}
Would you like a drink?
{% elif >= 5 %}
Would you like a soda?
{% else %}
Would you like some juice?
{% endif %}
In the example above, logic is implemented to determine which message will be printed according to the value of the variable age
.
The comparison operators available in Jinja are:
==
: Compares the two operands and returnstrue
if equal.!=
: Compares the two operands and returnstrue
if they are different.>
:true
if the left operand is greater than the right operand.>=
:true
if the left operand is greater than or equal to the right operand.<
:true
if the left operand is less than the right operand.<=
:true
if the left operand is less than or equal to the right operand.
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 together.
The elif
and else
blocks are optional, and you can use more than one elif
block if needed.
Use the structure for/else/endfor
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
name list, a line with a salutation for the name entered will be rendered.
If the name list 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.
Use Jinja on StackSpot
Generation of files in the Templates and Plugins
Jinja templates are text files that can contain expressions and variables that are interpolated by the engine to generate the final result. When creating a Template/Plugin in a Stack, a templates
folder is created which contains the Jinja templates that will be used to generate the files when the Template or Plugin is used.
When a user applies a Plugin or creates a new app, the rendering click of the Plugin or Template is executed. The jinja templates present in each of the Plugins/Templates 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:
name: example-plugin
description: Example plugin
types:
- app
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 three inputs are asked: 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 dynamically define folder and file names based on user inputs.
The content of the file {{file_name}}.txt
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 needed.
When applying the Plugin to an empty app
folder, the STK CLI will ask for inputs as in the example below:
$ cd app
$ stk-legacy 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 generated file structure will be:
app
├── example_folder
│ └── nested_file.txt
├── example.txt
├── file.txt
├── my-folder
│ └── my-file.txt
└── stk.yaml
Note that the name of the folder and files containing Jinja expressions were interpolated with the values entered by the user in the inputs.
The contents of the file my-file.txt after rendering will be:
Content: my-content
Note that the contents of the file have been interpolated by the Jinja engine by replacing the {{content}}
expression with the contents that were typed in by the user my-content
.
In the example the structure of a Plugin was used, but the structure is identical to that of Templates when used in the stk-legacy create app
command.
Jinja expressions in configuration files
In addition to file generation, Jinja expressions can be used in the configuration yamls of Plugins
, Templates
and Tasks
.
Jinja Expressions in Computed Inputs and Global Computed Inputs
Jinja expressions can be used to calculate the computed-inputs and global-computed-inputs of Templates and Plugins, as per the example below:
name: example-plugin
description: Example plugin
types:
- app
inputs:
- label: Name
type: text
name: name
computed-inputs:
nome_uppercase: {{name | upper}}
global-computed-inputs:
nome_lowercase: {{name | 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 entered name to uppercase and lowercase.
Jinja Expressions in Declarative Hooks
Jinja expressions can be used in some declarative Hook 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 shown in 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:
hooks:
- 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: {{patern_to_search}}
replace-by:
value: bla
- Declarative Hook of type
edit-xml
: Jinja expressions can be used onpath
and anyvalue
, as in the example below:
hooks:
- 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:
hooks:
- 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 in the example below:
hooks:
- type: edit-yaml
trigger: after-render
path: using-template.yaml
encoding: ascii
changes:
- yamlpath: "$.scripts"
update:
value: |
{{ json_tag }}: {{ json_body }}
Jinja expressions in Tasks
Jinja expressions can also be used in tasks to compose both commands and requirements validations, as in the example below:
name: complex-task
description: Runs a more complex task
inputs:
- label: User
type: text
default: my-user
name: user
required: 'true'
- label: password
type: password
default: my-pass
name: password
required: 'true'
supported-os:
- windows
- linux
- mac
requirements-check:
dependency-example:
check-command:
linux: echo "check-command for linux {{inputs.user}} with pass {{inputs.password}}"
mac: echo "check-command for mac {{inputs.user}} with pass {{inputs.password}}"
windows: echo "check-command for windows{{inputs.user}} with pass {{inputs.password}}"
command:
linux: echo "command for linux {{inputs.user}} with pass {{inputs.password}}"
mac: echo "command for mac {{inputs.user}} with pass {{inputs.password}}"
windows: echo "command for windows {{inputs.user}} with pass {{inputs.password}}"
Next steps
See Metadata's page to learn how and where to use metadata when creating your Stack.
Was this page helpful?