File Coverage

blib/lib/AutoReloader.pm
Criterion Covered Total %
statement 183 187 97.8
branch 50 60 83.3
condition 16 26 61.5
subroutine 26 28 92.8
pod 5 8 62.5
total 280 309 90.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             AutoReloader - Lazy loading and reloading of anonymous subroutines
4              
5             =head1 SYNOPSIS
6              
7             use AutoReloader;
8             my $sub = AutoReloader -> new ($file, $checksub, $autoprefix);
9             $result = $sub -> (@args);
10              
11             $sub -> check (0); # turn source file checking off for $sub
12             $sub -> checksub ($coderef); # provide alternative checking routine
13              
14             use AutoReloader qw (AUTOLOAD);
15             AutoReloader -> check (1); # turn source file checking on
16             $result = somefunc (@args);
17             *somefunc{CODE}->check(0); # turn off checking for this named sub
18              
19             =head1 DESCRIPTION
20              
21             AutoReloader provides lazy loading like AutoLoader, but for function files
22             which return an anonymous subroutine upon require.
23              
24             Before requiring that file, it is checked via some subroutine returning
25             a value (default is mtime). The returned value is remembered. At each
26             call to that sub the check subroutine is run again, and if the returned
27             value changed, the source file is reloaded.
28              
29             Importing the AUTOLOAD method provides for lazy loading of anonsubs as
30             named subs. The wrapped anonsub will be assigned to a symbol table entry
31             named after the filename root of the function source file.
32              
33             =head1 METHODS
34              
35             =over 4
36              
37             =item new ($file, $checksubref, $autoprefix)
38              
39             subroutine constructor. $file can be the path to some function file or
40             a function name which will be expanded to $autoprefix/__PACKAGE__/$function.al
41             and searched for in @INC. $checksubref and $autoprefix are optional.
42             If they are not provided, the default class settings are used.
43              
44             =item auto ($autoprefix)
45              
46             set or get the default autoprefix. Default is 'auto', just as with AutoLoader:
47             for e.g. POSIX::rand the source file would be auto/POSIX/rand.al . AutoReloader
48             lets you replace the 'auto' part of the path with something else. Class method
49             (for now).
50              
51             =item suffix ($suffix)
52              
53             set or get the suffix of your autoloaded files (e.g. '.al', '.pl', '.tmpl')
54             as a package variable.
55              
56             =item check (1)
57              
58             set or get the check flag. Turn checking on by setting this to some true value.
59             Default is off. Class and object method, i.e. AutoReloader->check(1) sets the
60             default to on, $sub->check(1) sets checking for a subroutine. For now, there's
61             no way to inculcate the class default on subs with a private check flag.
62              
63             =item checksub ($coderef)
64              
65             set the checking subroutine. Class and object method. This subroutine will be
66             invoked with a subroutines source filename (full path) every time the sub for
67             which it is configured - but only if check for that subroutine is true -, and
68             should return some value special to that file.
69             Default is 'sub { (stat $_[0]) [9] }', i.e. mtime.
70              
71             =back
72              
73             =head1 SEE ALSO
74              
75             AutoLoader, AutoSplit, DBIx::VersionedSubs
76              
77             =head1 BUGS
78              
79             AutoReloader subroutines are always reported as __ANON__ (e.g. with Carp::cluck),
80             even if they are assigned to a symbol table entry. Which might not be a bug.
81              
82             There might be others.
83              
84             =head1 Author
85              
86             shmem
87              
88             =head1 CREDITS
89              
90             Many thanks to thospel, Corion, diotalevi, tye and chromatic (these are their
91             http://perlmonks.org nicks) for review and most valuable hints.
92              
93             =head1 COPYRIGHT
94              
95             Copyright 2007 - 2021 by shmem
96              
97             This program is free software; you can redistribute it and/or modify it
98             under the same terms as Perl itself.
99              
100             =cut
101              
102             package AutoReloader;
103              
104 4     4   284885 use strict;
  4         34  
  4         123  
105 4     4   21 use warnings;
  4         7  
  4         122  
106 4     4   31 use Scalar::Util;
  4         8  
  4         168  
107 4     4   24 use File::Spec;
  4         9  
  4         203  
108              
109             our $VERSION = 0.03;
110              
111 4     4   29 use vars qw($Debug %AL);
  4         7  
  4         4423  
112             $Debug = 0;
113              
114             sub new {
115 2     2 1 318 my $class = shift;
116 2         6 my $caller = caller;
117 2         8 my $sub = gensub ($caller,@_);
118              
119 2         7 bless $sub, $class;
120             }
121              
122             sub auto {
123 5     5 1 831 shift if __PACKAGE__ || $_[0] eq (caller(0))[0];
124 5 100       27 $AL {'auto'} = shift if @_;
125 5         21 $AL {'auto'};
126             }
127              
128             sub check {
129 8     8 1 2002979 my $self = shift;
130 8 100       32 if(ref($self)) {
131 3 50       21 ${ $AL {'Sub'} -> {Scalar::Util::refaddr ($self)} -> {'check'} }
  3         29  
132             = shift if @_;
133 3         9 ${ $AL {'Sub'} -> {Scalar::Util::refaddr ($self)} -> {'check'} };
  3         16  
134             }
135             else {
136 5         11 $AL {'check'} = shift;
137 5         9 $AL {'check'};
138             }
139             }
140              
141             sub checksub {
142 6     6 1 1110 my $self = shift;
143 6 100       21 if(ref($self)) {
144 2 100       12 ${ $AL {'Sub'} -> {Scalar::Util::refaddr ($self)} -> {'checksub'} }
  1         7  
145             = shift if @_;
146 2         5 ${ $AL {'Sub'} -> {Scalar::Util::refaddr ($self)} -> {'checksub'} };
  2         16  
147             }
148             else {
149 4 50       18 $AL {'checksub'} = shift if @_;
150 4         8 $AL {'checksub'};
151             }
152             }
153             sub suffix {
154 0     0 1 0 shift if __PACKAGE__ || $_[0] eq (caller(0))[0];
155 0 0       0 $AL {'suffix'} = shift if @_;
156 0         0 $AL {'suffix'};
157             }
158             # default check subroutine
159             checksub ( __PACKAGE__, sub { (stat $_[0]) [9] } );
160             # default is not checking
161             check ( __PACKAGE__, 0);
162              
163             # gensub - returns an anonymous subroutine.
164             # Parameters:
165             # if one: filename (full path)
166             # if more: package, filename [, checkfuncref [, auto ]]
167              
168             sub gensub {
169 16 50   16 0 51 my $package = scalar(@_) == 1 ? caller : shift;
170 16         24 my $file = shift;
171 16   33     65 my $chkfunc = shift || $AL {'checksub'};
172 16   50     46 my $auto = shift || $AL {'auto'} || 'auto';
173 16         21 my $function;
174              
175             {
176 16         17 ($function = pop (@{[ File::Spec->splitpath($file) ]}) ) =~ s/\..*//;
  16         20  
  16         246  
177            
178 16 50 50     101 $file .= $AL {'suffix'} || '.al' unless $file =~ /\.\w+$/;
179 16 50       326 unless (-e $file) {
180 16         39 my ($filename, $seen);
181             {
182 16         25 $filename = File::Spec -> catfile ($auto, $package, $file);
  23         222  
183 23         76 foreach my $d ('.',@INC) { # check current working dir first
184 198         1324 my $f = File::Spec -> catfile ($d,$filename);
185 198 100       2227 if (-e $f) {
186 10         29 $file = $f;
187 10         23 last;
188             }
189             }
190 23 100       48 last if $seen;
191 16 100       182 unless (-e $file) {
192             # redo the search with a truncated filename
193 7         22 $file =~ s/(\w{12,})(\.\w+)$/substr($1,0,11).$2/e;
  1         7  
194 7         11 $seen++;
195 7         11 redo;
196             }
197             }
198             die
199 16 100       273 "Can't locate function file '$filename' for package '$package'\n"
200             unless -e $file;
201             }
202             }
203              
204 10 100       67 if (my $addr = $AL {'Inc'} -> {"$package\::$function"} ) {
205 1         8 return $AL {Sub} -> {$addr} -> {'outer'};
206             }
207             else {
208             # file not known yet
209 9         14 my $inner;
210 9         15 my $h = {};
211 9         28 my $cr = $chkfunc -> ($file);
212 9         34 my $subname = "$package\::$function";
213              
214             $h = {
215             file => $file,
216 9         56 check => \$AL {'check'},
217             checksub => \$chkfunc,
218             checkref => \$cr,
219             function => $subname,
220             };
221              
222 9 100       28 my $outer = load ($package, $file, $h) or die $@;
223 8         29 my $outeraddr = Scalar::Util::refaddr ($outer);
224              
225 8         14 $h -> {'outer'} = $outer;
226 8         27 Scalar::Util::weaken ($h -> {'outer'});
227              
228 8         24 $AL{Sub} -> {$outeraddr} = $h;
229 8         21 $AL{Inc} -> {$subname} = $outeraddr;
230 8         51 return bless $outer, __PACKAGE__;
231             }
232             };
233             {
234             my $load = \&load;
235             sub load {
236 14     14 0 45 my ($package, $file, $h) = @_;
237 14         66 delete $INC {$file};
238 14         1030 my $ref = eval "package $package; require '$file'";
239             #warn $@ if $@;
240 14 100       196 return undef if $@;
241             {
242             # just in case the require dinn' return a ref -
243             # then a named subroutine has been loaded.
244             # All other cases are errors.
245 13 100 66     25 unless (
  13         99  
246             Scalar::Util::reftype($ref)
247             and
248             Scalar::Util::reftype($ref) eq 'CODE') {
249 5         7 $ref = \&{$h -> {'function'}};
  5         16  
250 4     4   32 no strict 'refs';
  4         8  
  4         150  
251 4     4   22 no warnings 'redefine';
  4         8  
  4         1310  
252 5 50       12 *{$h -> {'function'} } = $h ->{'outer'} if $h -> {'outer'};
  0         0  
253             }
254 13         21 ${$h->{inner}} = $ref;
  13         55  
255            
256             my $sub = sub {
257 22     22   2021411 my $cr = $h -> {'checkref'};
258 22 100 66     38 if( ${ $h -> {'check'} } and ${ $h-> {'checksub'} }
  22   100     114  
  6         36  
259             and
260 6         21 ( my $c = ${ $h->{checksub} } -> ($file) ) != $$cr) {
261 5 50       86 warn "reloading $file" if $Debug;
262 5         24 $$cr = $c;
263 5         22 $load -> ($package, $file, $h);
264             }
265 22         97 goto ${ $h -> {'inner'} };
  22         91  
266 13         92 };
267             }
268             }
269             }
270              
271             sub DESTROY {
272 2     2   747 my $outeraddr = Scalar::Util::refaddr ($_[0]);
273 2         7 my $h = $AL {'Sub'} -> {$outeraddr};
274 2         9 delete $AL {'Inc'} -> { $h -> {'function'}};
275 2         178 delete $AL {'Sub'} -> {$outeraddr};
276             }
277              
278             sub AUTOLOAD {
279 4     4   31 no strict;
  4         5  
  4         735  
280 10     10   3072 my $sub = $AUTOLOAD;
281 10         18 my ($pkg, $func, $filename);
282             {
283 10         13 ($pkg, $func) = ($sub =~ /(.*)::([^:]+)$/);
  10         115  
284 10         102 $pkg = File::Spec -> catdir (split /::/, $pkg);
285             }
286 10         23 my $save = $@;
287 10         48 local $!; # Do not munge the value.
288 10         17 my $ref;
289 10   100     12 eval { local $SIG{__DIE__}; $ref = gensub ($pkg, $func, '', $AL{'auto'} || 'auto'); };
  10         35  
  10         57  
290 10 100       29 if ($@) {
291 4 100       14 if (substr ($sub,-9) eq '::DESTROY') {
292 4     4   28 no strict 'refs';
  4         7  
  4         486  
293 2     0   17 *$sub = sub {};
294 2         4 $@ = undef;
295             }
296 4 100       9 if ($@){
297 2         3 my $error = $@;
298 2         23 require Carp;
299 2         232 Carp::croak($error);
300             }
301             }
302 8         14 $@ = $save;
303 8 100       37 return unless $ref;
304 4     4   49 no warnings 'redefine';
  4         8  
  4         570  
305 6         20 *$AUTOLOAD = $ref;
306 6         30 goto $ref;
307             }
308              
309             # below are shameless plugs from AutoLoader 5.63
310              
311             sub import {
312 7     7   1934 my $pkg = shift;
313 7         16 my $callpkg = caller;
314 7 50       27 if ($pkg eq 'AutoReloader') {
315 7 100 66     67 if ( @_ and $_[0] =~ /^&?AUTOLOAD$/ ) {
316 4     4   27 no strict 'refs';
  4         22  
  4         406  
317 4         12 *{ $callpkg . '::AUTOLOAD' } = \&AUTOLOAD;
  4         22  
318 4         10 *{ $callpkg . '::can' } = \&can;
  4         1665  
319             }
320             }
321             }
322              
323             sub unimport {
324 2     2   605 my $callpkg = caller;
325              
326 4     4   27 no strict 'refs';
  4         6  
  4         796  
327              
328 2         6 for my $exported (qw( AUTOLOAD can )) {
329 4         7 my $symname = $callpkg . '::' . $exported;
330 4 100       4 undef *{ $symname } if \&{ $symname } == \&{ $exported };
  2         5  
  4         12  
  4         15  
331 4         7 *{ $symname } = \&{ $symname };
  4         18  
  4         6  
332             }
333             }
334              
335             sub can {
336 5     5 0 742 my ($self, $func) = @_;
337 5         28 my $parent = $self->SUPER::can( $func );
338 5 100       14 return $parent if $parent;
339 4   33     11 my $pkg = ref( $self ) || $self;
340 4         6 local $@;
341 4         5 my $ref;
342 4 100 50     6 $ref = eval { local $SIG{__DIE__}; $ref = gensub ($pkg, $func, '', $AL{'auto'} || 'auto'); }
  4         14  
  4         20  
343             or return undef;
344 4     4   29 no strict 'refs';
  4         8  
  4         132  
345 4     4   30 no warnings 'redefine';
  4         15  
  4         304  
346 1         2 *{ $pkg . '::' . $func } = $ref;
  1         5  
347 1         3 $ref;
348             }
349             1;
350             __END__