File Coverage

blib/lib/Term/Sk.pm
Criterion Covered Total %
statement 198 219 90.4
branch 74 102 72.5
condition 22 24 91.6
subroutine 19 23 82.6
pod 0 17 0.0
total 313 385 81.3


line stmt bran cond sub pod time code
1             package Term::Sk;
2             $Term::Sk::VERSION = '0.17';
3 1     1   810 use strict;
  1         2  
  1         42  
4 1     1   4 use warnings;
  1         2  
  1         30  
5              
6 1     1   1895 use Time::HiRes qw( time );
  1         1868  
  1         5  
7 1     1   227 use Fcntl qw(:seek);
  1         2  
  1         3231  
8              
9             require Exporter;
10              
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'} } );
14             our @EXPORT = qw();
15              
16             our $errcode = 0;
17             our $errmsg = '';
18              
19             sub new {
20 27     27 0 5918 shift;
21 27         46 my $self = {};
22 27         39 bless $self;
23              
24 27         31 $errcode = 0;
25 27         46 $errmsg = '';
26              
27 27         119 my %hash = (freq => 1, base => 0, target => 1_000, quiet => 0, test => 0, num => q{9_999});
28 27 50       107 %hash = (%hash, %{$_[1]}) if defined $_[1];
  27         170  
29              
30 27 50       92 my $format = defined $_[0] ? $_[0] : '%8c';
31              
32 27         82 $self->{base} = $hash{base};
33 27         37 $self->{target} = $hash{target};
34 27         34 $self->{quiet} = $hash{quiet};
35 27         43 $self->{test} = $hash{test};
36 27         45 $self->{format} = $format;
37 27         39 $self->{freq} = $hash{freq};
38 27         41 $self->{value} = $hash{base};
39 27         90 $self->{mock_tm} = $hash{mock_tm};
40 27         41 $self->{oldtext} = '';
41 27         41 $self->{line} = '';
42 27         37 $self->{pdisp} = '#';
43 27         37 $self->{commify} = $hash{commify};
44 27 100       83 $self->{token} = defined($hash{token}) ? ref($hash{token}) eq 'ARRAY' ? $hash{token} : [$hash{token}] : [];
    100          
45              
46 27 50       71 unless (defined $self->{quiet}) {
47 0         0 $self->{quiet} = !-t STDOUT;
48             }
49              
50 27 100       64 if ($hash{num} eq '9') {
51 1         2 $self->{sep} = '';
52 1         2 $self->{group} = 0;
53             }
54             else {
55 26 100       197 my ($sep, $group) = $hash{num} =~ m{\A 9 ([^\d\+\-]) (9+) \z}xms or do {
56 1         2 $errcode = 95;
57 1         3 $errmsg = qq{Can't parse num => '$hash{num}'};
58 1         9 die sprintf('Error-%04d: %s', $errcode, $errmsg);
59             };
60 25         48 $self->{sep} = $sep;
61 25         50 $self->{group} = length($group);
62             }
63              
64             # Here we de-compose the format into $self->{action}
65              
66 26         71 $self->{action} = [];
67              
68 26         40 my $fmt = $format;
69 26         69 while ($fmt ne '') {
70 48 100       362 if ($fmt =~ m{^ ([^%]*) % (.*) $}xms) {
71 44         115 my ($literal, $portion) = ($1, $2);
72 44 100       161 unless ($portion =~ m{^ (\d*) ([a-zA-Z]) (.*) $}xms) {
73 1         2 $errcode = 100;
74 1         3 $errmsg = qq{Can't parse '%[]' from '%$portion', total line is '$format'};
75 1         12 die sprintf('Error-%04d: %s', $errcode, $errmsg);
76             }
77              
78 43         94 my ($repeat, $disp_code, $remainder) = ($1, $2, $3);
79              
80 43 100       91 if ($repeat eq '') { $repeat = 1; }
  23         28  
81 43 50       120 if ($repeat < 1) { $repeat = 1; }
  0         0  
82              
83 43 100 100     917 unless ($disp_code eq 'b'
      100        
      100        
      100        
      100        
      100        
      100        
84             or $disp_code eq 'c'
85             or $disp_code eq 'd'
86             or $disp_code eq 'm'
87             or $disp_code eq 'p'
88             or $disp_code eq 'P'
89             or $disp_code eq 't'
90             or $disp_code eq 'k') {
91 1         2 $errcode = 110;
92 1         5 $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 1         12 die sprintf('Error-%04d: %s', $errcode, $errmsg);
94             }
95              
96 42 100       91 push @{$self->{action}}, {type => '*lit', len => length($literal), lit => $literal} if length($literal) > 0;
  40         195  
97 42         45 push @{$self->{action}}, {type => $disp_code, len => $repeat};
  42         130  
98 42         121 $fmt = $remainder;
99             }
100             else {
101 4         6 push @{$self->{action}}, {type => '*lit', len => length($fmt), lit => $fmt};
  4         19  
102 4         11 $fmt = '';
103             }
104             }
105              
106             # End of format de-composition
107              
108 24         43 $self->{tick} = 0;
109 24         34 $self->{out} = 0;
110 24 100       96 $self->{sec_begin} = $self->{mock_tm} ? $self->{mock_tm} : time;
111 24         55 $self->{sec_print} = 0;
112              
113 24         55 $self->show;
114              
115 24         95 return $self;
116             }
117              
118             sub mock_time {
119 5     5 0 2739 my $self = shift;
120              
121 5         16 $self->{mock_tm} = $_[0];
122             }
123              
124             sub whisper {
125 1     1 0 478 my $self = shift;
126            
127 1         5 my $back = qq{\010} x length $self->{oldtext};
128 1         4 my $blank = q{ } x length $self->{oldtext};
129              
130 1         5 $self->{line} = join('', $back, $blank, $back, @_, $self->{oldtext});
131              
132 1 50       7 unless ($self->{test}) {
133 0         0 local $| = 1;
134 0 0       0 if ($self->{quiet}) {
135 0         0 print @_;
136             }
137             else {
138 0         0 print $self->{line};
139             }
140             }
141             }
142              
143             sub get_line {
144 35     35 0 7365 my $self = shift;
145              
146 35         160 return $self->{line};
147             }
148              
149 61 100   61 0 5100 sub up { my $self = shift; $self->{value} += defined $_[0] ? $_[0] : 1; $self->show_maybe; }
  61         137  
  61         102  
150 0 0   0 0 0 sub down { my $self = shift; $self->{value} -= defined $_[0] ? $_[0] : 1; $self->show_maybe; }
  0         0  
  0         0  
151 28     28 0 486 sub close { my $self = shift; $self->{value} = undef; $self->show; }
  28         43  
  28         57  
152              
153 1     1 0 8 sub ticks { my $self = shift; return $self->{tick} }
  1         7  
154              
155             sub token {
156 2     2 0 1077 my $self = shift;
157 2         4 my $tk = shift;
158 2 100       10 $self->{token} = ref($tk) eq 'ARRAY' ? $tk : [$tk];
159 2         7 $self->show;
160             }
161              
162             sub tok_maybe {
163 1     1 0 562 my $self = shift;
164 1         3 my $tk = shift;
165 1 50       9 $self->{token} = ref($tk) eq 'ARRAY' ? $tk : [$tk];
166 1         5 $self->show_maybe;
167             }
168              
169             sub DESTROY {
170 27     27   13271 my $self = shift;
171 27         84 $self->close;
172             }
173              
174             sub show_maybe {
175 62     62 0 90 my $self = shift;
176              
177 62         102 $self->{line} = '';
178              
179 62 100       186 my $sec_now = ($self->{mock_tm} ? $self->{mock_tm} : time) - $self->{sec_begin};
180 62         73 my $sec_prev = $self->{sec_print};
181              
182 62         61 $self->{sec_print} = $sec_now;
183 62         74 $self->{tick}++;
184              
185 62 50       175 if ($self->{freq} eq 's') {
    50          
186 0 0       0 if (int($sec_prev) != int($sec_now)) {
187 0         0 $self->show;
188             }
189             }
190             elsif ($self->{freq} eq 'd') {
191 0 0       0 if (int($sec_prev * 10) != int($sec_now * 10)) {
192 0         0 $self->show;
193             }
194             }
195             else {
196 62 50       152 unless ($self->{tick} % $self->{freq}) {
197 62         114 $self->show;
198             }
199             }
200             }
201              
202             sub show {
203 116     116 0 127 my $self = shift;
204 116         140 $self->{out}++;
205              
206 116         232 my $back = qq{\010} x length $self->{oldtext};
207 116         165 my $blank = q{ } x length $self->{oldtext};
208              
209 116         113 my $text = '';
210 116 100       251 if (defined $self->{value}) {
211              
212             # Here we compose a string based on $self->{action} (which, of course, is the previously de-composed format)
213              
214 88         81 my $tok_ind = 0;
215              
216 88         77 for my $act (@{$self->{action}}) {
  88         176  
217 214         384 my ($type, $lit, $len) = ($act->{type}, $act->{lit}, $act->{len});
218              
219 214 100       386 if ($type eq '*lit') { # print (= append to $text) a simple literal
220 122         137 $text .= $lit;
221 122         196 next;
222             }
223 92 100       167 if ($type eq 't') { # print (= append to $text) time elapsed in format 'hh:mm:ss'
224 10         18 my $unit = int($self->{sec_print});
225 10         19 my $hour = int($unit / 3600);
226 10         18 my $min = int(($unit % 3600) / 60);
227 10         10 my $sec = $unit % 60;
228 10         42 my $stamp = sprintf '%02d:%02d:%02d', $hour, $min, $sec;
229              
230 10 100       25 $stamp = substr($stamp, -$len) if length($stamp) > $len;
231              
232 10         31 $text .= sprintf "%${len}.${len}s", $stamp;
233 10         19 next;
234             }
235 82 100       151 if ($type eq 'd') { # print (= append to $text) a revolving dash in format '/-\|'
236 10         28 $text .= substr('/-\|', $self->{out} % 4, 1) x $len;
237 10         19 next;
238             }
239 72 100       130 if ($type eq 'b') { # print (= append to $text) progress indicator format '#####_____'
240 13 50       51 my $progress = $self->{target} == $self->{base} ? 0 :
241             int ($len * ($self->{value} - $self->{base}) / ($self->{target} - $self->{base}) + 0.5);
242 13 50       34 if ($progress < 0) { $progress = 0 }
  0 50       0  
243 0         0 elsif ($progress > $len) { $progress = $len }
244 13         29 $text .= $self->{pdisp} x $progress.'_' x ($len - $progress);
245 13         28 next;
246             }
247 59 100       100 if ($type eq 'p') { # print (= append to $text) progress in percentage format '999%'
248 7 50       26 my $percent = $self->{target} == $self->{base} ? 0 :
249             100 * ($self->{value} - $self->{base}) / ($self->{target} - $self->{base});
250 7         31 $text .= sprintf "%${len}.${len}s", sprintf("%.0f%%", $percent);
251 7         17 next;
252             }
253 52 100       93 if ($type eq 'P') { # print (= append to $text) literally '%' characters
254 2         4 $text .= '%' x $len;
255 2         6 next;
256             }
257 50 100       88 if ($type eq 'c') { # print (= append to $text) actual counter value (commified)
258 31         92 $text .= sprintf "%${len}s", commify($self->{commify}, $self->{value}, $self->{sep}, $self->{group});
259 31         72 next;
260             }
261 19 100       39 if ($type eq 'm') { # print (= append to $text) target (commified)
262 12         40 $text .= sprintf "%${len}s", commify($self->{commify}, $self->{target}, $self->{sep}, $self->{group});
263 12         30 next;
264             }
265 7 50       15 if ($type eq 'k') { # print (= append to $text) token
266 7         29 $text .= sprintf "%-${len}s", $self->{token}[$tok_ind];
267 7         36 $tok_ind++;
268 7         10 next;
269             }
270             # default: do nothing, in the (impossible) event that $type is none of '*lit', 't', 'b', 'p', 'P', 'c', 'm' or 'k'
271             }
272              
273             # End of string composition
274             }
275              
276 116         308 $self->{line} = join('', $back, $blank, $back, $text);
277              
278 116 50 33     267 unless ($self->{test} or $self->{quiet}) {
279 0         0 local $| = 1;
280 0         0 print $self->{line};
281             }
282              
283 116         474 $self->{oldtext} = $text;
284             }
285              
286             sub commify {
287 43     43 0 48 my $com = shift;
288 43 100       86 if ($com) { return $com->($_[0]); }
  2         7  
289              
290 41         50 local $_ = shift;
291 41         48 my ($sep, $group) = @_;
292              
293 41 100       76 if ($group > 0) {
294 39         62 my $len = length($_);
295 39         74 for my $i (1..$len) {
296 76 100       632 last unless s/^([-+]?\d+)(\d{$group})/$1$sep$2/;
297             }
298             }
299 41         316 return $_;
300             }
301              
302             my $chunk_size = 10000;
303             my $bkup_size = 80;
304              
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)
309              
310 0     0 0 0 sub log_info { }
311 0     0 0 0 sub set_chunk_size { }
312 0     0 0 0 sub set_bkup_size { }
313              
314             sub rem_backspace {
315 3     3 0 1971 my ($fname) = @_;
316              
317 1 50   1   7 open my $ifh, '<', $fname or die "Error-0200: Can't open < '$fname' because $!";
  1         1  
  1         6  
  3         63  
318 3 50       1543 open my $tfh, '+>', undef or die "Error-0210: Can't open +> undef (tempfile) because $!";
319              
320 3         8 my $out_buf = '';
321              
322 3         24 while (read($ifh, my $inp_buf, $chunk_size)) {
323 3         7 $out_buf .= $inp_buf;
324              
325             # here we are removing the backspaces:
326 3         21 while ($out_buf =~ m{\010+}xms) {
327 4         25 my $pos_left = $-[0] * 2 - $+[0];
328 4 50       14 if ($pos_left < 0) {
329 0         0 $pos_left = 0;
330             }
331 4         24 $out_buf = substr($out_buf, 0, $pos_left).substr($out_buf, $+[0]);
332             }
333              
334 3 100       14 if (length($out_buf) > $bkup_size) {
335 1         3 print {$tfh} substr($out_buf, 0, -$bkup_size);
  1         5  
336 1         4 $out_buf = substr($out_buf, -$bkup_size);
337             }
338             }
339              
340 3         7 CORE::close $ifh; # We need to employ CORE::close because there is already another close subroutine defined in the current namespace "Term::Sk"
341              
342 3         4 print {$tfh} $out_buf;
  3         50  
343              
344             # Now copy back temp-file to original file:
345              
346 3 50       112 seek $tfh, 0, SEEK_SET or die "Error-0220: Can't seek tempfile to 0 because $!";
347 3 50       40 open my $ofh, '>', $fname or die "Error-0230: Can't open > '$fname' because $!";
348              
349 3         31 while (read($tfh, my $buf, $chunk_size)) { print {$ofh} $buf; }
  3         4  
  3         16  
350              
351 3         7 CORE::close $ofh;
352 3         143 CORE::close $tfh;
353             }
354              
355             1;
356              
357             __END__