The 2004 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 2nd day of Advent my True Language brought to me..

When your code is producing a large quantity of output to a terminal window it can be very hard to quickly tell what's going on just by glancing at the screen - especially if the terminal is producing so much text that things start to scroll off the top of the screen before you can complete reading them.

Term::ANSIColor allows you to send control codes to the terminal telling it to display your text in different colours or with different features (bold, underlining, etc) turned on. This enables you to much better format your output allowing you to break up fast flowing text visually, and provide a mechanism that you can use to show someone that very prominently something is wrong. It allows the user presented with scrolling text to simplify the question they have to ask - not, "What does this say...should I pause it and check for errors", to rather "Does this contain red text?".

Term::ANSIColor can be made to export a number of routines that return the escape codes that if sent to your terminal cause it to change colour. for example:

  use Term::ANSIColor qw(:constants);
  print RED, "This text is in red\n", RESET;
  print "This text is normal again.


  This text is in red
  This text is normal again

Unlike HTML there isn't a start and end tag - you turn on the features that you want and they stay on until you call RESET or get replaced with another competing feature. So, you can flip colours without having to turn the previous colours off like so:

  print RED, "red means stop, ",
        YELLOW, " yellow means wait, ",
        GREEN, "green means go!".
        RESET, "\n";
  red means stop, yellow means wait, green means go!

You can also make text bold and underlined

  print UNDERLINE, "Chapter 14", RESET, "\n\n";
  print "It was then, and ", BOLD, "only ", RESET, "then that...";
  Chapter 14
  It was then, and only then that...

And you can also change the background colour

  print ON_RED, "Hello", RESET, "\n";

And non-competing features can be combined

  print "We can make things look really ",
        BOLD, UNDERLINE, YELLOW, ON_GREEN, "ugly", RESET, "\n";
  We can make things look really ugly

Different, more advanced, terminals may also support additional features - see the manual for a description of features like dark, blink, reverse and concealed that you can also use.

Example 1: Separating Dumper Output

One good technique for debugging a problem is to generate debug output all over your code. You'll often see temporary code in my modules like this when I'm first testing my objects:

  sub foo
    my $self = shift;
    print STDERR "Got to foo\";
    print STDERR Dumper($self);

This produces copious output that allows me to track what methods are being called on my object, and the state of the object when said method is called. The problem with copious output is that it's often all to easy to drown yourself with so much debug info that it's impossible to tell the output from one Data::Dumper ends and another starts (especially when they flow over several screens) and if you're not careful you'll accidentally page down from one output to another, totally confusing yourself.

When this starts happening, I either reduce the number of Dumper statements I'm calling, or if I need them all to work out what's going on, I bring Term::ANSIColor to the rescue and colour code each method's debug output separately:

  sub foo
    my $self = shift;
    print STDERR RED;
    print STDERR "Got to foo\";
    print STDERR Dumper($self);
    print STDERR RESET;

Example 2: Site checker

This is a simple script that I wrote to check that a bunch of URLs were working as expected. Rather than cluttering up the display with a bunch of status messages I use Term::ANSIColor to change the colour of the URLs depending on if they are reachable or not. This way I can tell if there's a problem simply by glancing at the terminal the script is running in rather than having to actually read any of the output.

  # turn on perl's safety features
  use strict;
  use warnings;
  # load a lot of stuff
  use LWP::Simple qw(get);
  use Time::HiRes qw(gettimeofday tv_interval);
  use Term::ANSIColor qw(:constants);
  # define how long before we should get a warning about
  # things
  use constant MAX_ALLOWED_SECONDS => 5;
  # test some pages
  sub test_url
    my $url = shift;
    # what time is it now *exactly*
    my $t0 = [gettimeofday];
    # download the page
    my $result = get $url;
    # work out what color we should print in
    #   on red background  if couldn't load
    #   red                if took too long to load
    #   green              if okay
    if (!defined($result))
      { print ON_RED }
    elsif (tv_interval($t0) > MAX_ALLOWED_SECONDS)
      { print RED }
      { print GREEN }
    print $url;
    print RESET, "\n";

When this is run it produces this output:

Showing us that though and are fine, the list of all modules takes more than five seconds to download and has, unsurprisingly, errors.

Example 3: Test::Builder::Tester

Test::Builder::Tester is an example of where I use Term::ANSIColor in a module on CPAN. It compares the output of testing functions with the expected output, and complains if they differ. However, when Test::Builder::Tester complains that two test outputs differ it can be very hard to spot by eye where exactly the two sections of text are different, especially if they differ in invisible characters (for example, one string has an extra space on the end.) To help with this I provide options to turn on coloured output to highlight the differing text in red.

  ok 1 - use Acme::Test::Buffy;
  ok 2 - function 'is_buffy' exported
  ok 3 - works when correct
  ok 4 - works when correct with default text
  not ok 5 - works when incorrect
  #     Failed test (t/01basic.t at line 85)
  # STDERR is:
  # #     Failed test (t/01basic.t at line 81)
  # # Expected 'Buffy' but got 'buffy' instead
  # not:
  # #     Failed test (t/01basic.t at line 81)
  # # Expected 'Buffy' but got text 'buffy' instead
  # as expected

This is an example of using Term::ANSIColor to show information that I otherwise couldn't provide - there's no other way to straight forwardly indicate where the text differs without inserting something in the text itself.