File Coverage

blib/lib/Convert/Binary/C/Cached.pm
Criterion Covered Total %
statement 172 197 87.3
branch 100 134 74.6
condition 26 36 72.2
subroutine 17 19 89.4
pod 7 7 100.0
total 322 393 81.9


line stmt bran cond sub pod time code
1             ################################################################################
2             #
3             # MODULE: Convert::Binary::C::Cached
4             #
5             ################################################################################
6             #
7             # DESCRIPTION: Cached version of Convert::Binary::C module
8             #
9             ################################################################################
10             #
11             # Copyright (c) 2002-2020 Marcus Holland-Moritz. All rights reserved.
12             # This program is free software; you can redistribute it and/or modify
13             # it under the same terms as Perl itself.
14             #
15             ################################################################################
16              
17             package Convert::Binary::C::Cached;
18              
19 4     4   1341 use strict;
  4         8  
  4         109  
20 4     4   19 use Convert::Binary::C;
  4         5  
  4         94  
21 4     4   19 use Carp;
  4         12  
  4         265  
22 4     4   21 use vars qw( @ISA $VERSION );
  4         8  
  4         9106  
23              
24             @ISA = qw(Convert::Binary::C);
25              
26             $VERSION = '0.83';
27              
28             sub new
29             {
30 942     942 1 4607644 my $class = shift;
31 942         8276 my $self = $class->SUPER::new;
32              
33 942         2031 $self->{cache} = undef;
34 942         1488 $self->{parsed} = 0;
35 942         1204 $self->{uses_cache} = 0;
36              
37 942 100       2382 @_ % 2 and croak "Number of configuration arguments to new must be even";
38              
39 941 100       2914 @_ and $self->configure(@_);
40              
41 938         22115 return $self;
42             }
43              
44             sub configure
45             {
46 1981     1981 1 4599 my $self = shift;
47              
48 1981 100 100     6448 if (@_ < 2 and not defined wantarray) {
49 3 100       382 $^W and carp "Useless use of configure in void context";
50 3         91 return;
51             }
52              
53 1978 100 33     4993 if (@_ == 0) {
    50          
54 964         16242 my $cfg = $self->SUPER::configure;
55 964         2254 $cfg->{Cache} = $self->{cache};
56 964         8024 return $cfg;
57             }
58             elsif (@_ == 1 and $_[0] eq 'Cache') {
59 0         0 return $self->{cache};
60             }
61              
62 1014         1335 my @args;
63              
64 1014 50       2658 if (@_ == 1) {
    50          
65 0         0 @args = @_;
66             }
67             elsif (@_ % 2 == 0) {
68 1014         1668 while (@_) {
69 1441         3689 my %arg = splice @_, 0, 2;
70 1441 100       2400 if (exists $arg{Cache}) {
71 934 50       1963 if ($self->{parsed}) {
    100          
72 0         0 croak 'Cache cannot be configured after parsing';
73             }
74             elsif (ref $arg{Cache}) {
75 1         164 croak 'Cache must be a string value, not a reference';
76             }
77             else {
78 933 50       1534 if (defined $arg{Cache}) {
79 933         1026 my @missing;
80 933         1106 eval { require Data::Dumper };
  933         5359  
81 933 100       8056 $@ and push @missing, 'Data::Dumper';
82 933         1002 eval { require IO::File };
  933         3045  
83 933 100       9340 $@ and push @missing, 'IO::File';
84 933 100       1829 if (@missing) {
85 2 50       310 $^W and carp "Cannot load ", join(' and ', @missing), ", disabling cache";
86 2         15 undef $arg{Cache};
87             }
88             }
89 933         2785 $self->{cache} = $arg{Cache};
90             }
91             }
92 507         1390 else { push @args, %arg }
93             }
94             }
95              
96 1013         1449 my $opt = $self;
97              
98 1013 100       1567 if (@args) {
99 121         194 $opt = eval { $self->SUPER::configure(@args) };
  121         1618  
100 121 100       4342 $@ =~ s/\s+at.*?Cached\.pm.*//s, croak $@ if $@;
101             }
102              
103 969         1537 $opt;
104             }
105              
106             sub clean
107             {
108 46     46 1 102 my $self = shift;
109              
110 46         459 delete $self->{$_} for grep !/^(?:|cache|parsed|uses_cache)$/, keys %$self;
111              
112 46         98 $self->{parsed} = 0;
113 46         51 $self->{uses_cache} = 0;
114              
115 46         836 $self->SUPER::clean;
116             }
117              
118             sub clone
119             {
120 3     3 1 170 my $self = shift;
121              
122 3 50       36 unless (defined wantarray) {
123 3 100       174 $^W and carp "Useless use of clone in void context";
124 3         65 return;
125             }
126              
127 0         0 my $clone = $self->SUPER::clone;
128              
129 0         0 for (keys %$self) {
130 0 0       0 if ($_) {
131 0 0       0 $clone->{$_} = ref $_ eq 'ARRAY' ? [@{$self->{$_}}] : $self->{$_};
  0         0  
132             }
133             }
134              
135 0         0 $clone;
136             }
137              
138             sub parse_file
139             {
140 51     51 1 2305 my $self = shift;
141 51         144 my($warn,$error) = $self->__parse('file', $_[0]);
142 50         152 for my $w ( @$warn ) { carp $w }
  0         0  
143 50 100       644 defined $error and croak $error;
144 44 100       190 defined wantarray and return $self;
145             }
146              
147             sub parse
148             {
149 976     976 1 11213 my $self = shift;
150 976         2101 my($warn,$error) = $self->__parse('code', $_[0]);
151 975         2071 for my $w ( @$warn ) { carp $w }
  4         359  
152 975 100       7046 defined $error and croak $error;
153 918 100       3316 defined wantarray and return $self;
154             }
155              
156             sub dependencies
157             {
158 20     20 1 3224 my $self = shift;
159              
160 20 100       637 $self->{parsed} or croak "Call to dependencies without parse data";
161              
162 17 100       39 unless (defined wantarray) {
163 3 100       195 $^W and carp "Useless use of dependencies in void context";
164 3         67 return;
165             }
166              
167 14 100       68 $self->{files} || $self->SUPER::dependencies;
168             }
169              
170             sub __uses_cache
171             {
172 904     904   8845 my $self = shift;
173 904         1853 $self->{uses_cache};
174             }
175              
176             sub __parse
177             {
178 1027     1027   1332 my $self = shift;
179              
180 1027 100       2178 if (defined $self->{cache}) {
181 931 100       1743 $self->{parsed} and croak "Cannot parse more than once for cached objects";
182              
183 930         2032 $self->{$_[0]} = $_[1];
184              
185 930 100       1826 if ($self->__can_use_cache) {
186 47         113 my @WARN;
187             {
188 47     0   64 local $SIG{__WARN__} = sub { push @WARN, $_[0] };
  47         352  
  0         0  
189 47         97 eval { $self->SUPER::parse_file($self->{cache}) };
  47         66473  
190             }
191 47 100 66     312 unless ($@ or @WARN) {
192 30         73 $self->{parsed} = 1;
193 30         51 $self->{uses_cache} = 1;
194 30         112 return;
195             }
196 17         45 $self->clean;
197             }
198             }
199              
200 996         2506 $self->{parsed} = 1;
201              
202 996         1561 my(@warnings, $error);
203             {
204 996     4   1158 local $SIG{__WARN__} = sub { push @warnings, $_[0] };
  996         5237  
  4         36  
205              
206 996 100       2479 if ($_[0] eq 'file') {
207 23         40 eval { $self->SUPER::parse_file($_[1]) };
  23         230502  
208             }
209             else {
210 973         1284 eval { $self->SUPER::parse($_[1]) };
  973         124437  
211             }
212             }
213              
214 996 100       3509 if ($@) {
215 63         92 $error = $@;
216 63         447 $error =~ s/\s+at.*?Cached\.pm.*//s;
217             }
218             else {
219 933 100       3451 defined $self->{cache} and $self->__save_cache;
220             }
221              
222 995         4269 for (@warnings) { s/\s+at.*?Cached\.pm.*//s }
  4         22  
223              
224 995         3573 (\@warnings, $error);
225             }
226              
227             sub __can_use_cache
228             {
229 930     930   1206 my $self = shift;
230 930         3400 my $fh = IO::File->new;
231              
232 930 100 66     38146 unless (-e $self->{cache} and -s _) {
233 5 50       24 $ENV{CBCC_DEBUG} and print STDERR "CBCC: cache file '$self->{cache}' doesn't exist or is empty\n";
234 5         27 return 0;
235             }
236              
237 925 50       4406 unless ($fh->open($self->{cache})) {
238 0 0       0 $^W and carp "Cannot open '$self->{cache}': $!";
239 0 0       0 $ENV{CBCC_DEBUG} and print STDERR "CBCC: cannot open cache file '$self->{cache}'\n";
240 0         0 return 0;
241             }
242              
243 925         31029 my @warnings;
244 925         1384 my @config = do {
245 925         1231 my $config;
246 925 50       12096 unless (defined($config = <$fh>)) {
247 0 0       0 $ENV{CBCC_DEBUG} and print STDERR "CBCC: cannot read configuration\n";
248 0         0 return 0;
249             }
250 925 100       5828 unless ($config =~ /^#if\s+0/) {
251 5 50       14 $ENV{CBCC_DEBUG} and print STDERR "CBCC: invalid configuration\n";
252 5         70 return 0;
253             }
254 920         5084 local $/ = $/.'#endif';
255 920         3746 chomp($config = <$fh>);
256 920         20533 $config =~ s/^\*//gms;
257 920     0   6012 local $SIG{__WARN__} = sub { push @warnings, $_[0] };
  0         0  
258 920         66965 eval $config;
259             };
260              
261             # corrupt config
262 920 50 66     3386 if ($@ or @warnings or @config % 2) {
      66        
263 867 50       1985 $ENV{CBCC_DEBUG} and print STDERR "CBCC: broken configuration\n";
264 867         15026 return 0;
265             }
266              
267 53         181 my %config = @config;
268              
269 53 100       146 my $what = exists $self->{code} ? 'code' : 'file';
270              
271 53 100 100     289 unless (exists $config{$what} and
      100        
272             $config{$what} eq $self->{$what} and
273             __reccmp($config{cfg}, $self->configure)) {
274 3 50       8 if ($ENV{CBCC_DEBUG}) {
275 0         0 print STDERR "CBCC: configuration has changed\n";
276 0         0 print STDERR "CBCC: what='$what', \$config{$what}='$config{$what}' \$self->{$what}='$self->{$what}'\n";
277 0         0 my $dump = Data::Dumper->Dump([$config{cfg}, $self->configure], ['config', 'self']);
278 0         0 $dump =~ s/^/CBCC: /mg;
279 0         0 print STDERR $dump;
280             }
281 3         108 return 0;
282             }
283              
284 50         370 while (my($file, $spec) = each %{$config{files}}) {
  1174         3501  
285 1127 50       12005 unless (-e $file) {
286 0 0       0 $ENV{CBCC_DEBUG} and print STDERR "CBCC: file '$file' deleted\n";
287 0         0 return 0;
288             }
289 1127         3632 my($size, $mtime, $ctime) = (stat(_))[7,9,10];
290 1127 100 100     6345 unless ($spec->{size} == $size and
      66        
291             $spec->{mtime} == $mtime and
292             $spec->{ctime} == $ctime) {
293 3 50       10 $ENV{CBCC_DEBUG} and print STDERR "CBCC: size/mtime/ctime of '$file' changed\n";
294 3         73 return 0;
295             }
296             }
297              
298 47         101 $self->{files} = $config{files};
299              
300 47 50       118 $ENV{CBCC_DEBUG} and print STDERR "CBCC: '$self->{cache}' is usable\n";
301 47         1209 return 1;
302             }
303              
304             sub __save_cache
305             {
306 900     900   1805 my $self = shift;
307 900         4349 my $fh = IO::File->new;
308              
309 900 100       27474 $fh->open(">$self->{cache}") or croak "Cannot open '$self->{cache}': $!";
310              
311 899 100       94780 my $what = exists $self->{code} ? 'code' : 'file';
312              
313 899         3313 my $config = Data::Dumper->new([{ $what => $self->{$what},
314             cfg => $self->configure,
315             files => scalar $self->SUPER::dependencies,
316             }], ['*'])->Indent(1)->Dump;
317 899         112983 $config =~ s/[^(]*//;
318 899         16605 $config =~ s/^/*/gms;
319              
320             print $fh "#if 0\n", $config, "#endif\n\n",
321 899         1622 do { local $^W; $self->sourcify({ Context => 1 }) };
  899         2972  
  899         60796  
322             }
323              
324             sub __reccmp
325             {
326 4048     4048   6035 my($ref, $val) = @_;
327              
328 4048 50 66     6658 !defined($ref) && !defined($val) and return 1;
329 4038 50 33     10095 !defined($ref) || !defined($val) and return 0;
330              
331 4038 100       10669 ref $ref or return $ref eq $val;
332              
333 352 100       661 if (ref $ref eq 'ARRAY') {
    50          
334 201 100       332 @$ref == @$val or return 0;
335 200         391 for (0..$#$ref) {
336 2472 50       3890 __reccmp($ref->[$_], $val->[$_]) or return 0;
337             }
338             }
339             elsif (ref $ref eq 'HASH') {
340 151 50       404 keys %$ref == keys %$val or return 0;
341 151         427 for (keys %$ref) {
342 1525 100       2442 __reccmp($ref->{$_}, $val->{$_}) or return 0;
343             }
344             }
345 0         0 else { return 0 }
346              
347 350         798 return 1;
348             }
349              
350             1;
351              
352             __END__