The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

PerlIO::via::EscStatus - dumb terminal status display layer

SYNOPSIS

 use PerlIO::via::EscStatus qw(print_status);
 binmode (STDOUT, ':via(EscStatus)') or die;

 print_status ("Done 10% ...");
 print_status ("Done 20% ...");
 print "This is ordinary text output.\n";
 print_status ("Done 90% ...");
 print_status ("");   # erase status

DESCRIPTION

An EscStatus layer prints and reprints a status line using carriage returns and backspaces for a dumb terminal. This is meant as a progress or status display in a command line program.

    Working ... record 20 of 80 (25%)
                                     ^--cursor left here

Status lines are communicated to EscStatus "in band" in the output stream with an escape sequence. Currently this is an ANSI "APC" application control, followed by the status line. make_status and print_status below produce this.

    "\e_EscStatus\e\\Status string\n"

The layer clears and redraws the status when ordinary output text is printed, so it appears as normal. The status is also erased when the layer is popped (but unfortunately not when the stream is closed, see "BUGS" below).

Motivation

The idea of an output layer is that it lets you send ordinary output with plain print, printf, etc, and the layer takes care of what status is showing, to clear and redraw as necessary.

The alternative is a special message printing function to do the clearing. If you're in full control of your ordinary output then that's fine (for instance Term::ProgressBar does it that way), but if you might have parts of a library or program only setup with plain print then a layer is a good way to keep them from making a mess of the display.

The "in-band" method of passing status strings to the layer has the advantage that higher layers can buffer or do extra transformations and everything stays in the intended sequence. It's even possible for a status stream to come from a child process through a pipe or socket and stay in the escapes form until being re-sent to a final EscStatus layer on STDOUT.

The escape format chosen is meant to be easy to produce and tolerably readable if for some reason crunching by EscStatus is missed. The EscStatus::ShowAll layer lets you explicitly print all status lines for development, or the EscStatus::ShowNone layer lets you strip them for a quiet mode or batch mode operation. (See PerlIO::via::EscStatus::ShowAll and PerlIO::via::EscStatus::ShowNone.)

CHARACTERS

Each status line is truncated to the width of the terminal as determined by Term::Size::chars() (see Term::Size). No attempt is made (as yet) to monitor SIGWINCH for changes to the width, though the size is checked for each new line, so the next new status uses the new size.

EscStatus follows the "utf8" flag of the layer below it when first pushed, allowing extended characters to be printed. Often the layer below will be an ":encoding" for the user's terminal. The difference for EscStatus is in the string width calculations on utf8 multibyte sequences. Note that changes to the utf8 layer flag after pushing don't work properly (see "BUGS" below).

For string width calculations tabs (\t) are 8 spaces. Various East Asian "double-width" characters take two columns. BEL (\a), ANSI escapes, and various unicode modifier characters take no space. If a status line is truncated then all ANSI escapes are kept, so if say bold is turned on and off then the off escape is preserved.

If a lower layer expands a character because it's unencodable on the final output then that's likely to make a mess of the width calculation. For example the :encoding layer PERLQQ mode turns unencodables into 8 characters "\x{1234}", which is more than EscStatus will have allowed for. The suggestion is to expand or transform before EscStatus so it sees what's really going to go out. (An encode and re-decode is one way to do that, though a bit wasteful.)

FUNCTIONS

$str = make_status ($str,...)

Form a status line output string by concatenating the given $str strings and adding the necessary escape marker sequences. print_status prints it to STDOUT, make_status returns it as a string.

Any newlines in the middle of the strings are changed to spaces, since only a single line of status is possible.

OTHER NOTES

The suggestion is to push PerlIO::via::EscStatus onto STDOUT and leave STDERR alone. Leaving STDERR alone has the advantage of not putting anything in the way of an unexpected error print. You can trap "normal" errors and turn them into a print on STDOUT, leaving STDERR only for the unexpected. The alternative is to >&= alias stderr onto stdout. The latter makes sense since there's only one actual destination (the terminal), once you trust EscStatus not to lose anything!

When updating the displayed status it's important not to hammer the terminal with too much output. It can easily become the speed of the terminal and not the speed of the program which is the limiting factor. The trick generally is to print a new status only say once per second. This means the display isn't perfectly up-to-date, but the only time that's a problem is if the program goes away number crunching for a long time with an old status showing, in which case the wrong processing stage gets the blame for the delay.

BUGS

When the stream is closed the status shown by EscStatus is not erased. This is because PerlIO::via closes the sublayers first. Perhaps that can change in the future. The suggestion when closing is to either print an empty status to clear, or to pop the EscStatus (erasing works when popped).

    print_status ('');
    close STDOUT;  # or "exit 0" or whatever

If the utf8 flag on the stream is changed (by binmode) EscStatus doesn't notice and will keep using the state when it was first pushed. Perhaps this will change in the future (assuming there's sensible uses for turning it on and off dynamically).

As of Perl 5.10.0 an xsub using PerlIO_findFILE like Term::Size version 0.2 turns off the utf8 flag on the stream, preventing wide-char output. EscStatus has a workaround for its use of Term::Size but an application might need to do the same. The symptom is the usual "Wide character in print" warning (on a stream you thought you'd already set for wide output).

SEE ALSO

PerlIO::via, PerlIO::via::EscStatus::ShowAll, PerlIO::via::EscStatus::ShowNone, ProgressMonitor::Stringify::ToEscStatus

HOME PAGE

http://www.geocities.com/user42_kevin/perlio-via-escstatus/index.html

LICENSE

Copyright 2008, 2009 Kevin Ryde

PerlIO-via-EscStatus is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.

PerlIO-via-EscStatus is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with PerlIO-via-EscStatus. If not, see http://www.gnu.org/licenses/.