Skip to main content
Version: v4.4.0

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 create app or stk apply plugin commands, the following sequence of steps occurs:

  1. The configuration file (template.yaml or plugin.yaml) is loaded.
  2. Hooks with trigger before-input are executed.
  3. The inputs defined in the configuration are queried for the user.
  4. The computed-inputs are calculated.
  5. Hooks with trigger before-rendering are executed.
  6. Jinja templates contained in the templates folder of the Plugin/Template are rendered.
  7. Hooks with trigger after-render are executed.
  8. 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.

info

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 }}

info

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 returns true if equal.
  • !=: Compares the two operands and returns true 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: Returns true if both left and right expressions are true.
  • or : Returns true if one of the expressions is true.
  • not: Denies the result of an expression.
  • (expr): Group logical expressions together.
info

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.

info

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 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 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 both command and workdir 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 the path, changes.search.string, changes.search.pattern attributes, at all points where value is used, and on the not-exists within a when, 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 on path and any value, 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 on path and any value, 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 on path and any value, 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?