File Coverage

blib/lib/Convert/Binary/C/Cached.pm
Criterion Covered Total %
statement 173 197 87.8
branch 100 134 74.6
condition 26 36 72.2
subroutine 18 19 94.7
pod 7 7 100.0
total 324 393 82.4


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-2024 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 5     5   5175 use strict;
  5         12  
  5         174  
20 5     5   20 use Convert::Binary::C;
  5         9  
  5         98  
21 5     5   27 use Carp;
  5         8  
  5         323  
22 5     5   25 use vars qw( @ISA $VERSION );
  5         20  
  5         15269  
23              
24             @ISA = qw(Convert::Binary::C);
25              
26             $VERSION = '0.86';
27              
28             sub new
29             {
30 940     940 1 9847510 my $class = shift;
31 940         11430 my $self = $class->SUPER::new;
32              
33 940         2748 $self->{cache} = undef;
34 940         1985 $self->{parsed} = 0;
35 940         1565 $self->{uses_cache} = 0;
36              
37 940 100       3347 @_ % 2 and croak "Number of configuration arguments to new must be even";
38              
39 939 100       3696 @_ and $self->configure(@_);
40              
41 936         38856 return $self;
42             }
43              
44             sub configure
45             {
46 1977     1977 1 324304 my $self = shift;
47              
48 1977 100 100     8046 if (@_ < 2 and not defined wantarray) {
49 3 100       441 $^W and carp "Useless use of configure in void context";
50 3         66 return;
51             }
52              
53 1974 100 33     6196 if (@_ == 0) {
    50          
54 962         23638 my $cfg = $self->SUPER::configure;
55 962         2892 $cfg->{Cache} = $self->{cache};
56 962         11254 return $cfg;
57             }
58             elsif (@_ == 1 and $_[0] eq 'Cache') {
59 0         0 return $self->{cache};
60             }
61              
62 1012         1574 my @args;
63              
64 1012 50       6452 if (@_ == 1) {
    50          
65 0         0 @args = @_;
66             }
67             elsif (@_ % 2 == 0) {
68 1012         2232 while (@_) {
69 1407         5480 my %arg = splice @_, 0, 2;
70 1407 100       2876 if (exists $arg{Cache}) {
71 932 50       2915 if ($self->{parsed}) {
    100          
72 0         0 croak 'Cache cannot be configured after parsing';
73             }
74             elsif (ref $arg{Cache}) {
75 1         264 croak 'Cache must be a string value, not a reference';
76             }
77             else {
78 931 50       2033 if (defined $arg{Cache}) {
79 931         1275 my @missing;
80 931         1434 eval { require Data::Dumper };
  931         6890  
81 931 100       14989 $@ and push @missing, 'Data::Dumper';
82 931         1296 eval { require IO::File };
  931         3295  
83 931 100       16615 $@ and push @missing, 'IO::File';
84 931 100       2244 if (@missing) {
85 2 50       396 $^W and carp "Cannot load ", join(' and ', @missing), ", disabling cache";
86 2         18 undef $arg{Cache};
87             }
88             }
89 931         3635 $self->{cache} = $arg{Cache};
90             }
91             }
92 475         1261 else { push @args, %arg }
93             }
94             }
95              
96 1011         1537 my $opt = $self;
97              
98 1011 100       2158 if (@args) {
99 119         222 $opt = eval { $self->SUPER::configure(@args) };
  119         2072  
100 119 100       7492 $@ =~ s/\s+at.*?Cached\.pm.*//s, croak $@ if $@;
101             }
102              
103 967         2030 $opt;
104             }
105              
106             sub clean
107             {
108 46     46 1 2519 my $self = shift;
109              
110 46         596 delete $self->{$_} for grep !/^(?:|cache|parsed|uses_cache)$/, keys %$self;
111              
112 46         119 $self->{parsed} = 0;
113 46         114 $self->{uses_cache} = 0;
114              
115 46         2586 $self->SUPER::clean;
116             }
117              
118             sub clone
119             {
120 3     3 1 1477 my $self = shift;
121              
122 3 50       14 unless (defined wantarray) {
123 3 100       284 $^W and carp "Useless use of clone in void context";
124 3         32 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 49     49 1 9709 my $self = shift;
141 49         179 my($warn,$error) = $self->__parse('file', $_[0]);
142 48         156 for my $w ( @$warn ) { carp $w }
  0         0  
143 48 100       1133 defined $error and croak $error;
144 42 100       215 defined wantarray and return $self;
145             }
146              
147             sub parse
148             {
149 976     976 1 40607 my $self = shift;
150 976         2710 my($warn,$error) = $self->__parse('code', $_[0]);
151 975         2320 for my $w ( @$warn ) { carp $w }
  4         831  
152 975 100       13061 defined $error and croak $error;
153 918 100       3833 defined wantarray and return $self;
154             }
155              
156             sub dependencies
157             {
158 20     20 1 35267 my $self = shift;
159              
160 20 100       712 $self->{parsed} or croak "Call to dependencies without parse data";
161              
162 17 100       71 unless (defined wantarray) {
163 3 100       297 $^W and carp "Useless use of dependencies in void context";
164 3         34 return;
165             }
166              
167 14 100       107 $self->{files} || $self->SUPER::dependencies;
168             }
169              
170             sub __uses_cache
171             {
172 904     904   13554 my $self = shift;
173 904         2584 $self->{uses_cache};
174             }
175              
176             sub __parse
177             {
178 1025     1025   1627 my $self = shift;
179              
180 1025 100       2814 if (defined $self->{cache}) {
181 929 100       2458 $self->{parsed} and croak "Cannot parse more than once for cached objects";
182              
183 928         2531 $self->{$_[0]} = $_[1];
184              
185 928 100       2165 if ($self->__can_use_cache) {
186 45         114 my @WARN;
187             {
188 45     0   63 local $SIG{__WARN__} = sub { push @WARN, $_[0] };
  45         346  
  0         0  
189 45         85 eval { $self->SUPER::parse_file($self->{cache}) };
  45         64255  
190             }
191 45 100 66     301 unless ($@ or @WARN) {
192 28         61 $self->{parsed} = 1;
193 28         47 $self->{uses_cache} = 1;
194 28         93 return;
195             }
196 17         97 $self->clean;
197             }
198             }
199              
200 996         2710 $self->{parsed} = 1;
201              
202 996         2086 my(@warnings, $error);
203             {
204 996     4   1484 local $SIG{__WARN__} = sub { push @warnings, $_[0] };
  996         5648  
  4         65  
205              
206 996 100       2948 if ($_[0] eq 'file') {
207 23         46 eval { $self->SUPER::parse_file($_[1]) };
  23         332878  
208             }
209             else {
210 973         1499 eval { $self->SUPER::parse($_[1]) };
  973         174647  
211             }
212             }
213              
214 996 100       3369 if ($@) {
215 63         138 $error = $@;
216 63         674 $error =~ s/\s+at.*?Cached\.pm.*//s;
217             }
218             else {
219 933 100       4427 defined $self->{cache} and $self->__save_cache;
220             }
221              
222 995         6345 for (@warnings) { s/\s+at.*?Cached\.pm.*//s }
  4         40  
223              
224 995         4550 (\@warnings, $error);
225             }
226              
227             sub __can_use_cache
228             {
229 928     928   1546 my $self = shift;
230 928         4564 my $fh = IO::File->new;
231              
232 928 100 66     55053 unless (-e $self->{cache} and -s _) {
233 5 50       21 $ENV{CBCC_DEBUG} and print STDERR "CBCC: cache file '$self->{cache}' doesn't exist or is empty\n";
234 5         35 return 0;
235             }
236              
237 923 50       3668 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 923         38351 my @warnings;
244 923         1498 my @config = do {
245 923         1467 my $config;
246 923 50       18989 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 923 100       6456 unless ($config =~ /^#if\s+0/) {
251 5 50       19 $ENV{CBCC_DEBUG} and print STDERR "CBCC: invalid configuration\n";
252 5         126 return 0;
253             }
254 918         5848 local $/ = $/.'#endif';
255 918         4021 chomp($config = <$fh>);
256 918         14578 $config =~ s/^\*//gms;
257 918     104   7761 local $SIG{__WARN__} = sub { push @warnings, $_[0] };
  104         2841  
258 918         88853 eval $config;
259             };
260              
261             # corrupt config
262 918 50 66     3671 if ($@ or @warnings or @config % 2) {
      66        
263 867 50       2695 $ENV{CBCC_DEBUG} and print STDERR "CBCC: broken configuration\n";
264 867         18097 return 0;
265             }
266              
267 51         196 my %config = @config;
268              
269 51 100       167 my $what = exists $self->{code} ? 'code' : 'file';
270              
271 51 100 100     339 unless (exists $config{$what} and
      100        
272             $config{$what} eq $self->{$what} and
273             __reccmp($config{cfg}, $self->configure)) {
274 3 50       14 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         119 return 0;
282             }
283              
284 48         469 while (my($file, $spec) = each %{$config{files}}) {
  1076         2026  
285 1031 50       7644 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 1031         1706 my($size, $mtime, $ctime) = (stat(_))[7,9,10];
290 1031 100 100     3648 unless ($spec->{size} == $size and
      66        
291             $spec->{mtime} == $mtime and
292             $spec->{ctime} == $ctime) {
293 3 50       13 $ENV{CBCC_DEBUG} and print STDERR "CBCC: size/mtime/ctime of '$file' changed\n";
294 3         121 return 0;
295             }
296             }
297              
298 45         143 $self->{files} = $config{files};
299              
300 45 50       135 $ENV{CBCC_DEBUG} and print STDERR "CBCC: '$self->{cache}' is usable\n";
301 45         1558 return 1;
302             }
303              
304             sub __save_cache
305             {
306 900     900   1675 my $self = shift;
307 900         5277 my $fh = IO::File->new;
308              
309 900 100       31765 $fh->open(">$self->{cache}") or croak "Cannot open '$self->{cache}': $!";
310              
311 899 100       92372 my $what = exists $self->{code} ? 'code' : 'file';
312              
313 899         3830 my $config = Data::Dumper->new([{ $what => $self->{$what},
314             cfg => $self->configure,
315             files => scalar $self->SUPER::dependencies,
316             }], ['*'])->Indent(1)->Dump;
317 899         167269 $config =~ s/[^(]*//;
318 899         25843 $config =~ s/^/*/gms;
319              
320             print $fh "#if 0\n", $config, "#endif\n\n",
321 899         2046 do { local $^W; $self->sourcify({ Context => 1 }) };
  899         3974  
  899         177685  
322             }
323              
324             sub __reccmp
325             {
326 3791     3791   8474 my($ref, $val) = @_;
327              
328 3791 50 66     8726 !defined($ref) && !defined($val) and return 1;
329 3780 50 33     8292 !defined($ref) || !defined($val) and return 0;
330              
331 3780 100       8717 ref $ref or return $ref eq $val;
332              
333 341 100       784 if (ref $ref eq 'ARRAY') {
    50          
334 194 100       389 @$ref == @$val or return 0;
335 193         444 for (0..$#$ref) {
336 2258 50       2778 __reccmp($ref->[$_], $val->[$_]) or return 0;
337             }
338             }
339             elsif (ref $ref eq 'HASH') {
340 147 50       383 keys %$ref == keys %$val or return 0;
341 147         500 for (keys %$ref) {
342 1484 100       2684 __reccmp($ref->{$_}, $val->{$_}) or return 0;
343             }
344             }
345 0         0 else { return 0 }
346              
347 339         939 return 1;
348             }
349              
350             1;
351              
352             __END__