File Coverage

blib/lib/Progress/Any/Output/TermSpin.pm
Criterion Covered Total %
statement 60 95 63.1
branch 10 34 29.4
condition 5 11 45.4
subroutine 10 15 66.6
pod 2 4 50.0
total 87 159 54.7


line stmt bran cond sub pod time code
1             package Progress::Any::Output::TermSpin;
2              
3             our $DATE = '2015-01-29'; # DATE
4             our $VERSION = '0.02'; # VERSION
5              
6 1     1   1189 use 5.010001;
  1         3  
  1         33  
7 1     1   5 use strict;
  1         1  
  1         42  
8 1     1   705 use utf8;
  1         9  
  1         5  
9 1     1   32 use warnings;
  1         1  
  1         38  
10              
11             #use Color::ANSI::Util qw(ansifg ansibg);
12             #use Text::ANSI::Util qw(ta_mbtrunc ta_mbswidth ta_length);
13 1     1   4 use Time::HiRes qw(time);
  1         1  
  1         7  
14             #require Win32::Console::ANSI if $^O =~ /Win/;
15              
16             $|++;
17              
18             our %STYLES = (
19             line => {utf8=>0, chars=>'|/-\\'},
20             bubble => {utf8=>0, chars=>'.oOo'},
21             pie_utf8 => {utf8=>1, chars=>'○◔◑◕●'},
22             );
23              
24             # patch handles
25             my ($ph1, $ph2, $ph3);
26              
27             my $laa_obj;
28             my $has_printed_log;
29              
30             sub _patch {
31 2     2   2 my $out = shift;
32              
33 2 50       5 return if $ph1;
34 2         500 require Monkey::Patch::Action;
35             $ph1 = Monkey::Patch::Action::patch_package(
36             'Log::Any::Adapter::ScreenColoredLevel', 'hook_after_log', 'replace',
37             sub {
38 0     0   0 my $self = shift;
39 0         0 undef $out->{_lastlen};
40              
41             # don't print newline after each log, do it before instead. so we
42             # can print spinning cursor
43              
44 0         0 $laa_obj = $self;
45 0         0 $has_printed_log++;
46             }
47 2 50       3050 ) if defined &{"Log::Any::Adapter::ScreenColoredLevel::hook_after_log"};
  2         10  
48              
49             $ph2 = Monkey::Patch::Action::patch_package(
50             'Log::Any::Adapter::ScreenColoredLevel', 'hook_before_log', 'replace',
51             sub {
52 0     0   0 my $self = shift;
53              
54             # clean spinning cursor, if exists
55 0         0 $out->cleanup;
56              
57             # print newline before log (see above comment)
58 0 0       0 return unless $has_printed_log;
59 0         0 print { $self->{_fh} } "\n";
  0         0  
60              
61 0 0       0 $out->keep_delay_showing if $out->{show_delay};
62             }
63 2 50       2 ) if defined &{"Log::Any::Adapter::ScreenColoredLevel::hook_before_log"};
  2         6  
64              
65             $ph3 = Monkey::Patch::Action::patch_package(
66             'Log::Any::Adapter::ScreenColoredLevel', 'DESTROY', 'add_or_replace',
67             sub {
68 0     0   0 my $self = shift;
69              
70 0 0       0 return unless $has_printed_log;
71 0         0 print { $self->{_fh} } "\n";
  0         0  
72             }
73 2         9 );
74             }
75              
76             sub _unpatch {
77 1     1   1 undef $ph1;
78 1         1 undef $ph2;
79 1         2 undef $ph3;
80             }
81              
82             sub new {
83 2     2 1 2512 my ($class, %args0) = @_;
84              
85 2         3 my %args;
86              
87 2   50     12 $args{style} = delete($args0{style}) // 'line';
88 2 50       6 $STYLES{$args{style}} or die "Unknown style '$args{style}'";
89              
90 2   100     10 $args{fh} = delete($args0{fh}) // \*STDOUT;
91              
92 2   50     8 $args{speed} = delete($args0{speed}) // 0.2;
93              
94 2         4 $args{show_delay} = delete($args0{show_delay});
95              
96 2 50       5 keys(%args0) and die "Unknown output parameter(s): ".
97             join(", ", keys(%args0));
98              
99 2         5 $args{_last_hide_time} = time();
100              
101 2         5 my $self = bless \%args, $class;
102 2         5 $self->_patch;
103              
104             # XXX hackish
105 2         100 $Progress::Any::output_data{"$self"}{freq} = -$args{speed};
106              
107 2         31 $self;
108             }
109              
110             sub update {
111 2     2 0 2514 my ($self, %args) = @_;
112              
113 2         4 my $now = time();
114              
115             # if there is show_delay, don't display until we've surpassed it
116 2 50       5 if (defined $self->{show_delay}) {
117 0 0       0 return if $now - $self->{show_delay} < $self->{_last_hide_time};
118             }
119              
120             # cleanup if finished
121 2         2 my $p = $args{indicator};
122 2         5 my $tottgt = $p->total_target;
123 2         31 my $totpos = $p->total_pos;
124 2   33     35 my $is_complete = $p->{state} eq 'finished' ||
125             defined($tottgt) && $tottgt > 0 && $totpos == $tottgt;
126 2 50       5 if ($is_complete) {
127 0         0 $self->cleanup;
128 0         0 $self->keep_delay_showing;
129 0         0 return;
130             }
131              
132             # "erase" previous display
133 2         2 my $ll = $self->{_lastlen};
134 2 50       5 if (defined $self->{_lastlen}) {
135 0         0 print { $self->{fh} } "\b" x $self->{_lastlen};
  0         0  
136 0         0 undef $self->{_lastlen};
137             }
138              
139 2         3 my $chars = $STYLES{$self->{style}}{chars};
140 2 50       5 if (!defined($self->{_char_index})) {
141 2         3 $self->{_char_index} = 0;
142 2         3 $self->{_last_change_char_time} = $now;
143             } else {
144 0 0       0 if (($now - $self->{_last_change_char_time}) > $self->{speed}) {
145 0         0 $self->{_last_change_char_time} = $now;
146 0         0 $self->{_char_index}++;
147 0 0       0 $self->{_char_index} = 0 if $self->{_char_index} >= length($chars);
148             }
149             }
150 2         3 my $char = substr($chars, $self->{_char_index}, 1);
151 2         2 print { $self->{fh} } " ", $char;
  2         92  
152              
153 2         10 $self->{_lastlen} = 2;
154             }
155              
156             sub cleanup {
157 0     0 0 0 my ($self) = @_;
158              
159             # sometimes (e.g. when a subtask's target is undefined) we don't get
160             # state=finished at the end. but we need to cleanup anyway at the end of
161             # app, so this method is provided and will be called by e.g.
162             # Perinci::CmdLine
163              
164 0         0 my $ll = $self->{_lastlen};
165 0 0       0 return unless $ll;
166 0         0 print { $self->{fh} } "\b" x $ll, " " x $ll, "\b" x $ll;
  0         0  
167             }
168              
169             sub keep_delay_showing {
170 0     0 1 0 my $self = shift;
171              
172 0         0 $self->{_last_hide_time} = time();
173             }
174              
175             sub DESTROY {
176 1     1   6 my $self = shift;
177 1         2 $self->_unpatch;
178              
179 1 50       2 return unless $has_printed_log;
180 0   0       print { $laa_obj->{_fh} // \*STDOUT } "\n";
  0            
181 0           undef $laa_obj;
182             }
183              
184             1;
185             # ABSTRACT: Output progress to terminal as spinning cursor
186              
187             __END__