File Coverage

blib/lib/Autoload/AUTOCAN.pm
Criterion Covered Total %
statement 55 59 93.2
branch 17 22 77.2
condition 5 6 83.3
subroutine 9 9 100.0
pod n/a
total 86 96 89.5


line stmt bran cond sub pod time code
1             package Autoload::AUTOCAN;
2              
3 4     4   4227 use strict;
  4         33  
  4         116  
4 4     4   25 use warnings;
  4         8  
  4         103  
5 4     4   35 use Carp ();
  4         9  
  4         138  
6 4     4   42 use Scalar::Util ();
  4         8  
  4         1445  
7              
8             our $VERSION = '0.005';
9              
10             my $autoload_methods = <<'EOF';
11             sub AUTOLOAD {
12             my ($inv) = @_;
13             my ($package, $function) = our $AUTOLOAD =~ /^(.+)::(.+)$/;
14             Carp::croak qq[Undefined subroutine &${package}::$function called]
15             unless defined $inv && (!ref $inv or Scalar::Util::blessed $inv) && $inv->isa(__PACKAGE__);
16             return if $function eq 'DESTROY';
17             my $autocan = $inv->can('AUTOCAN');
18             my $sub = defined $autocan ? $inv->$autocan($function) : undef;
19             Carp::croak qq[Can't locate object method "$function" via package "$package"]
20             unless defined $sub and do { local $@; eval { $sub = \&$sub; 1 } };
21             # allow overloads and blessed subrefs; assign ref so overload is only invoked once
22             __INSTALL_SUB_CODE__
23             goto &$sub;
24             }
25             EOF
26              
27             my $autoload_functions = <<'EOF';
28             sub AUTOLOAD {
29             my ($package, $function) = our $AUTOLOAD =~ /^(.+)::(.+)$/;
30             my $autocan = __PACKAGE__->can('AUTOCAN');
31             my $sub = defined $autocan ? __PACKAGE__->$autocan($function) : undef;
32             Carp::croak qq[Undefined subroutine &${package}::$function called]
33             unless defined $sub and do { local $@; eval { $sub = \&$sub; 1 } };
34             # allow overloads and blessed subrefs; assign ref so overload is only invoked once
35             __INSTALL_SUB_CODE__
36             goto &$sub;
37             }
38             EOF
39              
40             my $install_can = <<'EOF';
41             sub can {
42             my ($package, $function) = @_;
43             my $sub = $package->SUPER::can($function);
44             return $sub if defined $sub;
45             return undef if $function eq 'AUTOCAN'; # don't recurse on AUTOCAN
46             my $autocan = $package->can('AUTOCAN');
47             $sub = defined $autocan ? $package->$autocan($function) : undef;
48             return undef unless defined $sub and do { local $@; eval { $sub = \&$sub; 1 } };
49             # allow overloads and blessed subrefs; assign ref so overload is only invoked once
50             __INSTALL_SUB_CODE__
51             return $sub;
52             }
53             EOF
54              
55             my $install_subs = <<'EOF';
56             {
57             require Sub::Util;
58             no strict 'refs';
59             *{"${package}::$function"} = Sub::Util::set_subname("${package}::$function", $sub);
60             }
61             EOF
62              
63             sub import {
64 4     4   36 my ($class, @args) = @_;
65            
66 4         8 my $autoload_code = $autoload_methods;
67 4         7 my $can_code = $install_can;
68 4         8 my $install_sub_code = '';
69            
70 4         11 foreach my $arg (@args) {
71 2 50       11 if ($arg eq 'methods') {
    100          
    50          
72 0         0 $autoload_code = $autoload_methods;
73             } elsif ($arg eq 'functions') {
74 1         3 $autoload_code = $autoload_functions;
75             } elsif ($arg eq 'install_subs') {
76 1         2 $install_sub_code = $install_subs;
77             } else {
78 0         0 Carp::croak "Unrecognized import argument '$arg'";
79             }
80             }
81            
82 4         23 $autoload_code =~ s/__INSTALL_SUB_CODE__/$install_sub_code/;
83 4         17 $can_code =~ s/__INSTALL_SUB_CODE__/$install_sub_code/;
84            
85 4         11 my $target = caller;
86            
87 4         11 my ($errored, $error);
88             {
89 4         6 local $@;
  4         8  
90 4 50 100 10   1324 unless (eval "package $target;\n$can_code\n$autoload_code\n1") {
  10 100 66 25   7991  
  10 100   1   54  
  10 100   1   112  
  10 100       479  
  7 50       20  
  7 100       23  
  7         415  
  5         15  
  5         15  
  29         2512  
  29         175  
  29         115  
  8         32  
  8         37  
  8         45  
  8         274  
  3         8  
  3         8  
  3         9  
  3         19  
  3         15  
  2         14  
  2         19  
  2         274  
  2         12  
  1         2  
  1         67  
91 0         0 $errored = 1;
92 0         0 $error = $@;
93             }
94             }
95            
96 4 50       507 die $error if $errored;
97             }
98              
99             1;
100              
101             =head1 NAME
102              
103             Autoload::AUTOCAN - Easily set up autoloading
104              
105             =head1 SYNOPSIS
106              
107             package My::Class;
108             use Moo; # or object system of choice
109             use Autoload::AUTOCAN;
110            
111             has count => (is => 'rw', default => 0);
112            
113             sub increment { $_[0]->count($_[0]->count + 1) }
114            
115             sub AUTOCAN {
116             my ($self, $method) = @_;
117             return sub { $_[0]->increment } if $method =~ m/inc/;
118             return undef;
119             }
120            
121             1;
122            
123             # elsewhere
124             my $obj = My::Class->new;
125             $obj->inc;
126             say $obj->count; # 1
127             $obj->increment; # existing method, not autoloaded
128             say $obj->count; # 2
129             $obj->do_increment;
130             say $obj->count; # 3
131             $obj->explode; # method not found error
132              
133             =head1 DESCRIPTION
134              
135             L is a very powerful mechanism for
136             dynamically handling function calls that are not defined. However, its
137             implementation is very complicated. For the simple case where you wish to
138             allow method calls to methods that don't yet exist, this module allows you to
139             define an C method which will return either a code reference or
140             C.
141              
142             L installs an C subroutine in the current package,
143             which is invoked when an unknown method is called. The installed C
144             will call C with the invocant (class or object the method was called
145             on) and the method name. If C returns a code reference, it will be
146             called with the same arguments as passed to the unknown method (including the
147             invocant). If C returns C, an error will be thrown as expected
148             when calling an undefined method.
149              
150             Along with C, the module installs a C method which returns code
151             references as normal for defined methods (see L), and delegates to
152             C for unknown methods.
153              
154             =head1 CONFIGURING
155              
156             L accepts import arguments to configure its behavior.
157              
158             =head2 functions
159              
160             C affects standard function calls in addition to method calls. By
161             default, the C provided by this module will die (as Perl normally
162             does without a defined C) if a nonexistent function is called without
163             a class or object invocant. If you wish to autoload functions instead of
164             methods, you can pass C as an import argument, and the installed
165             C will autoload functions using C from the current package,
166             rather than using the first argument as an invocant.
167              
168             package My::Functions;
169             use Autoload::AUTOCAN 'functions';
170            
171             sub AUTOCAN {
172             my ($package, $function) = @_;
173             return sub { $_[0]x5 } if $function =~ m/dup/;
174             return undef;
175             }
176            
177             # elsewhere
178             say My::Functions::duplicate('foo'); # foofoofoofoofoo
179             say My::Functions::foo('bar'); # undefined subroutine error
180              
181             =head2 install_subs
182              
183             By passing C as an import argument, any autoloaded function or
184             method returned by C will be installed into the package, so that
185             future invocations do not need to go through C. This should not be
186             used if the autoloaded code is expected to change in subsequent calls to
187             C, as the installed version will be called or returned by C
188             directly.
189              
190             package My::Class;
191             use Moo;
192             use Autoload::AUTOCAN 'install_subs';
193            
194             sub AUTOCAN {
195             my ($self, $method) = @_;
196             my $hash = expensive_calculation($method);
197             return sub { $hash };
198             }
199            
200             # elsewhere
201             my $obj = My::Class->new;
202             $obj->foo; # sub foo installed in My::Class
203             $obj->foo; # not autoloaded anymore
204              
205             =head1 CAVEATS
206              
207             If you use L, it will clean up the installed C
208             function. To avoid this, either use this module B L,
209             or add an exception for C as below.
210              
211             use Autoload::AUTOCAN;
212             use namespace::clean -except => 'AUTOLOAD';
213              
214             This issue does not seem to occur with L.
215              
216             =head1 BUGS
217              
218             Report any issues on the public bugtracker.
219              
220             =head1 AUTHOR
221              
222             Dan Book
223              
224             =head1 COPYRIGHT AND LICENSE
225              
226             This software is Copyright (c) 2017 by Dan Book.
227              
228             This is free software, licensed under:
229              
230             The Artistic License 2.0 (GPL Compatible)
231              
232             =head1 SEE ALSO
233              
234             L, L