File Coverage

blib/lib/Term/Sk.pm
Criterion Covered Total %
statement 206 228 90.3
branch 73 106 68.8
condition 24 30 80.0
subroutine 21 25 84.0
pod 0 19 0.0
total 324 408 79.4


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