File Coverage

blib/lib/Term/ProgressSpinner.pm
Criterion Covered Total %
statement 203 322 63.0
branch 59 148 39.8
condition 15 32 46.8
subroutine 37 50 74.0
pod 40 42 95.2
total 354 594 59.6


line stmt bran cond sub pod time code
1             package Term::ProgressSpinner;
2             our $VERSION = '0.04';
3 3     3   207309 use 5.006; use strict; use warnings;
  3     3   30  
  3     3   18  
  3         5  
  3         83  
  3         26  
  3         6  
  3         81  
4 3     3   1800 use IO::Handle; use Term::ANSIColor; use Time::HiRes qw//;
  3     3   18841  
  3     3   139  
  3         2003  
  3         25520  
  3         274  
  3         1734  
  3         4224  
  3         95  
5 3     3   1373 use Term::Size::Any qw/chars/;
  3         775  
  3         20  
6             if ($^O eq 'MSWin32') { eval "use Win32::Console::ANSI; use Win32::Console;"; }
7             our (%SPINNERS, %PROGRESS, %VALIDATE);
8              
9             BEGIN {
10             %VALIDATE = (
11             colours => {
12             map {
13 3     3   9154 my $c = $_;
  24         39  
14             (
15             $c => 1,
16             "bright_${c}" => 1,
17 24         47 (map { (
18 192         1298 "${c} on_${_}" => 1,
19             "${c} on_bright_${_}" => 1,
20             "bright_${c} on_${_}" => 1,
21             "bright_${c} on_bright_${_}" => 1,
22             ) } qw/black red green yellow blue magenta cyan white/)
23             )
24             } qw/black red green yellow blue magenta cyan white/
25             },
26             msg_regex => qr/\{(total|progress|spinner|percents|percentage|percentages|percent|counter|elapsed|elapsed_second|estimate|estimate_second|start_epoch|start_epoch_second|epoch|epoch_second|per_second|last_advance_epoch|last_advance_epoch_second|last_elapsed|last_elapsed_second|elapsed|elapsed_second)\}/
27             );
28 3         231 %SPINNERS = (
29             bar => {
30             width => 3,
31             index => [4, 2, 6],
32             chars => [
33             "▁",
34             "▂",
35             "▃",
36             "▄",
37             "▅",
38             "▆",
39             "▇",
40             "█"
41             ]
42             },
43             dots => {
44             width => 1,
45             index => [1],
46             chars => [
47             "⠋",
48             "⠙",
49             "⠹",
50             "⠸",
51             "⠼",
52             "⠴",
53             "⠦",
54             "⠧",
55             "⠇",
56             "⠏"
57             ]
58             },
59             around => {
60             width => 1,
61             index => [1],
62             chars => [
63             "⢀⠀",
64             "⡀⠀",
65             "⠄⠀",
66             "⢂⠀",
67             "⡂⠀",
68             "⠅⠀",
69             "⢃⠀",
70             "⡃⠀",
71             "⠍⠀",
72             "⢋⠀",
73             "⡋⠀",
74             "⠍⠁",
75             "⢋⠁",
76             "⡋⠁",
77             "⠍⠉",
78             "⠋⠉",
79             "⠋⠉",
80             "⠉⠙",
81             "⠉⠙",
82             "⠉⠩",
83             "⠈⢙",
84             "⠈⡙",
85             "⢈⠩",
86             "⡀⢙",
87             "⠄⡙",
88             "⢂⠩",
89             "⡂⢘",
90             "⠅⡘",
91             "⢃⠨",
92             "⡃⢐",
93             "⠍⡐",
94             "⢋⠠",
95             "⡋⢀",
96             "⠍⡁",
97             "⢋⠁",
98             "⡋⠁",
99             "⠍⠉",
100             "⠋⠉",
101             "⠋⠉",
102             "⠉⠙",
103             "⠉⠙",
104             "⠉⠩",
105             "⠈⢙",
106             "⠈⡙",
107             "⠈⠩",
108             "⠀⢙",
109             "⠀⡙",
110             "⠀⠩",
111             "⠀⢘",
112             "⠀⡘",
113             "⠀⠨",
114             "⠀⢐",
115             "⠀⡐",
116             "⠀⠠",
117             "⠀⢀",
118             "⠀⡀"
119             ]
120             },
121             pipe => {
122             width => 1,
123             index => [1],
124             chars => [
125             "┤",
126             "┘",
127             "┴",
128             "└",
129             "├",
130             "┌",
131             "┬",
132             "┐"
133             ]
134             },
135             moon => {
136             width => 1,
137             index => [1],
138             chars => [
139             "🌑 ",
140             "🌒 ",
141             "🌓 ",
142             "🌔 ",
143             "🌕 ",
144             "🌖 ",
145             "🌗 ",
146             "🌘 "
147             ]
148             },
149             circle => {
150             width => 1,
151             index => [1],
152             chars => [
153             "・",
154             "◦",
155             "●",
156             "○",
157             "◎",
158             "◉",
159             "⦿",
160             "◉",
161             "◎",
162             "○",
163             "◦",
164             "・",
165             ]
166             },
167             color_circle => {
168             width => 1,
169             index => [1],
170             chars => [
171             "🔴",
172             "🟠",
173             "🟡",
174             "🟢",
175             "🔵",
176             "🟣",
177             "⚫️",
178             "⚪️",
179             "🟤"
180             ]
181             },
182             color_circles => {
183             width => 3,
184             index => [1, 4, 7],
185             chars => [
186             "🔴",
187             "🟠",
188             "🟡",
189             "🟢",
190             "🔵",
191             "🟣",
192             "⚫️",
193             "⚪️",
194             "🟤"
195             ]
196             },
197             color_square => {
198             width => 1,
199             index => [1],
200             chars => [
201             "🟥",
202             "🟧",
203             "🟨",
204             "🟩",
205             "🟦",
206             "🟪",
207             "⬛️",
208             "⬜️",
209             "🟫"
210             ]
211             },
212             color_squares => {
213             width => 3,
214             index => [1, 3, 6],
215             chars => [
216             "🟥",
217             "🟧",
218             "🟨",
219             "🟩",
220             "🟦",
221             "🟪",
222             "⬛️",
223             "⬜️",
224             "🟫"
225             ]
226             },
227             earth => {
228             width => 1,
229             index => [1],
230             chars => [
231             "🌎",
232             "🌍",
233             "🌏"
234             ]
235             },
236             circle_half => {
237             width => 1,
238             index => [1],
239             chars => [
240             '◐',
241             '◓',
242             '◑',
243             '◒'
244             ]
245             },
246             clock => {
247             width => 1,
248             index => [1],
249             chars => [
250             "🕛 ",
251             "🕐 ",
252             "🕑 ",
253             "🕒 ",
254             "🕓 ",
255             "🕔 ",
256             "🕕 ",
257             "🕖 ",
258             "🕗 ",
259             "🕘 ",
260             "🕙 ",
261             "🕚 "
262             ]
263             },
264             pong => {
265             width => 1,
266             index => [1],
267             chars => [
268             "▐⠂ ▌",
269             "▐⠈ ▌",
270             "▐ ⠂ ▌",
271             "▐ ⠠ ▌",
272             "▐ ⡀ ▌",
273             "▐ ⠠ ▌",
274             "▐ ⠂ ▌",
275             "▐ ⠈ ▌",
276             "▐ ⠂ ▌",
277             "▐ ⠠ ▌",
278             "▐ ⡀ ▌",
279             "▐ ⠠ ▌",
280             "▐ ⠂ ▌",
281             "▐ ⠈ ▌",
282             "▐ ⠂▌",
283             "▐ ⠠▌",
284             "▐ ⡀▌",
285             "▐ ⠠ ▌",
286             "▐ ⠂ ▌",
287             "▐ ⠈ ▌",
288             "▐ ⠂ ▌",
289             "▐ ⠠ ▌",
290             "▐ ⡀ ▌",
291             "▐ ⠠ ▌",
292             "▐ ⠂ ▌",
293             "▐ ⠈ ▌",
294             "▐ ⠂ ▌",
295             "▐ ⠠ ▌",
296             "▐ ⡀ ▌",
297             "▐⠠ ▌"
298             ]
299             },
300             material => {
301             width => 1,
302             index => [1],
303             chars => [
304             "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
305             "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
306             "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
307             "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
308             "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
309             "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
310             "███████▁▁▁▁▁▁▁▁▁▁▁▁▁",
311             "████████▁▁▁▁▁▁▁▁▁▁▁▁",
312             "█████████▁▁▁▁▁▁▁▁▁▁▁",
313             "█████████▁▁▁▁▁▁▁▁▁▁▁",
314             "██████████▁▁▁▁▁▁▁▁▁▁",
315             "███████████▁▁▁▁▁▁▁▁▁",
316             "█████████████▁▁▁▁▁▁▁",
317             "██████████████▁▁▁▁▁▁",
318             "██████████████▁▁▁▁▁▁",
319             "▁██████████████▁▁▁▁▁",
320             "▁██████████████▁▁▁▁▁",
321             "▁██████████████▁▁▁▁▁",
322             "▁▁██████████████▁▁▁▁",
323             "▁▁▁██████████████▁▁▁",
324             "▁▁▁▁█████████████▁▁▁",
325             "▁▁▁▁██████████████▁▁",
326             "▁▁▁▁██████████████▁▁",
327             "▁▁▁▁▁██████████████▁",
328             "▁▁▁▁▁██████████████▁",
329             "▁▁▁▁▁██████████████▁",
330             "▁▁▁▁▁▁██████████████",
331             "▁▁▁▁▁▁██████████████",
332             "▁▁▁▁▁▁▁█████████████",
333             "▁▁▁▁▁▁▁█████████████",
334             "▁▁▁▁▁▁▁▁████████████",
335             "▁▁▁▁▁▁▁▁████████████",
336             "▁▁▁▁▁▁▁▁▁███████████",
337             "▁▁▁▁▁▁▁▁▁███████████",
338             "▁▁▁▁▁▁▁▁▁▁██████████",
339             "▁▁▁▁▁▁▁▁▁▁██████████",
340             "▁▁▁▁▁▁▁▁▁▁▁▁████████",
341             "▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
342             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████",
343             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
344             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
345             "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
346             "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
347             "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
348             "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
349             "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
350             "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
351             "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
352             "██████▁▁▁▁▁▁▁▁▁▁▁▁▁█",
353             "████████▁▁▁▁▁▁▁▁▁▁▁▁",
354             "█████████▁▁▁▁▁▁▁▁▁▁▁",
355             "█████████▁▁▁▁▁▁▁▁▁▁▁",
356             "█████████▁▁▁▁▁▁▁▁▁▁▁",
357             "█████████▁▁▁▁▁▁▁▁▁▁▁",
358             "███████████▁▁▁▁▁▁▁▁▁",
359             "████████████▁▁▁▁▁▁▁▁",
360             "████████████▁▁▁▁▁▁▁▁",
361             "██████████████▁▁▁▁▁▁",
362             "██████████████▁▁▁▁▁▁",
363             "▁██████████████▁▁▁▁▁",
364             "▁██████████████▁▁▁▁▁",
365             "▁▁▁█████████████▁▁▁▁",
366             "▁▁▁▁▁████████████▁▁▁",
367             "▁▁▁▁▁████████████▁▁▁",
368             "▁▁▁▁▁▁███████████▁▁▁",
369             "▁▁▁▁▁▁▁▁█████████▁▁▁",
370             "▁▁▁▁▁▁▁▁█████████▁▁▁",
371             "▁▁▁▁▁▁▁▁▁█████████▁▁",
372             "▁▁▁▁▁▁▁▁▁█████████▁▁",
373             "▁▁▁▁▁▁▁▁▁▁█████████▁",
374             "▁▁▁▁▁▁▁▁▁▁▁████████▁",
375             "▁▁▁▁▁▁▁▁▁▁▁████████▁",
376             "▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
377             "▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
378             "▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
379             "▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
380             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
381             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
382             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
383             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
384             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
385             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
386             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
387             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
388             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
389             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
390             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
391             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
392             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
393             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
394             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
395             "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁"
396              
397             ]
398              
399             }
400             );
401 3         20 $SPINNERS{default} = $SPINNERS{bar};
402 3         111 %PROGRESS = (
403             bar => {
404             chars => ['│', "█", '│']
405             },
406             equal => {
407             chars => ['[', "=", ']']
408             },
409             arrow => {
410             chars => ['│', "→", '│']
411             },
412             boxed_arrow => {
413             chars => ['│', "⍈", '│']
414             },
415             lines => {
416             chars => ['│', "≡", '│']
417             },
418             horizontal_lines => {
419             chars => ['│', "▤", '│']
420             },
421             vertical_lines => {
422             chars => ['│', "▥", '│']
423             },
424             hash => {
425             chars => ['[', "#", ']']
426             },
427             triangle => {
428             chars => ['│', '▶︎', '│' ]
429             },
430             den_triangle => {
431             chars => ['│', '⏅', '│' ]
432             },
433             circle => {
434             chars => ['│', 'Ⓞ', '│' ]
435             },
436             den_circle => {
437             chars => ['│', '⏂', '│' ]
438             },
439             shekel => {
440             chars => ['│', '₪', '│' ]
441             },
442             dots => {
443             chars => ['│', '▒', '│' ]
444             },
445             square => {
446             chars => ['│', '■', '│' ]
447             },
448             block => {
449             chars => ["【", "=", "】"]
450             }
451             );
452 3         12264 $PROGRESS{default} = $PROGRESS{bar};
453             }
454              
455             sub new {
456 25 50   25 1 765 my ($pkg, %args) = (shift, ref $_[0] ? %{$_[0]} : @_);
  0         0  
457             $args{$_} and ($VALIDATE{colours}{$args{$_}} or die "Invalid color for $_")
458 25   50     735 for qw/text_color total_color counter_color percent_color percentage_color percentages_color percents_color spinner_color progress_color elapsed_color last_elapsed_color estimate_color last_advance_epoch_color start_epoch_color epoch_color/;
      66        
459             return bless {
460             text_color => 'white',
461             total_color => 'white',
462             counter_color => 'white',
463             percent_color => 'white',
464             percentage_color => 'white',
465             percentages_color => 'white',
466             percents_color => 'white',
467             spinner_color => 'white',
468             elapsed_color => 'white',
469             start_epoch_color => 'white',
470             last_elapsed_color => 'white',
471             last_advance_epoch_color => 'white',
472             estimate_color => 'white',
473             epoch_color => 'white',
474             per_second_color => 'white',
475             spinner_options => $SPINNERS{ $args{spinner} || 'default' },
476             progress_color => 'white',
477             progress_width => 20,
478 25   50     1058 progress_options => $PROGRESS{ $args{progress} || 'default' },
      50        
      66        
479             output => \*STDERR,
480             progress_spinner_index => 0,
481             progress_spinners => [],
482             message => "{progress} {spinner} processed {percents} of {counter}/{total} {elapsed}/{estimate}",
483             terminal_height => 0,
484             terminal_line => 0,
485             %args
486             }, ref $pkg || $pkg;
487             }
488              
489             sub progress_spinner_index {
490 356     356 1 809 my ($self, $val) = @_;
491 356 100       702 if (defined $val) {
492 23 50 33     191 if (ref $val || $val !~ m/\d+/) {
493 0         0 die 'progress_spinner_index should be a integer';
494             }
495 23         60 $self->{progress_spinner_index} = $val;
496             }
497 356         867 return $self->{progress_spinner_index};
498             }
499              
500             sub terminal_height {
501 46     46 1 122 my ($self, $val) = @_;
502 46 50       113 if (defined $val) {
503 0 0 0     0 if (ref $val || $val !~ m/\d+/) {
504 0         0 die 'terminal_height should be a integer';
505             }
506 0         0 $self->{terminal_height} = $val;
507             }
508 46         188 return $self->{terminal_height};
509             }
510              
511             sub terminal_line {
512 69     69 1 152 my ($self, $val) = @_;
513 69 100       149 if (defined $val) {
514 23 50 33     321 if (ref $val || $val !~ m/\d+/) {
515 0         0 die 'terminal_line should be a integer';
516             }
517 23         66 $self->{terminal_line} = $val;
518             }
519 69         208 return $self->{terminal_line};
520             }
521              
522             sub progress_spinners {
523 1190     1190 1 2899 my ($self, $val) = @_;
524 1190 50       4053 if (defined $val) {
525 0 0 0     0 if (ref $val || "" ne 'ARRAY') {
526 0         0 die 'progress_spinners should be a array';
527             }
528 0         0 $self->{progress_spinners} = $val;
529             }
530 1190         5186 return $self->{progress_spinners};
531             }
532              
533             sub savepos {
534 23     23 1 51 my $self = shift;
535 23 50       84 my ($col, $rows) = $self->terminal_height ? (0, $self->terminal_height) : (Term::Size::Any::chars($self->output));
536 23         53 my $x = '';
537 23 50       61 if ($self->terminal_line) {
    0          
538 23         66 $x = $self->terminal_line;
539             } elsif ($^O eq 'MSWin32') {
540 0         0 my $CONSOLE = Win32::Console->new(Win32::Console::STD_OUTPUT_HANDLE());
541 0         0 ($x) = $CONSOLE->Cursor();
542             } else {
543 0         0 system "stty cbreak /dev/tty 2>&1";
544 0         0 $self->output->print("\e[6n");
545 0         0 $x .= getc STDIN for 0 .. 5;
546 0         0 system "stty -cbreak /dev/tty 2>&1";
547 0         0 my($n, $m)=$x=~m/(\d+)\;(\d+)/;
548 0         0 $x = $n;
549 0         0 $self->clear();
550             }
551 23 50       82 if ($x == $rows) {
552 0         0 $x--;
553 0         0 for (@{ $self->progress_spinners }) {
  0         0  
554 0         0 $_->{savepos} = $_->{savepos} - 1;
555             }
556             }
557 23         65 $self->{savepos} = $x;
558             }
559              
560             sub loadpos {
561 724     724 1 2203 my $self = shift;
562 724         2808 my $pos = $self->{savepos};
563 724         3240 $self->output->print("\e[$pos;1f");
564             }
565              
566             sub start {
567 23     23 1 74 my ($self, $total) = @_;
568 23 50       125 $self->total($total) if $total;
569 23         178 $self->start_epoch(Time::HiRes::time);
570 23         73 $self->output->print("\e[?25l");
571 23         1643 $self->savepos;
572 23         65 $self->output->print("\n");
573 23         1636 my $ps = $self->new(%{$self});
  23         363  
574 23         84 push @{ $self->progress_spinners }, $ps;
  23         78  
575 23         80 $self->progress_spinner_index($self->progress_spinner_index + 1);
576 23         85 return $ps;
577             }
578            
579             sub advance {
580 1001     1001 1 4813 my ($self, $ps, $prevent) = @_;
581 1001 100       2663 if ($ps) {
582 633 100       1892 if ($ps->counter < $ps->total) {
583 520         1700 $ps->counter($ps->counter + 1);
584 520         1625 my $spinner = $ps->spinner;
585 520         2934 for (1 .. $spinner->{width}) {
586 1080         2665 my $index = $spinner->{index}->[$_ - 1];
587 1080         1809 $spinner->{index}->[$_ - 1] = ($index + 1) % scalar @{$spinner->{chars}};
  1080         3626  
588             }
589 520 50       1597 select(undef, undef, undef, $ps->slowed) if $ps->slowed;
590 520 100       16959 $ps->draw() unless $prevent;
591             } else {
592 113         309 $self->finish($ps);
593             }
594             } else {
595 368         684 for my $spinner (@{$self->progress_spinners}) {
  368         1134  
596 429         1689 $self->advance($spinner, 1);
597             }
598 368 100       1207 scalar @{$self->{progress_spinners}} ? $self->draw() : $self->finish;
  368         6052  
599             }
600             }
601              
602             sub time_advance_elapsed {
603 724     724 0 1618 my ($self) = @_;
604 724         1885 my %time = ();
605 724         4453 $time{epoch} = Time::HiRes::time;
606 724         2693 $time{start_epoch} = $self->start_epoch;
607 724   66     2457 $time{last_advance_epoch} = $self->last_advance_epoch || $time{start_epoch};
608 724         2420 $time{last_elapsed} = $time{epoch} - $time{last_advance_epoch};
609 724         2050 $time{elapsed} = $time{epoch} - $time{start_epoch};
610 724         2102 for (qw/epoch start_epoch last_advance_epoch last_elapsed elapsed/) {
611 3620         14358 $time{"${_}_second"} = int($time{$_});
612             }
613 724         2762 $self->last_advance_epoch($time{epoch});
614 724         8616 return %time;
615             }
616              
617             sub draw {
618 1186     1186 1 3692 my ($self, $ps) = @_;
619 1186 100       3241 if ($ps) {
620 724         3709 $ps->loadpos;
621 724         92678 $ps->clear();
622 724         2895 my ($spinner, $progress, $available, %options) = ($ps->spinner, $ps->progress, $ps->progress_width, $ps->time_advance_elapsed);
623 724         3066 $options{total} = $ps->total;
624 724         2494 $options{counter} = $ps->counter;
625 724         2088 $options{spinner} = color($ps->spinner_color);
626             $options{spinner} .= $spinner->{chars}->[
627             $spinner->{index}->[$_ - 1]
628 724         30224 ] for (1 .. $spinner->{width});
629 724         2868 $options{spinner} .= color($ps->text_color);
630 724         18908 $options{percent} = int( ( $options{counter} / $options{total} ) * 100 );
631 724         2933 $options{percentage} = ($available / 100) * $options{percent};
632 724 100       2920 $options{estimate} = $options{percent} ? (($options{elapsed} / $options{percent}) * 100) - $options{elapsed} : 0;
633 724         2168 $options{estimate_second} = int($options{estimate} + 0.5);
634             $options{per_second} = $options{elapsed_seconds} ?
635             $options{counter} / int($options{elapsed_second})
636 724 50       2790 : 0;
637             $options{progress} = sprintf("%s%s%s%s%s",
638             color($ps->progress_color),
639             $progress->{chars}->[0],
640             ( $progress->{chars}->[1] x int($options{percentage} + 0.5) ) . ( ' ' x int( ($available - $options{percentage}) + 0.5 ) ),
641 724         2847 $progress->{chars}->[2],
642             color($ps->text_color)
643             );
644 724         21883 $options{percentages} = $options{percentage} . '%';
645 724         2276 $options{percents} = $options{percent} . '%';
646             $options{$_} = sprintf ("%s%s%s",
647             color($ps->{$_ . "_color"}),
648             $options{$_},
649             color($ps->text_color)
650 724         4395 ) for (qw/total percent percents percentage counter per_second/);
651 724         15816 for (qw/elapsed last_elapsed estimate last_advance_epoch start_epoch epoch/) {
652             $options{$_} = sprintf ("%s%s%s",
653             color($ps->{$_ . "_color"}),
654 4344         81894 $options{$_},
655             color($ps->text_color)
656             );
657             $options{"${_}_second"} = sprintf ("%s%s%s",
658             color($ps->{$_ . "_color"}),
659 4344         102703 $options{"${_}_second"},
660             color($ps->text_color)
661             );
662             }
663 724         16769 my $message = $ps->message;
664 724         23076 $message =~ s/$VALIDATE{msg_regex}/$options{$1}/ig;
665 724         2694 $message .= color('reset') . "\n";
666 724         15852 $ps->output->print($message);
667 724         66609 return $ps->drawn(1);
668             } else {
669 462         1152 for my $spinner (@{$self->progress_spinners}) {
  462         2403  
670 724         2471 $self->draw($spinner);
671             }
672 462         1418 return $self->drawn(1);
673             }
674             }
675            
676             sub finish {
677 129     129 1 262 my ($self, $sp) = @_;
678 129 100 100     409 if ($sp && scalar @{$self->progress_spinners}) {
  113         222  
679 112         190 my $i = 0;
680 112         180 for (@{ $self->progress_spinners }) {
  112         209  
681 155 100       378 if ($sp->progress_spinner_index == $_->progress_spinner_index) {
682 23         52 last;
683             }
684 132         263 $i++;
685             }
686 112         196 splice @{$self->progress_spinners}, $i, 1;
  112         252  
687              
688             } else {
689 17         42 $self->output->print("\e[?25h");
690 17         1259 $self->finished(1);
691             }
692 129         433 return 0;
693             }
694              
695             sub finished {
696 273 100   273 0 779 if (defined $_[1]) {
697 17         52 $_[0]->{finished} = $_[1];
698             }
699 273         757 $_[0]->{finished};
700             }
701              
702             sub drawn {
703 1925     1925 1 4980 my ($self, $val) = @_;
704 1925 50       5082 if (defined $val) {
705 1925         4296 $self->{drawn} = $val;
706             }
707 1925         10123 return $self->{drawn};
708             }
709            
710             sub clear {
711 739     739 1 7386 my ($self) = @_;
712 739         2394 $self->output->print("\r\e[2K");
713 739         47726 $self->drawn(0);
714             }
715              
716             sub message {
717 724     724 1 1680 my ($self, $val) = @_;
718 724 50       1983 if (defined $val) {
719 0 0       0 if (ref $val) {
720 0         0 die 'message should be a string';
721             }
722 0         0 $self->{message} = $val;
723             }
724 724         1878 return $self->{message};
725             }
726              
727             sub output {
728 2253     2253 1 5246 my ($self, $val) = @_;
729 2253 50       6119 if (defined $val) {
730 0         0 $self->{output} = $val;
731             }
732 2253         15170 return $self->{output};
733             }
734              
735             sub total {
736 1380     1380 1 3384 my ($self, $val) = @_;
737 1380 100       3534 if (defined $val) {
738 23 50       110 if ($val !~ m/\d+/) {
739 0         0 die "total should be a integer";
740             }
741 23         73 $self->{total} = $val;
742 23         56 $self->{counter} = 0;
743             }
744 1380         5170 return $self->{total};
745             }
746              
747             sub slowed {
748 1042     1042 1 2496 my ($self, $val) = @_;
749 1042 100       2659 if (defined $val) {
750 2 50       34 if ($val !~ m/\d+(\.\d+)?/) {
751 0         0 die "slowed should be a float";
752             }
753 2         9 $self->{slowed} = $val;
754             }
755 1042         52109377 return $self->{slowed};
756             }
757              
758             sub counter {
759 2397     2397 1 4739 my ($self, $val) = @_;
760 2397 100       5059 if (defined $val) {
761 520 50       4125 if ($val !~ m/\d+/) {
762 0         0 die "counter should be a integer";
763             }
764 520         1443 $self->{counter} = $val;
765             }
766 2397         7124 return $self->{counter};
767             }
768              
769             sub start_epoch {
770 747     747 1 1774 my ($self, $val) = @_;
771 747 100       2124 if (defined $val) {
772 23 50       353 if ($val !~ m/\d+(\.\d+)?/) {
773 0         0 die "start_epoch should be a epoch";
774             }
775 23         64 $self->{start_epoch} = $val;
776             }
777 747         2499 return $self->{start_epoch};
778             }
779              
780             sub last_advance_epoch {
781 1448     1448 1 3014 my ($self, $val) = @_;
782 1448 100       3640 if (defined $val) {
783 724 50       15701 if ($val !~ m/\d+(\.\d+)?/) {
784 0         0 die "last_advance_epoch should be a epoch";
785             }
786 724         2046 $self->{last_advance_epoch} = $val;
787             }
788 1448         4231 return $self->{last_advance_epoch};
789             }
790              
791             sub text_color {
792 14480     14480 1 327937 my ($self, $val) = @_;
793 14480 50       27982 if (defined $val) {
794 0 0       0 unless ($VALIDATE{colours}{$val}) {
795 0         0 die "$val is not a valid color";
796             }
797 0         0 $self->{text_color} = $val;
798             }
799 14480         33569 return $self->{text_color};
800             }
801              
802             sub spinner {
803 1259     1259 1 2907 my ($self, $spinner) = @_;
804 1259 100 50     3368 $self->{spinner_options} = $SPINNERS{$spinner} or die "Invalid spinner $spinner" if $spinner;
805 1259         5543 $self->{spinner_options};
806             }
807              
808             sub spinner_color {
809 724     724 1 1766 my ($self, $val) = @_;
810 724 50       1694 if (defined $val) {
811 0 0       0 unless ($VALIDATE{colours}{$val}) {
812 0         0 die "$val is not a valid color";
813             }
814 0         0 $self->{spinner_color} = $val;
815             }
816 724         4623 return $self->{spinner_color};
817             }
818              
819             sub progress {
820 739     739 1 1917 my ($self, $progress) = @_;
821 739 100 50     1879 $self->{progress_options} = $PROGRESS{$progress} or die "Invalid progress $progress" if $progress;
822 739         3060 $self->{progress_options};
823             }
824              
825             sub progress_color {
826 724     724 1 1782 my ($self, $val) = @_;
827 724 50       1930 if (defined $val) {
828 0 0       0 unless ($VALIDATE{colours}{$val}) {
829 0         0 die "$val is not a valid color";
830             }
831 0         0 $self->{progress_color} = $val;
832             }
833 724         2439 return $self->{progress_color};
834             }
835              
836             sub progress_width {
837 724     724 1 2153 my ($self, $val) = @_;
838 724 50       1943 if (defined $val) {
839 0         0 $self->{progress_width} = $val;
840             }
841 724         2761 return $self->{progress_width};
842             }
843              
844             sub percent_color {
845 0     0 1 0 my ($self, $val) = @_;
846 0 0       0 if (defined $val) {
847 0 0       0 unless ($VALIDATE{colours}{$val}) {
848 0         0 die "$val is not a valid color";
849             }
850 0         0 $self->{percent_color} = $val;
851             }
852 0         0 return $self->{percent_color};
853             }
854              
855             sub percents_color {
856 0     0 1 0 my ($self, $val) = @_;
857 0 0       0 if (defined $val) {
858 0 0       0 unless ($VALIDATE{colours}{$val}) {
859 0         0 die "$val is not a valid color";
860             }
861 0         0 $self->{percents_color} = $val;
862             }
863 0         0 return $self->{percents_color};
864             }
865              
866             sub percentage_color {
867 0     0 1 0 my ($self, $val) = @_;
868 0 0       0 if (defined $val) {
869 0 0       0 unless ($VALIDATE{colours}{$val}) {
870 0         0 die "$val is not a valid color";
871             }
872 0         0 $self->{percentage_color} = $val;
873             }
874 0         0 return $self->{percentage_color};
875             }
876              
877             sub percentages_color {
878 0     0 1 0 my ($self, $val) = @_;
879 0 0       0 if (defined $val) {
880 0 0       0 unless ($VALIDATE{colours}{$val}) {
881 0         0 die "$val is not a valid color";
882             }
883 0         0 $self->{percentages_color} = $val;
884             }
885 0         0 return $self->{percentages_color};
886             }
887              
888             sub total_color {
889 0     0 1 0 my ($self, $val) = @_;
890 0 0       0 if (defined $val) {
891 0 0       0 unless ($VALIDATE{colours}{$val}) {
892 0         0 die "$val is not a valid color";
893             }
894 0         0 $self->{total_color} = $val;
895             }
896 0         0 return $self->{total_color};
897             }
898              
899             sub counter_color {
900 0     0 1 0 my ($self, $val) = @_;
901 0 0       0 if (defined $val) {
902 0 0       0 unless ($VALIDATE{colours}{$val}) {
903 0         0 die "$val is not a valid color";
904             }
905 0         0 $self->{counter_color} = $val;
906             }
907 0         0 return $self->{counter_color};
908             }
909              
910             sub elapsed_color {
911 0     0 1 0 my ($self, $val) = @_;
912 0 0       0 if (defined $val) {
913 0 0       0 unless ($VALIDATE{colours}{$val}) {
914 0         0 die "$val is not a valid color";
915             }
916 0         0 $self->{elapsed_color} = $val;
917             }
918 0         0 return $self->{elapsed_color};
919             }
920              
921             sub last_elapsed_color {
922 0     0 1 0 my ($self, $val) = @_;
923 0 0       0 if (defined $val) {
924 0 0       0 unless ($VALIDATE{colours}{$val}) {
925 0         0 die "$val is not a valid color";
926             }
927 0         0 $self->{last_elapsed_color} = $val;
928             }
929 0         0 return $self->{last_elapsed_color};
930             }
931              
932             sub estimate_color {
933 0     0 1 0 my ($self, $val) = @_;
934 0 0       0 if (defined $val) {
935 0 0       0 unless ($VALIDATE{colours}{$val}) {
936 0         0 die "$val is not a valid color";
937             }
938 0         0 $self->{estimate_elapsed_color} = $val;
939             }
940 0         0 return $self->{estimate_elapsed_color};
941             }
942              
943             sub last_advance_epoch_color {
944 0     0 1 0 my ($self, $val) = @_;
945 0 0       0 if (defined $val) {
946 0 0       0 unless ($VALIDATE{colours}{$val}) {
947 0         0 die "$val is not a valid color";
948             }
949 0         0 $self->{last_advance_epoch_color} = $val;
950             }
951 0         0 return $self->{last_advance_epoch_color};
952             }
953              
954             sub start_epoch_color {
955 0     0 1 0 my ($self, $val) = @_;
956 0 0       0 if (defined $val) {
957 0 0       0 unless ($VALIDATE{colours}{$val}) {
958 0         0 die "$val is not a valid color";
959             }
960 0         0 $self->{start_epoch_color} = $val;
961             }
962 0         0 return $self->{start_epoch_color};
963             }
964              
965             sub epoch_color {
966 0     0 1 0 my ($self, $val) = @_;
967 0 0       0 if (defined $val) {
968 0 0       0 unless ($VALIDATE{colours}{$val}) {
969 0         0 die "$val is not a valid color";
970             }
971 0         0 $self->{epoch_color} = $val;
972             }
973 0         0 return $self->{epoch_color};
974             }
975              
976             sub per_second_color {
977 0     0 1 0 my ($self, $val) = @_;
978 0 0       0 if (defined $val) {
979 0 0       0 unless ($VALIDATE{colours}{$val}) {
980 0         0 die "$val is not a valid color";
981             }
982 0         0 $self->{per_second_color} = $val;
983             }
984 0         0 return $self->{per_second_color};
985             }
986              
987             sub sleep {
988 16     16 1 1753521 select(undef, undef, undef, $_[1]);
989             }
990              
991             1;
992              
993             __END__;