Usar Jinja na StackSpot

Nesta seção, você encontra as informações sobre o uso da linguagem Jinja na StackSpot.

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 create app ou stk apply plugin, a seguinte sequência de passos ocorre:

  1. O arquivo de configuração (template.yaml ou plugin.yaml) é carregado.
  2. São executados os Hooks com trigger before-input.
  3. Os inputs definidos na configuração são perguntados para o usuário.
  4. São calculados os computed-inputs.
  5. São executados os Hooks com trigger before-render.
  6. São renderizados os templates Jinja contidos na pasta templates do Plugin/Template.
  7. São executados os Hooks com trigger after-render.
  8. 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.

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

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 retornando true se forem iguais.
  • !=: Compara os dois operandos retornando true 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: Retorna true se as expressões à esquerda e à direta forem verdadeiras.
  • or: Retorna true se uma das expressões for verdadeira.
  • not: Nega o resultado de uma expressão.
  • (expr): Agrupa expressões lógicas.

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.

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 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 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 no command quanto no workdir 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 atributos **`path`**, **`changes.search.string`**, **`changes.search.pattern`**, em todos os pontos onde se usa **`value`** e no **`not-exists`** dentro de um **`when`**, conforme o exemplo abaixo:

```yaml
    - 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 no **`path`** e em qualquer **`value`**, conforme o exemplo abaixo:

```yaml
    - 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 no path e em qualquer value, 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 no path e em qualquer value, 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.