File Coverage

blib/lib/Chart/Kaleido.pm
Criterion Covered Total %
statement 99 135 73.3
branch 12 32 37.5
condition 9 29 31.0
subroutine 20 27 74.0
pod 0 9 0.0
total 140 232 60.3


line stmt bran cond sub pod time code
1             package Chart::Kaleido;
2              
3             # ABSTRACT: Base class for Chart::Kaleido
4              
5 1     1   470 use 5.010;
  1         3  
6 1     1   4 use strict;
  1         2  
  1         21  
7 1     1   4 use warnings;
  1         2  
  1         51  
8              
9             our $VERSION = '0.012'; # VERSION
10              
11 1     1   6 use Moo;
  1         1  
  1         7  
12 1     1   285 use Config;
  1         2  
  1         45  
13 1     1   6 use JSON;
  1         2  
  1         7  
14 1     1   126 use Types::Standard qw(Int Str);
  1         2  
  1         6  
15 1     1   954 use File::Which qw(which);
  1         892  
  1         48  
16 1     1   1233 use IPC::Run qw();
  1         32089  
  1         23  
17 1     1   6 use namespace::autoclean;
  1         2  
  1         15  
18              
19 1     1   68 use constant KALEIDO => 'kaleido';
  1         2  
  1         375  
20              
21              
22             has timeout => (
23             is => 'ro',
24             isa => Int,
25             default => 30,
26             );
27              
28             has base_args => (
29             is => 'ro',
30             init_arg => 0,
31             default => sub { [] },
32             );
33              
34             has _default_chromium_args => (
35             is => 'ro',
36             default => sub {
37             [
38             qw(
39             --disable-gpu
40             --allow-file-access-from-files
41             --disable-breakpad
42             --disable-dev-shm-usage
43             )
44             ];
45             }
46             );
47              
48             has disable_gpu => (
49             is => 'ro',
50             default => 1,
51             );
52              
53             has _stall_timeout => (
54             is => 'lazy',
55             builder =>
56 1     1   23 sub { IPC::Run::timeout( $_[0]->timeout, name => 'stall timeout' ) },
57             );
58              
59             has _h => ( is => 'rw' );
60              
61             has _ios => (
62             is => 'ro',
63             default => sub {
64             return { map { $_ => '' } qw(in out err) };
65             },
66             );
67              
68             # class attributes
69 0     0 0 0 sub all_formats { [] }
70 0     0 0 0 sub scope_name { "" }
71 0     0 0 0 sub scope_flags { [] }
72              
73             sub DEMOLISH {
74 0     0 0 0 my ($self) = @_;
75 0         0 $self->shutdown_kaleido;
76             }
77              
78             sub _reset {
79 1     1   3 my ($self) = @_;
80 1         6 $self->_ios->{in} = '';
81 1         4 $self->_ios->{out} = '';
82 1         3 $self->_ios->{err} = '';
83             }
84              
85             sub kaleido_args {
86 4     4 0 1118 my ($self) = @_;
87              
88 4         9 my @args = @{ $self->base_args };
  4         26  
89 4 50       52 unless ( $self->disable_gpu ) {
90 0         0 @args = grep { $_ ne '--disable-gpu' } @args;
  0         0  
91             }
92              
93 1     1   7 no strict 'refs';
  1         2  
  1         979  
94             push @args, map {
95 16         46 my $val = $self->$_;
96 16 50       29 if ( defined $val ) {
97 0         0 my $flag = $_;
98 0         0 $flag =~ s/_/-/g;
99              
100             # too bad Perl does not have a core boolean type..
101 0 0 0     0 if ( ref($val) =~ /^(JSON::.*::Boolean|boolean)$/ and $val ) {
102 0         0 "--$flag";
103             }
104             else {
105 0         0 "--$flag=$val";
106             }
107             }
108             else {
109 16         28 ();
110             }
111 4         10 } @{ $self->scope_flags };
  4         17  
112              
113 4         60 return \@args;
114             }
115              
116             sub ensure_kaleido {
117 2     2 0 26 my ( $self, $override_args ) = @_;
118 2   33     18 $override_args //= $self->kaleido_args;
119              
120 2 100 66     28 unless ( $self->_h and $self->_h->pumpable ) {
121 1         6 $self->_reset;
122             my $h = IPC::Run::start(
123 1         2 [ KALEIDO, @{ $self->kaleido_args } ],
124             \$self->_ios->{in},
125             \$self->_ios->{out},
126             \$self->_ios->{err},
127 1         2 $self->_stall_timeout,
128             );
129 1         10173 $self->_h($h);
130              
131 1         56 $self->_stall_timeout->start;
132 1         291 my $resp = $self->_get_kaleido_out;
133 1 50 33     32 if ( exists $resp->{code} and $resp->{code} == 0 ) {
134 1         18 return $resp->{version};
135             }
136             else {
137 0         0 die $resp->{message};
138             }
139             }
140             }
141              
142             sub shutdown_kaleido {
143 0     0 0 0 my ($self) = @_;
144              
145 0 0       0 if ( $self->_h ) {
146 0         0 eval { $self->finish; };
  0         0  
147 0 0       0 if ($@) {
148 0         0 $self->_h->kill_kill;
149             }
150             }
151             }
152              
153             sub do_transform {
154 2     2 0 8 my ( $self, $data ) = @_;
155              
156 2         12 $self->ensure_kaleido;
157              
158 2         132 my $json = JSON->new->allow_blessed(1)->convert_blessed(1);
159 2         76 $self->_ios->{in} .= $json->encode($data) . "\n";
160 2         52 $self->_stall_timeout->start;
161 2         538 my $resp = $self->_get_kaleido_out;
162 2         59 return $resp;
163             }
164              
165             sub version {
166 0     0 0 0 my ( $class, $force_check ) = @_;
167              
168 0 0       0 if ( $class->_check_alien($force_check) ) {
169 0         0 return Alien::Plotly::Kaleido->version;
170             }
171             else {
172 0         0 state $version;
173 0 0 0     0 if ( not $version or $force_check ) {
174 0         0 $version = $class->_detect_kaleido_version;
175             }
176 0         0 return $version;
177             }
178             }
179              
180             sub _get_kaleido_out {
181 3     3   18 my ($self) = @_;
182              
183 3         9 while (1) {
184 14         119 $self->_h->pump;
185 14         1369903 my $out = $self->_ios->{out};
186 14         559 my @lines = split( /\n/, $out );
187 14 100       68 next unless @lines;
188              
189 10         31 for my $line (@lines) {
190 10         15 my $data;
191 10         24 eval { $data = decode_json($line); };
  10         1120  
192 10 100       68 next if $@;
193 3         95 $self->_stall_timeout->reset;
194 3         335 $self->_ios->{out} = ''; # clear out buffer
195 3         31 return $data;
196             }
197             }
198             }
199              
200             sub _check_alien {
201 1     1   2 my ( $class, $force_check ) = @_;
202              
203 1         1 state $has_alien;
204              
205 1 50 33     4 if ( !defined $has_alien or $force_check ) {
206 1         2 $has_alien = 0;
207 1         1 eval { require Alien::Plotly::Kaleido; };
  1         439  
208 1 50 33     21430 if ( !$@ and Alien::Plotly::Kaleido->install_type eq 'share' ) {
209             $ENV{PATH} = join(
210             $Config{path_sep},
211             Alien::Plotly::Kaleido->bin_dir,
212 1   50     16551 $ENV{PATH} // ''
213             );
214 1         486 $has_alien = 1;
215             }
216             }
217 1         4 return $has_alien;
218             }
219              
220             sub _kaleido_available {
221 1     1   2 my ( $class, $force_check ) = @_;
222              
223 1         3 state $available;
224 1 50 33     7 if ( !defined $available or $force_check ) {
225 1         1 $available = 0;
226 1 0 33     3 if ( not $class->_check_alien($force_check)
227             and ( not which(KALEIDO) ) )
228             {
229 0         0 die "Kaleido tool (its 'kaleido' command) must be installed and "
230             . "in PATH in order to export images. "
231             . "Either install Alien::Plotly::Kaleido from CPAN, or install "
232             . "it manually (see https://github.com/plotly/Kaleido/releases)";
233             }
234 1         2 $available = 1;
235             }
236 1         2 return $available;
237             }
238              
239             sub _detect_kaleido_version {
240 0     0     my ($class) = @_;
241              
242 0           my $kaleido = which('kaleido');
243 0 0         if ($kaleido) {
244 0           my $kaleido = $class->new;
245 0           my $args = [ 'plotly', '--disable-gpu' ];
246 0           my $version = $kaleido->ensure_kaleido($args);
247 0           $kaleido->shutdown_kaleido;
248 0           return $version;
249             }
250              
251 0           die "Failed to detect kaleido version";
252             }
253              
254             __PACKAGE__->_kaleido_available;
255              
256             1;
257              
258             __END__