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:

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

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 =)

Code is on github. Comments, feature requests, bug reports and patches are welcome!

17 comments:

  1. Most excellent, I was using Data::Dump, but I felt a need for something better, specially when dumping DBIx::Class objects. Your filter system seems to be exactly what I'm looking for.

    Thanks!

    ReplyDelete
  2. 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.

    ReplyDelete
  3. @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!

    ReplyDelete
  4. Installing it... needs Module::Build? ... installing other half of CPAN ... done

    Oh nice.

    Very nice!

    Can it also print the name of the variable being printed?

    Does it also work on Windows?

    ReplyDelete
  5. @szabgab Thanks! I'm glad you liked it =)

    Wait, half of CPAN for Module::Build? It should be core, no? I didn't know you had to download it, I'll see what I can do to make the installation experience more pleasant.

    Right now the "caller_message" option can't print the name of the variable, only line number, package name and file name. I'm looking into PadWalker and Devel::ArgNames for that, patches are welcome!

    And yes, it works on Windows - or at least here it does! If you find any bugs or have a wishlist for it, you know how to find me ;)

    ReplyDelete
  6. Well... if you're not stuck on 5.8.x, it's core. I'll have to try it, then!

    ReplyDelete
  7. I notice your implementation directly applies colours, using Term::ANSIColor.

    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.

    ReplyDelete
  8. Very, very dumb question... How do you use Perl in "shell mode"?

    ReplyDelete
  9. Very nice but two annoyances:

    1) The default 'p' conflicts with perl debugger's p keyword (print expression) and you actually can't override it

    2) I'd like to say `p my $foo = Foo->new` during the development to get a quick dump, but it just doesn't compile because of the prototype, which is a bit disappointing.

    ReplyDelete
  10. I thought you were using it through the debugger, and you'd overwritten the p command somehow...

    I don't think it conflicts, though. If I do perl -MData::Printer -d ... I can use it from the debugger by typing

    p p $var...

    at the prompt.

    It does have some massive preqs which prevent it from being installed in our moldy old production perl though.

    ReplyDelete
  11. 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 :)

    @stas - it's Devel::REPL! I even have a plugin for it, Devel::REPL::Plugin::DataPrinter. Try it out :)

    @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 :)

    ReplyDelete
  12. @garu: shame on me, LOL! I wasn't even aware of the 'Read-eval-print loop' concept :P

    ReplyDelete
  13. Nice!

    $ perl -MDDP -le'use CPAN; $c=CPAN->new; p$c'
    CPAN {
    Parents CPAN::Debug, Exporter
    Linear @ISA CPAN, CPAN::Debug, Exporter
    public methods (51) : all_objects, anycwd, autobundle, AUTOLOAD, backtickcwd, bundle, checklock, clean, cleanup, cvs_import, cwd, delete, DESTROY, exists, expand, fastcwd, fforce, find_perl, force, get, getcwd, has_inst, has_usable, install, install_tested, instance, is_installed, is_tested, LOCK_EX, LOCK_NB, LOCK_SH, LOCK_UN, make, mkmyconfig, new, notest, perldoc, readhist, readme, recent, recompile, report, reset_tested, savehist, set_perl5lib, shell, smoke, soft_chdir_with_alternatives, suggest_myconfig, test, upgrade
    private methods (11) : _flock, _init_sqlite, _list_sorted_descending_is_tested, _perl_fingerprint, _redirect, _sqlite_running, _uniq, _unredirect, _yaml_dumpfile, _yaml_loadfile, _yaml_module
    internals: {}
    }

    ReplyDelete
  14. Call us at We24Support canon printers support number at 1-866-978-0799,Enhance your system functioning and keep it clean
    with We24Support services.
    canon printers support
    canon printer tech support

    ReplyDelete
  15. What a great post with nice details. I really appreciate your work. Can i print plastic business cards by any laser printer?

    ReplyDelete
  16. I'm impressed with the insight here that pretty printers are used mainly for data structure exploration, not serialization. Well done!
    plastic business cards

    ReplyDelete
  17. 12 years experience in photocopier industry & networking, Copy Ltd have been repairing and maintaining printers, photocopiers and fax machines for our many of satisfied customers. We currently have engineers operating from multiple locations around the London area.
    Photocopier Maintenance London | Best Photocopier Repair London | Photocopier Refurbishing in London | Best Color Photocopier Machine | Photocopier Services in London | Black & White Photocopier London | Ricoh London UK | Cheap Photocopier Machine London | Bizhub Dealers in London | Best Laser Printer in London | Office Depot Copier Repair | Refurbished Photocopier London | konica Minolta London UK | Affordable Copier Repair Service |

    ReplyDelete