Historial de cambios http://taquiones.net/historico.html Víctor Moral: Exception::Class Víctor Moral http://taquiones.net/perl/cpan/exception_class.html http://taquiones.net/perl/cpan/exception_class.html cpan perl sar Exception::Class

El módulo Exception::Class permite implementar un control de errores basado en excepciones dentro de un programa ó biblioteca.

Los principios básicos son:

  • Se declara una jerarquía de clases que permiten discernir qué clase de error fatal ocurrió, ó a qué grupo pertenece (mediante el operador isa). Esto presenta dos ventajas:
    • No es necesario importar funciones ni variables en cada uno de los módulos, puesto que las clases creadas lo son globalmente para la aplicación.
    • Al tener orientación a objetos se pueden derivar en clases nuevas, añadiendo ó quitando todo tipo de información, así como cambiando drásticamente la gestión de excepciones; tal vez unas graban algo en algún registro mientras que otras efectúan limpiezas en el sistema, las posibilidades son muchas.
  • Se envuelve mediante eval todas las llamadas a código que es posible que provoque una excepción ó que muera ante una condición dada.
  • Se comprueba inmediatamente después si ha habido una excepción, de qué tipo es y si podemos tratarla nosotros ó debemos lanzarla hacia atrás.
use Exception::Class (
    'MyException',
    'MyException::System' => {
        isa         =>  'MyException',
        description =>  'Operating system related failure',
        },
    'MyException::User' => {
        isa         =>  'MyException',
        description =>  'User interaction realted failure',
        },
    'MyException::User::Keyboard' => {
        isa         =>  'MyException::User',
        description =>  'Bad input from user',
        },
    'MyException::User::Gestures' => {
        isa         =>  'MyException::User',
        description =>  'User did an offensive gesture ',
        },
    );

TRY:
my $result = eval { my_code() };

my $ex;
if ($ex = Exception::Class->caught('MyException')) {
    if (is_recoverable()) {
        next TRY;
    }
    else {
        $ex->rethrow();
    }
}

Y por si el método de capturar excepciones parece complicado, Exception::Class proporciona algo de azucar sintáctico para ello:

TRY:

eval {
    dangerous_code();
    };

# Si el problema es por parte del usuario ...
if (my $ex = MyException::User>caught()) {
    # si es un gesto ofensivo 
    if ($ex->caught('MyException::User::Gestures'))) {
        # damos por terminado el proceso 
        croak "unacceptable user input";
    }
    else {
        # una nueva oportunidad ...
        next TRY;
    }
}
else {
    # es un error de sistema (ó cualquier otra cosa) que no podemos manejar
    $ex->rethrow();
}

Exception::Class dispone de un método heredable llamado caught() que acepta un nombre de clase y devuelve un objeto if la última excepción es de dicha clase ó una subclase de ella. Si no se le pasa ningún parámetro retorna el valor de $@.

Personalizando mensajes

Una de las ventajas de utilizar este tipo de gestión de errores está en la posibilidad de personalizar muchos de sus aspectos. En concreto el mensaje creado tras una excepción no capturada puede modificarse totalmente si definimos algunos métodos en el paquete que declara las clases:

package MyExceptions;

use Exception::Class (
    'MyExceptions' => {
        description => 'Parent class',
        },
    'MyExceptions::FileSystem' => {
        isa         => 'MyExceptions',
        description => 'Fatal error in file system access',
        fields      => [ qw( errno file ) ],
        },
    'MyExceptions::Network' => {
        isa         =>  'MyExceptions',
        description =>  'Fatal error in network access',
        fields      =>  [ qw( host port errno ) ],
        },
);

sub full_message {
    my  $self   =   shift;
    my @ret = ();
    foreach my $m ( $self->__first_line(),
                    $self->__message(),
                    $self->__fields() ) {
        push(@ret$mif $m;
    }

    return join(""@ret);
}

sub __first_line {
    my $self = shift;
    my $program_name = $0 || $self->file();
    my $local_time = localtime($self->time());
    my $package = $self->package();
    my $file = $self->file();
    my $line = $self->line();
    my $pid = $self->pid();
    my $uid = sprintf("uid=%u,gid=%u,euid=%u,egid=%u",
                $self->uid(), $self->gid(), $self->euid(),
                $self->egid());
    $package = sprintf("package %s,"$packageif $package;

    return <<EOF;
${program_name}(${pid}): error fatal in ${package}
  file ${file}, line ${line} at ${local_time}
  with ${uid}
EOF
}

sub __message {
    my $self = shift;

    my $ret = $self->message() || ref($self)->description()
            || 'unknown error';

    return sprintf("\n%s\n\n"$ret);
}

sub __fields {
    my $self = shift;
    my @ret = ( );

    foreach my $f (ref($self)->Fields()) {
        push(@retsprintf("    %s = %s\n"$f$self->{$f}));
    }

    return @ret ? ("  additional info:\n"@ret) : ();
}

En realidad, el método a sobrecargar es full_message, el resto son añadidos para hacer la salida más informativa.

Lo anterior, quizás un tanto excesivo para incluir repetidamente en cada nuevo proyecto, daría como resultado un mensaje similar a éste:

test.pl(3456): error fatal in package main, file test.pl,
    line 3 at lun abr  3 20:40:24 CEST 2006
    with uid=1000,gid=1000,euid=1000,egid=1000

  could not open password file

  additional info:
    errno = 1 operation not allowed

He dividido las funciones que extraen información porque me da más juego para obtenerla de varias fuentes, pero creo que es un ejemplo de lo que podría llegar a hacerse.

Nota: conviene advertir que para sobrecargar los métodos la primera clase que se declara en la línea use Exception::Class no debe tener un atributo isa específico. Exception::Class se encargará de convertirla en la clase madre de todas las demás, y la herencia de Perl del resto para que la sobrecarga funcione.

]]>
HTML::Template http://taquiones.net/perl/cpan/htmltemplate.html http://taquiones.net/perl/cpan/htmltemplate.html cpan fixme perl sar HTML::Template

Es uno de los gestores por excelencia de plantillas para aplicaciones Web, a tenor de su popularidad en aplicaciones y módulos de terceros.

PENDIENTE DE COMPLETAR

]]>
Victor Moral: List::MoreUtils Victor Moral http://taquiones.net/perl/cpan/list_moreutils.html http://taquiones.net/perl/cpan/list_moreutils.html cpan perl List::MoreUtils

El módulo List::MoreUtils es un complemento de List::Util y proporciona un conjunto de funciones probadas y optimizadas para tratar con listas.

El módulo no exporta ningún símbolo por defecto, y es necesario especificar aquellos que queremos usar directamente en nuestro espacio de nombres. Dispone de la posibilidad de importar todos mediante el convenio :all.

Funciones

A continuación la lista de funciones que más utilizo. Si en el futuro esto cambia iré añadiendo documentación y ejemplos.

any BLOCK LIST

Evalúa el código proporcionado sobre cada elemento de la lista (asignando a $_ el valor de cada uno) y retorna:

  • Verdadero: si alguno de los bloques de código devuelve un valor verdadero.
  • Falso: si todos ellos devuelven un valor falso
  • undef: si la lista está vacía
croak "Missing parameters"
    if any { !defined($_) } @parameters;

uniq LIST

Construye una nueva lista eliminando los valores repetidos en la lista proporcionada. El orden no se ve alterado y es el mismo que el que recibe.

En contexto escalar retorna el número de elementos no duplicados en la lista.

my @items = qw(1 1 2 3 6 6 7 9 9 10 11);
my @values = uniq @items;
]]>
Víctor Moral <victor@taquiones.net>: Log::Log4perl Víctor Moral <victor@taquiones.net> http://taquiones.net/perl/cpan/log4perl.html http://taquiones.net/perl/cpan/log4perl.html cpan dev perl sar Log::Log4perl

El módulo Log::Log4perl permite registrar eventos en un programa y es especialmente recomendable por las siguientes características:

PENDIENTE DE COMPLETAR

Plantillas para mensajes

Cuando se utiliza una plantilla (pattern layout) para generar mensajes en un canal (appender), es posible emplear un buen número de expresiones que son sustituídas por valores extraídos del entorno del evento.

La lista inicial es la siguiente:

  • %c Categoría del evento
  • %C Nombre del paquete (ó de la clase) del autor del registro, completamente cualificado.
  • %d %C Fully qualified package (or class) name of the caller %d Current date in yyyy/MM/dd hh:mm:ss format %F File where the logging event occurred %H Hostname (if Sys::Hostname is available) %l Fully qualified name of the calling method followed by %the callers source the file name and line number between parentheses. %L Line number within the file where the %log statement was issued %m The message to be logged %M Method or function where the %logging request was issued %n Newline (OS-independent) %p Priority of the %logging event %P pid of the %current process %r Number of %milliseconds %elapsed from %program start %to logging event %T A %stack %trace %of %functions %called %x %The %topmost %NDC %(see %below) %X{key} %The %entry %'key' %of %the %MDC %(see %below) %% %A %literal %percent %(%) %sign
]]>
Víctor Moral <victor@taquiones.net>: Module::Build Víctor Moral <victor@taquiones.net> http://taquiones.net/perl/cpan/modulebuild.html http://taquiones.net/perl/cpan/modulebuild.html cpan fixme perl sar Module::Build

Herramienta estándar para construir e instalar módulos. El proceso generalmente consiste en:

$ perl ./Build.PL
$ ./Build
$ ./Build test
$ ./Build install

Es decir, lo primero que hay que hacer es invocar a Perl con el archivo Build.PL -base de todo el proyecto-, y luego usar el programa resultante para efectuar el resto de las tareas.

Algunas de estas tareas son:

  • build

    Ejecuta las acciones code y docs. Es la acción predeterminada si se llama sin ningún parámetro más.

  • code

    Crea el directorio blib/ y copia en él todos los archivos .pm y .pod que encuentra bajo el directorio lib/.

  • docs

    Crea documentación ([páginas de manual UNIX][1] y archivos HTML, por ejemplo) para todos los elementos instalables que encuentre bajo blib/, y contengan documentación POD.

  • distmeta

    Actualiza el archivo de descripción del proyecto META.yml

  • distsign

    Añade una firma criptográfica al paquete.

  • dist

    Crea el paquete para distribución final.

  • distdir

  • disttest

    Comprueba que la distribución final sea instalable.

  • clean

    Efectúa limpieza en el directorio de los archivos creados durante la la fase anterior (build), pero no borra el directorio _build/ ni el programa Build.

  • distclean

    Ejecuta las acciones realclean y la acción distcheck, las cuales sí borran el programa Build y el directorio _build.

  • test

    Invoca por orden todos los test regresivos del directorio t/ ó el programa test.pl en el directorio raíz del proyecto.

  • fakeinstall

    Indica lo que haría la acción install pero sin llevarlo a cabo realmente.

  • install

    Instala los módulos de la librería, la documentación y los scripts en los directorios de instalación. Estos pueden definirse mediante el parámetro installdirs.

Algo que conviene advertir es que Module::Build está diseñado para instalar módulos y librerías Perl, y no para efectuar operaciones complejas en la construcción de paquetes (tipo deb y rpm), por lo que querer forzarlo a hacer otras cosas es fuente de frustación contínua y muchísimos sudores. Sin embargo no faltan los trucos y recetas para ello, como se puede ver más abajo.

El archivo Build.PL

Este archivo es un programa Perl con instrucciones para crear un programa Build que ayude a mantener el paquete. Debe usar el módulo Module::Build correctamente y es el punto de partida de todo paquete Perl.

Un ejemplo de un archivo podría ser:

./../../archive/perl/build.pl

#!/usr/bin/perl

use Module::Build;

Module::Build->new(
    module_name         =>  'MyIkiWiki::Tools',
    license             =>  q(gpl),
    dist_version        =>  '0.2',
    dist_author         =>  'Víctor Moral <victor@taquiones.net>',
    installdirs         =>  q(vendor),
    requires            =>  {
        'Perl6::Slurp'      =>  0,
        'List::MoreUtils'   =>  0,
        },
    script_files        =>  [
        'bin/ikitagit'
        ],
)->create_build_script();


donde se puede ver que todo consiste en crear un objeto Module::Build e invocar un método sobre él (create_build_script()) a fin de crear el otro ejecutable.

Los parámetros del ejemplo y algunos otros también importantes son:

  • module_name

    Nombre del módulo principal. Es un atajo que abarca los parámetros dist_name y dist_version_from.

  • dist_name:

    Nombre de la distribución tal y como va a empaquetarse.

  • dist_version_from

    Especifica un archivo del cual obtener la versión. Se analiza buscando una línea donde aparezca la variable VERSION y se evalúa como expresión Perl; el resultado será el número de versión.

  • license

    Nombre de la licencia bajo la que se publica el código. Puede elegirse entre bastantes, aunque la más comunes son perl y gpl.

  • dist_version

    Número de versión que abarca todo el paquete.

  • dist_author

    Nombre y dirección de correo del autor de la distribución. Si son varios acepta una referencia a una lista. Si se omite se utiliza la sección POD denominada =head1 AUTHOR del módulo del cuál se obtiene la versión.

  • installdirs

    Determina los directorios donde se instalará el software. Acepta como valores site, vendor y core.

  • requires

    Indica qué modulos son necesarios tener instalados para usar éste. Generalmente es un hash cuyas claves son los nombres Perl de los módulos y cuyos valores son la versión mínima exigible. Un valor de cero indica que se acepta cualquier versión de dicho módulo.

  • script_files

    Lista de archivos que serán considerados como ejecutables, e instalados en los directorios adecuados.

Instalando en otros directorios

Supongamos que tenemos una aplicación Perl que contiene un enorme número de módulos (ó tal vez sólo unos pocos), y no queremos que se instalen en los directorios del sistema, junto con los (llamémosles) otros módulos Perl generalistas. ¿ Cómo hacemos ésto ?

Disponemos de varios parámetros con los que podemos jugar:

  • installdirs

    Nos permite elegir entre tres conjuntos de directorios donde instalar componentes:

    • core
    • site
    • vendor
  • install_path

    Sirve para cambiar las rutas de componentes concretos; estos que detallo son los correspondientes al conjunto vendor en Debian.

    • lib: /usr/share/perl5
    • arch: /usr/lib/perl5
    • script: /usr/bin
    • bin: /usr/bin
    • bindoc: /usr/share/man/man1
    • libdoc: /usr/share/man/man3
    • binhtml
    • libhtml
  • install_base

    Nos permite cambiar con un sólo parámetro el directorio raíz desde el que se forman los demás. Por ejemplo podemos situar todos comenzando por /home/victor.

  • destdir

    Idóneo para crear paquetes Debian o similares, porque añade un prefijo a todas las rutas, como el de un directorio temporal /tmp/debian.

Cualquiera de estos parámetros puede indicarse en la ejecución del programa Build ó en el archivo Build.PL:

$ perl ./Build --destdir=/tmp/debian
$ perl ./Build --install_path lib=/usr/share/mipaquete/
$ perl ./Build --install_base /home/victor
$ perl ./Build --installdirs=core

Preprocesando módulos

Introducción

Lo descrito aquí es una situación real: estamos construyendo una librería para un programa que posteriormente va a ser empaquetado. No queremos que sus módulos, que sólo usa él, vayan en los directorios genéricos del sistema; preferimos utilizar el del propio paquete.

Y es sencillo de conseguir: basta con poner lo siguiente en cada programa que incluya estos módulos:

use lib qw(/usr/share/myprogram/);
use MyModule;

Para depurar los programas empleamos la siguiente fórmula:

$ perl -Ilib -d myprogram.pl

es decir, anteponemos el directorio lib del directorio de trabajo en la lista de directorios dónde localizar módulos. Y funciona estupendamente hasta que ...

... hasta que creamos un primer paquete de este programa y lo instalamos; luego intentamos algún cambio (de interfaz por ejemplo) e intentamos depurarlo.

Los errores son muy extraños y nada de lo que hagamos en los módulos en desarrollo parece afectar a la depuración del programa. ¿ Qué está ocurriendo ? Pues que no estamos usando la versión de desarrollo en las pruebas, estamos usando la versión instalada en el sistema.

Veamos la secuencia de hechos para un programa (myprogram) que utiliza dos librerías llamadas A y B. Ambas incluyen el párrafo que llama al módulo lib.

  1. Invocamos el depurador de esta forma: $ perl -Ilib -d myprogram
  2. Perl inserta al comienzo del array @INC el directorio lib y éste queda de esta forma:
    1. lib
    2. /etc/perl
    3. /usr/local/share/perl/5.8.8
    4. /usr/lib/perl5
  3. Luego busca el primer módulo, el A, que encuentra en lib/A.pm, lo lee y lo compila, encontrándose una llamada al módulo lib que introduce cambios en el entorno. Ahora @INC tiene ya este aspecto:
    1. /usr/share/myprogram
    2. lib
    3. /etc/perl
    4. /usr/local/share/perl/5.8.8
    5. /usr/lib/perl5
  4. Después se le indica que tiene que cargar el módulo B y ... al buscarlo encuentra primero /usr/share/myprogram/B.pm que lib/B.pm. Y ya está liada :-)

Conclusión: De esto se deduce que no es necesario que los dos módulos modifiquen la ruta de búsqueda de otros módulos para apuntar a la suya propia. Eso debería hacerlo únicamente el programa que les llama.

Pero como un desarrollo grande en Perl puede requerir que varias librerías medio en desarrollo, medio en producción, interaccionen entre sí veamos qué opciones tenemos.

Módulos preprocesados

PENDIENTE DE COMPLETAR

]]>
Víctor Moral <victor@taquiones.net>: Net::SMTP Víctor Moral <victor@taquiones.net> http://taquiones.net/perl/cpan/net_smtp.html http://taquiones.net/perl/cpan/net_smtp.html cpan perl sar Net::SMTP

El módulo Net::SMTP forma parte de la librería libnet, y está incluído en Debian en el paquete perl-modules.

Sirve para interactuar como cliente con un servidor SMTP, y es la mejor manera que he encontrado de reenviar mensajes de correo que he tenido que retocar de alguna forma. Cualquier otro módulo de gestión de mensajes y/ó buzones extrae partes del mensaje (como es lógico) y permite montarlo de nuevo, pero no tengo la seguridad de que conserven el mismo aspecto.

Está orientado a objetos y se usa de una manera muy simple:

  1. Se crea el objeto Net::SMTP y se le proporciona un nombre de servidor (opcional) y algunos parámetros como el puerto, el tiempo límite ó los mensajes de depuración (también opcionales). En ese momento la conexión está abierta y se deben usar los siguientes métodos en el mismo órden en el que los espera un servidor de correo (el protocolo viene descrito en el 821).
  2. Se envía al servidor la información sobre quién envía (MAIL FROM) y quién recibe (RCPT TO) con los métodos (Net::SMTP::mail y Net::SMTP::to).
  3. Se procede a enviar el cuerpo del mensaje en formato 822 utilizando el método Net::SMTP::data ó alguno de los encontrados en Net::Cmd como datasend para, si el servidor acepta los datos, seguir con ...
  4. ... un cierre de sesión con el método Net::SMTP::quit.

Y eso es todo lo que se necesita a un nivel básico para enviar un mensaje de correo directamente a un servidor SMTP. Conviene recordar que las direcciones del emisor y el receptor son los del envoltorio ó sobre del mensaje, y que el mensaje en sí puede contener cabeceras extras que desvíen el mensaje a otros receptores.

En uno de mis programas he tenido que crear un método de clase que me permite enviar a un receptor un correo completo, almacenado en un fichero ó en una lista.

### CLASS METHOD ###
# Usage         : Taquiones::Admin->send_raw_email( $email_text );
# Purpose       : Envía un mensaje de correo en bruto usando una conexión SMTP
#               : con el servidor local
# Returns       : 1 = Todo bien
#               : 0 = Algo ha fallado
# Parameters    : - Usuario receptor del correo (puede incluir el nombre de la
#               :   máquina.
#               : - Texto con el mensaje a enviar (incluyendo cabeceras) en
#               :   formato RFC822.
# Throws        : - Taquiones::X::Email 
# Commments     : El método hace uso de Net::SMTP y de las variables de
#               : configuración `email_server` y 'email_from'.
# See also      : n/a

sub send_raw_email {
    my  $class  =   shift;
    my  $to     =   shift;
    my  $text   =   shift;

    # Abrimos conexión con el servidor de correo 
    my  $smtp   =   Net::SMTP->new(
                        Host    => $config{email_server},
                        Debug   => $config{debug},
                        Timeout => 600,
                    );

    if (not $smtp) {
        Taquiones::X::Email->throw();
    }
    else {
        # añadimos la parte local al receptor (si no tiene)
        $to = sprintf("%s\@localhost"$toif not $to =~ m{@}xms;

        # Añadimos de quién a quién va el correo 
        $smtp->mail($config{email_from});
        $smtp->recipient($to);
    
        # y la parte de datos del mismo 
        my @message_lines = _get_message_in_lines( $text );
        if (not $smtp->data@message_lines )) {
            Taquiones::X::Email->throw(message => 'mensaje NO aceptado',
                                       text => $text);
        }
        else {
            # fin de sesión
            $smtp->quit();
        }
    }

    return 1;
}

Hace referencia a una función exterior que consigue el texto del mensaje en una lista, y que puede implementarse de muchas formas.

]]>
Víctor Moral: Perl6::Form Víctor Moral http://taquiones.net/perl/cpan/perl6_form.html http://taquiones.net/perl/cpan/perl6_form.html cpan fixme perl sar Perl6::Form

El módulo Perl6::Form implementa el operador form de la versión 6 de Perl, para que pueda ser empleado en programas bajo la versión 5.

El autor es Damian Conway y aunque su última versión está fechada en Febrero del año 2004 puedo dar fé de que es perfectamente utilizable en producción.

Los formatos en Perl son un mecanismo para crear plantillas de texto con campos de longitud fija, que se rellenan con valores procedentes de variables.

Para usar un formato se deben intercalar líneas de texto conteniendo las plantillas con valores para rellenar las plantillas.

use Perl6::Form;

my $name = 'Victor Moral';
my $birth_date = '30/03/1969';

my $text = form 'Nombre: {<<<<<<<<<<<<<<}', 
                $name,
                'Fecha.: {||||||||||||||}', 
                $birth_date;

print $text;                

y lo anterior da como resultado:

Nombre: Victor Moral
Fecha.:    30/03/1969

La documentación del módulo es exahustiva y contiene suficientes ejemplos como para resolver casos complejos ó muy raros. También da ideas sobre cuales son sus límites, pero conviene aclarar algunos términos propios que utiliza.

Campos

Para la función form un campo está diseñado con las siguientes convenciones:

  • Cada campo está delimitado por un par de llaves ({}).
  • Dentro de las llaves los caracteres menor qué (<), mayor qué (>), barra vertical (|) y comillas simples (') indican diferentes tipos de de campos monolínea.
  • Igualmente los corchetes de apertura ([) y cierre (]), las letras i mayúsculas (I) y las dobles comillas (") hacen lo propio con campos multilínea.
  • La dirección de los corchetes y los caracteres menor y mayor qué definen el sentido hacia el que fluirá el texto, incluyendo los campos númericos:

    {<<<<<<<<<<<}   El texto irá a la izquierda
    {>>>>>>>>>>>}                     El texto irá a la derecha 
    {>>>>>><<<<<}             El texto irá centrado
    {<<<<<<>>>>>}   El   texto   irá   de   margen   a   margen
    
  • Un carácter igual (=) en uno ó ambos extremos del formato hará que el texto sea centrado verticalmente, según las líneas que ocupe el formato completo.

  • Un carácter subrayado en la misma posición (en cualquiera ó ambos extremos) hará que el texto sea distribuido verticalmente, pero justificándolo hacia abajo.

Referencia

Tipo de campo Campo monolínea Campo multilínea (en bloque)
A la izquierda {<<<<<<<<} {[[[[[[[[}
A la derecha {>>>>>>>>} {]]]]]]]]}
Centrado {>>>><<<<} {]]]][[[[}
Centrado (formato alternativo) {||||||||} {IIIIIIII}
Completo de margen a margen {<<<<>>>>} {&#91;&#91;]]}
Valores numéricos y varios separadores
Literal {''''''''} {""""}
Numérico {>>>>>.<<} {]]]]].[[}
Numérico (estilo Euro) {>>>>>,<<} {]]]]],[[}
Separador de miles con comas {>,>>>,>>>.<<} {],]]],]]].[[}
Separador de miles con espacios {> >>> >>>.<<} {] ]]] ]]].[[}
Separador de miles con coma estilo Euro {>.>>>.>>>,<<} {].]]].]]],[[}
Separador de miles con comas (al estilo del ejército suizo) {>'>>>'>>>,<<} {]']]]']]],[[}
Separador de miles estilo subcontinental {>>,>>,>>>.<<} {]],]],]]].[[}
Números con signo negativo
Numérico con signo {->>>.<<<} {-]]].[[[}
Numérico con post signo {>>>>.<<-} {]]]].[[-}
Numérico con signo negativo entre paréntesis {(>>>.<<)} {(]]].[[)}
Símbolo monetario como prefijo {$>>>.<<<} {$]]].[[[}
Símbolo monetario como sufijo {>>>.<<<DM} {]]].[[[DM}
Símbolo monetario entre medias {>>>$<< Esc} {]]]$[[ Esc}
En vertical
Justificado a la izquierda y centrado {=<<<<<<=} {=[[[[[[=}
Idem a la derecha {=>>>>>>=} {=]]]]]]=}
Cifra separada con miles (euro) {>.>>>.>>>,<<=} {].]]].]]],[[=}
Justificado a la izquierda y abajo {_<<<<<<_} {_[[[[[[_}
Idem a la derecha y abajo {_>>>>>>_} {_]]]]]]_}

Glosario

  • Format (plantilla) Es un texto que se emplea como plantilla para crear texto. Puede contener cero ó más campos, normalmente acompañado de caracteres literales y espacios en blanco entre ellos.
  • Text (texto) Es el texto creado mediante el reemplazo de los campos de un formato con los valores especificados. El texto que retorna form es un ejemplo de ello.
  • Field (campo) Una zona de longitud fija dentro de una plantilla, dentro de la cual se introducirán los datos.
  • Data (datos) Un texto ó un valor numérico (ó una lista de tales valores) que se interpolan dentro de una plantilla para rellenar un campo particular.
  • Single-line field (campo monolínea) Un campo cuya interpolación con el valor correspondiente no ocupará más de una línea.
  • Block field (campo en bloque) Es un campo cuya interpolación con el valor correspondiente ocupará una serie de líneas - tantas como necesite - y terminará produciendo un bloque de texto.
  • Text block (bloque de texto) Lo forma una columna de texto formada por líneas separadas por caracteres de nueva línea. Es el resultado de interpolar un valor demasiado grande para una única línea en un campo en bloque.
  • Column (columna) El espacio necesario, en el dispositivo de salida, para representar un carácter. Éste ocupará una única columna en la mayor parte de los casos, con la obvia excepción de los caracteres de doble ancho CJK.

Enlaces

]]>
Pod::Coverage y Test::Pod::Coverage http://taquiones.net/perl/cpan/podcoverage.html http://taquiones.net/perl/cpan/podcoverage.html cpan perl Pod::Coverage

El módulo Pod::Coverage analiza un fuente de Perl buscando documentación referente a métodos y funciones.

Concretamente intenta localizar bloques =head (de nivel dos en adelante) y elementos de listas =item que hace referencia a una función.

En el siguiente ejemplo:

package Foo;

=item foo

The foo subroutine ...

=cut 

sub foo { 1; }

sub bar { 1; }

1;
__END__

La cobertura sería del 50% (0.5) puesto que únicamente aparece una referencia a la función foo.

Los parámetros en la creación de un objeto Pod::Coverage son interesantes sobre todo si se utilizan mecanismos de herencia:

  • package: nombre del paquete a analizar.
  • private: lista de expresiones regulares que indican qué símbolos deben ser considerados como privados y para los que no es necesario buscar documentación. La lista es completa e incluye todas las operaciones con objetos Tie.
  • also_private: lista de elementos (expresiones regulares) a añadir a la anterior
  • trustme: lista como la anterior que determina qué símbolos debe asumir el módulo como correctamente documentados aunque no encuentre nada sobre ellos.
  • pod_from: ruta opcional al archivo del cual obtener la documentación; si se omite se utiliza Pod::Find.

En realidad este módulo es verdaderamente útil cuando se utiliza la versión para construir tests, de la que hablaremos a continuación.

Test::Pod::Coverage

El módulo Test::Pod::Coverage proporciona mecanismos para testear la cobertura de la documentación sobre los módulos fuente.

Un ejemplo de uso es:

use Test::Pod::Coverage tests=>1;
pod_coverage_ok( "Foo::Bar""Foo::Bar is covered" );

Los métodos principales son los siguientes:

podcoverageok( $module, [ $params, ] $msg)

Comprueba la cobertura documental del módulo indicado. Se le puede proporcionar un referencia a un hash con parámetros para Pod::Coverage y, por supuesto, un texto para identificar el test.

Entre los parámetros existe uno especial llamado coverage_class que determina qué clase utilizar para trabajar; el valor predeterminado es, obviamente, Pod::Coverage.

allpodcoverage_ok( [$parms, ] $msg )

Igual que la anterior, pero utilizando todos los módulos de la distribución. Para buscarlos emplea el siguiente método.

all_modules( [@dirs] )

Localiza todos los módulos en la lista de directorios proporcionada incluyendo la búsqueda en subdirectorios.

Si no se le proporciona ningún parámetro emplea, por este orden, los siguientes:

  1. blib
  2. lib

Los módulos que retorna están en nomenclatura Perl, al estilo de Foo::Bar y no como Foo/Bar.pm.

Herencia y cobertura

Supongamos que tenemos una clase padre que proporciona tres métodos heredables: new, init y dump, los cuales puede o no estar correctamente documentados por lo que escribimos un test como el siguiente:

pod_coverage_ok( "MyPackage" );

en el cual no se permiten excepciones, si la cobertura no es completa el test falla.

Ahora tenemos tres módulos derivados de él que crean sus correspondientes objetos mediante herencia, y que no van a documentar estos métodos porque ni siquiera figuran físicamente en ellos.

Podemos añadir estas líneas al test anterior:

my $trustme = { 
    trustme => [qr/^(new|init|dump)$/] 
    };

pod_coverage_ok( "MyPackage::Type1",    $trustme );
pod_coverage_ok( "MyPackage::Null",     $trustme );
pod_coverage_ok( "MyPackage::Complex",  $trustme );

y nos aseguramos de que los módulos fallen por sí mismos, y no por lo que hereden de otros.

Cobertura automática

Si queremos incluir un test de cobertura de documentación en nuestros módulos, pero que no sean un requisito imprescindible para instalarlos el autor recomienda utilizar el siguiente test:

use Test::More;
eval "use Test::Pod::Coverage";
plan skip_all => "Test::Pod::Coverage required for testing pod coverage" if $@;

all_pod_coverage_ok();

Que sólo actuará en caso de que exista este módulo presintalado y que, de paso, encontrará todos los módulos de la distribución y efectuará un test sobre ellos.

]]>
Smart::Comments http://taquiones.net/perl/cpan/smartcomments.html http://taquiones.net/perl/cpan/smartcomments.html cpan fixme perl sar Smart::Comments

La librería de comentarios inteligentes es de tipo filtro de código y permite incluir directivas de depuración y trazado en los comentarios de un programa, de manera que dota a estos de inteligencia en más de un sentido según palabras del autor Damian Conway.

Lo cierto es que dado que se trata de comentarios en un fuente, conservarlos es siempre algo inteligente. ;-)

Uso

./../../archive/perl/smart_comments.pl

    1  #!/usr/bin/perl
    2  
    3  use strict;
    4  use Carp;
    5  
    6  # use Smart::Comments;
    7  
    8  # Valores iniciales
    9  our $text = 'Hola';
   10  our $dinero = 3000.03;
   11  
   12  ### Texto: $text
   13  ### Dinero: $dinero
   14  
   15  $text =~ s/Hola/Adios/;
   16  $dinero /= 24;
   17  
   18  ### Texto: $text
   19  ### Dinero: $dinero
   20  
   21  exit (0);

Descripción

El módulo actúa sobre cierto tipo de comentarios, generalmente aquellos que comienzan por tres caracteres almohadilla (###) consecutivos. El resto de la línea es interpretado por el módulo y proporciona una determina información a la salida estándar de errores, ó efectúa alguna acción dramática como detener el programa si no se cumplen determinadas condiciones (Assert).

Para cambiar el nivel de inteligencia de los comentarios se puede cargar el módulo y pasarle uno o más marcadores sobre los que queremos que actúe:

use Smart::Comments '####''#####';

Esto es útil para, por ejemplo, seleccionar barras de progreso de comentarios de depuración, en caso de que deseemos mostrar sólo las primeras.

##### Comenzamos aquí ...

for (@values) {     ### Progress: 0 .. 100
    do_stuff();
}

Depurando mediante comentarios

El módulo acepta los siguientes casos:

Label : Expression

Label es cualquier texto hasta los dos puntos y expression es cualquier expresión Perl, incluyendo una simple variable.

Cuando se activa se muestra la etiqueta seguida del resultado de la evaluación de la expresión.

    ### Expected: 2 * $prediction
    ###      Got: $result

Expression

Similar al anterior sólo que no incluye un texto, por lo que se muestra la expresión literalmente, el carácter dos puntos y el resultado de la expresión cuando este comentario está activo.

Text ...

Un texto finalizado por tres puntos y que sirve como comentario de depuración (ó trazado) ya que es sólo eso lo que se va a mostrar.

Eso sí, dentro de este texto es posible utilizar dos secuencias especiales, encerradas entre símbolos de mayor y menor y que tienen sinónimos

  • now | time | when

    Expande a la hora y fecha en el momento de ejecutarse

  • here | line | loc | place | where

    Expande al nombre del módulo Perl y al número de línea

Ejemplo para ilustrar esto:

 ### [<now>] Acquiring data...

 ### Acquiring data at <loc>...

que expande a:

 ### [Fri Nov 18 15:11:15 EST 2005] Acquiring data...

 ### Acquiring data at "demo.pl", line 7...

naturalmente pueden utilizarse las dos. Y eso resulta también bastante útil para procesos por lotes donde se quiera disponer de un trazado de ejecución automágico.

Chequeos y aseveraciones

También es posible utilizar la expresión Perl como comprobante (ó garante) de que hemos alcanzado determinado punto en la ejecución de un programa. Smart::Comments dispone de varias directivas para este fin.

Chequeos

Utilizando alguna de las siguientes palabras clave: check, verify ó confirm creamos una comprobación automática sobre la expresión en contexto booleano. Si el valor resultante es falso (esto es, que falla) se emite un aviso (empleando warn) en el que se informa de la expresión que ha fallado, el por qué y los diferentes valores implicados en el momento del fallo.

Un ejemplo sería:

 ### require: $min < $result && $result < $max

que daría como resultado un aviso de este tipo:

    ### $min < $result && $result < $max was not true at demo.pl line 86.
    ###     $min was: 7
    ###     $result was: 1000004
    ###     $max was: 99

Aseveraciones

Es el mismo mecanismo que para los chequeos, pero emitiendo una excepción en el programa y parando la ejecución si nadie la atrapa. Es decir, utiliza die en lugar de warn.

Las palabras clave en este caso son: require, assert, ensure e insist.

Barras de progreso

PENDIENTE DE COMPLETAR

Experiencias

Uso en desarrollo

Una de las primeas cosas a advertir sobre este módulo es que actúa únicamente sobre aquél que lo carga. No es, como tal vez pueda entenderse, una especie de infección viral que actuá sobre todos y cada uno de los módulos que conforman un programa.

Sí lo hace con el módulo principal si cargamos esta librería en el intérprete Perl mediante el parámetro -M, pero nada más; no recorre el resto de los módulos dotando de inteligencia a los comentarios allí donde los encuentra.

Esto plantea un problema cuando queremos utilizarlo en una gran aplicación, compuesta de muchas librerías de nuestra manufactura, y donde necesitamos que esté activo en varias de ellas. A mí se me ha ocurrido una primera solución parcial para cuando necesitamos esta característica en un paquete concreto:

    1  package MyPackage;
    2  
    3  BEGIN {
    4      (my $package = __PACKAGE__) =~ s{::}{_}g;
    5  
    6      if (defined($ENV{$package}) and 
    7          $ENV{$package}) eq 'Smart::Comments') {
    8          require "Smart::Comments";
    9          Smart::Comments->import();
   10      }
   11  }

Si se añade el código anterior a cualquier paquete, éste primero transformará el nombre del mismo cambiándo todos los separadores Perl :: por subrayados, y luego comprobará que exista una variable de entorno con ese nombre y que contenga el valor Smart::Comments; en ese caso cargará la librería a mano y los comentarios inteligentes harán su aparición durante el resto del proceso y para este paquete en concreto.

Una solución más elegante ...

Es la que proporciona un usuario de perlmonks llamado diotalevi en este hilo.

use if $ENV{DEBUG}, 'Smart::Comments';

Es decir, hacer uso de un módulo Perl llamado if, del que no tenía idea de su existencia y que parece bastante prometedor (a pesar de lo farragoso de su código). Y aunque desarrollado como módulo, entra en la categoría de pragmas del lenguaje.

Podría quedar más o menos así si mezclamos las dos ideas:

BEGIN {
    (my $package = __PACKAGE__) =~ s{::}{_}g;

    use if '$ENV{$package} =~ /Smart::Comments/''Smart::Comments';
}

Me gusta más porque me permite:

  • Dejar libre la palabra DEBUG para la aplicación principal.
  • Especificar qué librerías exactamente quiero tener con comentarios inteligentes.
  • Pasar más información a una librería en concreto (advertir la expresión regular en la condición) y no limitarla sólo a esta característica.

Su síntaxis también cuenta

Los comentarios inteligentes son expresiones Perl y como tal pueden fallar estrepitosamente si están mal escritas. Conviene hacer una comprobación de síntaxis (perl -cw) con esta librería activa para descartar fallos en la ejecución; no hacerlo complica bastante la tarea de depuración porque ya son dos frentes abiertos: tú código y los comentarios.

]]>
Víctor Moral <victor@taquiones.net>: Sysadmin::Install Víctor Moral <victor@taquiones.net> http://taquiones.net/perl/cpan/sysadmin_install.html http://taquiones.net/perl/cpan/sysadmin_install.html cpan dev perl sar Sysadmin::Install

El módulo Sysadmin::Install facilita las tareas de administración en programas Perl.

PENDIENTE DE COMPLETAR

]]>