6
use Time::HiRes qw( time );
11
our @ISA = qw(Exporter);
13
our %EXPORT_TAGS = ( 'all' => [ qw(set_chunk_size set_bkup_size rem_backspace) ] );
15
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
19
our $VERSION = '0.16';
32
my %hash = (freq => 1, base => 0, target => 1_000, quiet => 0, test => 0, num => q{9_999});
33
%hash = (%hash, %{$_[1]}) if defined $_[1];
35
my $format = defined $_[0] ? $_[0] : '%8c';
37
$self->{base} = $hash{base};
38
$self->{target} = $hash{target};
39
$self->{quiet} = $hash{quiet};
40
$self->{test} = $hash{test};
41
$self->{format} = $format;
42
$self->{freq} = $hash{freq};
43
$self->{value} = $hash{base};
44
$self->{mock_tm} = $hash{mock_tm};
45
$self->{oldtext} = '';
48
$self->{commify} = $hash{commify};
49
$self->{token} = defined($hash{token}) ? ref($hash{token}) eq 'ARRAY' ? $hash{token} : [$hash{token}] : [];
51
unless (defined $self->{quiet}) {
52
$self->{quiet} = !-t STDOUT;
55
if ($hash{num} eq '9') {
60
my ($sep, $group) = $hash{num} =~ m{\A 9 ([^\d\+\-]) (9+) \z}xms or do {
62
$errmsg = qq{Can't parse num => '$hash{num}'};
63
die sprintf('Error-%04d: %s', $errcode, $errmsg);
66
$self->{group} = length($group);
69
# Here we de-compose the format into $self->{action}
75
if ($fmt =~ m{^ ([^%]*) % (.*) $}xms) {
76
my ($literal, $portion) = ($1, $2);
77
unless ($portion =~ m{^ (\d*) ([a-zA-Z]) (.*) $}xms) {
79
$errmsg = qq{Can't parse '%[<number>]<alpha>' from '%$portion', total line is '$format'};
80
die sprintf('Error-%04d: %s', $errcode, $errmsg);
83
my ($repeat, $disp_code, $remainder) = ($1, $2, $3);
85
if ($repeat eq '') { $repeat = 1; }
86
if ($repeat < 1) { $repeat = 1; }
88
unless ($disp_code eq 'b'
95
or $disp_code eq 'k') {
97
$errmsg = qq{Found invalid display-code ('$disp_code'), expected ('b', 'c', 'd', 'm', 'p', 'P' 't' or 'k') in '%$portion', total line is '$format'};
98
die sprintf('Error-%04d: %s', $errcode, $errmsg);
101
push @{$self->{action}}, {type => '*lit', len => length($literal), lit => $literal} if length($literal) > 0;
102
push @{$self->{action}}, {type => $disp_code, len => $repeat};
106
push @{$self->{action}}, {type => '*lit', len => length($fmt), lit => $fmt};
111
# End of format de-composition
115
$self->{sec_begin} = $self->{mock_tm} ? $self->{mock_tm} : time;
116
$self->{sec_print} = 0;
126
$self->{mock_tm} = $_[0];
132
my $back = qq{\010} x length $self->{oldtext};
133
my $blank = q{ } x length $self->{oldtext};
135
$self->{line} = join('', $back, $blank, $back, @_, $self->{oldtext});
137
unless ($self->{test}) {
139
if ($self->{quiet}) {
151
return $self->{line};
154
sub up { my $self = shift; $self->{value} += defined $_[0] ? $_[0] : 1; $self->show_maybe; }
155
sub down { my $self = shift; $self->{value} -= defined $_[0] ? $_[0] : 1; $self->show_maybe; }
156
sub close { my $self = shift; $self->{value} = undef; $self->show; }
158
sub ticks { my $self = shift; return $self->{tick} }
163
$self->{token} = ref($tk) eq 'ARRAY' ? $tk : [$tk];
170
$self->{token} = ref($tk) eq 'ARRAY' ? $tk : [$tk];
184
my $sec_now = ($self->{mock_tm} ? $self->{mock_tm} : time) - $self->{sec_begin};
185
my $sec_prev = $self->{sec_print};
187
$self->{sec_print} = $sec_now;
190
if ($self->{freq} eq 's') {
191
if (int($sec_prev) != int($sec_now)) {
195
elsif ($self->{freq} eq 'd') {
196
if (int($sec_prev * 10) != int($sec_now * 10)) {
201
unless ($self->{tick} % $self->{freq}) {
211
my $back = qq{\010} x length $self->{oldtext};
212
my $blank = q{ } x length $self->{oldtext};
215
if (defined $self->{value}) {
217
# Here we compose a string based on $self->{action} (which, of course, is the previously de-composed format)
221
for my $act (@{$self->{action}}) {
222
my ($type, $lit, $len) = ($act->{type}, $act->{lit}, $act->{len});
224
if ($type eq '*lit') { # print (= append to $text) a simple literal
228
if ($type eq 't') { # print (= append to $text) time elapsed in format 'hh:mm:ss'
229
my $unit = int($self->{sec_print});
230
my $hour = int($unit / 3600);
231
my $min = int(($unit % 3600) / 60);
232
my $sec = $unit % 60;
233
my $stamp = sprintf '%02d:%02d:%02d', $hour, $min, $sec;
235
$stamp = substr($stamp, -$len) if length($stamp) > $len;
237
$text .= sprintf "%${len}.${len}s", $stamp;
240
if ($type eq 'd') { # print (= append to $text) a revolving dash in format '/-\|'
241
$text .= substr('/-\|', $self->{out} % 4, 1) x $len;
244
if ($type eq 'b') { # print (= append to $text) progress indicator format '#####_____'
245
my $progress = $self->{target} == $self->{base} ? 0 :
246
int ($len * ($self->{value} - $self->{base}) / ($self->{target} - $self->{base}) + 0.5);
247
if ($progress < 0) { $progress = 0 }
248
elsif ($progress > $len) { $progress = $len }
249
$text .= $self->{pdisp} x $progress.'_' x ($len - $progress);
252
if ($type eq 'p') { # print (= append to $text) progress in percentage format '999%'
253
my $percent = $self->{target} == $self->{base} ? 0 :
254
100 * ($self->{value} - $self->{base}) / ($self->{target} - $self->{base});
255
$text .= sprintf "%${len}.${len}s", sprintf("%.0f%%", $percent);
258
if ($type eq 'P') { # print (= append to $text) literally '%' characters
262
if ($type eq 'c') { # print (= append to $text) actual counter value (commified)
263
$text .= sprintf "%${len}s", commify($self->{commify}, $self->{value}, $self->{sep}, $self->{group});
266
if ($type eq 'm') { # print (= append to $text) target (commified)
267
$text .= sprintf "%${len}s", commify($self->{commify}, $self->{target}, $self->{sep}, $self->{group});
270
if ($type eq 'k') { # print (= append to $text) token
271
$text .= sprintf "%-${len}s", $self->{token}[$tok_ind];
275
# default: do nothing, in the (impossible) event that $type is none of '*lit', 't', 'b', 'p', 'P', 'c', 'm' or 'k'
278
# End of string composition
281
$self->{line} = join('', $back, $blank, $back, $text);
283
unless ($self->{test} or $self->{quiet}) {
288
$self->{oldtext} = $text;
293
if ($com) { return $com->($_[0]); }
296
my ($sep, $group) = @_;
299
my $len = length($_);
300
for my $i (1..$len) {
301
last unless s/^([-+]?\d+)(\d{$group})/$1$sep$2/;
307
my $chunk_size = 10000;
310
# Decision by Klaus Eichner, 31-MAY-2011:
311
# ---------------------------------------
312
# Make subs log_info(), set_chunk_size() and set_bkup_size() effectively dummy operations (i.e. they
313
# don't have any effect whatsoever)
316
sub set_chunk_size { }
317
sub set_bkup_size { }
322
open my $ifh, '<', $fname or die "Error-0200: Can't open < '$fname' because $!";
323
open my $tfh, '+>', undef or die "Error-0210: Can't open +> undef (tempfile) because $!";
327
while (read($ifh, my $inp_buf, $chunk_size)) {
328
$out_buf .= $inp_buf;
330
# here we are removing the backspaces:
331
while ($out_buf =~ m{\010+}xms) {
332
my $pos_left = $-[0] * 2 - $+[0];
336
$out_buf = substr($out_buf, 0, $pos_left).substr($out_buf, $+[0]);
339
if (length($out_buf) > $bkup_size) {
340
print {$tfh} substr($out_buf, 0, -$bkup_size);
341
$out_buf = substr($out_buf, -$bkup_size);
345
CORE::close $ifh; # We need to employ CORE::close because there is already another close subroutine defined in the current namespace "Term::Sk"
347
print {$tfh} $out_buf;
349
# Now copy back temp-file to original file:
351
seek $tfh, 0, SEEK_SET or die "Error-0220: Can't seek tempfile to 0 because $!";
352
open my $ofh, '>', $fname or die "Error-0230: Can't open > '$fname' because $!";
354
while (read($tfh, my $buf, $chunk_size)) { print {$ofh} $buf; }
366
Term::Sk - Perl extension for displaying a progress indicator on a terminal.
372
my $ctr = Term::Sk->new('%d Elapsed: %8t %21b %4p %2d (%8c of %11m)',
373
{quiet => 0, freq => 10, base => 0, target => 100, pdisp => '!'});
375
$ctr->up for (1..100);
377
$ctr->down for (1..100);
379
$ctr->whisper('abc');
381
my last_line = $ctr->get_line;
385
print "Number of ticks: ", $ctr->ticks, "\n";
389
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
390
long running processes.
392
A sample code fragment that uses Term::Sk:
396
print qq{This is a test of "Term::Sk"\n\n};
399
my $format = '%2d Elapsed: %8t %21b %4p %2d (%8c of %11m)';
401
my $ctr = Term::Sk->new($format,
402
{freq => 10, base => 0, target => $target, pdisp => '!'});
413
for my $i (0..10_000) {
414
$test += sin($i) * cos($i);
418
Another example that counts upwards:
422
my $format = '%21b %4p';
424
my $ctr = Term::Sk->new($format, {freq => 's', base => 0, target => 70});
433
At any time, after Term::Sk->new(), you can query the number of ticks (i.e. number of calls to
434
$ctr->up or $ctr->down) using the method 'ticks':
438
my $ctr = Term::Sk->new('%6c', {freq => 's', base => 0, target => 70});
446
print "Number of ticks: ", $ctr->ticks, "\n";
448
This example uses a simple progress bar in quiet mode (nothing is printed to STDOUT), but
449
instead, the content of what would have been printed can now be extracted using the get_line() method:
453
my $format = 'Ctr %4c';
455
my $ctr = Term::Sk->new($format, {freq => 2, base => 0, target => 10, quiet => 1});
457
my $line = $ctr->get_line;
459
print "This is what would have been printed upon new(): [$line]\n";
464
$line = $ctr->get_line;
466
print "This is what would have been printed upon $i. call to up(): [$line]\n";
471
$line = $ctr->get_line;
473
print "This is what would have been printed upon close(): [$line]\n";
475
Here are some examples that show different values for option {num => ...}
477
my $format = 'act %c max %m';
479
my $ctr1 = Term::Sk->new($format, {base => 1234567, target => 2345678});
480
# The following numbers are shown: act 1_234_567 max 2_345_678
482
my $ctr2 = Term::Sk->new($format, {base => 1234567, target => 2345678, num => q{9,999}});
483
# The following numbers are shown: act 1,234,567 max 2,345,678
485
my $ctr3 = Term::Sk->new($format, {base => 1234567, target => 2345678, num => q{9'99}});
486
# The following numbers are shown: act 1'23'45'67 max 2'34'56'78
488
my $ctr4 = Term::Sk->new($format, {base => 1234567, target => 2345678, num => q{9}});
489
# The following numbers are shown: act 1234567 max 2345678
491
my $ctr5 = Term::Sk->new($format, {base => 1234567, target => 2345678,
492
commify => sub{ join '!', split m{}xms, $_[0]; }});
493
# The following numbers are shown: act 1!2!3!4!5!6!7 max 2!3!4!5!6!7!8
497
=head2 Format strings
499
The first parameter to new() is the format string which contains the following
504
=item characters '%d'
506
a revolving dash, format '/-\|'
508
=item characters '%t'
510
time elapsed, format 'hh:mm:ss'
512
=item characters '%b'
514
progress bar, format '#####_____'
516
=item characters '%p'
518
Progress in percentage, format '999%'
520
=item characters '%c'
522
Actual counter value (commified by '_'), format '99_999_999'
524
=item characters '%m'
526
Target maximum value (commified by '_'), format '99_999_999'
528
=item characters '%k'
530
Token which updates its value before being displayed. An example use
531
of this would be a loop wherein every step of the loop could be
532
identified by a particular string. For example:
534
my $ctr = Term::Sk->new('Processing %k counter %c',
535
{base => 0, token => 'Albania'});
536
foreach my $country (@list_of_european_nations) {
537
$ctr->token($country);
545
You can also have more than one token on a single line. Here is an example:
547
my $ctr = Term::Sk->new('Processing %k Region %k counter %c',
548
{base => 0, token => ['Albania', 'South']});
549
foreach my $country (@list_of_european_nations) {
550
$ctr->token([$country, 'North']);
558
The C<token> method is used to update the token value immediately on the screen.
560
The C<tok_maybe> method is used to set the token value, but the screen is not refreshed immediately.
562
If '%k' is used, then the counter must be instantiated with an initial value for the token.
564
=item characters '%P'
566
The '%' character itself
572
The second parameter are the following options:
576
=item option {freq => 999}
578
This option sets the refresh-frequency on STDOUT to every 999 up() or
579
down() calls. If {freq => 999} is not specified at all, then the
580
refresh-frequency is set by default to every up() or down() call.
582
=item option {freq => 's'}
584
This is a special case whereby the refresh-frequency on STDOUT is set to every
587
=item option {freq => 'd'}
589
This is a special case whereby the refresh-frequency on STDOUT is set to every
592
=item option {base => 0}
594
This specifies the base value from which to count. The default is 0
596
=item option {target => 10_000}
598
This specifies the maximum value to which to count. The default is 10_000.
600
=item option {pdisp => '!'}
602
This option (with the exclamation mark) is obsolete and has no effect whatsoever. The
603
progressbar will always be displayed using the hash-symbol "#".
605
=item option {quiet => 1}
607
This option disables most printing to STDOUT, but the content of the would be printed
608
line is still available using the method get_line(). The whisper-method, however,
609
still shows its output.
611
The default is in fact {quiet => !-t STDOUT}
613
=item option {num => '9_999'}
615
This option configures the output number format for the counters.
617
=item option {commify => sub{...}}
619
This option allows one to register a subroutine that formats the counters.
621
=item option {test => 1}
623
This option is used for testing purposes only, it disables all printing to STDOUT, even
624
the whisper shows no output. But again, the content of the would be printed line is
625
still available using the method get_line().
631
The new() method immediately displays the initial values on screen. From now on,
632
nothing must be printed to STDOUT and/or STDERR. However, you can write to STDOUT during
633
the operation using the method whisper().
635
We can either count upwards, $ctr->up, or downwards, $ctr->down. Everytime we do so, the
636
value is either incremented or decremented and the new value is replaced on STDOUT. We should
637
do so regularly during the process. Both methods, $ctr->up(99) and $ctr->down(99) can take an
638
optional argument, in which case the value is incremented/decremented by the specified amount.
640
When our process has finished, we must close the counter ($ctr->close). By doing so, the last
641
displayed value is removed from STDOUT, as if nothing had happened. Now we are allowed to print
642
again to STDOUT and/or STDERR.
644
=head2 Post hoc transformation
646
In some cases it makes sense to redirected STDOUT to a flat file. In this case, the backspace
647
characters remain in the flat file.
649
There is a function "rem_backspace()" that removes the backspaces (including the characters that
650
they are supposed to remove) from a redirected file.
652
Here is a simplified example:
654
use Term::Sk qw(rem_backspace);
656
my $flatfile = "Test hijabc\010\010\010xyzklmttt\010\010yzz";
658
printf "before (len=%3d): '%s'\n", length($flatfile), $flatfile;
660
rem_backspace(\$flatfile);
662
printf "after (len=%3d): '%s'\n", length($flatfile), $flatfile;
666
Klaus Eichner, January 2008
668
=head1 COPYRIGHT AND LICENSE
670
Copyright (C) 2008-2011 by Klaus Eichner
672
This library is free software; you can redistribute it and/or modify
673
it under the same terms as Perl itself.
2
$Term::Sk::VERSION = '0.17';
6
use Time::HiRes qw( time );
11
our @ISA = qw(Exporter);
12
our %EXPORT_TAGS = ( 'all' => [ qw(set_chunk_size set_bkup_size rem_backspace) ] );
13
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
27
my %hash = (freq => 1, base => 0, target => 1_000, quiet => 0, test => 0, num => q{9_999});
28
%hash = (%hash, %{$_[1]}) if defined $_[1];
30
my $format = defined $_[0] ? $_[0] : '%8c';
32
$self->{base} = $hash{base};
33
$self->{target} = $hash{target};
34
$self->{quiet} = $hash{quiet};
35
$self->{test} = $hash{test};
36
$self->{format} = $format;
37
$self->{freq} = $hash{freq};
38
$self->{value} = $hash{base};
39
$self->{mock_tm} = $hash{mock_tm};
40
$self->{oldtext} = '';
43
$self->{commify} = $hash{commify};
44
$self->{token} = defined($hash{token}) ? ref($hash{token}) eq 'ARRAY' ? $hash{token} : [$hash{token}] : [];
46
unless (defined $self->{quiet}) {
47
$self->{quiet} = !-t STDOUT;
50
if ($hash{num} eq '9') {
55
my ($sep, $group) = $hash{num} =~ m{\A 9 ([^\d\+\-]) (9+) \z}xms or do {
57
$errmsg = qq{Can't parse num => '$hash{num}'};
58
die sprintf('Error-%04d: %s', $errcode, $errmsg);
61
$self->{group} = length($group);
64
# Here we de-compose the format into $self->{action}
70
if ($fmt =~ m{^ ([^%]*) % (.*) $}xms) {
71
my ($literal, $portion) = ($1, $2);
72
unless ($portion =~ m{^ (\d*) ([a-zA-Z]) (.*) $}xms) {
74
$errmsg = qq{Can't parse '%[<number>]<alpha>' from '%$portion', total line is '$format'};
75
die sprintf('Error-%04d: %s', $errcode, $errmsg);
78
my ($repeat, $disp_code, $remainder) = ($1, $2, $3);
80
if ($repeat eq '') { $repeat = 1; }
81
if ($repeat < 1) { $repeat = 1; }
83
unless ($disp_code eq 'b'
90
or $disp_code eq 'k') {
92
$errmsg = qq{Found invalid display-code ('$disp_code'), expected ('b', 'c', 'd', 'm', 'p', 'P' 't' or 'k') in '%$portion', total line is '$format'};
93
die sprintf('Error-%04d: %s', $errcode, $errmsg);
96
push @{$self->{action}}, {type => '*lit', len => length($literal), lit => $literal} if length($literal) > 0;
97
push @{$self->{action}}, {type => $disp_code, len => $repeat};
101
push @{$self->{action}}, {type => '*lit', len => length($fmt), lit => $fmt};
106
# End of format de-composition
110
$self->{sec_begin} = $self->{mock_tm} ? $self->{mock_tm} : time;
111
$self->{sec_print} = 0;
121
$self->{mock_tm} = $_[0];
127
my $back = qq{\010} x length $self->{oldtext};
128
my $blank = q{ } x length $self->{oldtext};
130
$self->{line} = join('', $back, $blank, $back, @_, $self->{oldtext});
132
unless ($self->{test}) {
134
if ($self->{quiet}) {
146
return $self->{line};
149
sub up { my $self = shift; $self->{value} += defined $_[0] ? $_[0] : 1; $self->show_maybe; }
150
sub down { my $self = shift; $self->{value} -= defined $_[0] ? $_[0] : 1; $self->show_maybe; }
151
sub close { my $self = shift; $self->{value} = undef; $self->show; }
153
sub ticks { my $self = shift; return $self->{tick} }
158
$self->{token} = ref($tk) eq 'ARRAY' ? $tk : [$tk];
165
$self->{token} = ref($tk) eq 'ARRAY' ? $tk : [$tk];
179
my $sec_now = ($self->{mock_tm} ? $self->{mock_tm} : time) - $self->{sec_begin};
180
my $sec_prev = $self->{sec_print};
182
$self->{sec_print} = $sec_now;
185
if ($self->{freq} eq 's') {
186
if (int($sec_prev) != int($sec_now)) {
190
elsif ($self->{freq} eq 'd') {
191
if (int($sec_prev * 10) != int($sec_now * 10)) {
196
unless ($self->{tick} % $self->{freq}) {
206
my $back = qq{\010} x length $self->{oldtext};
207
my $blank = q{ } x length $self->{oldtext};
210
if (defined $self->{value}) {
212
# Here we compose a string based on $self->{action} (which, of course, is the previously de-composed format)
216
for my $act (@{$self->{action}}) {
217
my ($type, $lit, $len) = ($act->{type}, $act->{lit}, $act->{len});
219
if ($type eq '*lit') { # print (= append to $text) a simple literal
223
if ($type eq 't') { # print (= append to $text) time elapsed in format 'hh:mm:ss'
224
my $unit = int($self->{sec_print});
225
my $hour = int($unit / 3600);
226
my $min = int(($unit % 3600) / 60);
227
my $sec = $unit % 60;
228
my $stamp = sprintf '%02d:%02d:%02d', $hour, $min, $sec;
230
$stamp = substr($stamp, -$len) if length($stamp) > $len;
232
$text .= sprintf "%${len}.${len}s", $stamp;
235
if ($type eq 'd') { # print (= append to $text) a revolving dash in format '/-\|'
236
$text .= substr('/-\|', $self->{out} % 4, 1) x $len;
239
if ($type eq 'b') { # print (= append to $text) progress indicator format '#####_____'
240
my $progress = $self->{target} == $self->{base} ? 0 :
241
int ($len * ($self->{value} - $self->{base}) / ($self->{target} - $self->{base}) + 0.5);
242
if ($progress < 0) { $progress = 0 }
243
elsif ($progress > $len) { $progress = $len }
244
$text .= $self->{pdisp} x $progress.'_' x ($len - $progress);
247
if ($type eq 'p') { # print (= append to $text) progress in percentage format '999%'
248
my $percent = $self->{target} == $self->{base} ? 0 :
249
100 * ($self->{value} - $self->{base}) / ($self->{target} - $self->{base});
250
$text .= sprintf "%${len}.${len}s", sprintf("%.0f%%", $percent);
253
if ($type eq 'P') { # print (= append to $text) literally '%' characters
257
if ($type eq 'c') { # print (= append to $text) actual counter value (commified)
258
$text .= sprintf "%${len}s", commify($self->{commify}, $self->{value}, $self->{sep}, $self->{group});
261
if ($type eq 'm') { # print (= append to $text) target (commified)
262
$text .= sprintf "%${len}s", commify($self->{commify}, $self->{target}, $self->{sep}, $self->{group});
265
if ($type eq 'k') { # print (= append to $text) token
266
$text .= sprintf "%-${len}s", $self->{token}[$tok_ind];
270
# default: do nothing, in the (impossible) event that $type is none of '*lit', 't', 'b', 'p', 'P', 'c', 'm' or 'k'
273
# End of string composition
276
$self->{line} = join('', $back, $blank, $back, $text);
278
unless ($self->{test} or $self->{quiet}) {
283
$self->{oldtext} = $text;
288
if ($com) { return $com->($_[0]); }
291
my ($sep, $group) = @_;
294
my $len = length($_);
295
for my $i (1..$len) {
296
last unless s/^([-+]?\d+)(\d{$group})/$1$sep$2/;
302
my $chunk_size = 10000;
305
# Decision by Klaus Eichner, 31-MAY-2011:
306
# ---------------------------------------
307
# Make subs log_info(), set_chunk_size() and set_bkup_size() effectively dummy operations (i.e. they
308
# don't have any effect whatsoever)
311
sub set_chunk_size { }
312
sub set_bkup_size { }
317
open my $ifh, '<', $fname or die "Error-0200: Can't open < '$fname' because $!";
318
open my $tfh, '+>', undef or die "Error-0210: Can't open +> undef (tempfile) because $!";
322
while (read($ifh, my $inp_buf, $chunk_size)) {
323
$out_buf .= $inp_buf;
325
# here we are removing the backspaces:
326
while ($out_buf =~ m{\010+}xms) {
327
my $pos_left = $-[0] * 2 - $+[0];
331
$out_buf = substr($out_buf, 0, $pos_left).substr($out_buf, $+[0]);
334
if (length($out_buf) > $bkup_size) {
335
print {$tfh} substr($out_buf, 0, -$bkup_size);
336
$out_buf = substr($out_buf, -$bkup_size);
340
CORE::close $ifh; # We need to employ CORE::close because there is already another close subroutine defined in the current namespace "Term::Sk"
342
print {$tfh} $out_buf;
344
# Now copy back temp-file to original file:
346
seek $tfh, 0, SEEK_SET or die "Error-0220: Can't seek tempfile to 0 because $!";
347
open my $ofh, '>', $fname or die "Error-0230: Can't open > '$fname' because $!";
349
while (read($tfh, my $buf, $chunk_size)) { print {$ofh} $buf; }
361
Term::Sk - Perl extension for displaying a progress indicator on a terminal.
367
my $ctr = Term::Sk->new('%d Elapsed: %8t %21b %4p %2d (%8c of %11m)',
368
{quiet => 0, freq => 10, base => 0, target => 100, pdisp => '!'});
370
$ctr->up for (1..100);
372
$ctr->down for (1..100);
374
$ctr->whisper('abc');
376
my last_line = $ctr->get_line;
380
print "Number of ticks: ", $ctr->ticks, "\n";
384
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
385
long running processes.
387
A sample code fragment that uses Term::Sk:
391
print qq{This is a test of "Term::Sk"\n\n};
394
my $format = '%2d Elapsed: %8t %21b %4p %2d (%8c of %11m)';
396
my $ctr = Term::Sk->new($format,
397
{freq => 10, base => 0, target => $target, pdisp => '!'});
408
for my $i (0..10_000) {
409
$test += sin($i) * cos($i);
413
Another example that counts upwards:
417
my $format = '%21b %4p';
419
my $ctr = Term::Sk->new($format, {freq => 's', base => 0, target => 70});
428
At any time, after Term::Sk->new(), you can query the number of ticks (i.e. number of calls to
429
$ctr->up or $ctr->down) using the method 'ticks':
433
my $ctr = Term::Sk->new('%6c', {freq => 's', base => 0, target => 70});
441
print "Number of ticks: ", $ctr->ticks, "\n";
443
This example uses a simple progress bar in quiet mode (nothing is printed to STDOUT), but
444
instead, the content of what would have been printed can now be extracted using the get_line() method:
448
my $format = 'Ctr %4c';
450
my $ctr = Term::Sk->new($format, {freq => 2, base => 0, target => 10, quiet => 1});
452
my $line = $ctr->get_line;
454
print "This is what would have been printed upon new(): [$line]\n";
459
$line = $ctr->get_line;
461
print "This is what would have been printed upon $i. call to up(): [$line]\n";
466
$line = $ctr->get_line;
468
print "This is what would have been printed upon close(): [$line]\n";
470
Here are some examples that show different values for option {num => ...}
472
my $format = 'act %c max %m';
474
my $ctr1 = Term::Sk->new($format, {base => 1234567, target => 2345678});
475
# The following numbers are shown: act 1_234_567 max 2_345_678
477
my $ctr2 = Term::Sk->new($format, {base => 1234567, target => 2345678, num => q{9,999}});
478
# The following numbers are shown: act 1,234,567 max 2,345,678
480
my $ctr3 = Term::Sk->new($format, {base => 1234567, target => 2345678, num => q{9'99}});
481
# The following numbers are shown: act 1'23'45'67 max 2'34'56'78
483
my $ctr4 = Term::Sk->new($format, {base => 1234567, target => 2345678, num => q{9}});
484
# The following numbers are shown: act 1234567 max 2345678
486
my $ctr5 = Term::Sk->new($format, {base => 1234567, target => 2345678,
487
commify => sub{ join '!', split m{}xms, $_[0]; }});
488
# The following numbers are shown: act 1!2!3!4!5!6!7 max 2!3!4!5!6!7!8
492
=head2 Format strings
494
The first parameter to new() is the format string which contains the following
499
=item characters '%d'
501
a revolving dash, format '/-\|'
503
=item characters '%t'
505
time elapsed, format 'hh:mm:ss'
507
=item characters '%b'
509
progress bar, format '#####_____'
511
=item characters '%p'
513
Progress in percentage, format '999%'
515
=item characters '%c'
517
Actual counter value (commified by '_'), format '99_999_999'
519
=item characters '%m'
521
Target maximum value (commified by '_'), format '99_999_999'
523
=item characters '%k'
525
Token which updates its value before being displayed. An example use
526
of this would be a loop wherein every step of the loop could be
527
identified by a particular string. For example:
529
my $ctr = Term::Sk->new('Processing %k counter %c',
530
{base => 0, token => 'Albania'});
531
foreach my $country (@list_of_european_nations) {
532
$ctr->token($country);
540
You can also have more than one token on a single line. Here is an example:
542
my $ctr = Term::Sk->new('Processing %k Region %k counter %c',
543
{base => 0, token => ['Albania', 'South']});
544
foreach my $country (@list_of_european_nations) {
545
$ctr->token([$country, 'North']);
553
The C<token> method is used to update the token value immediately on the screen.
555
The C<tok_maybe> method is used to set the token value, but the screen is not refreshed immediately.
557
If '%k' is used, then the counter must be instantiated with an initial value for the token.
559
=item characters '%P'
561
The '%' character itself
567
The second parameter are the following options:
571
=item option {freq => 999}
573
This option sets the refresh-frequency on STDOUT to every 999 up() or
574
down() calls. If {freq => 999} is not specified at all, then the
575
refresh-frequency is set by default to every up() or down() call.
577
=item option {freq => 's'}
579
This is a special case whereby the refresh-frequency on STDOUT is set to every
582
=item option {freq => 'd'}
584
This is a special case whereby the refresh-frequency on STDOUT is set to every
587
=item option {base => 0}
589
This specifies the base value from which to count. The default is 0
591
=item option {target => 10_000}
593
This specifies the maximum value to which to count. The default is 10_000.
595
=item option {pdisp => '!'}
597
This option (with the exclamation mark) is obsolete and has no effect whatsoever. The
598
progressbar will always be displayed using the hash-symbol "#".
600
=item option {quiet => 1}
602
This option disables most printing to STDOUT, but the content of the would be printed
603
line is still available using the method get_line(). The whisper-method, however,
604
still shows its output.
606
The default is in fact {quiet => !-t STDOUT}
608
=item option {num => '9_999'}
610
This option configures the output number format for the counters.
612
=item option {commify => sub{...}}
614
This option allows one to register a subroutine that formats the counters.
616
=item option {test => 1}
618
This option is used for testing purposes only, it disables all printing to STDOUT, even
619
the whisper shows no output. But again, the content of the would be printed line is
620
still available using the method get_line().
626
The new() method immediately displays the initial values on screen. From now on,
627
nothing must be printed to STDOUT and/or STDERR. However, you can write to STDOUT during
628
the operation using the method whisper().
630
We can either count upwards, $ctr->up, or downwards, $ctr->down. Everytime we do so, the
631
value is either incremented or decremented and the new value is replaced on STDOUT. We should
632
do so regularly during the process. Both methods, $ctr->up(99) and $ctr->down(99) can take an
633
optional argument, in which case the value is incremented/decremented by the specified amount.
635
When our process has finished, we must close the counter ($ctr->close). By doing so, the last
636
displayed value is removed from STDOUT, as if nothing had happened. Now we are allowed to print
637
again to STDOUT and/or STDERR.
639
=head2 Post hoc transformation
641
In some cases it makes sense to redirected STDOUT to a flat file. In this case, the backspace
642
characters remain in the flat file.
644
There is a function "rem_backspace()" that removes the backspaces (including the characters that
645
they are supposed to remove) from a redirected file.
647
Here is a simplified example:
649
use Term::Sk qw(rem_backspace);
651
my $flatfile = "Test hijabc\010\010\010xyzklmttt\010\010yzz";
653
printf "before (len=%3d): '%s'\n", length($flatfile), $flatfile;
655
rem_backspace(\$flatfile);
657
printf "after (len=%3d): '%s'\n", length($flatfile), $flatfile;
661
Klaus Eichner, January 2008
663
=head1 COPYRIGHT AND LICENSE
665
Copyright (C) 2008-2011 by Klaus Eichner
667
This library is free software; you can redistribute it and/or modify
668
it under the same terms as Perl itself.