Data::Printer - o pretty-printer do Perl

Breno G. de Oliveira
Publicado em 01/09/2011

Data::Printer - o pretty-printer do Perl

Todo programador Perl cedo ou tarde esbarra no Data::Dumper. Trata-se de um módulo muito bacana que faz parte do core e é capaz de exibir estruturas de dados complexas como strings. O problema é que o Data::Dumper (e variações, como o Data::Dump) são projetadas para criar strings que possam ser transformadas novamente em código através de um eval. Essa restrição, embora essencial para serialização de dados, torna-se um problema quando nosso objetivo não é serialização e sim a inspeção visual do conteúdo.

Veja uma comparação visual rápida entre o Data::Dumper e o Data::Printer:

A primeira coisa que notamos é a saída colorida, índices nos arrays e algumas informações a mais em expressões regulares. Mas o Data::Printer oferece muito mais que isso. Que tal depurarmos um objeto?

E se a sua estrutura de dados estiver associada a outros elementos?

A ideia por trás do Data::Printer é a de que a grande maioria dos desenvolvedores (pelo menos os que eu conheço) usa ferramentas como essa principalmente para ver o que está acontecendo dentro de suas variáveis e objetos, e não para serializar dados pra dentro e fora do Perl. Por isso, seu foco não é serialização, e sim exibir variáveis e objetos do Perl na tela, devidamente formatados (para serem inspecionados por um humano). O Data::Printer é de certa forma similar ao "awesome_print" do Ruby, mas possui muito mais opções de personalização e algumas features bacanas adaptadas dos "dumpers" tradicionais do Perl.

Por exemplo, a função de exibição é a "p()", um mnemônico simples e fácil de lembrar. Mas se você está acostumado a chamar a função "Dumper()" tradicional, mudar o nome da função é muito simples:

  use Data::Printer alias => 'Dumper';

  Dumper( %foo );  # pronto, problema resolvido!

O Data::Printer vem com configurações padrão bastante coerentes, então para usá-lo você normalmente só precisa escrever "use Data::Printer" (ou melhor ainda: "use DDP") e começar a olhar suas variáveis com a função "p()". Mas, se necessário, você pode personalizar praticamente todos os aspectos do Data::Printer tais como cores, índices, separadores de hash, valores padrão, o que mais quiser!

Veja um exemplo que expõe absolutamente todas as opções do Data::Printer, com seus valores padrão:

    use Data::Printer {
        name           => 'var',   # nome a ser exibido em referências cíclicas
        indent         => 4,       # quantos espaços por identação
        hash_separator => '   ',   # o que usar para separar chaves de valores em hash
        colored        => 'auto',  # colorizar saída (1 para sempre colorir, 0 para nunca)
        index          => 1,       # exibir índices de arrays
        multiline      => 1,       # exibir em várias linhas
        max_depth      => 0,       # quão fundo percorrer as estruturas (0 para tudo)
        sort_keys      => 1,       # ordenar chaves de hash
        deparse        => 0,       # usar B::Deparse para expandir (expor) funções
        show_tied      => 1,       # expor variáveis amarradas (tied)
        show_tainted   => 1,       # expor variáveis manchadas (tainted)
        show_weak      => 1,       # expor referências fracas (weak refs)
        return_value   => 'dump',  # tipo do valor de retorno (mais detalhes abaixo no artigo)

        caller_info    => 0,       # incluir informação sobre o que está sendo exibido
        use_prototypes => 1,       # usar protótipos: permite p(%foo) (sem referência),
                                   # mas impede passagem de dados anônimos

        color => {
             array       => 'bright_white',  # número dos índices em arrays
             number      => 'bright_blue',   # números
             string      => 'bright_yellow', # strings
             class       => 'bright_green',  # nome de classes
             undef       => 'bright_red',    # o valor 'undef'
             hash        => 'magenta',       # chaves de hash
             regex       => 'yellow',        # expressões regulares
             code        => 'green',         # referências para código
             glob        => 'bright_cyan',   # globs (normalmente handles de arquivos)
             repeated    => 'white on_red',  # referência para valores já vistos
             caller_info => 'bright_cyan',   # mensagem de detalhes sobre exibição
             weak        => 'cyan',          # referências fracas (weak refs)
             tainted     => 'red',           # conteúdo manchado (tainted)
        },

        class_method   => '_data_printer', # permite que classes usem métodos com esse nome
                                           # para exibir a si próprias

        class => {
            internals  => 1,       # exibir estrutura interna de classes

            inherited  => 'none',  # exibir métodos herdados. Valores possíveis são
                                   # 'none' (nenhum), 'all' (todos), 'private' (apenas métodos privados)
                                   # ou 'public' (apenas métodos públicos)

            parents    => 1,       # exibir classes pai
            linear_isa => 1,       # exibir toda a hierarquia @ISA, linearizada

            expand     => 1,       # quão fundo percorrer objeto (caso ele contenha outros objetos).
                                   # '1' para apenas se expandir. '0' para não se expandir, e 'all'
                                   # para expandir a si e a todos os sub-objetos

            sort_methods => 1,     # ordenar métodos públicos e privados

            show_methods => 'all'  # exibir métodos. 'all' para todos, 'none' para nenhum,
                                   # 'public' para exibir apenas métodos públicos e 'private'
                                   # para privados
        },
    };

Filtros

Algumas vezes não queremos ver o conteúdo de determinados objetos, apenas uma ou outra informação que ele contenha. Sabendo disso, o Data::Printer também oferece a opção de criar filtros para sobrescrever qualquer tipo de exibição:

  use Data::Printer filters => {
     'DateTime'      => sub { $_[0]->ymd },
     'HTTP::Request' => sub { $_[0]->uri },
  };

Caso seu filtro seja muito complexo, você ainda pode criá-lo como um módulo separado facilmente - pode até mesmo colocar seu filtro no CPAN para que outros também possam se beneficiar dele! O próprio Data::Printer já vem com filtros para a família DateTime e DBI. Abaixo, um exemplo da exibição de um DBI handle simples no tradicional Data::Dumper e no Data::Printer com o filtro "DB":

Outra coisa bacana é a possibilidade de criar filtros automáticos para suas classes simplesmente adicionando o método _data_printer() a elas. Você não precisa nem adicionar o Data::Printer como dependência, e ele vai usar essa função para filtrar sua classe em vez de fazer um dump genérico.

Algumas dicas de uso

Por padrão, o Data::Printer exibe a saída no STDERR sempre que chamado puramente (contexto "void"):

    p @var;

Se preferir fazer algo diferente com a saída, basta chamar usar a função p() em um contexto não-void, atribuindo a uma variável ou usada como parâmetro para outra função. Nesses casos, o p() vai retornar a string em vez de escrever no STDERR. Por exemplo:

    print p(@var);        # exibe no STDOUT

    my $saída = p(@var);  # coloca string de dump em $saída

Note que, dessa forma, o Data::Printer não colore a string e retorna apenas o texto - o que é útil para manipular a string sem se preocupar com os símbolos ANSI de coloração. Para os casos em que você quer a string colorida, é só ativar a opção colored => 1 - seja no arquivo de configuração (o .dataprinter), durante o "use" ou como argumento para a função p().

Veja, por exemplo, como é fácil transformar a saída do Data::Printer em um HTML colorido, pronto para ser exibido no navegador:

   use HTML::FromANSI;
   use DDP;

   my $objeto = ...;

   my $html = ansi2html( p($objeto) );

Agora, se você não pretende usar a string retornada e pretende sempre exibi-la no terminal, uma dica bacana é trocar o tipo do valor de retorno, alterando a opção return_value do valor padrão 'dump' para 'pass'. Isso fará com que o Data::Printer exiba o conteúdo da estrutura de dados no STDERR e retorne a própria estrutura de dados recebida. Assim você pode facilmente fazer dumps sem precisar adicionar novas linhas de código. Por exemplo, pode mudar uma linha que fiz:

  return %valores;

por:

  return p %valores;

Com o modo 'pass' você consegue até mesmo usar o Data::Printer no meio de uma chamada encadeada de métodos, para ver o conteúdo de seu objeto em um determinado estado facilmente. Basta trocar isso:

  $objeto->foo->bar->baz->bleep;

por:

  use DDP;

  $objeto->foo->bar->DDP::p->baz->bleep;




E o dump será exibido sem interferir no seu código!

Em Resumo...

Se você quer serializar/armazenar/restaurar estruturas de dados Perl, o Data::Printer NÃO vai te ajudar. O melhor que você tem a fazer é procurar soluções como a família Dumper/Dump, Storable, JSON ou o que você encontrar no CPAN.

Mas se você só quer mesmo ver/examinar o conteúdo de estruturas de dados e objetos, experimente o Data::Printer! Ah, e se você gosta de REPLs, você pode usá-lo como dumper padrão do Devel::REPL =)

O código está no github. Comentários, sugestões e patches são muito bem vindos!

Autor

Breno G. Oliveira < garu no cpan org >

blog comments powered by Disqus