Como criar um Módulo Perl para reutilização de código?
Você talvez esteja criando cada vez mais scripts para os seus sistemas, os quais precisam usar um mesmo conjunto de funções.
Você já dominou a antiga técnica de copiar-e-colar, mas você não está satisfeito com o resultado.
Você provavelmente conhece muitos módulos Perl que lhe permitem usar suas funções e você também quer criar um.
Contudo, você não sabe como criar tal módulo.
O módulo
package My::Math; use strict; use warnings; use Exporter qw(import); our @EXPORT_OK = qw(add multiply); sub add { my ($x, $y) = @_; return $x + $y; } sub multiply { my ($x, $y) = @_; return $x * $y; } 1;
Grave isto em algumdir/lib/My/Math.pm (ou algumdir\lib\My\Math.pm no Windows).
O Script
#!/usr/bin/perl use strict; use warnings; use My::Math qw(add); print add(19, 23);
Grave isto em algumdir/bin/app.pl (ou algumdir\bin\app.pl no Windows).
Agora execute perl algumdir/bin/app.pl. (ou algumdir\bin\app.pl no Windows).
Será impresso um erro como este:
Can't locate My/Math.pm in @INC (@INC contains: ... ... ... BEGIN failed--compilation aborted at somedir/bin/app.pl line 9.
Qual é o problema?
No script carregamos o módulo usando a palavra chave use. Especificamente através da linha com o use My::Math qw(add);. Isto vasculha os diretórios listados na variável padrão@INC, procurando um subdiretório chamado My e naquele subdiretório um arquivo chamado Math.pm
O problema é que o seu arquivo .pm não está em nenhum dos subdiretórios padrão do perl: não está em nenhum dos diretórios listados em @INC.
Você pode mover seu módulo, ou mudar o conteúdo da sua variável @INC.
A primeira sugestão pode ser problemática, especialmente em sistemas onde existe uma forte separação entre o administrador do sistema e o usuário. Por exemplo, em sistemas UNIX e Linux apenas o usuário "root" (o administrador) tem permissão de escrita nesses diretórios. Então, em geral é mais fácil e mais correto modificar a variável @INC.
Mude @INC a partir da linha de comando
Antes de tentarmos carregar o módulo, temos que nos certificar de que o módulo está na variável @INC.
Tente isto:
perl -Ialgumdir/lib/ algumdir/bin/app.pl.
Isso vai imprimir a resposta: 42.
Nesse caso, a chave -I do perl nos ajudou a adicionar o caminho do diretório à @INC.
Mude @INC de dentro do script
Porque sabemos que o diretório "My", que contém nosso módulo, está em um lugar fixo relativo ao script, temos outra possibilidade de mudar o script:
#!/usr/bin/perl use strict; use warnings; use File::Basename qw(dirname); use Cwd qw(abs_path); use lib dirname(dirname abs_path $0) . '/lib'; use My::Math qw(add); print add(19, 23);
e execute novamente com este comando:
perl algumdir/bin/app.pl.
Agora funciona.
Expliquemos a mudança:
Como a variável @INC modificada aponta para um diretório relativo
Esta linha: use lib dirname(dirname abs_path $0) . '/lib'; adiciona o diretório lib (relativo ao script) ao início da @INC.
$0 contém o nome do script atual. abs_path() de Cwd retorna o caminho absoluto do script.
Dado um caminho para um arquivo ou diretório, a chamada para dirname() de File::Basename retorna uma parte do caminho referente ao diretório, retirando apenas a parte relativa ao nome do arquivo.
Em nosso caso, $0 contém app.pl
abs_path($0) retorna .../algumdir/bin/app.pl
dirname(abs_path $0) retorna .../algumdir/bin
dirname(dirname abs_path $0) retorna .../algumdir
Este é o diretório raiz do nosso projeto. dirname(dirname abs_path $0) . '/lib' então aponta para .../algumdir/lib
Então o que temos aí é basicamente
use lib '.../algumdir/lib';
mas sem deixar escrito no código o caminho real da árvore inteira.
Todo trabalho dessa chamada é adicionar o '.../algumdir/lib' como o primeiro elemento de @INC.
Uma vez que isto esteja feito, a chamada subsequente para use My::Math qw(add); vai encontrar o diretório 'My' em '.../algumdir/lib' e o 'Math.pm' em '.../algumdir/lib/My'.
A vantagem desta solução é que o usuário do script não tem que lembrar de colocar o -I... na linha de comando.
Existem outras formas de modificar @INC para você usar em outras situações.
Explicando o use
Como eu escrevi anteriormente, a chamada para o use vai olhar para o diretório My e o arquivo Math.pm dentro dele.
O primeiro que ele encontra será carregado na memória e a função
import de My::Math será chamada com os parâmetros que seguem o nome
do módulo. Em nosso caso import( qw(add) ) que é o mesmo que chamar
import( 'add' ).
Não há muito mais a explicar no script. Depois da declaração use
terminar sua chamada para a função import, podemos então chamar a
recém importada função add do módulo My::Math. Como se eu tivesse
declarado a função no mesmo script.
O que é mais interessante é ver as partes do módulo.
Um módulo em Perl é um escopo no arquivo correspondendo ao escopo daquele módulo.
A palavra chave package cria tal escopo. Um nome de módulo My::Math
se refere ao um arquivo My/Math.pm. Um nome de módulo como A::B::C se refere a
um arquivo A/B/C.pm localizado em algum lugar nos diretórios listados em @INC.
Como você lembra, a declaração use My::Math qw(add); no script vai
carregar o módulo e depois chamar a função import. A maioria das
pessoas não quer implementar sua própria função import, então elas carregam o
módulo Exporter e importam a função 'import'.
Sim, é um pouco confuso. O importante a lembrar é que o Exporter lhe fornece o
import.
Esta função import vai procurar o array @EXPORT_OK dentro do módulo
que você está escrevendo e preparar as funções nele contidas para a importação
sob demanda.
Ok, talvez eu precise esclarecer:
O módulo "exporta" funções e o script as "importa".
A última coisa que preciso mencionar é o 1; no final do módulo.
Basicamente a declaração use está executando o módulo e nele ela precisa ver
algum tipo de declaração final com algum valor verdadeiro. Pode ser qualquer
coisa. Alguns põe 42;, outros, os bem-humorados colocam
"FALSE". Afinal de contas, toda string não vazia é considerada verdadeira em Perl.
Isto confunde quase todo mundo. Existem ainda aqueles que citam poemas!
"As últimas palavras."
Na real isto é legal, contudo pode confundir alguns à primeira vista.
Existem também duas funções no módulo. Decidimos exportar ambas, mas o usuário
(autor do script) quis importar apenas uma das subrotinas.
Com exceção de algumas linhas que expliquei acima, é bastante simples criar um
módulo Perl.
Claro que existem outras coisas que você poderá aprender sobre módulos que
aparecerão em outros artigos, mas não há nada que lhe impeça de transferir
algumas funções comuns para um módulo.
Talvez mais um aviso sobre como chamar seu módulo:
É altamente recomendado usar letras maiúsculas como a primeira letra de toda
palavra do nome do módulo e letras minúsculas no restante.
Também é recomendável usar um escopo com vários níveis de profundidade.
Se você trabalha em uma empresa chamada Abc, eu recomendaria que todos os
módulos fossem precedidos pelo nome de escopo Abc::. Se dentro da empresa
existe um projeto chamado Xyz, então todos os módulos a ele pertencentes
deveriam estar em Abc::Xyz::.
Então se você tem um módulo que lida com configurações, você poderia chamar o
pacote de Abc::Xyz::Config que faz referência ao arquivo
.../projectdir/lib/Abc/Xyz/Config.pm.
Por favor evite chama-lo apenas de Config.pm. Isso vai confundir o Perl (que
vem com seu próprio Config.pm) e você.
A explicação do script
A explicação do módulo
Conclusão
Nomeando módulos
Published on 2013-05-12