File Coverage

blib/lib/Term/StatusBar.pm
Criterion Covered Total %
statement 3 157 1.9
branch 0 88 0.0
condition 0 47 0.0
subroutine 1 14 7.1
pod 7 8 87.5
total 11 314 3.5


line stmt bran cond sub pod time code
1             package Term::StatusBar;
2 1     1   5570 no warnings 'portable';
  1         2  
  1         2520  
3              
4             $|++;
5             require 5.6.0;
6             our ($AUTOLOAD, $FH);
7             our $VERSION = '1.18';
8              
9              
10             sub new {
11 0     0 1   my ($class, %params) = @_;
12              
13 0   0       my $self = bless{
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
14             startRow => $params{startRow} || 1,
15             startCol => $params{startCol} || 1,
16             startPos => $params{startPos} || 'top',
17             label => $params{label} || 'Status: ',
18             scale => $params{scale} || 40,
19             totalItems => $params{totalItems} || 1,
20             avgItems => 1,
21             updateCount => 0,
22             char => $params{char} || ' ',
23             count => 0,
24             itemsPP => 1,
25             updateInc => int($params{updateInc}) || 1,
26             curItems => $params{totalItems} || 1,
27             baseScale => 100,
28             start => 0,
29             maxCol => 80,
30             maxRow => 24,
31             prevSubText => undef,
32             subText => undef,
33             subTextAlign => $params{subTextAlign} || 'left',
34             reverse => $params{reverse} || 0,
35             barColor => $params{barColor} || "\033[7;37m",
36             fillColor => $params{fillColor} || "\033[7;34m",
37             colorTerm => $params{colorTerm}ne'0',
38             barStart => undef,
39             subTextChange => undef,
40             subTextLength => undef,
41             fh => $params{fh} || *STDOUT,
42             precision => $params{precision} || 0,
43             showTime => $params{showTime} || 0,
44             lastTime => undef,
45             itemAccum => $params{totalItems} || 1,
46             }, ref $class || $class;
47              
48 0           $FH = $self->{fh};
49              
50 0 0         if (!$self->{colorTerm}){
51 0           $self->{barColor} = '';
52             }
53              
54 0           $self->subText($params{subText});
55 0 0         $self->setItems($params{totalItems}) if $params{totalItems};
56 0           $self->{barStart} = length($self->{label})+1;
57              
58             ## Check if scale exceeds current width of screen
59             ## and adjust accordingly. Not much we can do if
60             ## label exceeds screen width
61 0           $self->_get_max_term();
62              
63 0 0         if ($self->{startPos} eq 'bottom'){
64 0 0         $self->{startRow} = $self->{maxRow}-($self->{subText}?2:1);
65             }
66              
67 0 0         if (($self->{scale} + $self->{barStart} + 5) >= $self->{maxCol}){
68 0           $self->{scale} = $self->{maxCol} - 5 - $self->{barStart};
69             }
70              
71 0 0         if ($self->{precision} > 4){ $self->{precision} = 4; }
  0            
72              
73 0 0         if ($self->{showTime}){
74 0           eval { require Time::HiRes };
  0            
75              
76 0 0         if (!$@){
77 0 0         if ($self->{startPos} ne 'bottom'){
78 0           $self->{startRow}++;
79             }
80             }
81             else{
82 0           $self->{showTime} = 0;
83             }
84             }
85              
86 0           $SIG{INT} = \&{__PACKAGE__."::sigint"};
  0            
87 0           return $self;
88             }
89              
90             ##
91             ## Just in case this isn't done in caller. We
92             ## need to be able to reset the display.
93             ##
94             sub sigint {
95 0     0 0   my $self = shift;
96 0 0         my $offset = $self->{startRow} + ($self->{reverse}?-5:5);
97 0           print $FH "\033[$offset;1H\033[0m\n\n";
98 0           exit;
99             }
100              
101              
102             ##
103             ## Used to get/set object variables.
104             ##
105             sub AUTOLOAD {
106 0     0     my ($self, $val) = @_;
107 0           (my $method = $AUTOLOAD) =~ s/.*:://;
108              
109 0 0         if (exists $self->{$method}){
110 0 0         if (defined $val){
111 0           $self->{$method} = $val;
112             }
113             else{
114 0           return $self->{$method};
115             }
116             }
117             }
118              
119              
120             ##
121             ## Sets the subText and redisplays
122             ##
123             sub subText {
124 0     0 1   my ($self, $newSubText) = @_;
125 0 0         return $self->{subText} if !defined $newSubText;
126              
127 0 0         if ($newSubText ne $self->{subText}){
128 0           $self->{subText} = $newSubText;
129 0           $self->{subTextLength} = length($newSubText);
130 0           $self->{subTextChange} = 1;
131 0           print $FH $self->_printSubText();
132             }
133             else{
134 0           $self->{subTextChange} = 0;
135             }
136             }
137              
138              
139             ##
140             ## Set totalItems, curItems, and itemsPP
141             ##
142             sub setItems {
143 0     0 1   my ($self, $num) = @_;
144              
145             ## Items must be > 0
146 0 0         $num = 1 if !$num;
147 0 0         $self->{totalItems} = $self->{curItems} = abs($num) if !$self->{count};
148              
149 0 0         if ($self->{totalItems} > $self->{baseScale}){
150 0           $self->{itemsPP} = int($self->{totalItems}/$self->{baseScale});
151             }
152             }
153              
154              
155             ##
156             ## Adds more text to current subText
157             ##
158             sub addSubText {
159 0     0 1   my ($self, $text) = @_;
160 0 0 0       return if !defined $text || $text eq '';
161              
162 0 0         $self->{prevSubText} = $self->{subText} if !$self->{prevSubText};
163 0           $self->{subText} = $self->{prevSubText} . $text;
164 0           $self->{subTextChange} = 1;
165             }
166              
167              
168             ##
169             ## Init object on screen
170             ##
171             sub start {
172 0     0 1   my ($self) = @_;
173              
174 0           print $FH "\033[$self->{startRow};$self->{startCol}H", (' 'x($self->{maxCol}-$self->{startCol}));
175 0           print $FH "\033[$self->{startRow};$self->{startCol}H$self->{label}";
176 0           print $FH $self->{barColor}, ($self->{char}x$self->{scale}), "\033[0m";
177              
178 0 0         print $FH $self->_printPercent($self->{reverse}?100:0);
179 0           print $FH $self->_printSubText();
180              
181 0           $self->{start}++;
182             }
183              
184              
185             ##
186             ## Updates approximate time
187             ##
188              
189             sub _calcTime {
190 0     0     my ($self) = @_;
191 0 0         return if !$self->{showTime};
192 0           my ($time);
193              
194 0 0 0       if (!$self->{reverse} && $self->{lastTime}){
195 0           my $tp = &Time::HiRes::tv_interval($self->{lastTime});
196 0           my $tmp = $self->{itemAccum};
197 0           $self->{itemAccum} = $self->{totalItems} - $self->{count};
198              
199             ## Prevent divide by zero errors
200 0 0         if ($tmp-$self->{itemAccum} > 0){
201 0           $tp = ($tp/($tmp-$self->{itemAccum}))*$self->{itemAccum};
202             }
203             else{
204 0           goto NO_TIME;
205             }
206              
207 0           my ($hours, $mins, $secs) = ("00")x3;
208 0 0         if ($tp >= 3600){
209 0           $hours = sprintf("%02d", int($tp/3600));
210 0           $tp -= $hours*3600;
211             }
212 0 0         if ($tp >= 60){
213 0           $mins = sprintf("%02d", int($tp/60));
214 0           $tp -= $mins*60;
215             }
216 0 0         if ($tp >= 1){
217 0           $secs = sprintf("%02d", int($tp));
218             }
219              
220 0           $time = "$hours:$mins:$secs";
221             }
222             else{
223 0           NO_TIME:
224             $time = "00:00:00";
225             }
226              
227 0           my $pos = int($self->{scale}/2) + $self->{barStart}-5;
228 0           my $t = "\033[".($self->{startRow}-1).";$self->{startCol}H";
229 0           $t .= ' 'x($self->{barStart}+$self->{scale});
230 0           $t .= "\033[".($self->{startRow}-1).";${pos}H".$time;
231              
232 0           print $FH $t;
233 0           $self->{lastTime} = [&Time::HiRes::gettimeofday()];
234             }
235              
236              
237             ##
238             ## Updates the status bar on screen
239             ##
240             sub update {
241 0     0 1   my ($self, $items) = @_;
242 0 0         $self->start if !$self->{start};
243 0           $self->{updateCount}++;
244              
245             ## Determines if an update is needed
246 0 0         if (!$items){
247 0           $self->{count}++;
248              
249 0 0         if (--$self->{curItems} % ($self->{itemsPP}*int($self->{updateInc}))){
250 0           return;
251             }
252             }
253             else{
254             ## This stuff is for uneven updates, like processing files by line
255 0           $self->{curItems} -= $items;
256 0           $self->{count} += $items;
257 0           $self->{avgItems} = int($self->{count}/$self->{updateCount});
258              
259 0 0         if ($self->{curItems} % ($self->{avgItems}*int($self->{updateInc}))){
260 0           return;
261             }
262             }
263              
264 0           my $percent = $self->{count}/$self->{totalItems};
265 0 0         $percent = 1-$percent if $self->{reverse};
266 0           my $count = int($percent*$self->{scale});
267 0           $percent = sprintf("%.$self->{precision}f", $percent*100);
268              
269 0           $self->_calcTime();
270              
271             ## Due to calls to int(), the numbers sometimes do not work out
272             ## exactly. If the bar is suppose to be full and at 100% this
273             ## makes sure it happens
274 0 0         if ($self->{totalItems} - $self->{count} < $self->{itemsPP}){
275 0           $count = $self->{scale};
276 0 0         $percent = $self->{reverse}?0:100;
277             }
278              
279 0           my $startCol = $self->{barStart}+$count;
280 0           my $bar;
281              
282             ## Make sure bar has correct color at its final state
283 0 0         if ($percent != 0){
284 0           $bar = "\033[$self->{startRow};$self->{barStart}H\033[K".$self->{fillColor}.($self->{char}x($count))."\033[0m";
285 0           $bar .= "\033[$self->{startRow};${startCol}H".$self->{barColor}.($self->{char}x($self->{scale}-$count))."\033[0m";
286             }
287             else{
288 0           $bar = "\033[$self->{startRow};${startCol}H".$self->{barColor}.($self->{char}x($self->{scale}-$count))."\033[0m";
289             }
290              
291 0           $bar .= $self->_printPercent($percent);
292 0           $bar .= $self->_printSubText();
293              
294 0           print $FH $bar;
295             }
296              
297              
298             ##
299             ## Clear the count of status bar. This is so you can
300             ## use the same object several times and set the
301             ## scale and totalItems differently each run
302             ##
303             sub reset {
304 0     0 1   my ($self, $newDefaults) = @_;
305              
306 0           @$self{qw(count start prevSubText subText
307             subTextChange subTextLength curItems
308             totalItems)} = (0,0,'','',0,0,0,0);
309              
310 0 0         if ($newDefaults){
311 0           for my $k (keys %$newDefaults){
312             ## Just in case
313 0 0         next if $k eq 'reset';
314 0           $self->$k($newDefaults->{$k});
315             }
316             }
317             }
318              
319              
320             ##
321             ## Prints percent to screen
322             ##
323             sub _printPercent {
324 0     0     my ($self, $percent) = @_;
325              
326 0           my $t = "\033[$self->{startRow};".($self->{barStart}+$self->{scale}+1)."H";
327 0           $t .= "\033[37m$percent% \033[0m";
328              
329 0           return $t;
330             }
331              
332              
333             ##
334             ## Calculates position to place sub-text
335             ##
336             sub _printSubText {
337 0     0     my ($self) = @_;
338 0           my ($pos, $t, $subTemp);
339              
340 0 0 0       return if !$self->{subText} || !$self->{subTextChange};
341              
342             ## Truncate subText if necessary
343 0 0         if ($pos+$self->{subTextLength} > $self->{scale}+$self->{barStart}){
344 0           $subTemp = $self->{subText};
345 0           $self->{subText} = substr($self->{subText}, 0, $self->{subTextLength}-($self->{scale}+$self->{barStart})).'...';
346 0           $self->{subTextLength} = length($self->{subText});
347             }
348              
349 0 0         if ($self->{subTextAlign} eq 'center'){
    0          
350 0           my $tmp = int($self->{scale}/2) + $self->{barStart};
351 0           $pos = $tmp - int($self->{subTextLength}/2);
352             }
353             elsif ($self->{subTextAlign} eq 'right'){
354 0           $pos = $self->{barStart} + $self->{scale} + $self->{startCol} - $self->{subTextLength};
355             }
356             else{
357 0           $pos = $self->{startCol}+$self->{barStart};
358             }
359              
360 0 0         $pos = 0 if $pos < 0;
361              
362 0           $t = "\033[".($self->{startRow}+1).";$self->{startCol}H\033[K";
363 0           $t .= "\033[".($self->{startRow}+1).";${pos}H".$self->{subText};
364              
365             ## Restore original subText and length
366 0 0         if ($subTemp){
367 0           $self->{subText} = $subTemp;
368 0           $self->{subTextLength} = length($self->{subText});
369             }
370              
371 0           return $t;
372             }
373              
374              
375             sub _get_max_term{
376 0     0     my ($self) = @_;
377              
378             ## suck in Term::Size, if possible
379 0           eval { require Term::Size };
  0            
380              
381             ## no Term::Size; try using tput to find terminal width
382 0 0         if($@){
383             ## find tput via poor man's "which"
384 0           for my $path (split /:/, $ENV{'PATH'}){
385 0 0         next if !(-x "$path/tput");
386 0           chomp($self->{maxCol} = `$path/tput cols`);
387 0           last;
388             }
389             }
390             else {
391 0           ($self->{maxCol}, $self->{maxRow}) = &Term::Size::chars($self->{fh});
392             }
393             }
394              
395              
396             1;
397             __END__