Biblioteca Tenjin para templates em Ruby
Aprendendo e usando uma alternativa à biblioteca padrão ERB, no Ruby, para templates de texto
Por meu blog não ser ainda um blog de "um milhão de dólares", tenho sempre experimentado alternativas para geri-lo. Já usei:
- Wordpress
- Ghost
- GitHub
- Medium
Tudo isso em meio à uma vontade de usar um sistema gerador de sites estático. Mas sempre que ia aprender algum, a preguiça batia, era muita coisa nova para aprender, e não estava no topo de minhas prioridades.
Eis que um dia me vi pensando em ter mais liberdade, registrei um domínio e comecei a pensar no que usar para colocar meu blog nele.
Como há muito tempo mantenho meu textos todos arquivados em Markdown, resolvi fazer um script para gerar os HTMLs, usando SSI para facilitar a vida, num servidor Nginx.
Comecei escrevendo em bash, e logo me toquei que escrever em Ruby ou Python seria mais fácil, especialmente no momento de manipular strings. Como estou em processo de imersão no Ruby, ele foi o escolhido.
Rapidinho terminei o programa e coloquei o blog no ar.
Próximo passo
Já tinha um programa para gerar meu blog, então pensei, porque melhorar, porque não melhorar, "melhorei-lho-o".
Passei a ampliá-lo e organizá-lo para ser um gem, coisa simples. Primeiro passo, usar um sistema de template. Escolha óbvia, parti para o ERB
Mas as coisas não são tão simples sem a ajuda do Rails, onde tudo é facilitado no uso do ERB.
O programa ficou complicado demais com minhas gambiarras para ter sub-templates.
Alívio
Pesquisei um pouco e descobri o Tenjin.
De forma surpreendente para mim, consegui alterar todo o meu código para substituir o ERB por ele, sem problemas e bem rápido. O código ficou menos macarrônico e até mesmo a conversão dos templates foi fácil.
Tenjin
Ele se gaba de ser o cara mais rápido do velho-oeste. Além de Ruby, tem versões para Python, Perl, PHP e Javascript.
Instalação
O padrão:
gem install tenjin
Marcação
No seu arquivo de template, inclua <?rb COMANDO ?>
para incorporar qualquer comando Ruby. Use ${EXP}
para incluir expressões, que devam ter conteúdo HTML convertido para texto, e com #{EXP}
para incluir expressões que não precisam de conversão.
Código no template
Por exemplo, se temos o arquivo example.rbhtml
:
<ul>
<?rb for i in 10.times ?>
<li>${i}</li>
<?rb end ?>
</ul>
E, no mesmo diretório, o arquivo example.rb
require 'tenjin'
engine = Tenjin::Engine.new
puts engine.render ARGV[0]
Ao executar ruby example.rb example.rbhtml
, temos como saída:
➤ ruby example.rb
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<ul>
Contextos
Um template começa a ser útil quando, a partir de nosso programa, podemos "enviar" parâmetros para que o template seja renderizado.
No método engine
há o parâmetro context
que resolve essa parada. Trata-se de um hash que será reconhecido no template, Vamos passar o limite do loop por parâmetro, alterando nosso arquivo example.rbhtml
para:
<ul>
<?rb for i in @limit.times ?>
<li>${i}</li>
<?rb end ?>
</ul>
E o programa example.rb
para:
require 'tenjin'
engine = Tenjin::Engine.new
context = { limit: Integer(ARGV[1]) }
puts engine.render ARGV[0], context
Ao executar ruby example.rb example.rbhtml 8
(passando como segundo parâmetro o limite):
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
</ul>
E que tal colocar um título? Alteremos o programa para:
require 'tenjin'
engine = Tenjin::Engine.new
context = { limit: Integer(ARGV[1]), title: ARGV[2] }
puts engine.render ARGV[0], context
Modifiquemos o template para:
<h1>${@title}</h1>
<ul>
<?rb for i in @limit.times ?>
<li>${i}</li>
<?rb end ?>
</ul>
E a saída do comando ruby example.rb example.rbhtml 5 Exemplo
será:
<h1>Exemplo</h1>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
Percebem a diferente entre uma variável "local" e uma variável vinda do contexto?
Padrão recomendado
A documentação ainda recomenda declarar, no template, quais variáveis vem pelo contexto. Seguindo esse pattern, nosso template fica dessa forma:
<?rb #@ARGS title, limit ?>
<h1>${@title}</h1>
<ul>
<?rb for i in @limit.times ?>
<li>${i}</li>
<?rb end ?>
</ul>
Incluindo outro template
Nosso template pode ainda incluir outros. Por exemplo, podemos ter um arquivo include_template.rbhtml
:
<p>Nosso limite é o número ${@limit}.</p>
E no nosso template:
<?rb #@ARGS title, limit ?>
<h1>${@title}</h1>
<?rb import 'include_template.rbhtml' ?>
<ul>
<?rb for i in @limit.times ?>
<li>${i}</li>
<?rb end ?>
</ul>
Executando ruby example.rb example.rbhtml 5 'Exemplo com Include'
temos:
<h1>Exemplo com Include</h1>
<p>Nosso limite é o número 5.</p>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
O contexto se aplica ao template incluído.
Concluindo
O guia do usuário tem mais exemplos e lista muitas outras funcionalidades, como suporte a layout:
Além de ter me ajudado a resolver a questão de um sistema de template, o Tenjin tornou meu código mais simples e me fez aprender um pouco mais sobre Ruby.
Deparei-me com um problema ao rodar meus testes, que me deixou um pouco desconfiado do código, pois ao rodar os testes, em geral, os warnings do Ruby estão ativados. E o Tenjin gera muitos!
Ou se desabilita os warnings, não gostei disso pois gosto de deixar o código limpo, ou parte para a desabilitação dos warnings apenas para o Tenjin, como podemos ver aqui:
Ruby verbose mode and how it's broken
Só não estou usando essa opção por não ter estudado com mais afinco, e ser um pouco antiga. Mas eu testei, e funciona.