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 using 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, though unfortunately not when the stream is closed (see "BUGS" below).

See examples/demo.pl for a simple complete program.

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 and should be cleared and redrawn.

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 strips 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 (eg. examples/fracs.pl). The difference for EscStatus is in the string width calculations for utf8 multibyte sequences. Note that changing the utf8 flag after pushing doesn'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. See examples/wide.pl for a complete program printing double-width East Asian characters.

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. See examples/colour.pl for an example of SGR colour escapes.

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 an 8 character sequence "\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. That makes sense since there's only one actual destination (the terminal), once you trust EscStatus not to lose anything!

When updating a 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. Generally the trick 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.

Term::Size version 0.2 uses PerlIO_findFILE and as of Perl 5.10.0 that 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

Term::Sk formatting progress status messages, and examples/term-sk.pl here for combining that with EscStatus.

HOME PAGE

http://user42.tuxfamily.org/perlio-via-escstatus/index.html

LICENSE

Copyright 2008, 2009, 2010, 2011, 2012, 2017 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/.