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