File Coverage

blib/lib/Term/Activity.pm
Criterion Covered Total %
statement 144 158 91.1
branch 42 56 75.0
condition 9 18 50.0
subroutine 18 19 94.7
pod 0 3 0.0
total 213 254 83.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.20 2014/04/30
177              
178             =head1 COPYRIGHT
179              
180             (c) 2003-2014, 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   33505 use 5.6.0;
  4         12  
  4         188  
206 4     4   20 use strict;
  4         7  
  4         129  
207 4     4   18 use warnings;
  4         5  
  4         9594  
208              
209             $Term::Activity::VERSION='1.20';
210              
211             sub new {
212 7     7 0 974 my $class = $_[0];
213 7         18 my $self = {};
214 7         27 bless($self,$class);
215              
216             ## configurables
217              
218 7         15 our $chars = undef; # custom charset to use
219 7         17 our $count = 0; # full count
220 7         15 our $debug = 0; # debug output
221 7         15 our $interval = 100; # how often to update the terminal
222 7         14 our $name = ''; # optional label name
223 7         42 our $start = time; # starting time
224              
225              
226 7         14 my $raw_skin = 'wave';
227              
228 7 100 33     66 if ( UNIVERSAL::isa($_[1],'HASH') ) {
    50          
229              
230 5 100       27 $chars = $_[1]->{chars} if defined $_[1]->{chars};
231 5 50       21 $count = $_[1]->{count} if defined $_[1]->{count};
232 5 100       35 $debug = $_[1]->{debug} if defined $_[1]->{debug};
233 5 50       25 $interval = $_[1]->{interval} if defined $_[1]->{interval};
234 5 50       19 $name = $_[1]->{label} if defined $_[1]->{label};
235 5 100       23 $start = $_[1]->{time} if defined $_[1]->{time};
236 5 100 100     53 $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         20 $name =~ s/[\r\n]//g;
245              
246             ## basic settings
247              
248 7         27 our $width = $self->_width_init; # Terminal width
249 7         16 our $last = $start; # last update time
250              
251 7         12 our $marker = 0; # starting position
252 7         20 our $skip = $width - 19; # The area for the chars
253 7         296 our $ants = [ map { ' '; } ( 1 .. $skip ) ]; # characters to display
  427         632  
254              
255             ## bootstrap
256              
257 7         56 our $name_length = length $name; # Length of optional name label
258              
259 7         23 our $ants_method_init = '_ants_' . $raw_skin . '_init';
260 7         18 our $ants_method = '_ants_' . $raw_skin;
261              
262 7         46 $self->_debug("Intializing skin: $raw_skin ($ants_method)");
263 7         36 $self->$ants_method_init($chars);
264              
265 7         49 $self->_debug("Starting count : $count");
266 7         47 $self->_debug("Starting size : $width");
267 7         29 $self->_debug("Starting interval : $interval");
268 7         30 $self->_debug("Starting time : $start");
269 7         28 $self->_debug("Starting last time : $last");
270              
271 7         26 return $self;
272             }
273              
274             sub DESTROY {
275 7     7   3395 my $self = shift @_;
276 7 100       365 if ( our $count > 0 ) {
277 4         11 $self->_update;
278 4         233 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 17164 my $self = shift @_;
290 4000         5013 our ($count,$interval);
291              
292 4000         4572 $count++;
293 4000         12556 $self->_debug("tick() count: $count interval: $interval");
294              
295 4000 100       10545 print STDERR "\n" if $count == 1;
296 4000 100       10449 return 0 if $count % $interval;
297 3434         8350 return $self->_update;
298             }
299              
300             sub _ants_flat_init {
301 2     2   5 my $self = shift @_;
302 2         4 my $char = shift @_;
303 2         3 our $chars;
304 2 100 66     13 if ( ref $char && scalar(@$char) > 1 ) {
305 1         4 $chars = $char;
306             } else {
307 1         3 $chars = [ '.', '=', '~', '#', '^', '-' ];
308             }
309             }
310              
311             sub _ants_flat {
312 1634     1634   2471 our ( $ants, $chars, $marker, $skip );
313              
314 1634 100       4272 if ($skip > $#$ants) {
315 1630         4099 for my $i ( 0 .. $#$ants - $skip ) {
316 0         0 unshift @$ants, $chars->[0];
317             }
318             } else {
319 4         11 for my $i ( 0 .. $#$ants - $skip ) {
320 16         28 pop @$ants;
321             }
322             }
323 1634 100       3151 if ( $marker >= $skip ) {
324 27         58 push @$chars, shift @$chars;
325 27         38 $marker = 0;
326             } else {
327 1607         3540 $ants->[$marker++] = $chars->[0];
328             }
329 1634         10575 return join('',@$ants);
330             }
331              
332             sub _ants_wave {
333 1804     1804   2645 our ( $ants, $chars, $marker, $skip );
334              
335 1804 50       3795 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         4583 for my $i ( 1 .. $#$ants - $skip) {
341 16         28 pop @$ants;
342             }
343             }
344 1804 100       5049 if ( $marker >= $skip ) {
345 30         65 $ants->[$skip] = $chars->[0]->[1];
346 30         62 push @$chars, shift @$chars;
347 30         41 $marker = 0;
348             } else {
349 1774         3872 $ants->[$marker++] = $chars->[0]->[1];
350 1774         3518 $ants->[$marker] = $chars->[0]->[0];
351             }
352 1804         13302 return join('',@$ants);
353             }
354              
355             sub _ants_wave_init {
356 5     5   15 my $self = shift @_;
357 5         10 my $c = shift @_;
358 5         7 our $chars;
359 5 100       19 if ($c) {
360 1         3 $chars = $c;
361             } else {
362 4         19 $chars = [ [ '\\', '~' ], [ '/', '_' ] ];
363             }
364             }
365              
366             sub _clock {
367 3438     3438   4723 my $self = shift @_;
368 3438         4564 my $sec = time - our $start;
369 3438         6017 my $hr = int($sec/3600);
370 3438         16481 $sec -= $hr * 3600;
371 3438         4550 my $min = int($sec/60);
372 3438         8986 $sec -= $min * 60;
373 3438         5286 return join ':', map { $self->_zedten($_); } ($hr,$min,$sec);
  10314         31390  
374             }
375              
376             sub _commaify {
377 6876     6876   8934 my $self = shift @_;
378 6876         12480 my $num = shift @_;
379 6876         15923 1 while $num =~ s/(\d)(\d\d\d)(?!\d)/$1,$2/;
380 6876         19650 return $num;
381             }
382              
383             sub _debug {
384 10918     10918   14325 my $self = shift @_;
385 10918 100       29265 return unless our $debug > 0;
386 6         520 print STDERR join( ' ', 'DEBUG:', @_ ) . "\n";
387             }
388              
389             sub _update {
390 3438     3438   4406 my $self = shift @_;
391 3438         5055 our ($ants_method,$count,$interval,$name,$skip,$width,$name_length);
392              
393 3438         6483 $self->_debug('_update()');
394              
395 3438         6537 my $o_interval = $self->_commaify($interval);
396 3438         5301 my $o_interval_length = length $o_interval;
397 3438         7950 my $o_count = $self->_commaify($count);
398 3438         4450 my $o_count_length = length $o_count;
399              
400 3438         7128 $self->_update_width;
401              
402 3438         5471 $skip = $width - 19 - $o_interval_length - $o_count_length - $name_length;
403              
404 3438         3969 my $format;
405             my $out;
406              
407 3438 50       8799 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 3438         3966 $skip++; # Without the name, gobble up the extra space
412 3438         8300 $format = "\r\%s \%${o_interval_length}s : [\%${skip}s] \%${o_count_length}s ";
413 3438         6676 $out = sprintf $format, $self->_clock, $o_interval, $self->$ants_method, $o_count;
414             }
415              
416 3438         9322 $self->_update_interval;
417              
418 3438         5700 $format = "\%-.${width}s";
419              
420 3438         9761 $self->_debug("_update sprintf: $format\n$out");
421              
422 3438         273292 return print STDERR sprintf $format, $out;
423             }
424              
425             sub _update_interval {
426 3438     3438   4695 my $self = shift @_;
427 3438         4021 my $now = time;
428              
429 3438         4111 our ($interval, $last);
430 3438         5766 my $delta = $now - $last;
431              
432 3438 50 33     18865 if ( $delta > 5 && $interval > $delta ) { # The query is way slow, adjust down
    50 33        
    100          
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 3437         4425 $interval++;
439             }
440              
441 3438         6477 $last = time;
442             }
443              
444             sub _update_width {
445 3438     3438   4955 my $self = shift @_;
446 3438 50       9577 our $width = chars(*STDOUT{IO}) if our $use_term_size;
447             }
448              
449             sub _width_init {
450 7     7   16 my $default = 80;
451 7         15 our $use_term_size = 0;
452              
453 7         12 eval { require Term::Size };
  7         3708  
454              
455 7 50       26099 return $default if $@;
456              
457 7         377 import Term::Size 'chars';
458 7         245 my ( $cols, $rows ) = chars(*STDOUT{IO});
459              
460 7 50       29 if ( $cols > 0 ) {
461 0         0 $use_term_size = 1;
462 0         0 return $cols;
463             }
464              
465 7         26 return $default;
466             }
467              
468             sub _zedten {
469 10314     10314   13670 my $self = shift @_;
470 10314         13210 my $in = shift @_;
471 10314 50 33     61327 $in = '0'.$in if $in < 10 && $in > -1;
472 10314         37603 return $in;
473             }
474              
475             1;