=head1 NAME Sub::Auto - Lazy loading and reloading of anonymous subroutines =head1 SYNOPSIS use Sub::Auto; my $sub = Sub::Auto -> new ($file, $checksub, $autoprefix); $result = $sub -> (@args); $sub -> check (0); # turn source file checking off for $sub $sub -> checksub ($coderef); # provide alternative checking routine use Sub::Auto qw (AUTOLOAD); Sub::Auto -> check (1); # turn source file checking on $result = somefunc (@args); *somefunc{CODE}->check(0); # turn off checking for this named sub =head1 DESCRIPTION Sub::Auto provides lazy loading like AutoLoader, but also for function files which return an anonymous subroutine upon require (as its last evaluated statement). Before requiring that file, it is checked via some subroutine returning a value (default is mtime). The returned value is remembered. At each call to that sub the check subroutine is run again if this subroutine's check flag is set; and if the returned value changed, the source file is reloaded. Importing the AUTOLOAD method provides for lazy loading of anonsubs as named subs. The wrapped anonsub will be assigned to a symbol table entry named after the filename root of the function source file. =head1 METHODS =over 4 =item new ($file, $checksubref, $autoprefix) subroutine constructor. $file can be the path to some function file or a function name which will be expanded to $autoprefix/__PACKAGE__/$function.al and searched for in @INC. $checksubref and $autoprefix are optional. If they are not provided, the default class settings are used. =item auto ($autoprefix) set or get the default autoprefix. Default is 'auto', just as with AutoLoader: for e.g. POSIX::rand the source file would be auto/POSIX/rand.al . Sub::Auto lets you replace the 'auto' part of the path with something else. Class method (for now). =item suffix ($suffix) set or get the suffix of your autoloaded files (e.g. '.al', '.pl', '.tmpl') as a package variable. =item check (1) set or get the check flag. Turn checking on by setting this to some true value. Default is off. Class and object method, i.e. Sub::Auto->check(1) sets the default to on, $sub->check(1) sets checking for a subroutine. For now, there's no way to inculcate the class default on subs with a private check flag. =item checksub ($coderef) set the checking subroutine. Class and object method. This subroutine will be invoked with a subroutines source filename (full path) every time the sub for which it is configured - but only if check for that subroutine is true -, and should return some value special to that file. Default is 'sub { (stat $_[0]) [9] }', i.e. mtime. =back =head1 SEE ALSO AutoLoader, AutoSplit, autouse, DBIx::VersionedSubs =head1 TODO =over 4 =item eliminate paranoia make this module truly subclassable. Turn lexical private subs into our() vars or into named subs. Make the %AL hash accessible. All that means re-think code calling semantics and uses of __PACKAGE__ . =item provide for more path changes and access methods of subroutines The 'auto' part of a subroutine should be changeable, as well as the full path to a subroutine source file. Then, a subroutine's access method should be made more flexible, e.g. reading code from some database, retrieve via LWP, or else. =back =head1 BUGS Sub::Auto subroutines are always reported as __ANON__ (e.g. with Carp::cluck), even if they are assigned to a symbol table entry. Which might not be a bug. There might be others. =head1 Author shmem =head1 COPYRIGHT Copyright 2007 by shmem This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut package Sub::Auto; use Exporter qw(import); use strict; use warnings; use Scalar::Util; use File::Spec; our $VERSION = 0.01; our @EXPORT_OK = qw (AUTOLOAD); my $Debug = 0; our ($gensub, $load); our %AL; # hash holding all info about subs sub new { my $class = shift; my $caller = caller; my $sub = $gensub -> ($caller,@_); bless $sub, $class; } sub auto { shift if __PACKAGE__ || $_[0] eq (caller(0))[0]; $AL {'auto'} = shift if @_; $AL {'auto'}; } sub check { my $self = shift; if(ref($self)) { ${ $AL {Sub} -> {Scalar::Util::refaddr($self)} -> {'check'} } = shift; } else { $AL {'check'} = shift; } } sub checksub { my $self = shift; if(ref($self)) { ${ $AL{Sub} -> {Scalar::Util::refaddr($self)} -> {'checksub'} } = shift; } else { $AL {'checksub'} = shift; } } sub suffix { shift if __PACKAGE__ || $_[0] eq (caller(0))[0]; $AL {'suffix'} = shift if @_; $AL {'suffix'}; } checksub ( __PACKAGE__, sub { (stat $_[0]) [9] } ); # default check subroutine check ( __PACKAGE__, 0); # default is not checking # $gensub - returns an anonymous subroutine. # Parameters: # if one: filename (full path) # if more: package, filename [, checkfuncref [, auto ]] $gensub = sub { my $package = scalar(@_) == 1 ? caller : shift; my $file = shift; my $chkfunc = shift || $AL {'checksub'}; my $auto = shift || $AL {'auto'} || 'auto'; my $function; { ($function = pop (@{[ File::Spec->splitpath($file) ]}) ) =~ s/\..*//; $file .= $AL {'suffix'} || '.al' unless $file =~ /\.\w+$/; unless (-e $file) { my ($filename, $seen); { $filename = File::Spec -> catfile ($auto, $package, $file); foreach my $d ('.',@INC) { # check current working dir first my $f = File::Spec -> catfile ($d,$filename); #warn "checking for $f\n"; if (-e $f) { $file = $f; #warn "got it! $file\n"; last; } } # redo the search with a truncated filename last if $seen; unless (-e $file) { $file =~ s/(\w{12,})(\.\w+)$/substr($1,0,11).$2/e; $seen++; redo; } } die "Can't locate function file '$filename' for package '$package'\n" unless -e $file; } } if (my $addr = $AL {'Inc'} -> {"$package\::$function"} ) { return $AL {'Sub'} -> {$addr} -> {'outer'}; } else { # file not known yet my $inner; my $h = {}; my $cr = $chkfunc -> ($file); my $subname = "$package\::$function"; $h = { file => $file, check => \$AL {'check'}, checksub => \$chkfunc, checkref => \$cr, function => $subname, }; my $outer = $load -> ($package, $file, $h) or die $@; my $outeraddr = Scalar::Util::refaddr ($outer); $h -> {'outer'} = $outer; Scalar::Util::weaken ($h -> {outer}); $AL{Sub} -> {$outeraddr} = $h; $AL{Inc} -> {$subname} = $outeraddr; return bless $outer, __PACKAGE__; } }; $load = sub { my ($package, $file, $h) = @_; delete $INC {$file}; my $ref = eval "package $package; require '$file'"; # warn $@ if $@; return undef if $@; { # just in case the require dinn' return a ref - # then it's likely a named subroutine has been loaded # see chromatics note below # UNIVERSAL::isa($ref,'CODE') or $ref = \&{$h -> {'function'}}; Scalar::Util::reftype($ref) and Scalar::Util::reftype($ref) eq 'CODE' or $ref = \&{$h -> {'function'}}; ${$h->{inner}} = $ref; my $sub = sub { my $cr = $h -> {'checkref'}; if( ${ $h -> {'check'} } and ${ $h-> {'checksub'} } and ( my $c = ${ $h->{checksub} } -> ($file) ) != $$cr) { warn "reloading $file" if $Debug; $$cr = $c; $load -> ($package, $file, $h); } goto ${ $h -> {inner} }; }; } }; sub DESTROY { my $outeraddr = Scalar::Util::refaddr ($_[0]); my $h = $AL {Sub} -> {$outeraddr}; delete $AL {Inc} -> { $h -> {function}}; delete $AL {Sub} -> {$outeraddr}; } sub AUTOLOAD { no strict; my $sub = $AUTOLOAD; my ($pkg, $func, $filename); { ($pkg, $func) = ($sub =~ /(.*)::([^:]+)$/); $pkg = File::Spec -> catdir (split /::/, $pkg); } my $save = $@; local $!; # Do not munge the value. my $ref; eval { local $SIG{__DIE__}; $ref = $gensub -> ($pkg, $func, '', $AL{'auto'} || 'auto'); }; if ($@) { if (substr ($sub,-9) eq '::DESTROY') { no strict 'refs'; *$sub = sub {}; $@ = undef; } if ($@){ my $error = $@; require Carp; Carp::croak($error); } } $@ = $save; no warnings 'redefine'; *$AUTOLOAD = $ref; goto $ref; } sub unimport { my $callpkg = caller; no strict 'refs'; my $symname = $callpkg . '::AUTOLOAD'; undef *{ $symname } if \&{ $symname } == \&AUTOLOAD; *{ $symname } = \&{ $symname }; } 1; __END__ #### use Sub::Auto qw(AUTOLOAD);