File Coverage

blib/lib/Term/Spinner/Color.pm
Criterion Covered Total %
statement 26 126 20.6
branch 0 30 0.0
condition 0 6 0.0
subroutine 9 21 42.8
pod 6 12 50.0
total 41 195 21.0


line stmt bran cond sub pod time code
1             package Term::Spinner::Color;
2 1     1   16717 use strict;
  1         1  
  1         62  
3 1     1   6 use warnings;
  1         1  
  1         54  
4 1     1   33 use 5.010;
  1         7  
5 1     1   770 use POSIX;
  1         6586  
  1         5  
6 1     1   2805 use Time::HiRes qw( sleep );
  1         1156  
  1         5  
7 1     1   831 use Term::ANSIColor;
  1         5014  
  1         108  
8 1     1   817 use Term::Cap;
  1         2430  
  1         40  
9 1     1   664 use utf8;
  1         9  
  1         5  
10 1     1   568 use open ':std', ':encoding(UTF-8)';
  1         956  
  1         5  
11              
12             $| = 1; # Disable buffering on STDOUT.
13              
14             # Couple of instance vars for colors and frame sets
15             my @colors = qw( red green yellow blue magenta cyan white );
16             my %frames = (
17             'ascii_propeller' => [ qw(/ - \\ |) ],
18             'ascii_plus' => [ qw(x +) ],
19             'ascii_blink' => [ qw(o -) ],
20             'ascii_v' => [ qw(v < ^ >) ],
21             'ascii_inflate' => [ qw(. o O o) ],
22             'uni_dots' => [ qw(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏) ],
23             'uni_dots2' => [ qw(⣾ ⣽ ⣻ ⢿ ⡿ ⣟ ⣯ ⣷) ],
24             'uni_dots3' => [ qw(⣷ ⣯ ⣟ ⡿ ⢿ ⣻ ⣽ ⣾) ],
25             'uni_dots4' => [ qw(⠋ ⠙ ⠚ ⠞ ⠖ ⠦ ⠴ ⠲ ⠳ ⠓) ],
26             'uni_dots5' => [ qw(⠄ ⠆ ⠇ ⠋ ⠙ ⠸ ⠰ ⠠ ⠰ ⠸ ⠙ ⠋ ⠇ ⠆) ],
27             'uni_dots6' => [ qw(⠋ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋') ],
28             'uni_dots7' =>
29             [ qw(⠁ ⠉ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠤ ⠄ ⠄ ⠤ ⠴ ⠲ ⠒ ⠂ ⠂ ⠒ ⠚ ⠙ ⠉ ⠁) ],
30             'uni_dots8' =>
31             [ qw(⠈ ⠉ ⠋ ⠓ ⠒ ⠐ ⠐ ⠒ ⠖ ⠦ ⠤ ⠠ ⠠ ⠤ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠉ ⠈) ],
32             'uni_dots9' =>
33             [ qw(⠁ ⠁ ⠉ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠤ ⠄ ⠄ ⠤ ⠠ ⠠ ⠤ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠉ ⠈ ⠈) ],
34             'uni_dots10' => [ qw(⢹ ⢺ ⢼ ⣸ ⣇ ⡧ ⡗ ⡏) ],
35             'uni_dots11' => [ qw(⢄ ⢂ ⢁ ⡁ ⡈ ⡐ ⡠) ],
36             'uni_dots12' => [ qw(⠁ ⠂ ⠄ ⡀ ⢀ ⠠ ⠐ ⠈) ],
37             'uni_bounce' => [ qw(⠁ ⠂ ⠄ ⠂) ],
38             'uni_pipes' => [ qw(┤ ┘ ┴ └ ├ ┌ ┬ ┐) ],
39             'uni_hippie' => [ qw(☮ ✌ ☺ ♥) ],
40             'uni_hands' => [ qw(☜ ☝ ☞ ☟) ],
41             'uni_arrow_rot' => [ qw(➫ ➭ ➬ ➭) ],
42             'uni_cards' => [ qw(♣ ♤ ♥ ♦) ],
43             'uni_triangle' => [ qw(◢ ◣ ◤ ◥) ],
44             'uni_square' => [ qw(◰ ◳ ◲ ◱) ],
45             'uni_pie' => [ qw(◴ ◷ ◶ ◵) ],
46             'uni_circle' => [ qw(◐ ◓ ◑ ◒) ],
47             'uni_qtr_circle' => [ qw(◜ ◝ ◞ ◟) ],
48             'uni_three_lines' => [ qw(⚞ ☰ ⚟ ☰) ],
49             'uni_trigram_down' => [ qw(☰ ☱ ☲ ☴) ],
50             'uni_trigram_bounce' => [ qw(☰ ☱ ☲ ☴ ☰ ☴ ☲ ☱) ],
51             'uni_count' => [ qw(➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉) ],
52             'uni_ellipsis_propeller' => [ qw(⋮ ⋰ ⋯ ⋱) ],
53             'uni_earth' => [ qw(🌍 🌏 🌎) ],
54             'uni_moon' => [ qw(🌑 🌘 🌗 🌖 🌕 🌔 🌒) ],
55             'uni_junk_food' => [ qw(🌭 🌮 🌯 🍔 🍕 🍟) ],
56             'uni_clapping' => [ qw(👐 👏) ],
57             'uni_diamonds' => [ qw(🔹 🔷 🔸 🔶) ],
58             'wide_ascii_prog' =>
59             [ qw([>----] [=>---] [==>--] [===>-] [====>] [----<] [---<=] [--<==] [-<===] [<====]) ],
60             'wide_ascii_propeller' =>
61             [ qw([|----] [=/---] [==---] [===\-] [====|] [---\=] [---==] [-/===]) ],
62             'wide_ascii_snek' =>
63             [ qw([>----] [~>---] [~~>--] [~~~>-] [~~~~>] [----<] [---<~] [--<~~] [-<~~~] [<~~~~]) ],
64             'wide_uni_greyscale' =>
65             [ qw(░░░░░░░ ▒░░░░░░ ▒▒░░░░░ ▒▒▒░░░░ ▒▒▒▒░░░ ▒▒▒▒▒░░ ▒▒▒▒▒▒░ ▒▒▒▒▒▒▒ ▒▒▒▒▒▒░ ▒▒▒▒▒░░ ▒▒▒▒░░░ ▒▒▒░░░░ ▒▒░░░░░ ▒░░░░░░ ░░░░░░░) ],
66             'wide_uni_greyscale2' =>
67             [ qw(░░░░░░░ ▒░░░░░░ ▒▒░░░░░ ▒▒▒░░░░ ▒▒▒▒░░░ ▒▒▒▒▒░░ ▒▒▒▒▒▒░ ▒▒▒▒▒▒▒ ░▒▒▒▒▒▒ ░░▒▒▒▒▒ ░░░▒▒▒▒ ░░░░▒▒▒ ░░░░░▒▒ ░░░░░▒ ░░░░░░) ],
68             );
69              
70             sub new {
71 0     0 0   my ($class, %args) = @_;
72 0           my $self = {};
73              
74             # seq can either be an array ref with a whole set of frames, or can be the
75             # name of a frame set.
76 0 0         if ( ! defined($args{'seq'}) ) {
    0          
77 0           $self->{'seq'} = $frames{'wide_uni_greyscale2'};
78             } elsif ( ref($args{'seq'}) ne 'ARRAY' ) {
79 0           $self->{'seq'} = $frames{$args{'seq'}};
80             } else {
81 0           $self->{'seq'} = $args{'seq'};
82             }
83              
84 0   0       $self->{'delay'} = $args{'delay'} || 0.2;
85 0   0       $self->{'color'} = $args{'color'} || 'cyan';
86 0   0       $self->{'colorcycle'} = $args{'colorcycle'} || 0;
87 0           $self->{'bksp'} = chr(0x08);
88 0           $self->{'last_size'} = length($self->{'seq'}[0]);
89 0           return bless $self, $class;
90             }
91              
92             sub start {
93 0     0 1   my $self = shift;
94 0           print "\x1b[?25l"; # Hide cursor
95 0           $self->{'last_size'} = length($self->{'seq'}[0]);
96 0           print colored("$self->{'seq'}[0]", $self->{'color'});
97             }
98              
99             sub next {
100 0     0 1   my $self = shift;
101 0           state $pos = 1;
102              
103 0 0         if( $self->{'colorcycle'} ) {
104 0           push @colors, shift @colors; # rotate the colors list
105 0           $self->{'color'} = $colors[0];
106             }
107              
108 0           print $self->{'bksp'} x $self->{'last_size'};
109 0           print colored("$self->{'seq'}[$pos]", $self->{'color'});
110              
111 0           $pos = ++$pos % scalar @{ $self->{'seq'} };
  0            
112 0           $self->{'last_size'} = length($self->{'seq'}[$pos]);
113             }
114              
115             sub done {
116 0     0 1   my $self = shift;
117              
118 0           print $self->{'bksp'} x $self->{'last_size'};
119 0           print "\x1b[?25h"; # Show cursor
120             }
121              
122             # Fork and run spinner asynchronously, until signal received.
123             sub auto_start {
124 0     0 1   my $self = shift;
125              
126 0           my $pid = fork();
127 0 0         die ("Failed to fork progress indicator.\n") unless defined $pid;
128              
129 0 0         if ($pid) { # Parent
130 0           $self->{'child'} = $pid;
131 0           return;
132             } else { # Kid stuff
133 0           $self->start();
134 0           while (1) {
135 0           sleep $self->{'delay'};
136 0           $self->next();
137             }
138 0           exit 0; # Should never get here?
139             }
140             }
141              
142             sub auto_done {
143 0     0 1   my $self = shift;
144              
145 0           kill 'KILL', $self->{'child'};
146 0           my $pid = wait();
147 0           $self->done();
148             }
149              
150             # Run, OK? Does a thing, or a list of things. usually long-running things,
151             # runs a spinner, and prints a nice status message (check or X, whether
152             # success or err), when done.
153             sub run_ok {
154 0     0 1   my $self = shift;
155 0           my $exp = shift; # A list of functions to call and wait on.
156 0           my $message = shift; # String to print before the spinner
157 0           my $termwidth = `tput cols`;
158 0 0         $termwidth = 80 unless $termwidth <= 80;
159 0           my $cols = $termwidth - length($message) - $self->{'last_size'} - 1;
160 0           my ($ok, $nok);
161 0 0         if ($self->{'last_size'} == 1) {
    0          
162 0           $ok = colored("✔", 'green');
163 0           $nok = colored("✘", 'red');
164             } elsif ($self->{'last_size'} == 5) {
165 0           $ok = colored("[ ✔ ]", 'white on_green');
166 0           $nok = colored("[ ✘ ]", 'white on_red');
167             } else { # Better be 7, or it'll look goofy, but still work
168 0           $ok = colored("[ ✔ ]", 'white on_green');
169 0           $nok = colored("[ ✘ ]", 'white on_red');
170             }
171              
172 0           print $message;
173 0           print ' ' x $cols;
174 0 0         if ( ref($exp) eq 'ARRAY' ) { # List of expressions
175 0           $self->start();
176 0           foreach my $exp (@{$exp}) {
  0            
177 0           $self->next();
178 0           my $res = eval $exp; ## no critic
179 0 0         unless ($res) {
180 0           $self->done();
181 0           say $nok;
182 0           return 0;
183             }
184             }
185 0           $self->done();
186 0           say $ok;
187             } else { # Single expression
188 0           $self->auto_start();
189 0           my $res = eval $exp; ## no critic
190 0 0         unless ($res) {
191 0           $self->auto_done();
192 0           say $nok;
193 0           return 0;
194             }
195 0           $self->auto_done();
196 0           say $ok;
197             }
198             }
199              
200             # Return list of available frames
201             sub available_frames {
202 0     0 0   my $self = shift;
203 0           return \%frames;
204             }
205              
206             sub available_colors {
207 0     0 0   my $self = shift;
208 0           return @colors;
209             }
210              
211             # ok!
212             # Call this if you want a nice checkmark after you spinn
213             sub ok {
214 0     0 0   my $self = shift;
215 0           my $ok;
216 0 0         if ($self->{'last_size'} == 1) {
    0          
217 0           $ok = colored("✔", 'green');
218             } elsif ($self->{'last_size'} == 5) {
219 0           $ok = colored("[ ✔ ]", 'white on_green');
220             } else { # Better be 7, or it'll look goofy, but still work
221 0           $ok = colored("[ ✔ ]", 'white on_green');
222             }
223 0           say $ok;
224             }
225              
226             # nok!
227             # call this to tell user everything is not ok. prints a red x
228             sub nok {
229 0     0 0   my $self = shift;
230 0           my $nok;
231 0 0         if ($self->{'last_size'} == 1) {
    0          
232 0           $nok = colored("✘", 'red');
233             } elsif ($self->{'last_size'} == 5) {
234 0           $nok = colored("[ ✘ ]", 'white on_red');
235             } else { # Better be 7, or it'll look goofy, but still work
236 0           $nok = colored("[ ✘ ]", 'white on_red');
237             }
238 0           say $nok;
239             }
240              
241             # Frame length of the first frame of a seq...this won't work if
242             # if the frames change size, but the rest of the spinnner will
243             # probably also screw up in that case.
244             sub frame_length {
245 0     0 0   my $self = shift;
246 0           return length($self->{'seq'}[0]);
247             }
248              
249             1;
250              
251             __END__