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   1456 use strict;
  4         8  
  4         117  
20 4     4   19 use Convert::Binary::C;
  4         8  
  4         83  
21 4     4   19 use Carp;
  4         17  
  4         235  
22 4     4   25 use vars qw( @ISA $VERSION );
  4         7  
  4         9493  
23              
24             @ISA = qw(Convert::Binary::C);
25              
26             $VERSION = '0.84';
27              
28             sub new
29             {
30 941     941 1 4606699 my $class = shift;
31 941         7773 my $self = $class->SUPER::new;
32              
33 941         1905 $self->{cache} = undef;
34 941         1351 $self->{parsed} = 0;
35 941         1290 $self->{uses_cache} = 0;
36              
37 941 100       2505 @_ % 2 and croak "Number of configuration arguments to new must be even";
38              
39 940 100       2624 @_ and $self->configure(@_);
40              
41 937         21036 return $self;
42             }
43              
44             sub configure
45             {
46 1979     1979 1 5688 my $self = shift;
47              
48 1979 100 100     6541 if (@_ < 2 and not defined wantarray) {
49 3 100       477 $^W and carp "Useless use of configure in void context";
50 3         123 return;
51             }
52              
53 1976 100 33     4596 if (@_ == 0) {
    50          
54 963         17084 my $cfg = $self->SUPER::configure;
55 963         2265 $cfg->{Cache} = $self->{cache};
56 963         7788 return $cfg;
57             }
58             elsif (@_ == 1 and $_[0] eq 'Cache') {
59 0         0 return $self->{cache};
60             }
61              
62 1013         1422 my @args;
63              
64 1013 50       2404 if (@_ == 1) {
    50          
65 0         0 @args = @_;
66             }
67             elsif (@_ % 2 == 0) {
68 1013         1890 while (@_) {
69 1424         3753 my %arg = splice @_, 0, 2;
70 1424 100       2557 if (exists $arg{Cache}) {
71 933 50       1958 if ($self->{parsed}) {
    100          
72 0         0 croak 'Cache cannot be configured after parsing';
73             }
74             elsif (ref $arg{Cache}) {
75 1         180 croak 'Cache must be a string value, not a reference';
76             }
77             else {
78 932 50       1554 if (defined $arg{Cache}) {
79 932         1038 my @missing;
80 932         1230 eval { require Data::Dumper };
  932         5926  
81 932 100       8638 $@ and push @missing, 'Data::Dumper';
82 932         1114 eval { require IO::File };
  932         2764  
83 932 100       10545 $@ and push @missing, 'IO::File';
84 932 100       1874 if (@missing) {
85 2 50       305 $^W and carp "Cannot load ", join(' and ', @missing), ", disabling cache";
86 2         15 undef $arg{Cache};
87             }
88             }
89 932         2684 $self->{cache} = $arg{Cache};
90             }
91             }
92 491         1380 else { push @args, %arg }
93             }
94             }
95              
96 1012         1430 my $opt = $self;
97              
98 1012 100       1708 if (@args) {
99 120         211 $opt = eval { $self->SUPER::configure(@args) };
  120         1686  
100 120 100       5066 $@ =~ s/\s+at.*?Cached\.pm.*//s, croak $@ if $@;
101             }
102              
103 968         1569 $opt;
104             }
105              
106             sub clean
107             {
108 46     46 1 107 my $self = shift;
109              
110 46         546 delete $self->{$_} for grep !/^(?:|cache|parsed|uses_cache)$/, keys %$self;
111              
112 46         105 $self->{parsed} = 0;
113 46         68 $self->{uses_cache} = 0;
114              
115 46         993 $self->SUPER::clean;
116             }
117              
118             sub clone
119             {
120 3     3 1 197 my $self = shift;
121              
122 3 50       41 unless (defined wantarray) {
123 3 100       201 $^W and carp "Useless use of clone in void context";
124 3         77 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 50     50 1 2606 my $self = shift;
141 50         149 my($warn,$error) = $self->__parse('file', $_[0]);
142 49         166 for my $w ( @$warn ) { carp $w }
  0         0  
143 49 100       883 defined $error and croak $error;
144 43 100       181 defined wantarray and return $self;
145             }
146              
147             sub parse
148             {
149 976     976 1 11770 my $self = shift;
150 976         2099 my($warn,$error) = $self->__parse('code', $_[0]);
151 975         1892 for my $w ( @$warn ) { carp $w }
  4         461  
152 975 100       7781 defined $error and croak $error;
153 918 100       2901 defined wantarray and return $self;
154             }
155              
156             sub dependencies
157             {
158 20     20 1 3304 my $self = shift;
159              
160 20 100       523 $self->{parsed} or croak "Call to dependencies without parse data";
161              
162 17 100       40 unless (defined wantarray) {
163 3 100       211 $^W and carp "Useless use of dependencies in void context";
164 3         431 return;
165             }
166              
167 14 100       87 $self->{files} || $self->SUPER::dependencies;
168             }
169              
170             sub __uses_cache
171             {
172 904     904   9088 my $self = shift;
173 904         2060 $self->{uses_cache};
174             }
175              
176             sub __parse
177             {
178 1026     1026   1346 my $self = shift;
179              
180 1026 100       2136 if (defined $self->{cache}) {
181 930 100       1662 $self->{parsed} and croak "Cannot parse more than once for cached objects";
182              
183 929         1848 $self->{$_[0]} = $_[1];
184              
185 929 100       1688 if ($self->__can_use_cache) {
186 46         115 my @WARN;
187             {
188 46     0   62 local $SIG{__WARN__} = sub { push @WARN, $_[0] };
  46         325  
  0         0  
189 46         95 eval { $self->SUPER::parse_file($self->{cache}) };
  46         63745  
190             }
191 46 100 66     299 unless ($@ or @WARN) {
192 29         70 $self->{parsed} = 1;
193 29         47 $self->{uses_cache} = 1;
194 29         105 return;
195             }
196 17         41 $self->clean;
197             }
198             }
199              
200 996         2439 $self->{parsed} = 1;
201              
202 996         1483 my(@warnings, $error);
203             {
204 996     4   1212 local $SIG{__WARN__} = sub { push @warnings, $_[0] };
  996         5191  
  4         47  
205              
206 996 100       2399 if ($_[0] eq 'file') {
207 23         44 eval { $self->SUPER::parse_file($_[1]) };
  23         233620  
208             }
209             else {
210 973         1340 eval { $self->SUPER::parse($_[1]) };
  973         121214  
211             }
212             }
213              
214 996 100       3186 if ($@) {
215 63         114 $error = $@;
216 63         526 $error =~ s/\s+at.*?Cached\.pm.*//s;
217             }
218             else {
219 933 100       3754 defined $self->{cache} and $self->__save_cache;
220             }
221              
222 995         4174 for (@warnings) { s/\s+at.*?Cached\.pm.*//s }
  4         28  
223              
224 995         3426 (\@warnings, $error);
225             }
226              
227             sub __can_use_cache
228             {
229 929     929   1032 my $self = shift;
230 929         3500 my $fh = IO::File->new;
231              
232 929 100 66     36575 unless (-e $self->{cache} and -s _) {
233 5 50       22 $ENV{CBCC_DEBUG} and print STDERR "CBCC: cache file '$self->{cache}' doesn't exist or is empty\n";
234 5         29 return 0;
235             }
236              
237 924 50       3966 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 924         31194 my @warnings;
244 924         1204 my @config = do {
245 924         1101 my $config;
246 924 50       12319 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 924 100       5851 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 919         4576 local $/ = $/.'#endif';
255 919         3543 chomp($config = <$fh>);
256 919         19015 $config =~ s/^\*//gms;
257 919     0   5738 local $SIG{__WARN__} = sub { push @warnings, $_[0] };
  0         0  
258 919         65124 eval $config;
259             };
260              
261             # corrupt config
262 919 50 66     3247 if ($@ or @warnings or @config % 2) {
      66        
263 867 50       2149 $ENV{CBCC_DEBUG} and print STDERR "CBCC: broken configuration\n";
264 867         14017 return 0;
265             }
266              
267 52         182 my %config = @config;
268              
269 52 100       144 my $what = exists $self->{code} ? 'code' : 'file';
270              
271 52 100 100     277 unless (exists $config{$what} and
      100        
272             $config{$what} eq $self->{$what} and
273             __reccmp($config{cfg}, $self->configure)) {
274 3 50       7 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         66 return 0;
282             }
283              
284 49         325 while (my($file, $spec) = each %{$config{files}}) {
  1123         3166  
285 1077 50       11604 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 1077         3667 my($size, $mtime, $ctime) = (stat(_))[7,9,10];
290 1077 100 100     6013 unless ($spec->{size} == $size and
      66        
291             $spec->{mtime} == $mtime and
292             $spec->{ctime} == $ctime) {
293 3 50       8 $ENV{CBCC_DEBUG} and print STDERR "CBCC: size/mtime/ctime of '$file' changed\n";
294 3         73 return 0;
295             }
296             }
297              
298 46         98 $self->{files} = $config{files};
299              
300 46 50       114 $ENV{CBCC_DEBUG} and print STDERR "CBCC: '$self->{cache}' is usable\n";
301 46         1100 return 1;
302             }
303              
304             sub __save_cache
305             {
306 900     900   1617 my $self = shift;
307 900         4478 my $fh = IO::File->new;
308              
309 900 100       26755 $fh->open(">$self->{cache}") or croak "Cannot open '$self->{cache}': $!";
310              
311 899 100       107076 my $what = exists $self->{code} ? 'code' : 'file';
312              
313 899         2802 my $config = Data::Dumper->new([{ $what => $self->{$what},
314             cfg => $self->configure,
315             files => scalar $self->SUPER::dependencies,
316             }], ['*'])->Indent(1)->Dump;
317 899         112516 $config =~ s/[^(]*//;
318 899         15965 $config =~ s/^/*/gms;
319              
320             print $fh "#if 0\n", $config, "#endif\n\n",
321 899         1845 do { local $^W; $self->sourcify({ Context => 1 }) };
  899         2959  
  899         61247  
322             }
323              
324             sub __reccmp
325             {
326 3935     3935   5895 my($ref, $val) = @_;
327              
328 3935 50 66     6403 !defined($ref) && !defined($val) and return 1;
329 3924 50 33     9994 !defined($ref) || !defined($val) and return 0;
330              
331 3924 100       11145 ref $ref or return $ref eq $val;
332              
333 350 100       727 if (ref $ref eq 'ARRAY') {
    50          
334 200 100       340 @$ref == @$val or return 0;
335 199         398 for (0..$#$ref) {
336 2366 50       3746 __reccmp($ref->[$_], $val->[$_]) or return 0;
337             }
338             }
339             elsif (ref $ref eq 'HASH') {
340 150 50       409 keys %$ref == keys %$val or return 0;
341 150         408 for (keys %$ref) {
342 1519 100       2566 __reccmp($ref->{$_}, $val->{$_}) or return 0;
343             }
344             }
345 0         0 else { return 0 }
346              
347 348         825 return 1;
348             }
349              
350             1;
351              
352             __END__