Monday, 30 May 2011

Data::Printer - a colored pretty printer for Perl

Wait, stop. Is this Yet Another Data::Dumper?

Well, yes and no. Data::Dumper (and friends) are meant to stringify data structures in a way that makes them still suitable for being eval'ed back in. That's really awesome, but poses a huge constraint over pretty-printers. Earlier this year, brian d foy talked about the amazing powers of Data::Dump, but it still suffers from those constraints. Same goes for the (also great) Data::Dump::Streamer.

Here's a quick visual comparison between the ever popular Data::Dumper and the new Data::Printer:

First thing you'll notice is the colored output, indexed arrays and a little extra regex information. But Data::Printer offers much more than that. How about debugging objects?

And what if your data is attached to others?

The idea behind Data::Printer is that most developers (at least to my experience) use such tools mostly just to see what's going on inside their variables and objects, not to serialize data in and out of Perl. So I decided to make a module that would focus on that: display Perl variables and objects on screen, properly formatted (to be inspected by a human). Data::Printer is somewhat similar to Ruby's "awesome_print", but I made sure to include more customization options and some neat features present in Perl's data dumpers.

For example, I called the printer function "p()" as it's nice and short and should steer clear of name collisions. But if you're so used to calling "Dumper()" in your code it just comes out naturally while you type, you can try this:

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

Dumper(%foo); # there, problem solved!

Data::Printer comes with (I hope!) very sane defaults, so usually all you have to do is "use Data::Printer" (or even shorter: "use DDP") and start peeking at data structures with the exported "p()" function. But pretty is a matter of personal taste, and from colors to array indexes to the hash separator and their default values, you can customize just about anything!

Sounds neat, but I'm not gonna type all that every time!

And you shouldn't - which is why Data::Printer looks for a file called .dataprinter in your home directory and lets you keep all your preferred settings right there, so you only have to worry about it once :-)

Filters

There are times when you don't really wish to see an entire object's internals during your review, just that important piece of information that you're holding in it. Data::Printer also offers you the ability to easily add filters to override any kind of data display:

If your filters are too complex you can create them as a separate module and load them by name. You can even upload them to CPAN so others can benefit from it! In fact, Data::Printer already ships with some (hopefully useful) filters for the whole DateTime family of modules (not just DateTime, but also Time::Piece and friends), and some Database ones as well (currently DBI and DBIx::Class):

You can also make your classes Data::Printer-aware simply by adding a _data_printer() function to them. You don't have to add Data::Printer as a dependency at all, and it will use that function to filter your class by default instead of doing a regular dump.

In Short...

If you want to serialize/store/restore Perl data structures, this module will NOT help you, and you should try other solutions such as the Dumper/Dump family, Storable, JSON, or whatever you can find on CPAN.

But if you only care about seeing what's going on inside your data structures and objects, give Data::Printer a try! Oh, and if you're into REPLs, you can add it as your default dumper for Devel::REPL too =)

I'm impressed with the insight here that pretty printers are used mainly for data structure exploration, not serialization. Well done! Data::Dumper with DateTime objects and such is horrid, I love the idea of a filter on those really huge objects, and prettier pretty printing is great to begin with. I foresee this module getting plenty of use.

@melo - thanks! As mentioned, there's the beginning of a DBIC filter via the DB standalone filter. It currently displays connection details on Schemas, and SQL queries on ResultSets. If you get to improve this in any way for general purposes, drop me a pull request - and remember to add your name on the contributors list!

@doherty - thanks, I hope it gets a lot of use too :) As for DateTime, I agree with you so badly I even considered making the DateTime filter on by default as it's been really useful to me, but it didn't seem fair to do it without other people's input on this (and besides, one can always add it to the .dataprinter file). Please let me know if there's anything I can do to make the Data::Printer experience even better!

Just a thought, but I wonder if you'd consider splitting the implementation out a little bit, by building a string in the abstract using perhaps a String::Tagged object to store string content with formatting. This can then be rendered to a console using Term::ANSIColor as in your current code, but could also be rendered to HTML using String::Tagged::HTML, or to any number of other output representations as yet to be defined.

Wow, thank you guys so much for the nice comments and suggestions. It's great to see that Data::Printer scratches not only my itches, but a lot of other people's too :)

@curtis - please do, and let me know what you think!

@LeoNerd - just had a look at String::Tagged and it looks nice. I might do something with it in the future, but right now the ANSI escape characters can also be though of as tags and converted at will. For example, one can use HTML::FromANSI and just say ansi2html( p($object) ) to get Data::Printer's output in HTML. Admittedly it's not as fancy or scalable as String::Tagged, but it does the trick :)

@miyagawa (1) - I actually named it 'p' as a homage to the perl debugger's version :) The shell above is not the debugger, it's just Devel::REPL. I tried it the same way as Dotan above and got the right output. If you were talking about something else, please let me know so I can try to fix it in future versions!

@miyagawa (2) - Speaking of future versions, 0.17 will have optional prototypes and allow you to say `p my $foo = Foo->new` just like that! I hope you can give it a try and let me know if I missed anything :)

@dotan - thanks for the debugger analysis! As for the massive preqs, I think they come basically from Class::MOP (meaning "Moose") and old perls without Module::Build. I'll try to bundle Module::Build (or even switch builders) in the future. As for Class::MOP, you can try local::lib to install it in your environment without touching your production perl, or letting me know of a simpler way to get class information without it :)