Jinja
A engine da StackSpot faz o uso da linguagem Jinja para permitir que os Templates e Plugins contidos nas Stacks façam a geração de arquivos baseados nos parâmetros de entrada informados pelos usuários ao criar um app ou aplicar um Plugin.
Além da geração de arquivos, as expressões Jinja podem ser usadas nos arquivos de configuração dos Templates, Plugins e Tasks permitindo uma grande flexibilidade na definição de configurações baseadas nas entradas do usuário.
Ciclo de renderização de Templates e Plugins
Antes de utilizar o Jinja, é importante entender como funciona o ciclo de renderização dos Templates e Plugins na StackSpot. Sempre que um Template ou Plugin é aplicados em um projeto, seja pelos comandos stk-legacy create app
ou stk-legacy apply plugin
, a seguinte sequência de passos ocorre:
- O arquivo de configuração (
template.yaml
ouplugin.yaml
) é carregado. - São executados os Hooks com trigger
before-input
. - Os inputs definidos na configuração são perguntados para o usuário.
- São calculados os
computed-inputs
. - São executados os Hooks com trigger
before-render
. - São renderizados os templates Jinja contidos na pasta
templates
do Plugin/Template. - São executados os Hooks com trigger
after-render
. - Merge de arquivos caso existam no destino.
Os parâmetros de entrada informados pelo usuário no passo 3 são usados como variáveis, elas podem ser usadas em expressões Jinja tanto nos templates usados para geração de arquivos, quanto em alguns pontos da configuração dos Templates e Plugins.
Conceitos básicos do Jinja
A linguagem Jinja assim como a maioria das linguagens de templates, permite através de marcações específicas em um texto, a definição de expressões que são interpoladas pela engine para renderizar o resultado final.
As marcações Jinja em um texto podem ser as seguintes:
{{ ... }}
, para expressões que serão renderizadas;{% ... %}
, para estruturas de controle;{# ... #}
, para comentários.
Confira abaixo um exemplo simples de um template Jinja:
<!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>
Quando a variável name
receber os valores ['João', 'Maria']
, a renderização do template deve retornar o seguinte resultado abaixo:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
<h1>Hello João!</h1>
<h1>Hello Maria!</h1>
</body>
Em resumo, ao serem avaliadas pela engine de templates da StackSpot, as expressões e estruturas de controle do Jinja são avaliadas para produzir o resultado final, seja na geração de arquivos ou na configuração de comandos e Hooks presentes nos arquivos de configuração dos Templates e Plugins de uma Stack.
Expressões Jinja
As expressões devem ser colocadas entre {{ ... }}
, isso indica que algo que deve ser renderizado pelo Jinja. As expressões podem conter valores literais, variáveis, cálculos e expressões complexas.
Para saber mais sobre todas as expressões Jinja, consulte a documentação do Jinja
Confira a seguir um resumo dos pontos mais relevantes do Jinja para um criador de Stacks na StackSpot.
Variáveis
O uso mais comum de expressões Jinja na StackSpot é o de utilizar o valor de uma variável nos arquivos renderizados. As variáveis presentes no contexto do Jinja são provenientes dos inputs
dos arquivos de configuração dos Templates, Plugins e Tasks, perguntados para o usuário quando eles são aplicados em um projeto.
Por exemplo, imagine que um Plugin ou Template tenha o seguinte input
definido em seu arquivo Yaml:
inputs:
- label: Digite seu nome
type: text
name: nome
A expressão para renderizar o nome digitado pelo usuário no input será:
{{ nome }}
Ao aplicar o Plugin ou Template em questão, no projeto aplicado, o input vai perguntar para o usuário o seu nome. Os arquivos gerados que contiverem a expressão {{ nome }}
, terão o nome digitado pelo usuário renderizados no lugar da expressão.
Filtros
As variáveis podem ser transformadas através de filtros. A sintaxe para aplicar um filtro em uma variável é:
{{ variavel | filtro }}
É possível aplicar mais de um filtro encadeando vários pipes ( |
), conforme o exemplo abaixo:
{{ variavel | filtro1 | filtro2 }}
No exemplo acima o valor da variável é modificado pelo filtro1
e depois modificado pelo filtro2
.
Confira o exemplo do filtro padrão capitalize
:
Esse filtro converte o nome informado para o formato onde, a primeira letra é maiúscula e as outras minúsculas.
{{ nome | capitalize }}
Para saber mais sobre todos os filtros padrões do Jinja, consulte a documentação do Jinja
Além dos filtros padrão do Jinja, a StackSpot disponibiliza alguns filtros úteis para os criadores de Stacks:
pascalcase
: Converte strings para o formato PascalCase.camelcase
: Converte strings para o formato camelCase.kebabcase
: Converte strings para o formato kebab-case.cobolcase
: Converte strings para o formato COBOL-CASE.snakecase
: Converte strings para o formato snake_case.macrocase
: Converte strings para o formato MACRO_CASE.group_id_folder
: Convete strings trocando "." por "/". Esse filtro é útil para montar nomes de pastas a partir de nomes e pacotes Java.
Estruturas de controle
As estruturas de controle presentes no Jinja são as if/elif/else/endif
e for/else/endfor
.
Usar a estrutura if/elif/else/endif
Os comandos da estrutura if/elif/else/endif
permitem a definição de blocos que serão renderizados de acordo com uma condição. O exemplo abaixo ilustra a utilização de um bloco if
para controlar a impressão de parte de um template:
{% if idade >= 18 %}
Aceita um drink?
{% elif >= 5 %}
Aceita um refrigerante?
{% else %}
Aceita um suco?
{% endif %}
No exemplo acima, é implementada uma lógica para determinar a mensagem que será impressa de acordo com o valor da variável idade
.
Os operadores de comparação disponíveis no Jinja são:
==
: Compara os dois operandos retornandotrue
se forem iguais.!=
: Compara os dois operandos retornandotrue
se forem diferentes.>
:true
se o operando à esquerda for maior que o operando à direita.>=
:true
se o operando à esquerda for maior ou igual ao operando à direita.<
:true
se o operando à esquerda for menor que o operando à direita.<=
:true
se o operando à esquerda for menor ou igual ao operando à direita.
Os operadores lógicos abaixo podem ser usados nas expressões:
and
: Retornatrue
se as expressões à esquerda e à direta forem verdadeiras.or
: Retornatrue
se uma das expressões for verdadeira.not
: Nega o resultado de uma expressão.(expr)
: Agrupa expressões lógicas.
Os blocos elif
e else
são opcionais e pode-se usar mais de um bloco elif
, caso necessário.
Usar a estrutura for/else/endfor
Os comandos da estrutura for/else/endfor
permitem a definição de um bloco de repetição para cada elemento de uma lista conforme o exemplo abaixo:
{% for nome in nomes %}
Olá {{ nome }}!
{% else %}
Sem nomes informados!
{% endfor %}
No exemplo acima, para cada nome na lista de nomes nomes
será renderizada uma linha com uma saudação para o nome informado.
Caso a lista de nomes esteja vazia será renderizado o conteúdo do bloco após o else
.
O bloco else
é opcional e pode ser omitido caso não seja necessário.
Utilizar Jinja na StackSpot
Geração de arquivos nos Templates e Plugins
Os templates Jinja são arquivos de texto que podem conter expressões e variáveis que são interpoladas pela engine para gerar o resultado final. Ao criar um Template/Plugin em uma Stack, é criada uma pasta templates
que contém os templates Jinja que serão usados para gerar os arquivos quando o Template ou Plugin for utilizado.
Quando um usuário aplica um Plugin ou cria um novo app, o cliclo de renderização do Plugin ou Template é executado. Os templates jinja presentes em cada um dos Plugins/Templates sendo aplicados, são interpolados usando os inputs informados pelo usuário como variáveis que podem ser usadas em expressões Jinja nesses arquivos.
As expressões Jinja também podem ser usadas para definir nomes dinâmicos de arquivos e pastas, baseados nos inputs do usuário.
Para ilustar o uso de Jinja na geração de arquivos, considere um Plugin com a seguinte estrutura:
example-plugin/
├── plugin.yaml
└── templates
├── file.txt
├── example_folder
| └── nested_file.txt
└── {{folder_name}}
└── {{file_name}}.txt
A configuração do Plugin no arquivo plugin.yaml
, contém a definição dos inputs que serão perguntados para o usuário:
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
No exemplo acima são perguntados três inputs: folder_name
, file_name
e content
que são strings.
Observe na estrutura do Plugin que há uma pasta chamada {{folder_name}}
e um arquivo {{file_name}}.txt
que exemplifica como usar expressões jinja para definir nomes de pastas e arquivos dinamicamente, baseando-se nos inputs dos usuários.
O conteúdo do arquivo {{file_name}}.txt
será:
Content: {{content}}
Os arquivos file.txt
e nested_file.txt
estão vazios e só ilustram que toda a estrutura de arquivos contida na pasta templates
é gerada após a aplicação do Plugin, podendo ter quantos arquivos e pastas forem necessários.
Ao ser aplicado o Plugin em uma pasta vazia app
, o STK CLI irá perguntar os inputs conforme o exemplo abaixo:
$ 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.
E a estrutura de arquivos gerada será:
app
├── example_folder
│ └── nested_file.txt
├── example.txt
├── file.txt
├── my-folder
│ └── my-file.txt
└── stk.yaml
Observe que o nome da pasta e dos arquivos que continham expressões Jinja foram interpolados com os valores digitados pelo usuário nos inputs.
O conteúdo do arquivo my-file.txt após a renderização será:
Content: my-content
Observe que o conteúdo do arquivo foi interpolado pela engine do Jinja substituíndo a expressão {{content}}
pelo conteúdo que foi digitado pelo usuário my-content
.
No exemplo a estrutura de um Plugin foi utilizada, mas a estrutura é idêntica a dos Templates, quando são utilizados no comando stk-legacy create app
.
Expressões Jinja em arquivos de configuração
Além da geração de arquivos, as expressões Jinja podem ser usadas nos yamls de configuração de Plugins, Templates e Tasks.
Expressões Jinja em Computed inputs e Global computed inputs
As expressões Jinja podem ser usadas para calcular os computed-inputs e global-computed inputs de Templates e Plugins, conforme o exemplo abaixo:
name: example-plugin
description: Example plugin
types:
- app
inputs:
- label: Nome
type: text
name: nome
computed-inputs:
nome_uppercase: {{nome | upper}}
global-computed-inputs:
nome_lowercase: {{nome | lower}}
No exemplo acima, as variáveis nome_uppercase
e nome_lowercase
são criadas usando expressões Jinja que pegam o input nome
e aplicam filtros para converter o nome digitado em letras maiúsculas e minúsculas.
Expressões Jinja em Hooks Declarativos
As expressões Jinja podem ser usadas em algumas configurações de Hooks declarativos e nos snippets de arquivos usados pelos hooks. A seguir apresentamos os atributos que aceitam expressões Jinja para cada tipo de Hook.
- Hook Declarativo do tipo
run
: Expressões Jinja podem ser usadas tanto nocommand
quanto noworkdir
conforme o exemplo abaixo:
hooks:
- type: run
trigger: before-render
workdir: {{folder_name}}/{{nested_folder_name}}
command: echo "Hello {{name}}!"
- Hook Declarativo do tipo
edit
: Expressões Jinja podem ser usadas nos atributospath
,changes.search.string
,changes.search.pattern
, em todos os pontos onde se usavalue
e nonot-exists
dentro de umwhen
, conforme o exemplo abaixo:
- 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
- Hook Declarativo do tipo
edit-xml
: Expressões Jinja podem ser usadas nopath
e em qualquervalue
, conforme o exemplo abaixo:
- type: edit-xml
trigger: after-render
path: {{some_variable}}.xml
changes:
- xpath: .//dependency
append:
value: |
<a>{{some_variable}}</a>
- Hook Declarativo do tipo
edit-json
: Expressões Jinja podem ser usadas nopath
e em qualquervalue
, conforme o exemplo abaixo:
- type: edit-json
trigger: after-render
path: using-template.json
encoding: ascii
changes:
- jsonpath: "$.scripts"
update:
value: |
{
"{{ json_tag }}": "{{ json_value }}"
}
- Hook Declarativo do tipo
edit-yaml
: Expressões Jinja podem ser usadas nopath
e em qualquervalue
, conforme o exemplo abaixo:
- type: edit-yaml
trigger: after-render
path: using-template.yaml
encoding: ascii
changes:
- yamlpath: "$.scripts"
update:
value: |
{{ json_tag }}: {{ json_body }}
Expressões Jinja em Tasks
As expressões Jinja também podem ser usadas em tasks para compor tanto os comandos quanto as validações de requisitos, conforme o exemplo abaixo:
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}}"
Próximos passos
Confira a seção sobre o uso do Metadata e conheça onde e como utilizar metadados na criação da sua Stack.
Esta página foi útil?