File Coverage

blib/lib/Module/Extract/Use.pm
Criterion Covered Total %
statement 89 92 96.7
branch 23 26 88.4
condition n/a
subroutine 26 27 96.3
pod 5 5 100.0
total 143 150 95.3


line stmt bran cond sub pod time code
1             package Module::Extract::Use;
2 4     4   3061 use strict;
  4         10  
  4         122  
3              
4 4     4   27 use warnings;
  4         30  
  4         108  
5 4     4   18 no warnings;
  4         8  
  4         158  
6              
7 4     4   2291 use subs qw();
  4         99  
  4         131  
8 4     4   24 use vars qw($VERSION);
  4     0   5  
  4         5254  
9              
10             $VERSION = '1.047';
11              
12             =encoding utf8
13              
14             =head1 NAME
15              
16             Module::Extract::Use - Discover the modules a module explicitly uses
17              
18             =head1 SYNOPSIS
19              
20             use Module::Extract::Use;
21              
22             my $extor = Module::Extract::Use->new;
23              
24             my @modules = $extor->get_modules( $file );
25             if( $extor->error ) { ... }
26              
27             my $details = $extor->get_modules_with_details( $file );
28             foreach my $detail ( @$details ) {
29             printf "%s %s imports %s\n",
30             $detail->module, $detail->version,
31             join ' ', @{ $detail->imports }
32             }
33              
34             =head1 DESCRIPTION
35              
36             Extract the names of the modules used in a file using a static
37             analysis. Since this module does not run code, it cannot find dynamic
38             uses of modules, such as C. It only reports modules
39             that the file loads directly or are in the import lists for L
40             or L.
41              
42             The module can handle the conventional inclusion of modules with either
43             C or C as the statement:
44              
45             use Foo;
46             require Foo;
47              
48             use Foo 1.23;
49             use Foo qw(this that);
50              
51             It now finds C as an expression, which is useful to lazily
52             load a module once (and may be faster):
53              
54             sub do_something {
55             state $rc = require Foo;
56             ...
57             }
58              
59             Additionally, it finds module names used with C and C,
60             either of which establish an inheritance relationship:
61              
62             use parent qw(Foo);
63             use base qw(Foo);
64              
65             In the case of namespaces found in C or C, the value of
66             the C method is false. In all other cases, it is true. You
67             can then skip those namespaces:
68              
69             my $details = $extor->get_modules_with_details( $file );
70             foreach my $detail ( @$details ) {
71             next unless $detail->direct;
72              
73             ...
74             }
75              
76             This module does not discover runtime machinations to load something,
77             such as string evals:
78              
79             eval "use Foo";
80              
81             my $bar = 'Bar';
82             eval "use $bar";
83              
84             If you want that, you might consider L (a confusingly
85             similar name).
86              
87             =cut
88              
89             =over 4
90              
91             =item new
92              
93             Makes an object. The object doesn't do anything just yet, but you need
94             it to call the methods.
95              
96             =cut
97              
98             sub new {
99 3     3 1 1754 my $class = shift;
100              
101 3         8 my $self = bless {}, $class;
102              
103 3         9 $self->init;
104              
105 3         9 $self;
106             }
107              
108             =item init
109              
110             Set up the object. You shouldn't need to call this yourself.
111              
112             =cut
113              
114             sub init {
115 3     3 1 9 $_[0]->_clear_error;
116             }
117              
118             =item get_modules( FILE )
119              
120             Returns a list of namespaces explicity use-d in FILE. Returns the
121             empty list if the file does not exist or if it can't parse the file.
122              
123             Each used namespace is only in the list even if it is used multiple
124             times in the file. The order of the list does not correspond to
125             anything so don't use the order to infer anything.
126              
127             =cut
128              
129             sub get_modules {
130 7     7 1 23582 my( $self, $file ) = @_;
131              
132 7         26 $self->_clear_error;
133              
134 7         14 my $details = $self->get_modules_with_details( $file );
135              
136 7         16 my @modules = map { $_->module } @$details;
  12         22  
137              
138 7         57 @modules;
139             }
140              
141             =item get_modules_with_details( FILE )
142              
143             Returns a list of hash references, one reference for each namespace
144             explicitly use-d in FILE. Each reference has keys for:
145              
146             namespace - the namespace, always defined
147             version - defined if a module version was specified
148             imports - an array reference to the import list
149             pragma - true if the module thinks this namespace is a pragma
150             direct - false if the module name came from parent or base
151              
152             Each used namespace is only in the list even if it is used multiple
153             times in the file. The order of the list does not correspond to
154             anything so don't use the order to infer anything.
155              
156             =cut
157              
158             sub get_modules_with_details {
159 9     9 1 2076 my( $self, $file ) = @_;
160              
161 9         23 $self->_clear_error;
162              
163 9         25 my $modules = $self->_get_ppi_for_file( $file );
164 9 100       6853 return [] unless defined $modules;
165              
166 8         25 $modules;
167             }
168              
169             sub _get_ppi_for_file {
170 9     9   20 my( $self, $file ) = @_;
171              
172 9 100       162 unless( -e $file ) {
173 1         9 $self->_set_error( ref( $self ) . ": File [$file] does not exist!" );
174 1         3 return;
175             }
176              
177 8         1836 require PPI;
178              
179 8         340863 my $Document = eval { PPI::Document->new( $file ) };
  8         54  
180 8 50       136655 unless( $Document ) {
181 0         0 $self->_set_error( ref( $self ) . ": Could not parse file [$file]" );
182 0         0 return;
183             }
184              
185             # this handles the
186             # use Foo;
187             # use Bar;
188 8         37 my $regular_modules = $self->_regular_load( $Document );
189              
190             # this handles
191             # use parent qw(...)
192 8         41 my $isa_modules = $self->_isa_load( $regular_modules );
193              
194             # this handles
195             # my $rc = require Foo;
196 8         69 my $expression_loads = $self->_expression_load( $Document );
197              
198 8         27 my @modules = map { @$_ }
  24         53  
199             $regular_modules,
200             $isa_modules,
201             $expression_loads
202             ;
203              
204 8         45 return \@modules;
205             }
206              
207             sub _regular_load {
208 8     8   26 my( $self, $Document ) = @_;
209              
210             my $modules = $Document->find(
211             sub {
212 1204     1204   12678 $_[1]->isa( 'PPI::Statement::Include' )
213             }
214 8         85 );
215              
216 8 100       160 return [] unless $modules;
217              
218 5         42 my %Seen;
219             my @modules =
220 29 100       436 grep { ! $Seen{ $_->{module} }++ && $_->{module} }
221             map {
222 5         21 my $hash = bless {
223             direct => 1,
224             content => $_->content,
225             pragma => $_->pragma,
226             module => $_->module,
227             imports => [ $self->_list_contents( $_->arguments ) ],
228 29 50       1451 version => eval{ $_->module_version->literal || ( undef ) },
  29         514  
229             }, 'Module::Extract::Use::Item';
230             } @$modules;
231              
232 5         35 \@modules;
233             }
234              
235             sub _isa_load {
236 8     8   21 my( $self, $modules ) = @_;
237             my @isa_modules =
238             map {
239 1         3 my $m = $_;
240             map {
241 1         5 bless {
242             content => $m->content,
243             pragma => '',
244             direct => 0,
245             module => $_,
246             imports => [],
247             version => undef,
248             }, 'Module::Extract::Use::Item';
249 1         2 } @{ $m->imports };
  1         3  
250             }
251 8 100       24 grep { $_->module eq 'parent' or $_->module eq 'base' }
  22         46  
252             @$modules;
253              
254 8         17 \@isa_modules;
255             }
256              
257             sub _expression_load {
258 8     8   55 my( $self, $Document ) = @_;
259              
260             my $in_statements = $Document->find(
261             sub {
262 1204 100   1204   13847 $_[1]->isa( 'PPI::Token::Word' ) &&
263             $_[1]->content eq 'require'
264             }
265 8         57 );
266              
267 8 100       139 return [] unless $in_statements;
268              
269             my @modules =
270             map {
271 1         4 bless {
  2         107  
272             content => $_->parent->content,
273             pragma => undef,
274             direct => 1,
275             module => $_->snext_sibling->content,
276             imports => [],
277             version => undef,
278             }, 'Module::Extract::Use::Item';
279             }
280             @$in_statements;
281              
282 1         67 \@modules;
283             }
284              
285 0         0 BEGIN {
286             package Module::Extract::Use::Item;
287              
288 1     1   2717 sub direct { $_[0]->{direct} }
289 2     2   30 sub content { $_[0]->{content} }
290 1     1   7 sub pragma { $_[0]->{pragma} }
291 56     56   179 sub module { $_[0]->{module} }
292 1     1   3 sub imports { $_[0]->{imports} }
293 1     1   4 sub version { $_[0]->{version} }
294             }
295              
296             sub _list_contents {
297 29     29   2877 my( $self, $node ) = @_;
298              
299 29         49 eval {
300 29 100       124 if( ! defined $node ) {
    100          
    100          
    50          
301 15         32 return;
302             }
303             elsif( $node->isa( 'PPI::Token::QuoteLike::Words' ) ) {
304 6         36 ( $node->literal )
305             }
306             elsif( $node->isa( 'PPI::Structure::List' ) ) {
307 1     5   40 my $nodes = $node->find( sub{ $_[1]->isa( 'PPI::Token::Quote' ) } );
  5         82  
308 1         27 map { $_->string } @$nodes;
  2         14  
309             }
310             elsif( $node->isa( 'PPI::Token::Quote' ) ) {
311 7         28 ( $node->string );
312             }
313             };
314              
315             }
316              
317             =item error
318              
319             Return the error from the last call to C.
320              
321             =cut
322              
323 1     1   2 sub _set_error { $_[0]->{error} = $_[1]; }
324              
325 19     19   45 sub _clear_error { $_[0]->{error} = '' }
326              
327 2     2 1 36 sub error { $_[0]->{error} }
328              
329             =back
330              
331             =head1 TO DO
332              
333             =head1 SEE ALSO
334              
335             L, L
336              
337             =head1 SOURCE AVAILABILITY
338              
339             The source code is in Github:
340              
341             https://github.com/briandfoy/module-extract-use
342              
343             =head1 AUTHOR
344              
345             brian d foy, C<< >>
346              
347             =head1 COPYRIGHT AND LICENSE
348              
349             Copyright © 2008-2020, brian d foy C<< >>. All rights reserved.
350              
351             This project is under the Artistic License 2.0.
352              
353             =cut
354              
355             1;