File Coverage

blib/lib/Devel/TraceLoad/Hook.pm
Criterion Covered Total %
statement 51 51 100.0
branch 13 14 92.8
condition n/a
subroutine 10 10 100.0
pod 1 1 100.0
total 75 76 98.6


line stmt bran cond sub pod time code
1             package Devel::TraceLoad::Hook;
2              
3 2     2   188833 use strict;
  2         8  
  2         81  
4 2     2   12 use warnings;
  2         4  
  2         75  
5              
6             =head1 NAME
7              
8             Devel::TraceLoad::Hook - Install a hook function to be called for each require.
9              
10             =head1 VERSION
11              
12             This document describes Devel::TraceLoad::Hook version 1.04
13              
14             =head1 SYNOPSIS
15              
16             register_require_hook( sub {
17             my ( $when, $depth, $arg, $p, $f, $l, $rc, $err ) = @_;
18             # ... do stuff ...
19             } );
20              
21             =head1 DESCRIPTION
22              
23             Allows hook functions that will be called before and after each
24             C (and C) to be registered.
25              
26             =head1 INTERFACE
27              
28             =cut
29              
30 2     2   11 use base qw(Exporter);
  2         8  
  2         206  
31 2     2   11 use vars qw/$VERSION @EXPORT_OK/;
  2         4  
  2         363  
32              
33             @EXPORT_OK = qw( register_require_hook );
34             $VERSION = '1.04';
35              
36             my @hooks;
37              
38             {
39             my $installed = 0;
40              
41             sub _install_hook {
42 3 100   3   13 return if $installed;
43 2         5 my $depth = 0;
44 2     2   12 no warnings 'redefine';
  2         3  
  2         1600  
45             *CORE::GLOBAL::require = sub {
46              
47 164     164   135305 my ( $p, $f, $l ) = caller;
48 164 50       481 my $arg = @_ ? $_[0] : $_;
49 164         169 my $rc;
50              
51 164         371 $depth++;
52              
53             # If a 'before' hook throws an error we'll still call the
54             # 'after' hooks - to keep everything in balance.
55 164         184 eval { _call_hooks( 'before', $depth, $arg, $p, $f, $l ) };
  164         473  
56              
57             # Only call require if the 'before' hooks succeeded.
58 164 100       373 $rc = eval { CORE::require $arg } unless $@;
  162         40568  
59              
60             # Save the error for later
61 164         6353 my $err = $@;
62              
63             # Call the 'after' hooks whatever happened.
64             {
65 164         186 local $@; # Things break if we trample on $@
  164         167  
66 164         243 eval {
67 164         386 _call_hooks( 'after', $depth, $arg, $p, $f, $l, $rc, $err );
68             };
69 164 100       485 if ( my $err = $@ ) {
70 2         18 $err =~ s/\s+/ /g;
71 2         16 warn "Unexpected error $err in require hook\n";
72             }
73             }
74              
75 164         194 $depth--;
76              
77 164 100       282 if ( $err ) {
78              
79             # TODO: We don't seem to get the expected line number fix up here
80 5         89 $err =~ s/at \s+ .*? \s+ line \s+ \d+/at $f line $l/x;
81 5         91 die $err;
82             }
83 159         455 return $rc;
84 2         14 };
85 2         5 $installed++;
86             }
87             }
88              
89             sub import {
90 3     3   14 my $pkg = shift;
91 3         9 _install_hook();
92 3         15 local $Exporter::ExportLevel += 1;
93 3         2015 return $pkg->SUPER::import( @_ );
94             }
95              
96             sub _call_hooks {
97 328     328   453 my @errs = ();
98              
99 328         600 for my $hook ( @hooks ) {
100 154         182 eval { $hook->( @_ ) };
  154         417  
101 154 100       895 push @errs, $@ if $@;
102             }
103              
104             # Rethrow after calling all the hooks. We assume that usually only
105             # one hook will fail and that we'll be rethrowing that error here -
106             # but we concatenate all the errors so that when multiple hooks fail
107             # someone gets to see the diagnostic.
108              
109 328 100       913 die join( ', ', @errs ) if @errs;
110             }
111              
112             =head2 C<< register_require_hook >>
113              
114             Register a function to be called immediately before and after each
115             C (and C).
116              
117             The registered function should look something like this:
118              
119             sub done_require {
120             my ( $when, $depth, $arg, $p, $f, $l, $rc, $err ) = @_;
121             # ... do stuff ...
122             }
123            
124             The arguments are as follows:
125              
126             =over
127              
128             =item C<$when>
129              
130             The hook function is called both before and after the require is
131             executed. The first argument will contain either 'before' or 'after' as
132             appropriate.
133              
134             =item C<$depth>
135              
136             How deeply nested this require is.
137              
138             =item C<$arg>
139              
140             The argument to C.
141              
142             =item C<$p>, C<$f>, C<$l>
143              
144             The package, file and line where the calling C or C is.
145              
146             =item C<$rc>, C<$err>
147              
148             When the hook function is called after a C C<$rc> and
149             C<$err> will contain the return value of the require and any error
150             that it raised.
151              
152             =back
153              
154             You may throw an error (using C) from the hook function. If an
155             error is thrown during 'before' processing the real call to C
156             will not take place. In this way it is possible to simulate a module
157             being unavailable.
158              
159             See L for a complete example of this interface.
160              
161             =cut
162              
163 2     2 1 28 sub register_require_hook { push @hooks, @_ }
164              
165             1;
166              
167             __END__