Já não é novidade que o RSS é uma das ferramentas de troca de informação mais relevantes na “Web 2.0”. Há até quem diga que o RSS é o “Unix Pipe” da Internet. Ultimamente tenho me concentrado muito em usar essa ferramenta para construir aplicações e sites, e o Perl é o meu fiel companheiro nessas atividades.
Uma aplicação muito importante que usa o RSS é o “Planeta”. Os planetas funcionam agregando diversos blogs relacionados a um tema. Eles são muito úteis para pessoas como eu que acompanham algumas dezenas de feeds. Se você nunca viu um planeta, pode acessar o http://planeta.gnulinuxbrasil.org/ ou o http://planeta.debianbrasil.org/ .
O que um agregador faz é obter o documento RSS de cada um dos blogs, mesclar os artigos de cada um desses documentos, reordenar, filtrar e fazer outras transformações para então exibir de uma maneira específica. Nesse artigo vamos construir uma aplicação planeta, mas vamos antes começar pelo princípio.
Preparando o ambiente
Se você está em uma máquina Linux ou qualquer outro Unix-like, você já tem 99% de chance de ter o Perl instalado, para saber isso, é só abrir na interface de linha de comando e digitar:
perl -v
Se aparecer a mensagem dizendo a versão do Perl instalada, então ótimo, senão você precisa instalar o Perl. Para Windows eu recomendo usar o http://strawberryperl.com/, para máquinas Unix, recomendo falar com o administrador da máquina e pedir para ele instalar. No caso de ambientes de hospedagem, pergunte se ele tem hospedagem Perl, preferencialmente com mod_perl ou FCGI, você também precisa que o provedor instale módulos Perl que estão na CPAN. No caso de o provedor se recusar a instalar módulos, você pode instalar os módulos localmente no seu espaço de hospedagem, mas isso fica como assunto para outro artigo.
Antes de prosseguir, é importante entender que existe uma cultura de compartilhamento de código na comunidade Perl que é muito levada a sério. Diferente de outras linguagens, no mundo Perl não é uma vantagem não depender de módulos externos, muito pelo contrário. Nenhum programador Perl de verdade vai deixar de usar um módulo da CPAN em favor de uma implementação própria só para “eliminar dependências externas”. Nas palavras de Audrey Tang (que já esteve no Brasil para um CONISLI), “A minha linguagem é a CPAN, o resto é sintaxe”. Se você não conhece a CPAN, entre em http://search.cpan.org/ e procure por soluções para problemas que você tem com frequência. Há uma grande chance que alguém já tenha resolvido esse problema e que a solução está empacotada lá.
Dito isso, vamos à nossa primeira dependência, o módulo XML::Feed. Se você está em uma máquina Debian, Ubuntu ou outros derivados de Debian, você pode fazer (como root):
apt-get install libxml-feed-perl
Aliás, fica já a nota de que em máquinas Debian ou derivados, muitos módulos da CPAN já estão disponíveis e seguem sempre a lógica de ter o prefixo “lib”, o sufixo “-perl” e o nome do módulo em minúsculas e trocando o “::” por “-“, então “XML::Feed” vira “libxml-feed-perl”.
Em outros sistemas operacionais ou distribuições de Linux você pode executar:
cpan install XML::Feed
Se você estiver executando esse comando pela primeira vez, ele vai passar por uma rotina de configuração. Se for esse o caso, preencha todas as perguntas que ele fizer que no final ele vai instalar o módulo.
Com o módulo instalado, abra o seu editor de programação favorito (no meu caso o emacs, mas também vale o vi, gedit, kate, eclipse, text-mate, Padre, e muitos outros) e vamos criar um esboço do nosso agregador.
Iniciando o agregador
Antes de qualquer coisa, coloque no seu arquivo
use strict;
use warnings;
Isso vai impor algumas regras de estilo saudáveis e irá também te dar algumas mensagens de aviso no caso de alguma coisa estar errado, costumamos chamar isso de “o mantra do programador Perl”.
Em seguida podemos carregar o módulo que vamos utilizar.
use XML::Feed;
Agora precisamos de uma lista de feeds. Poderíamos colocar isso em um arquivo de configurações, mas para simplificar, vamos colocar no código mesmo.
my @feed_uris = qw(
https://imasters.com.br/feed/
http://planeta.gnulinuxbrasil.org/atom.xml
http://planeta.debianbrasil.org/atom.xml
);Você provavelmente está estranhando essa linha. O “qw” em Perl significa “Quote Words”. Ele vai gerar uma lista de strings sem que você precise ficar abrindo e fechando aspas e sem usar vírgulas, uma conveniência sintática muito legal.
Agora que temos a lista de endereços, precisamos obter os feeds. Se você olhar a documentação do módulo XML::Feed (na cpan você pode ver a documentação de todos os módulos, ou através do comando perldoc depois do módulo instalado na sua máquina), você vai ver que o método “parse” pode receber como argumento um objeto URI, e nesse caso ele irá conectar a esse endereço e obter o feed, que é o que a gente precisa. Então chegamos à nossa segunda dependência, o módulo URI, que você pode instalar do mesmo jeito. Vamos então carregar o módulo.
use URI;
Agora a coisa está começando a ficar interessante. Precisamos obter os feeds a partir dos endereços, como podemos fazer isso? Há mais de um jeito de fazer (There Is More Than One Way To Do It, no original), você já deve estar imaginando um loop “for”, “while” ou até um “foreach”. Mas vamos usar uma coisa muito mais legal, o “map”.
my @feeds =
map { XML::Feed->parse(URI->new($_)) }
@feed_uris;O map em Perl funciona executando o bloco de código uma vez para cada elemento do array de entrada, definindo aquele valor na variável “$_”, que em Perl chamamos de “it” ou “a coisa”, e atribuindo ao array de saída uma lista com o resultado de cada execução do bloco. O código equivalente usando um “for” seria mais ou menos o seguinte:
my @feeds;
for my $feed_uri (@feed_uris) {
push @feeds, XML::Feed->parse(URI->new($feed_uri));
}Uma vez que você conheça o “map”, a opção de cima é bem mais legível que a opção de baixo, e bem mais curta também.
Agora que temos os feeds, podemos obter as entradas de cada um deles, para isso vamos usar o map de novo.
my @entries =
map { $_->entries }
@feeds;E com isso você entende que o map pode retornar um array de um tamanho diferente do tamanho de entrada, temos agora um array com todas as entradas de todos os feeds. Mas esta lista não está na ordem que queremos, ela está ordenada por feed, e queremos fazer uma reordenação pela data do artigo, é aí que o Perl nos ajuda de novo, com o operador “sort”:
my @sorted =
sort { $b->issued <=> $a->issued }
@entries;O operador sort funciona utilizando o bloco de código como rotina de comparação, declarando duas variáveis $a e $b, e utilizando o resultado desse bloco para fazer a ordenação da lista. Nesse caso, estamos retornando o resultado da comparação entre os atributos “issued” de cada entrada. O operador espaçonave “<=>” faz a comparação numérica de dois valores, em oposição ao operador “cmp”, que faz a comparação de strings. Nesse momento você deve estar reclamando: “Mas a data não é um número!” e, de fato, não é. A questão é que o Perl suporta overload de operadores, e o XML::Feed utiliza o módulo DateTime que faz esse overload, provendo a semântica desejada. Mas isso é assunto para outro artigo, vamos voltar ao agregador.
O problema é que infelizmente nem todo post é consistente, as vezes não tem a data lá, então para isso precisamos ter um fallback, que é, se não tem data de publicação, pegar a data de modificação, e se não tiver nenhum, vamos jogar ele lá no final. Para isso podemos usar os operadores de curto-circuito, que fazem com que toda essa lógica se torne:
my @sorted =
sort { ($b->issued || $b->modified || 0)
<=>
($a->issued || $a->modified || 0) }
@entries;Mas não precisamos ter as duas variáveis @entries e @sorted, e sim a da lista já ordenada. Nesse caso podemos “aninhar” o operador sort com o operador map, e temos o seguinte código:
my @entries =
sort { ($b->issued || $b->modified || 0)
<=>
($a->issued || $a->modified || 0) }
map { $_->entries }
@feeds;Mostrando o resultado
Em outros artigos vamos falar sobre o Catalyst e o Template Toolkit, que são ferramentas muito importantes para o desenvolvedor web. Para este artigo, vamos usar o bom e velho “print”. Se você estiver em um servidor de hospedagem, salve o arquivo como “.cgi”, inclua “#!/usr/bin/perl” (ou o que o seu provedor indicar) como a primeira linha do arquivo, e adicione as linhas:
use CGI qw(:standard);
print header('text/plain');
Vamos então listar os títulos dos blogs que estamos agregando, usando o “join” com o “map”:
print "Blogs:\n\t";
print
join "\n\t",
map { $_->title }
@feeds;
print "\n\n";O operador “join” irá retornar uma string concatenando todos os elementos da lista de entrada separados pelo primeiro argumento, nesse caso o retorno do “map”, que retorna o título de cada feed. Vamos fazer o mesmo agora para os artigos:
print "Artigos:\n\t";
print
join "\n\t",
map { $_->title }
@entries;
print "\n\n";Filtrando entradas
Nosso agregador já funciona, só que ele mostra artigos demais. Como exercício final desse artigo vamos excluir as entradas com mais de um mês de existência. Para fazer isso vamos em primeiro lugar descobrir quando foi um mês atrás usando os módulos DateTime e DateTime::Duration.
use DateTime;
use DateTime::Duration;
my $hoje = DateTime->now();
my $mes = DateTime::Duration->new(months => 1);
my $um_mes_atras = $hoje - $mes;Aqui novamente o overload de operadores entra em ação – obter um mês atrás foi simplesmente uma subtração. Agora falta a gente filtrar os posts mais velhos. Para isso vamos usar o operador “grep”.
my @recentes =
grep { $um_mes_atras < ($_->issued || $_->modified || 0) }
@entries;E mais uma vez o overload de operadores salva o dia junto com os operadores de curto-circuito. Considerando que nunca vamos precisar dos posts mais velhos que um mês, podemos aninhar esse grep já no princípio.
my @entries =
sort { ($b->issued || $b->modified || 0)
<=>
($a->issued || $a->modified || 0) }
grep { $um_mes_atras < ($_->issued || $_->modified || 0) }
map { $_->entries }
@feeds;Código Final
Bom. Depois de todas as variações chegamos à versão final, quero dizer, à primeira versão, do nosso agregador. Espero que vocês tenham gostado e que esse seja só o primeiro de muitos artigos aqui no iMasters. Abaixo, então, o código fonte:
#!/usr/bin/perl
use strict;
use warnings;
use XML::Feed;
use URI;
use DateTime;
use DateTime::Duration;
use CGI qw(:standard);
print header('text/plain');
my @feed_uris = qw(
https://imasters.com.br/feed/
http://planeta.gnulinuxbrasil.org/atom.xml
http://planeta.debianbrasil.org/atom.xml
);
my @feeds =
map { XML::Feed->parse(URI->new($_)) }
@feed_uris;
my $um_mes_atras =
DateTime->now() - DateTime::Duration->new(months => 1);
my @entries =
sort { ($b->issued || $b->modified || 0)
<=>
($a->issued || $a->modified || 0) }
grep { $um_mes_atras
<
($_->issued || $_->modified || 0) }
map { $_->entries }
@feeds;
print "Blogs:\n\t";
print
join "\n\t",
map { $_->title }
@feeds;
print "\n\n";
print "Artigos:\n\t";
print
join "\n\t",
map { $_->title }
@entries;
print "\n\n";






