File Coverage

blib/lib/ProgressMonitor/Stringify/Fields/Bar.pm
Criterion Covered Total %
statement 79 82 96.3
branch 18 32 56.2
condition 5 9 55.5
subroutine 12 12 100.0
pod 2 2 100.0
total 116 137 84.6


line stmt bran cond sub pod time code
1             package ProgressMonitor::Stringify::Fields::Bar;
2            
3 2     2   24055 use warnings;
  2         5  
  2         68  
4 2     2   14 use strict;
  2         4  
  2         72  
5            
6 2     2   14 use ProgressMonitor::Exceptions;
  2         4  
  2         136  
7             require ProgressMonitor::Stringify::Fields::AbstractDynamicField if 0;
8            
9             # Attributes:
10             # innerWidth
11             # The computed width of the bar itself
12             # idleTravellerIndex
13             # The location to write the 'traveller' when total is unknown
14             # idleSpinnerIndex
15             # The next spinner in the sequence to use when resolution is too small
16             # allEmpty
17             # A precomputed bar that is empty
18             # lastFiller
19             # The last string to fill the bar (to detect resolution issues)
20             #
21             use classes
22 2         19 extends => 'ProgressMonitor::Stringify::Fields::AbstractDynamicField',
23             new => 'new',
24             attrs_pr => ['innerWidth', 'idleTravellerIndex', 'idleSpinnerIndex', 'allEmpty', 'lastFiller', 'inited'],
25             throws => ['X::ProgressMonitor::InsufficientWidth'],
26 2     2   11 ;
  2         4  
27            
28             sub new
29             {
30 1     1   20 my $class = shift;
31 1         3 my $cfg = shift;
32            
33 1         19 my $self = $class->SUPER::_new($cfg, $CLASS);
34            
35 1         12 $cfg = $self->_get_cfg;
36            
37             # compute the width, taking into account all the user choices
38             #
39 1         5 my $minWidth = $cfg->get_minWidth;
40 1         7 my $width =
41             length($cfg->get_leftWall) + $cfg->get_idleLeftSpace + length($cfg->get_idleTraveller) + $cfg->get_idleRightSpace +
42             length($cfg->get_rightWall);
43 1 50       18 $width = $minWidth if $minWidth > $width;
44 1 50       5 X::ProgressMonitor::InsufficientWidth->throw($width) if ($width > $cfg->get_maxWidth);
45            
46 1         15 $self->_set_width($width);
47            
48             # init the instance vars not affected by width
49             #
50 1         4 $self->{$ATTR_idleTravellerIndex} = 0;
51 1         8 $self->{$ATTR_idleSpinnerIndex} = 0;
52 1         4 $self->{$ATTR_lastFiller} = $cfg->get_fillCharacter;
53 1         9 $self->{$ATTR_inited} = 0;
54            
55 1         20 return $self;
56             }
57            
58             sub widthChange
59             {
60 1     1 1 2 my $self = shift;
61            
62 1         5 my $cfg = $self->_get_cfg;
63            
64             # recompute some vars
65             #
66 1         19 my $innerWidth = $self->get_width - length($cfg->get_leftWall) - length($cfg->get_rightWall);
67 1         11 $self->{$ATTR_innerWidth} = $innerWidth;
68 1         4 $self->{$ATTR_allEmpty} = $cfg->get_emptyCharacter x $innerWidth;
69            
70 1         9 return;
71             }
72            
73             sub render
74             {
75 23     23 1 33 my $self = shift;
76 23         32 my $state = shift;
77 23         101 my $tick = shift;
78 23         34 my $totalTicks = shift;
79 23         31 my $clean = shift;
80            
81 23         81 my $cfg = $self->_get_cfg;
82            
83 23         56 my $iw = $self->{$ATTR_innerWidth};
84 23         53 my $bar = $self->{$ATTR_allEmpty};
85 23 100       45 if (defined($totalTicks))
86             {
87             # the total is known, so compute how much filler we need to indicate the ratio
88             #
89 12 50 33     74 my $ratio = defined($totalTicks) && $totalTicks > 0 ? ($tick / $totalTicks) : 0;
90 12         46 my $filler = $cfg->get_fillCharacter x ($ratio * $iw);
91 12         90 substr($bar, 0, length($filler), $filler);
92            
93             # unless we're requested to be 'clean' and in case the filler is the
94             # same as last time (and we're not full), twirl the spinner
95             #
96 12 50 66     150 if (!$clean && $ratio < 1 && $filler eq $self->{$ATTR_lastFiller})
      66        
97             {
98 0         0 my $lf = length($filler);
99 0         0 my $seq = $cfg->get_idleSpinnerSequence;
100 0 0       0 substr($bar, ($lf == 0 ? 0 : $lf - 1), 1, $seq->[$self->{$ATTR_idleSpinnerIndex}++ % @$seq]);
101             }
102 12         35 $self->{$ATTR_lastFiller} = $filler;
103             }
104             else
105             {
106 11 100       30 if (!$self->{$ATTR_inited})
107             {
108             # first call, do nothing
109             #
110 1         2 $self->{$ATTR_inited} = 1;
111             }
112             else
113             {
114             # total is unknown (or we're still in prep mode)
115             # run the traveller in round robin in the bar
116             #
117 10         27 my $begin = $self->{$ATTR_idleTravellerIndex}++ % $iw;
118 10         34 my $it = $cfg->get_idleTraveller;
119 10         63 for (0 .. (length($it) - 1))
120             {
121 30         66 substr($bar, $begin, 1, substr($it, $_, 1));
122 30 100       89 $begin = 0 if ++$begin >= $iw;
123             }
124             }
125             }
126            
127 23         95 return $cfg->get_leftWall . $bar . $cfg->get_rightWall;
128             }
129            
130             ###
131            
132             package ProgressMonitor::Stringify::Fields::BarConfiguration;
133            
134 2     2   2804 use strict;
  2         6  
  2         63  
135 2     2   54 use warnings;
  2         5  
  2         99  
136            
137             use classes
138 2         33 extends => 'ProgressMonitor::Stringify::Fields::AbstractDynamicFieldConfiguration',
139             attrs => [
140             'emptyCharacter', 'fillCharacter', 'leftWall', 'rightWall',
141             'idleTraveller', 'idleLeftSpace', 'idleRightSpace', 'idleSpinnerSequence'
142             ],
143 2     2   10 ;
  2         3  
144            
145             sub defaultAttributeValues
146             {
147 1     1   3 my $self = shift;
148            
149             return {
150 1         2 %{$self->SUPER::defaultAttributeValues()},
  1         13  
151             emptyCharacter => '.',
152             fillCharacter => '*',
153             leftWall => '[',
154             rightWall => ']',
155             idleTraveller => '==>',
156             idleLeftSpace => 1,
157             idleRightSpace => 1,
158             idleSpinnerSequence => ['-', '\\', '|', '/'],
159             };
160             }
161            
162             sub checkAttributeValues
163             {
164 1     1   3 my $self = shift;
165            
166 1         11 $self->SUPER::checkAttributeValues;
167            
168 1 50       9 X::Usage->throw("length of leftWall can not be less than 0") if length($self->get_leftWall) < 0;
169 1 50       11 X::Usage->throw("length of rightWall can not be less than 0") if length($self->get_rightWall) < 0;
170 1 50       11 X::Usage->throw("length of emptyCharacter must have length 1") if length($self->get_emptyCharacter) != 1;
171 1 50       10 X::Usage->throw("length of fillCharacter must have length 1") if length($self->get_fillCharacter) != 1;
172 1 50       9 X::Usage->throw("idleLeftSpace can not be less than 0") if $self->get_idleLeftSpace < 0;
173 1 50       10 X::Usage->throw("idleRightSpace can not be less than 0") if $self->get_idleRightSpace < 0;
174 1         9 my $seq = $self->get_idleSpinnerSequence;
175 1 50       7 X::Usage->throw("idleSpinnerSequence must be an array") unless ref($seq) eq 'ARRAY';
176 1         4 for (@$seq)
177             {
178 4 50       12 X::Usage->throw("all idleSpinnerSequence elements must have length of 1") if length($_) != 1;
179             }
180            
181 1         4 return;
182             }
183            
184             ###########################
185            
186             =head1 NAME
187            
188             ProgressMonitor::Stringify::Field::Bar - a field implementation that renders progress
189             as a bar.
190            
191             =head1 SYNOPSIS
192            
193             # call someTask and give it a monitor to call us back
194             #
195             my $bar = ProgressMonitor::Stringify::Fields::Bar->new;
196             someTask(ProgressMonitor::Stringify::ToStream->new({fields => [ $bar ]});
197            
198             =head1 DESCRIPTION
199            
200             This is a dynamic field representing progress as a bar typically of this form:
201             "[###....]" etc. It will consume as much room as it can get unless limited by maxWidth.
202            
203             It is very configurable in terms of what it prints. By default it will also do
204             useful things to indicate 'idle' progress, i.e. either no ticks advanced, but still
205             tick is called, or just 'unknown' work.
206            
207             Inherits from ProgressMonitor::Stringify::Fields::AbstractDynamicField.
208            
209             =head1 METHODS
210            
211             =over 2
212            
213             =item new( $hashRef )
214            
215             Configuration data:
216            
217             =over 2
218            
219             =item emptyCharacter (default => '.')
220            
221             The character that should be used to indicate an empty location in the bar.
222            
223             =item fillCharacter (default => '#')
224            
225             The character that should be used to indicate a full location in the bar.
226            
227             =item leftWall (default => '[')
228            
229             The string that should be used to indicate the left wall of the bar. This can
230             be set to an empty string if you don't want a wall.
231            
232             =item rightWall (default => ']')
233            
234             The string that should be used to indicate the right wall of the bar. This can
235             be set to an empty string if you don't want a wall.
236            
237             =item idleTraveller (default => '==>')
238            
239             The string that should be used as a moving piece in order to indicate progress
240             for totals that are unknown.
241            
242             =item idleLeftSpace (default => 1)
243            
244             Amount of characters that should be allocated to the left of the idleTraveller.
245             This is necessary to insure that the idleTraveller has at least some room to travel
246             in.
247            
248             =item idleLeftRight (default => 1)
249            
250             Amount of characters that should be allocated to the right of the idleTraveller.
251             This is necessary to insure that the idleTraveller has at least some room to travel
252             in.
253            
254             =item idleSpinnerSequence (default => ['-', '\\', '|', '/'])
255            
256             This should be a reference to a list of characters that should be used in sequence
257             for ticks that doesn't advance the bar, but we still want to show that something
258             is happening. If you do not wish this to happen at all, set to a single element list
259             with the fillCharacter.
260            
261             =back
262            
263             =back
264            
265             =head1 AUTHOR
266            
267             Kenneth Olwing, C<< >>
268            
269             =head1 BUGS
270            
271             I wouldn't be surprised! If you can come up with a minimal test that shows the
272             problem I might be able to take a look. Even better, send me a patch.
273            
274             Please report any bugs or feature requests to
275             C, or through the web interface at
276             L.
277             I will be notified, and then you'll automatically be notified of progress on
278             your bug as I make changes.
279            
280             =head1 SUPPORT
281            
282             You can find general documentation for this module with the perldoc command:
283            
284             perldoc ProgressMonitor
285            
286             =head1 ACKNOWLEDGEMENTS
287            
288             Thanks to my family. I'm deeply grateful for you!
289            
290             =head1 COPYRIGHT & LICENSE
291            
292             Copyright 2006,2007 Kenneth Olwing, all rights reserved.
293            
294             This program is free software; you can redistribute it and/or modify it
295             under the same terms as Perl itself.
296            
297             =cut
298            
299             1; # End of ProgressMonitor::Stringify::Fields::Bar