File Coverage

blib/lib/Term/Activity.pm
Criterion Covered Total %
statement 143 157 91.0
branch 41 56 73.2
condition 10 21 47.6
subroutine 18 19 94.7
pod 0 3 0.0
total 212 256 82.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Term::Activity - Process Activity Display Module
4              
5             =head1 SYNOPSIS
6              
7             This module is designed to produce informational STDERR output while a
8             process is funinctioning over many iterations or outputs. It is instanced
9             with an optional name and other configurable values and is then called on
10             each iterative loop.
11              
12             =head1 DESCRIPTION
13              
14             The information displayed is the current time processed (measured since
15             the instancing of the module), the number of actions second, a text-graphic
16             indicator of activity (skinnable), and the total count of actions thus far.
17              
18             An example output (on a small terminal) might appear like this:
19              
20             03:13:54 1 : [~~~~~~~~~~~~~~~~~\_______________] 9,461
21              
22             Showing that nearly three hours and 14 minues have occured with a
23             current rate of 1 action per second, for a total of 9,461 total actions.
24             (For the curious, the skin shown is the default skin, AKA 'wave')
25              
26             The display occurs on a single line that is updated regularly. The
27             display automatically calibrates itself so that it appears to update
28             approximately once a second.
29              
30             When the Term::Activity module passes out of scope it updates the display
31             with the final time, count, and a newline before exiting.
32              
33             Term::Activity can resize itself to the width of the current window if
34             Term::Size is installed. If not, it defaults to an 80-character display.
35             Term::Size is thouroughly reccomended.
36              
37             =head1 USAGE
38              
39             =head2 Basic Usage:
40              
41             my $ta = new Term::Activity;
42              
43             while ( doing stuff ) {
44             $ta->tick;
45             }
46              
47             =head2 Process labels:
48              
49             You can label the output with a string to be displayed along with the
50             other output. This is handy for scripts that go through multiple
51             processess.
52              
53             You can either instance them as a scalar value:
54              
55             my $ta = new Term::Activity 'Batch7';
56              
57             Or via a configuration hash reference:
58              
59             my $ta = new Term::Activity ({ label => 'Batch7' });
60              
61             Also, through the course of processing, you can change the label.
62              
63             $ta->relabel("New Label");
64              
65             =head2 Skins:
66              
67             Skins can be selected via a configuration hash reference. Currently there
68             are two skins 'wave' and 'flat.' "Wave" is the default skin.
69              
70             my $ta = new Term::Activity ({ skin => 'flat' });
71              
72             The "flat" skin cycles through a series of characters. You may also
73             provide an arrayreference of your favorite characters if you'd like
74             different ones:
75              
76             my $ta = new Term::Activity ({
77             skin => 'flat',
78             chars => [ '-', '=', '%', '=', '-' ]
79             });
80              
81             =head2 Start Time:
82              
83             The start time for the process timer is initialized when the
84             Term::Activity is created. Sometimes, with longer programs you want the
85             count to remain constant through several different forms of processing.
86             You can set the start time to a previous start time to do this.
87              
88             The parameter is called 'time' in the initilization hash:
89              
90             my $start_time = time;
91              
92             # Stuff happens
93              
94             my $ta = new Term::Activity ({
95             time => $start_time
96             });
97            
98             =head2 Count:
99              
100             As with the time, you might want to start at a later count, so you can keep track of
101             total count across several runs.
102              
103             The parameter to change the starting count is called 'count' in the initialization hash:
104              
105              
106             my $ta = new Term::Activity ({
107             count => $start_count
108             });
109              
110              
111             =head2 Interval:
112              
113             The interval is how often the screen is updated to reflect changes. By default,
114             Term::Activity auto-tunes this towards an update approximately each second.
115              
116             Initially, however, there is no way of knowing how often you will call tick(), so an
117             assumed interval of 100 iterations before update is the starting value.
118              
119             For slower processes, you probably want to start this at 1 - that is, a visual update at
120             each call of tick()
121              
122             my $ta = new Term::Activity ({
123             interval => 1
124             });
125              
126             =head2 Debug:
127              
128             By setting the debug parameter to 1 a very verbose debug output is
129             produced along with the regular output to let you see settings have been
130             selected and what computations are being performed.
131              
132             my $ta = new Term::Activity ({
133             debug => 1
134             });
135              
136             =head2 Multiple Instances:
137              
138             As stated above, when the Term::Activity module passes out of scope it
139             updates the display with the final time, count, and a newline before exiting.
140             Consuquently if you would like to use Term::Activity multiple times in a
141             single program you will need to undefine the object and reinstance it:
142              
143             my $ta = new Term::Activity;
144              
145             while ( doing stuff ) {
146             $ta->tick;
147             }
148              
149             $ta = undef;
150             $ta = new Term::Activity;
151              
152             while ( doing more stuff ) {
153             $ta->tick;
154             }
155              
156             (lather. rinse. repeat.)
157              
158             =head1 KNOWN ISSUES
159              
160             Resizing the window during execution may cause the status bar to stop
161             refreshing properly.
162              
163             Is the window is too small to accomodate the time, label, count, and
164             basic spacing (that is, there is less that 0 spaces for the activity to
165             be displayed) the effect, while being preety in a watching-the-car-wreck
166             way, it is not informative. Remember to keep your label strings short.
167              
168             =head1 BUGS AND SOURCE
169              
170             Bug tracking for this module: https://rt.cpan.org/Dist/Display.html?Name=Term-Activity
171            
172             Source hosting: http://www.github.com/bennie/perl-Term-Activity
173              
174             =head1 VERSION
175              
176             Term::Activity v1.21 2022/01/06
177              
178             =head1 COPYRIGHT
179              
180             (c) 2003-2022, Phillip Pollard
181              
182             =head1 LICENSE
183              
184             This source code is released under the "Perl Artistic License 2.0," the text of
185             which is included in the LICENSE file of this distribution. It may also be
186             reviewed here: http://opensource.org/licenses/artistic-license-2.0
187              
188             =head1 AUTHORSHIP
189              
190             Additional contributions by Kristina Davis
191              
192             Derived from Util::Status 1.12 2003/09/08
193             With permission granted from Health Market Science, Inc.
194              
195             =head1 SEE ALSO:
196              
197             Term::ProgressBar
198              
199             =cut
200              
201             #*************************************************************************
202              
203             package Term::Activity;
204              
205 4     4   66792 use 5.6.0;
  4         44  
206 4     4   21 use strict;
  4         7  
  4         130  
207 4     4   23 use warnings;
  4         6  
  4         9233  
208              
209             $Term::Activity::VERSION='1.21';
210              
211             sub new {
212 7     7 0 2100 my $class = $_[0];
213 7         17 my $self = {};
214 7         17 bless($self,$class);
215              
216             ## configurables
217              
218 7         18 our $chars = undef; # custom charset to use
219 7         11 our $count = 0; # full count
220 7         13 our $debug = 0; # debug output
221 7         13 our $interval = 100; # how often to update the terminal
222 7         15 our $name = ''; # optional label name
223 7         14 our $start = time; # starting time
224              
225              
226 7         14 my $raw_skin = 'wave';
227              
228 7 100 33     45 if ( UNIVERSAL::isa($_[1],'HASH') ) {
    50          
229              
230 5 100       20 $chars = $_[1]->{chars} if defined $_[1]->{chars};
231 5 50       16 $count = $_[1]->{count} if defined $_[1]->{count};
232 5 100       16 $debug = $_[1]->{debug} if defined $_[1]->{debug};
233 5 50       14 $interval = $_[1]->{interval} if defined $_[1]->{interval};
234 5 50       14 $name = $_[1]->{label} if defined $_[1]->{label};
235 5 100       14 $start = $_[1]->{time} if defined $_[1]->{time};
236 5 100 100     44 $raw_skin = 'flat' if defined $_[1]->{skin} and lc($_[1]->{skin}) eq 'flat';
237              
238             } elsif ( defined $_[1] and length $_[1] ) {
239              
240 0         0 $name = $_[1];
241              
242             }
243              
244 7         17 $name =~ s/[\r\n]//g;
245              
246             ## basic settings
247              
248 7         20 our $width = $self->_width_init; # Terminal width
249 7         14 our $last = $start; # last update time
250              
251 7         9 our $marker = 0; # starting position
252 7         16 our $skip = $width - 19; # The area for the chars
253 7         35 our $ants = [ map { ' '; } ( 1 .. $skip ) ]; # characters to display
  427         672  
254              
255             ## bootstrap
256              
257 7         37 our $name_length = length $name; # Length of optional name label
258              
259 7         24 our $ants_method_init = '_ants_' . $raw_skin . '_init';
260 7         15 our $ants_method = '_ants_' . $raw_skin;
261              
262 7         37 $self->_debug("Intializing skin: $raw_skin ($ants_method)");
263 7         42 $self->$ants_method_init($chars);
264              
265 7         28 $self->_debug("Starting count : $count");
266 7         30 $self->_debug("Starting size : $width");
267 7         35 $self->_debug("Starting interval : $interval");
268 7         27 $self->_debug("Starting time : $start");
269 7         28 $self->_debug("Starting last time : $last");
270              
271 7         50 return $self;
272             }
273              
274             sub DESTROY {
275 7     7   2177 my $self = shift @_;
276 7 100       400 if ( our $count > 0 ) {
277 4         10 $self->_update;
278 4         50 print STDERR "\n";
279             }
280             }
281              
282             sub relabel {
283 0     0 0 0 our $name = $_[1];
284 0         0 our $name_length = length $name;
285 0         0 return $name_length;
286             }
287              
288             sub tick {
289 4000     4000 0 22411 my $self = shift @_;
290 4000         4853 our ($count,$interval);
291              
292 4000         5421 $count++;
293 4000         13626 $self->_debug("tick() count: $count interval: $interval");
294              
295 4000 100       8110 print STDERR "\n" if $count == 1;
296 4000 100       7579 return 0 if $count % $interval;
297 3604         6030 return $self->_update;
298             }
299              
300             sub _ants_flat_init {
301 2     2   4 my $self = shift @_;
302 2         4 my $char = shift @_;
303 2         3 our $chars;
304 2 100 66     11 if ( ref $char && scalar(@$char) > 1 ) {
305 1         3 $chars = $char;
306             } else {
307 1         6 $chars = [ '.', '=', '~', '#', '^', '-' ];
308             }
309             }
310              
311             sub _ants_flat {
312 1804     1804   2499 our ( $ants, $chars, $marker, $skip );
313              
314 1804 100       3587 if ($skip > $#$ants) {
315 1800         3980 for my $i ( 0 .. $#$ants - $skip ) {
316 0         0 unshift @$ants, $chars->[0];
317             }
318             } else {
319 4         14 for my $i ( 0 .. $#$ants - $skip ) {
320 18         27 pop @$ants;
321             }
322             }
323 1804 100       3345 if ( $marker >= $skip ) {
324 30         63 push @$chars, shift @$chars;
325 30         48 $marker = 0;
326             } else {
327 1774         4092 $ants->[$marker++] = $chars->[0];
328             }
329 1804         8769 return join('',@$ants);
330             }
331              
332             sub _ants_wave {
333 1804     1804   2431 our ( $ants, $chars, $marker, $skip );
334              
335 1804 50       3446 if ($skip > $#$ants) {
336 0         0 for my $i ( 1 .. $#$ants - $skip) {
337 0         0 unshift @$ants, $chars->[0]->[0];
338             }
339             } else {
340 1804         4021 for my $i ( 1 .. $#$ants - $skip) {
341 16         24 pop @$ants;
342             }
343             }
344 1804 100       3166 if ( $marker >= $skip ) {
345 30         64 $ants->[$skip] = $chars->[0]->[1];
346 30         62 push @$chars, shift @$chars;
347 30         43 $marker = 0;
348             } else {
349 1774         3775 $ants->[$marker++] = $chars->[0]->[1];
350 1774         2555 $ants->[$marker] = $chars->[0]->[0];
351             }
352 1804         8602 return join('',@$ants);
353             }
354              
355             sub _ants_wave_init {
356 5     5   42 my $self = shift @_;
357 5         12 my $c = shift @_;
358 5         7 our $chars;
359 5 100       15 if ($c) {
360 1         3 $chars = $c;
361             } else {
362 4         16 $chars = [ [ '\\', '~' ], [ '/', '_' ] ];
363             }
364             }
365              
366             sub _clock {
367 3608     3608   4822 my $self = shift @_;
368 3608         5686 my $sec = time - our $start;
369 3608         7138 my $hr = int($sec/3600);
370 3608         4930 $sec -= $hr * 3600;
371 3608         4764 my $min = int($sec/60);
372 3608         4552 $sec -= $min * 60;
373 3608         6023 return join ':', map { $self->_zedten($_); } ($hr,$min,$sec);
  10824         16834  
374             }
375              
376             sub _commaify {
377 7216     7216   9741 my $self = shift @_;
378 7216         9499 my $num = shift @_;
379 7216         14373 1 while $num =~ s/(\d)(\d\d\d)(?!\d)/$1,$2/;
380 7216         13903 return $num;
381             }
382              
383             sub _debug {
384 11258     11258   14265 my $self = shift @_;
385 11258 100       21855 return unless our $debug > 0;
386 6         98 print STDERR join( ' ', 'DEBUG:', @_ ) . "\n";
387             }
388              
389             sub _update {
390 3608     3608   4838 my $self = shift @_;
391 3608         4269 our ($ants_method,$count,$interval,$name,$skip,$width,$name_length);
392              
393 3608         6929 $self->_debug('_update()');
394              
395 3608         5994 my $o_interval = $self->_commaify($interval);
396 3608         7285 my $o_interval_length = length $o_interval;
397 3608         5500 my $o_count = $self->_commaify($count);
398 3608         4995 my $o_count_length = length $o_count;
399              
400 3608         7855 $self->_update_width;
401              
402 3608         5357 $skip = $width - 19 - $o_interval_length - $o_count_length - $name_length;
403              
404 3608         4868 my $format;
405             my $out;
406              
407 3608 50       5852 if ( $name_length ) {
408 0         0 $format = "\r\%s \%${o_interval_length}s : [\%${skip}s] \%${o_count_length}s \%${name_length}s ";
409 0         0 $out = sprintf $format, $self->_clock, $o_interval, $self->$ants_method, $o_count, $name;
410             } else {
411 3608         4291 $skip++; # Without the name, gobble up the extra space
412 3608         7086 $format = "\r\%s \%${o_interval_length}s : [\%${skip}s] \%${o_count_length}s ";
413 3608         5484 $out = sprintf $format, $self->_clock, $o_interval, $self->$ants_method, $o_count;
414             }
415              
416 3608         9538 $self->_update_interval;
417              
418 3608         5656 $format = "\%-.${width}s";
419              
420 3608         9186 $self->_debug("_update sprintf: $format\n$out");
421              
422 3608         110721 return print STDERR sprintf $format, $out;
423             }
424              
425             sub _update_interval {
426 3608     3608   5042 my $self = shift @_;
427 3608         4551 my $now = time;
428              
429 3608         4234 our ($interval, $last);
430 3608         4963 my $delta = $now - $last;
431              
432 3608 50 33     11999 if ( $delta > 5 && $interval > $delta ) { # The query is way slow, adjust down
    50 33        
    50          
433 0         0 $interval = int($interval/$delta);
434 0 0       0 $interval = 1 unless $interval;
435             } elsif ( $delta > 2 && $interval > 1 ) { # The query is a little slow
436 0         0 $interval--;
437             } elsif ( $delta < 1 ) { # The query is fast
438 3608         4737 $interval++;
439             }
440              
441 3608         5359 $last = time;
442             }
443              
444             sub _update_width {
445 3608     3608   4858 my $self = shift @_;
446 3608 50       6313 our $width = chars(*STDOUT{IO}) if our $use_term_size;
447             }
448              
449             sub _width_init {
450 7     7   13 my $default = 80;
451 7         14 our $use_term_size = 0;
452              
453 7         12 eval { require Term::Size };
  7         48  
454              
455 7 50       20 return $default if $@;
456              
457 7         219 import Term::Size 'chars';
458 7         104 my ( $cols, $rows ) = chars(*STDOUT{IO});
459              
460 7 50 33     36 if ( defined($cols) and $cols > 0 ) {
461 0         0 $use_term_size = 1;
462 0         0 return $cols;
463             }
464              
465 7         19 return $default;
466             }
467              
468             sub _zedten {
469 10824     10824   13582 my $self = shift @_;
470 10824         13196 my $in = shift @_;
471 10824 50 33     33718 $in = '0'.$in if $in < 10 && $in > -1;
472 10824         31140 return $in;
473             }
474              
475             1;