File Coverage

blib/lib/Devel/TraceLoad.pm
Criterion Covered Total %
statement 65 130 50.0
branch 20 58 34.4
condition 2 11 18.1
subroutine 13 17 76.4
pod n/a
total 100 216 46.3


line stmt bran cond sub pod time code
1             package Devel::TraceLoad;
2              
3 1     1   10 use warnings;
  1         1  
  1         105  
4 1     1   4 use strict;
  1         1  
  1         73  
5 1     1   4 use Carp;
  1         2  
  1         105  
6 1     1   4 use Devel::TraceLoad::Hook qw( register_require_hook );
  1         1  
  1         7  
7              
8             =head1 NAME
9              
10             Devel::TraceLoad - Discover which modules a Perl program loads.
11              
12             =head1 VERSION
13              
14             This document describes Devel::TraceLoad version 1.04
15              
16             =cut
17              
18 1     1   4 use vars qw( $VERSION );
  1         1  
  1         66  
19             $VERSION = '1.04';
20              
21 1     1   4 use constant OUTFILE => 'traceload';
  1         98  
  1         483  
22              
23             my %opts = (
24             after => 0, # Display summary after execution
25             during => 0, # Display loads as they happen
26             yaml => 0, # Summary is YAML, implies after
27             dump => 0, # Dump to 'traceload' in the current dir
28             summary => 0, # Display summary of dependencies
29             stdout => 0, # Output to stdout
30             );
31              
32             # Naughty: used by the test suite
33             sub _option {
34 0     0   0 my $name = shift;
35 0 0       0 $opts{$name} = shift if @_;
36 0         0 return $opts{name};
37             }
38              
39             sub _is_version {
40 133     133   169 my $ver = shift;
41 133 50       254 return unless defined $ver;
42 133 100       585 return $ver if $ver =~ /^ \d+ (?: [.] \d+ )* $/x;
43 88         184 return;
44             }
45              
46             sub _get_version {
47 43     43   53 my $pkg = shift;
48 1     1   4 no strict 'refs';
  1         1  
  1         9597  
49 43         46 return _is_version( ${"${pkg}::VERSION"} );
  43         159  
50             }
51              
52             sub _get_module {
53 44     44   68 my $file = shift;
54 44 50       94 return $file if $file =~ m{^/};
55 44         176 $file =~ s{/}{::}g;
56 44         288 $file =~ s/[.]pm$//;
57 44         4864 return $file;
58             }
59              
60             sub _text_out {
61 0     0   0 my ( $fh, $log, $depth ) = @_;
62 0         0 my $pad = ' ' x $depth;
63              
64 0         0 for my $info ( @$log ) {
65 0         0 my @comment = ();
66              
67 0 0       0 push @comment,
68             defined $info->{version}
69             ? "version: $info->{version}"
70             : 'no version';
71              
72 0 0       0 if ( my $err = $info->{error} ) {
73 0         0 $err =~ s/\(.*//g;
74 0         0 $err =~ s/\s+/ /g;
75 0         0 $err =~ s/\s+$//;
76 0         0 push @comment, "error: $err";
77             }
78              
79 0 0       0 print $fh sprintf( "%s%s (%s), line %d: %s%s\n",
80             $pad, $info->{file}, $info->{pkg}, $info->{line}, $info->{module},
81             ( @comment ? ' (' . join( ', ', @comment ) . ')' : '' ) );
82 0         0 _text_out( $fh, $info->{nested}, $depth + 1 );
83             }
84             }
85              
86             sub _gather_deps {
87 0     0   0 my ( $by_dep, $log ) = @_;
88 0         0 for my $info ( @$log ) {
89 0         0 push @{ $by_dep->{ $info->{module} } }, $info;
  0         0  
90 0         0 _gather_deps( $by_dep, $info->{nested} );
91             }
92             }
93              
94             sub _underline {
95 0     0   0 my $str = shift;
96 0         0 return "\n$str\n" . ( '=' x length( $str ) ) . "\n\n";
97             }
98              
99             {
100             my @load_log = ();
101             my @version_log = ();
102              
103             sub import {
104 1     1   11 my ( $class, @args ) = @_;
105              
106             # Parse args
107 1         4 for my $arg ( @args ) {
108 0 0 0     0 my $set = ( $arg =~ s/^([+-])(.+)/$2/ ) ? ( $1 eq '+' || 0 ) : 1;
109 0 0       0 croak "Unknown option: $arg" unless exists $opts{$arg};
110 0         0 $opts{$arg} = $set;
111             }
112              
113             # dump, yaml imply after
114 1   33     13 $opts{after} ||= $opts{yaml} || $opts{dump};
      33        
115              
116 1 50       5 $opts{fh} = $opts{stdout} ? \*STDOUT : \*STDERR;
117 1         3 $opts{dump_name} = OUTFILE;
118 1         2 $opts{enabled} = 1;
119              
120 1 50       5 if ( $opts{yaml} ) {
121 0         0 eval 'use YAML';
122 0 0       0 if ( $@ ) {
123 0         0 $opts{yaml} = 0;
124 0         0 $opts{after} = 0;
125 0         0 croak "YAML not available";
126             }
127 0         0 $opts{dump_name} .= '.yaml';
128             }
129              
130 1         3 my @stack = ( \@load_log );
131 1         6 my $exclude = qr{ [.] (?: al | ix ) $}x;
132              
133             # Register callback function
134             register_require_hook(
135             sub {
136 90     90   191 my ( $when, $depth, $arg, $p, $f, $l, $rc, $err ) = @_;
137              
138 90 50       904 return unless $opts{enabled};
139 90 50       465 return if $arg =~ $exclude;
140              
141             # require
142 90 100       151 if ( my $ver = _is_version( $arg ) ) {
143 2 100       9 if ( $when eq 'before' ) {
144 1         6 my $info = {
145             file => $f,
146             line => $l,
147             pkg => $p,
148             version => $ver, # Version desired
149             };
150              
151 1         5 push @version_log, $info;
152             }
153             }
154             else {
155 88 100       217 if ( $when eq 'before' ) {
    50          
156 44         72 my $module = _get_module( $arg );
157              
158 44 50       112 if ( $opts{during} ) {
159 0         0 my $pad = ' ' x ( $depth - 1 );
160 0         0 my $fh = $opts{fh};
161 0         0 print $fh "$pad$f, line $l: $module\n";
162             }
163              
164 44         199 my $info = {
165             file => $f, # File executing require
166             line => $l, # Line # of require
167             pkg => $p, # Package executing require
168             module => $module, # Module being required
169             nested => [], # List of nested requires
170             };
171              
172 44         56 push @{ $stack[-1] }, $info;
  44         90  
173 44         159 push @stack, $info->{nested};
174             }
175             elsif ( $when eq 'after' ) {
176 44         160 pop @stack;
177 44         305 my $info = $stack[-1][-1];
178 44         136 $info->{rc} = $rc;
179 44 100       172 if ( $err ) {
180 1         4 $info->{error} = $err;
181             }
182             else {
183 43         82 $info->{version} = _get_version( $info->{module} );
184             }
185             }
186             }
187             }
188 1         8 );
189             }
190              
191             END {
192 1 50   1   394 if ( $opts{after} ) {
193 0         0 $opts{enabled} = 0;
194 0         0 my $fh = $opts{fh};
195 0 0       0 if ( $opts{dump} ) {
196 0 0       0 open $fh, '>', $opts{dump_name}
197             or croak "Can't write $opts{dump_name} ($!)";
198             }
199              
200 0 0       0 if ( $opts{yaml} ) {
201 0         0 print $fh Dump( \@load_log );
202             }
203             else {
204 0         0 print $fh _underline( "Loaded Modules" );
205 0 0       0 if ( @load_log ) {
206 0         0 _text_out( $fh, \@load_log, 0 );
207             }
208             else {
209 0         0 print $fh "No modules loaded\n";
210             }
211             }
212             }
213              
214 1 50       9 if ( $opts{summary} ) {
215 0         0 my $fh = $opts{fh};
216              
217             # Cross-reference of loaded modules
218 0         0 print $fh _underline( "Loaded Modules Cross Reference" );
219              
220 0         0 my %loaded = ();
221 0         0 _gather_deps( \%loaded, \@load_log );
222 0 0       0 if ( %loaded ) {
223              
224             my $cmp_info = sub {
225 0   0     0 return lc $a->{pkg} cmp lc $b->{pkg}
226             || $a->{line} <=> $b->{line};
227 0         0 };
228              
229 0         0 for my $module ( sort { lc $a cmp lc $b } keys %loaded ) {
  0         0  
230 0         0 my $ver = _get_version( $module );
231 0 0       0 print $fh $module, defined $ver ? " ($ver)" : '', "\n";
232              
233 0         0 for my $info ( sort $cmp_info @{ $loaded{$module} } ) {
  0         0  
234 0         0 print $fh sprintf( " %s (%s), line %d\n",
235             $info->{file}, $info->{pkg}, $info->{line} );
236             }
237             }
238             }
239             else {
240 0         0 print $fh "No modules loaded\n";
241             }
242              
243             # Required versions
244 0         0 print $fh _underline( "Required versions" );
245 0 0       0 if ( @version_log ) {
246 0         0 for my $ver ( sort { $b->{version} <=> $a->{version} }
  0         0  
247             @version_log ) {
248 0         0 print $fh sprintf(
249             "%12s %s (%s), line %d\n",
250             $ver->{version}, $ver->{file},
251             $ver->{pkg}, $ver->{line}
252             );
253             }
254             }
255             else {
256 0         0 print $fh "No versions required\n";
257             }
258              
259             }
260             }
261             }
262              
263             1;
264              
265             __END__