Paulo Henrique Rodrigues Pinheiro

Um blog sobre programação para programadores!


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:

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:

Tenjin Users Guide

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.