NAME

Term::Sk - Perl extension for displaying a progress indicator on a terminal.

SYNOPSIS

use Term::Sk;

my $ctr = Term::Sk->new('%d Elapsed: %8t %21b %4p %2d (%8c of %11m)',
  {quiet => 0, freq => 10, base => 0, target => 100});

$ctr->whisper('This is a test: ');

$ctr->up for (1..100);

$ctr->down for (1..100);

my last_line = $ctr->get_line;

$ctr->close;

print "Number of ticks: ", $ctr->ticks, "\n";

DESCRIPTION

Term::Sk is a class to implement a progress indicator ("Sk" is a short form for "Show Key"). This is used to provide immediate feedback for long running processes.

Examples

A sample code fragment that uses Term::Sk:

use Term::Sk;

print qq{This is a test of "Term::Sk"\n\n};

my $target = 2_845;
my $format = '%2d Elapsed: %8t %21b %4p %2d (%8c of %11m)';

my $ctr = Term::Sk->new($format,
  {freq => 10, base => 0, target => $target});

for (1..$target) {
    $ctr->up;
    do_something();
}

$ctr->close;

sub do_something {
    my $test = 0;
    for my $i (0..10_000) {
        $test += sin($i) * cos($i);
    }
}

Another example that counts upwards:

use Term::Sk;

my $format = '%21b %4p';

my $ctr = Term::Sk->new($format, {freq => 's', base => 0, target => 70});

for (1..10) {
    $ctr->up(7);
    sleep 1;
}

$ctr->close;

At any time, after Term::Sk->new(), you can query the number of ticks (i.e. number of calls to $ctr->up or $ctr->down) using the method 'ticks':

use Term::Sk;

my $ctr = Term::Sk->new('%6c', {freq => 's', base => 0, target => 70})
  or die "Error 0010: Term::Sk->new, (code $Term::Sk::errcode) $Term::Sk::errmsg";

for (1..4288) {
    $ctr->up;
}

$ctr->close;

print "Number of ticks: ", $ctr->ticks, "\n";

This example uses a simple progress bar in quiet mode (nothing is printed to STDOUT), but instead, the content of what would have been printed can now be extracted using the get_line() method:

use Term::Sk;

my $format = 'Ctr %4c';

my $ctr = Term::Sk->new($format, {freq => 2, base => 0, target => 10, quiet => 1});

my $line = $ctr->get_line;
$line =~ s/\010/</g;
print "This is what would have been printed upon new(): [$line]\n";

for my $i (1..10) {
    $ctr->up;

    $line = $ctr->get_line;
    $line =~ s/\010/</g;
    print "This is what would have been printed upon $i. call to up(): [$line]\n";
}

$ctr->close;

$line = $ctr->get_line;
$line =~ s/\010/</g;
print "This is what would have been printed upon close(): [$line]\n";

Parameters

The first parameter to new() is the format string which contains the following special characters:

characters '%d'

a revolving dash, format '/-\|'

characters '%t'

time elapsed, format 'hh:mm:ss'

characters '%b'

progress bar, format '#####_____'

characters '%p'

Progress in percentage, format '999%'

characters '%c'

Actual counter value (commified by '_'), format '99_999_999'

characters '%m'

Target maximum value (commified by '_'), format '99_999_999'

characters '%P'

The '%' character itself

The second parameter are the following options:

option {freq => 999}

This option sets the refresh-frequency on STDOUT to every 999 up() or down() calls. If {freq => 999} is not specified at all, then the refresh-frequency is set by default to every up() or down() call.

option {freq => 's'}

This is a special case whereby the refresh-frequency on STDOUT is set to every second.

option {freq => 'd'}

This is a special case whereby the refresh-frequency on STDOUT is set to every 1/10th of a second.

option {base => 0}

This specifies the base value from which to count. The default is 0

option {target => 10_000}

This specifies the maximum value to which to count. The default is 10_000.

option {quiet => 1}

This option disables most printing to STDOUT, but the content of the would be printed line is still available using the method get_line(). The whisper-method, however, still shows its output.

The default is in fact {quiet => !-t STDOUT}

option {test => 1}

This option is used for testing purposes only, it disables all printing to STDOUT, even the whisper shows no output. But again, the content of the would be printed line is still available using the method get_line().

The new() method immediately displays the initial values on screen. From now on, nothing must be printed to STDOUT and/or STDERR. However, you can write to STDOUT during the operation using the method whisper().

We can either count upwards, $ctr->up, or downwards, $ctr->down. Everytime we do so, the value is either incremented or decremented and the new value is replaced on STDOUT. We should do so regularly during the process. Both methods, $ctr->up(99) and $ctr->down(99) can take an optional argument, in which case the value is incremented/decremented by the specified amount.

When our process has finished, we must close the counter ($ctr->close). By doing so, the last displayed value is removed from STDOUT, as if nothing had happened. Now we are allowed to print again to STDOUT and/or STDERR.

tee'ed STDOUT

There is one case where the idiom {quiet => !-t STDOUT} doesn't quite work, and that is when STDOUT is tee'ed, like so:

system 'perl subprog.pl | tee data1.txt';

The output here goes to the terminal and to 'data.txt' at the same time (via the 'tee' command). Suppose that 'subprog.pl' uses Term::Sk, the question now arises whether, or not, we want STDOUT to be displayed, i.e. whether or not we want the option {quiet => ...} to be true.

On one hand, we want STDOUT to be displayed, because STDOUT is clearly connected to the terminal. On the other hand, we don't want STDOUT to be displayed, because STDOUT is also connected to the flat file 'data1.txt', and we don't want any messages from Term::Sk in a flat file.

The solution here is to split the output inside Term::Sk by setting the environment variable $ENV{'TERM_SK_OUTPUT'} to '/dev/tty' on Linux, or by setting it to 'CON:' on Windows. This effectively overrides any {quiet => ...} setting and makes sure that output from Term::Sk is displayed on the terminal, but not on the flat file.

What does this all mean in practice ?

If you call a simple subprogram without redirection, then nothing changes, a simple 'system' is enough:

# prog1.pl
system 'perl subprog.pl';

If you call a subprogram with redirection, then nothing changes either, a simple 'system' is enough:

# prog2.pl
system 'perl subprog.pl >data.txt';

If, however, you call a subprogram with tee'ed redirection, then you need to prepare the program as follows:

# prog3.pl
{
    local $ENV{'TERM_SK_OUTPUT'} = '/dev/tty';
    system 'perl subprog.pl | tee data1.txt';
}

Please be aware, that if 'subprog.pl' itself calls another sub-sub-program with simple redirection to a flat file, then $ENV{'TERM_SK_OUTPUT'} must be reset by localising it without initialisation, like so:

# subprog.pl
{
    local $ENV{'TERM_SK_OUTPUT'}; # reset
    system 'perl subsubprog.pl >data2.txt';
}

Line buffering and whisper()

Due to the way that line buffering works, it is not easy to mix normal print's and Term::Sk on the same line. Therefore, if you want to mix output on the same line as Term::Sk, you should use the whisper() method.

Here is an example:

use Term::Sk;

print "This output on an entire line on its own\n";

my $ctr = Term::Sk->new('%d Elapsed: %8t %21b %4p %2d (%8c of %11m)',
  {quiet => 0, freq => 10, base => 0, target => 100});

$ctr->whisper('This is an output line shared with Term::Sk --> ');

$ctr->up for (1..100);

$ctr->close;

AUTHOR

Klaus Eichner, January 2008

COPYRIGHT AND LICENSE

Copyright (C) 2008 by Klaus Eichner

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.