File Coverage

blib/lib/Sub/WrapPackages.pm
Criterion Covered Total %
statement 189 190 99.4
branch 32 36 88.8
condition 14 16 87.5
subroutine 30 30 100.0
pod 1 1 100.0
total 266 273 97.4


line stmt bran cond sub pod time code
1 19     19   924787 use strict;
  19         52  
  19         769  
2 19     19   115 use warnings;
  19         41  
  19         657  
3              
4 19     19   19480 use Data::Dumper;
  19         178346  
  19         2408  
5              
6             package Sub::WrapPackages;
7              
8 19     19   162 use vars '$VERSION';
  19         42  
  19         1195  
9 19     19   97 use vars '%ORIGINAL_SUBS'; # coderefs of what we're wrapping, keyed
  19         42  
  19         945  
10             # by package::sub
11 19     19   97 use vars '@MAGICINCS'; # list of magic INC subs, used by lib.pm hack
  19         34  
  19         909  
12 19     19   103 use vars '%INHERITED'; # coderefs of inherited methods (before proxies
  19         286  
  19         898  
13             # installed), keys by package::sub
14 19     19   95 use vars '%WRAPPED_BY_WRAPPER'; # coderefs of original subs, keyed by
  19         34  
  19         842  
15             # stringified coderef of wrapper
16 19     19   98 use vars '%WRAPPER_BY_WRAPPED'; # coderefs of wrapper subs, keyed by
  19         33  
  19         680  
17             # stringified coderef of original sub
18 19     19   20985 use Sub::Prototype ();
  19         331531  
  19         867  
19 19     19   42412 use Devel::Caller::IgnoreNamespaces;
  19         7551  
  19         784  
20             Devel::Caller::IgnoreNamespaces::register(__PACKAGE__);
21              
22 19     19   3041 use lib ();
  19         2970  
  19         4169  
23              
24             $VERSION = '2.0';
25              
26             =head1 NAME
27              
28             Sub::WrapPackages - add pre- and post-execution wrappers around all the
29             subroutines in packages or around individual subs
30              
31             =head1 SYNOPSIS
32              
33             use Sub::WrapPackages
34             packages => [qw(Foo Bar Baz::*)], # wrap all subs in Foo and Bar
35             # and any Baz::* packages
36             subs => [qw(Barf::a, Barf::b)], # wrap these two subs as well
37             wrap_inherited => 1, # and wrap any methods
38             # inherited by Foo, Bar, or
39             # Baz::*
40             pre => sub {
41             print "called $_[0] with params ".
42             join(', ', @_[1..$#_])."\n";
43             },
44             post => sub {
45             print "$_[0] returned $_[1]\n";
46             };
47              
48             =head1 COMPATIBILITY
49              
50             While this module does broadly the same job as the 1.x versions did,
51             the interface may have changed incompatibly. Sorry. Hopefully it'll
52             be more maintainable and slightly less crazily magical. Also, caller()
53             should now work properly, ignoring wrappings.
54              
55             =head1 DESCRIPTION
56              
57             This module installs pre- and post- execution subroutines for the
58             subroutines or packages you specify. The pre-execution subroutine
59             is passed the
60             wrapped subroutine's name and all its arguments. The post-execution
61             subroutine is passed the wrapped sub's name and its results.
62              
63             The return values from the pre- and post- subs are ignored, and they
64             are called in the same context (void, scalar or list) as the calling
65             code asked for.
66              
67             Normal usage is to pass a bunch of parameters when the module is used.
68             However, you can also call Sub::WrapPackages::wrapsubs with the same
69             parameters.
70              
71             =head1 PARAMETERS
72              
73             Either pass parameters on loading the module, as above, or pass them
74             to ...
75              
76             =head2 the wrapsubs subroutine
77              
78             =over 4
79              
80             =item the subs arrayref
81              
82             In the synopsis above, you will see two named parameters, C and
83             C. Any subroutine mentioned in C will be wrapped.
84             Any subroutines mentioned in 'subs' must already exist - ie their modules
85             must be loaded - at the time you try to wrap them.
86              
87             =item the packages arrayref
88              
89             Any package mentioned here will have all its subroutines wrapped,
90             including any that it imports at load-time. Packages can be loaded
91             in any order - they don't have to already be loaded for Sub::WrapPackages
92             to work its magic.
93              
94             You can specify wildcard packages. Anything ending in ::* is assumed
95             to be such. For example, if you specify Orchard::Tree::*, then that
96             matches Orchard::Tree, Orchard::Tree::Pear, Orchard::Apple::KingstonBlack
97             etc, but not - of course - Pine::Tree or My::Orchard::Tree.
98              
99             Note, however, that if a module exports a subroutine at load-time using
100             C then that sub will be wrapped in the exporting module but not in
101             the importing module. This is because import() runs before we get a chance
102             to fiddle with things. Sorry.
103              
104             Deferred wrapping of subs in packages that aren't yet loaded works
105             via a subroutine inserted in @INC. This means that if you mess around
106             with @INC, eg by inserting a directoy at the beginning of the path, the
107             magic might not get a chance to run. If you C to mess with
108             @INC though, it should work, as I've over-ridden lib's import() method.
109             That said, code this funky has no right to work. Use with caution!
110              
111             =item wrap_inherited
112              
113             In conjunction with the C arrayref, this wraps all calls to
114             inherited methods made through those packages. If you call those
115             methods directly in the superclass then they are not affected - unless
116             they're wrapped in the superclass of course.
117              
118             =item pre and post
119              
120             References to the subroutines you want to use as wrappers.
121              
122             =back
123              
124             =head1 BUGS
125              
126             AUTOLOAD and DESTROY are not treated as being special. I'm not sure
127             whether they should be or not.
128              
129             If you use wrap_inherited but classes change their inheritance tree at
130             run-time, then very bad things will happen. VERY BAD THINGS. So don't
131             do that. You shouldn't be doing that anyway. Mind you, you shouldn't
132             be doing the things that this module does either. BAD PROGRAMMER, NO
133             BIKKIT!
134              
135             If you find any other lurking horrors, please report them using
136             L.
137              
138             =head1 FEEDBACK
139              
140             I like to know who's using my code. All comments, including constructive
141             criticism, are welcome. Please email me.
142              
143             =head1 SOURCE CODE REPOSITORY
144              
145             L
146              
147             =head1 COPYRIGHT and LICENCE
148              
149             Copyright 2003-2009 David Cantrell EFE
150              
151             This software is free-as-in-speech software, and may be used, distributed, and modified under the terms of either the GNU General Public Licence version 2 or the Artistic Licence. It's up to you which one you use. The full text of the licences can be found in the files GPL2.txt and ARTISTIC.txt, respectively.
152              
153             =head1 THANKS TO
154              
155             Thanks to Tom Hukins for sending in a test case for the situation when
156             a class and a subclass are both defined in the same file, and for
157             prompting me to support inherited methods;
158              
159             to Dagfinn Ilmari Mannsaker for help with the craziness for
160             fiddling with modules that haven't yet been loaded;
161              
162             to Lee Johnson for reporting a bug caused by perl 5.10's
163             constant.pm being Far Too Clever, and providing a patch and test;
164              
165             to Adam Trickett who thought this was a jolly good idea;
166              
167             and to Ed
168             Summers, whose code for figgering out what functions a package contains
169             I borrowed out of L.
170              
171             =cut
172              
173             sub import {
174 19     19   293 shift;
175 19 100       162 wrapsubs(@_) if(@_);
176             }
177              
178             sub _subs_in_packages {
179 55     55   169 my @targets = map { $_.'::' } @_;
  61         200  
180              
181 55         87 my @subs;
182 55         107 foreach my $package (@targets) {
183 19     19   120 no strict;
  19         39  
  19         46367  
184 61         95 while(my($k, $v) = each(%{$package})) {
  403         1905  
185 342 100 100     937 push @subs, $package.$k if(ref($v) ne 'SCALAR' && defined(&{$v}));
  341         2169  
186             }
187             }
188 55         238 return @subs;
189             }
190              
191             sub _make_magic_inc {
192 29     29   91 my %params = @_;
193 29         48 my $wildcard_packages = [map { s/::.//; $_; } grep { /::\*$/ } @{$params{packages}}];
  2         8  
  2         6  
  35         123  
  29         126  
194 29         48 my $nonwildcard_packages = [grep { $_ !~ /::\*$/ } @{$params{packages}}];
  35         111  
  29         70  
195              
196             push @MAGICINCS, sub {
197 116     116   2290343 my($me, $file) = @_;
198 116         434 (my $module = $file) =~ s{/}{::}g;
199 116         675 $module =~ s/\.pm//;
200             return undef unless(
201 10         91 (grep { $module =~ /^$_(::|$)/ } @{$wildcard_packages}) ||
  116         556  
  146         52405  
202 116 100 100     264 (grep { $module eq $_ } @{$nonwildcard_packages})
  115         218  
203             );
204 9         20 local @INC = grep { $_ ne $me } @INC;
  103         246  
205 9         45 local $/;
206 9         28 my @files = grep { -e $_ } map { join('/', $_, $file) } @INC;
  94         2223  
  94         225  
207 9 50       407 open(my $fh, $files[0]) || die("Can't locate $file in \@INC\n");
208 9         320 my $text = <$fh>;
209 9         96 close($fh);
210 9         47 %Sub::WrapPackages::params = %params;
211              
212 9         217 $text =~ /(.*?)(__DATA__.*|__END__.*|$)/s;
213 9         43 my($code, $trailer) = ($1, $2);
214 9         39 $text = $code.qq[
215             ;
216             Sub::WrapPackages::wrapsubs(
217             %Sub::WrapPackages::params,
218             packages => [qw($module)]
219             );
220             1;
221             ]."\n$trailer";
222 9     6   149 open($fh, '<', \$text);
  6         42  
  6         12  
  6         53  
223 9         10744 $fh;
224 29         247 };
225 29         110 unshift @INC, $MAGICINCS[-1];
226             }
227              
228             sub _getparents {
229 26     26   181 my $package = shift;
230 26         1456 my @parents = eval '@'.$package.'::ISA';
231 26         112 return @parents, (map { _getparents($_) } @parents);
  13         51  
232             }
233              
234             sub wrapsubs {
235 32     32 1 3127 my %params = @_;
236              
237 32 100 66     1029 if(exists($params{packages}) && ref($params{packages}) =~ /^ARRAY/) {
    50          
238 29         52 my $wildcard_packages = [map { (my $foo = $_) =~ s/::.$//; $foo; } grep { /::\*$/ } @{$params{packages}}];
  2         9  
  2         8  
  35         122  
  29         78  
239 29         55 my $nonwildcard_packages = [grep { $_ !~ /::\*$/ } @{$params{packages}}];
  35         115  
  29         65  
240              
241             # defer wrapping stuff that's not yet loaded
242 29         174 _make_magic_inc(%params);
243              
244             # wrap wildcards that are loaded
245 29 100       51 if(@{$wildcard_packages}) {
  29         138  
246 2         203 foreach my $loaded (map { (my $f = $_) =~ s!/!::!g; $f =~ s/\.pm$//; $f } keys %INC) {
  154         362  
  154         402  
  154         280  
247 154         255 my $pattern = '^('.join('|',
248 154         160 map { (my $f = $_) =~ s/::\*$/::/; $f } @{$wildcard_packages}
  154         307  
  154         205  
249             ).')';
250 154 100       461 wrapsubs(%params, packages => [$loaded]) if($loaded =~ /$pattern/);
251             }
252             }
253              
254             # wrap non-wildcards that are loaded
255 29 100       128 if($params{wrap_inherited}) {
256 11         18 foreach my $package (@{$nonwildcard_packages}) {
  11         38  
257 13         40 my @parents = _getparents($package);
258              
259             # get inherited (but not over-ridden!) subs
260 18         84 my %subs_in_package = map {
261 13         42 s/.*:://; ($_, 1);
  18         64  
262             } _subs_in_packages($package);
263              
264 50         132 my @subs_to_define = grep {
265 50         161 !exists($subs_in_package{$_})
266             } map {
267 13         53 s/.*:://; $_;
  50         90  
268             } _subs_in_packages(@parents);
269              
270             # define proxy method that just does a goto to get
271             # to the right place. We then later wrap the proxy
272 13         37 foreach my $sub (@subs_to_define) {
273 43 100       155 next if(exists($INHERITED{$package."::$sub"}));
274 39         371 $INHERITED{$package."::$sub"} = $package->can($sub);
275             # if the inherited method is already wrapped,
276             # point this proxy at the original method
277             # so we don't wrap a wrapper
278 39 100       172 if(exists($WRAPPED_BY_WRAPPER{$INHERITED{$package."::$sub"}})) {
279 5         20 $INHERITED{$package."::$sub"} =
280             $WRAPPED_BY_WRAPPER{$INHERITED{$package."::$sub"}};
281             }
282 39         2265 eval qq{
283             sub ${package}::$sub {
284             goto &{\$Sub::WrapPackages::INHERITED{"${package}::$sub"}};
285             }
286             };
287 39 50       166 die($@) if($@);
288             }
289             }
290             }
291 29         50 push @{$params{subs}}, _subs_in_packages(@{$params{packages}});
  29         84  
  29         105  
292             } elsif(exists($params{packages})) {
293 0         0 die("Bad param 'packages'");
294             }
295              
296 32 50 66     152 return undef if(!$params{pre} && !$params{post});
297 32   100 4   122 $params{pre} ||= sub {};
  4         8  
298 32   100 12   125 $params{post} ||= sub {};
  12         26  
299              
300 32         51 foreach my $sub (@{$params{subs}}) {
  32         347  
301 151 100       418 next if(exists($ORIGINAL_SUBS{$sub}));
302              
303 143         154 $ORIGINAL_SUBS{$sub} = \&{$sub};
  143         473  
304             my $imposter = sub {
305 53     53   22483 local *__ANON__ = $sub;
306 53         128 my(@r, $r) = ();
307 53         119 my $wa = wantarray();
308 53 100       213 if(!defined($wa)) {
    100          
309 8         39 $params{pre}->($sub, @_);
310 8         1821 $ORIGINAL_SUBS{$sub}->(@_);
311 8         841 $params{post}->($sub);
312             } elsif($wa) {
313 16         62 my @f = $params{pre}->($sub, @_);
314 16         251 @r = $ORIGINAL_SUBS{$sub}->(@_);
315 16         447 @f = $params{post}->($sub, @r);
316             } else {
317 29         149 my $f = $params{pre}->($sub, @_);
318 29         3318 $r = $ORIGINAL_SUBS{$sub}->(@_);
319 29         203 $f = $params{post}->($sub, $r);
320             }
321 53 100       5277 return wantarray() ? @r : $r;
322 143         902 };
323 143 100       466 Sub::Prototype::set_prototype($imposter, prototype($ORIGINAL_SUBS{$sub}))
324             if(prototype($ORIGINAL_SUBS{$sub}));
325              
326             {
327 19     19   168 no strict 'refs';
  19         41  
  19         821  
  143         156  
328 19     19   110 no warnings 'redefine';
  19         61  
  19         3012  
329 143         420 $WRAPPED_BY_WRAPPER{$imposter} = $ORIGINAL_SUBS{$sub};
330 143         384 $WRAPPER_BY_WRAPPED{$ORIGINAL_SUBS{$sub}} = $imposter;
331              
332 143         171 *{$sub} = $imposter;
  143         51087  
333             };
334             }
335             }
336              
337             package lib;
338 19     19   111 use strict; no strict 'refs';
  19     19   132  
  19         797  
  19         100  
  19         37  
  19         484  
339 19     19   134 use warnings; no warnings 'redefine';
  19     19   55  
  19         643  
  19         104  
  19         38  
  19         3640  
340              
341             my $originallibimport = \&{'lib::import'};
342             my $newimport = sub {
343 2     2   19 $originallibimport->(@_);
344 2         229 my %magicincs = map { $_, 1 } @Sub::WrapPackages::MAGICINCS;
  1         4  
345 21         38 @INC = (
346 21         82 (grep { exists($magicincs{$_}); } @INC),
347 2         6 (grep { !exists($magicincs{$_}); } @INC)
348             );
349             };
350              
351             *{'lib::import'} = $newimport;
352              
353             1;