File Coverage

blib/lib/Module/Used.pm
Criterion Covered Total %
statement 105 106 99.0
branch 34 42 80.9
condition 6 6 100.0
subroutine 19 19 100.0
pod 4 4 100.0
total 168 177 94.9


line stmt bran cond sub pod time code
1             package Module::Used;
2              
3 2     2   2667 use 5.008003;
  2         6  
  2         69  
4 2     2   9 use utf8;
  2         3  
  2         12  
5              
6 2     2   49 use strict;
  2         3  
  2         60  
7 2     2   10 use warnings;
  2         2  
  2         77  
8              
9 2     2   2188 use version; our $VERSION = qv('v1.3.0');
  2         4705  
  2         13  
10              
11 2     2   2221 use English qw< -no_match_vars >;
  2         82357  
  2         17  
12 2     2   4228 use Const::Fast qw< const >;
  2         7882  
  2         15  
13              
14 2     2   225 use Exporter qw< import >;
  2         4  
  2         260  
15              
16             our @EXPORT_OK = qw<
17             modules_used_in_files
18             modules_used_in_string
19             modules_used_in_document
20             modules_used_in_modules
21             >;
22             our %EXPORT_TAGS = (
23             all => [@EXPORT_OK],
24             );
25              
26              
27 2     2   2358 use Module::Path qw< module_path >;
  2         1329  
  2         112  
28 2     2   2529 use PPI::Document ();
  2         1010299  
  2         2504  
29              
30              
31             sub modules_used_in_files {
32 2     2 1 1278 my (@files) = @_;
33              
34 2         5 my %modules;
35              
36 2         6 foreach my $file (@files) {
37 2         7 my $document = _create_document_from_file($file);
38 2         14 my @loaded_modules = modules_used_in_document($document);
39 2         28 @modules{@loaded_modules} = (1) x @loaded_modules;
40             } # end foreach
41              
42 2         19035 return keys %modules;
43             } # end modules_used_in_files()
44              
45              
46             sub modules_used_in_modules {
47 1     1 1 1236 my (@modules) = @_;
48 1         3 my @files;
49             my $fullpath;
50              
51 1         4 foreach my $module (@modules) {
52 1 50       6 $fullpath = module_path($module)
53             or die qq;
54              
55 1         265 push @files, $fullpath;
56             }
57              
58 1         6 return modules_used_in_files(@files);
59             } # end modules_used_in_modules()
60              
61              
62             sub modules_used_in_string {
63 20     20 1 61413 my ($string) = @_;
64              
65 20 50       181 my $document = PPI::Document->new(\$string, readonly => 1)
66             or die qq, PPI::Document->errstr(), ".\n";
67              
68 20         48176 return modules_used_in_document($document);
69             } # end modules_used_in_string()
70              
71              
72             sub modules_used_in_document {
73 22     22 1 49 my ($document) = @_;
74              
75 22         44 my %modules;
76              
77 22         109 my $includes = $document->find('PPI::Statement::Include');
78 22 100       109750 if ($includes) {
79 20         51 foreach my $statement ( @{$includes} ) {
  20         69  
80 37         186 my $module = $statement->module();
81 37 100       1248 if ($module) {
82 34         95 $modules{$module} = 1;
83              
84 34 100 100     243 if ($module eq 'base' or $module eq 'parent') {
85 6         24 my @loaded_modules =
86             _modules_loaded_by_base_or_parent($statement);
87              
88 6         36 @modules{@loaded_modules} = (1) x @loaded_modules;
89             } # end if
90             } # end if
91             } # end foreach
92              
93 20         47 my @moose_modules;
94 20 100       91 if ( $modules{Moose} ) {
    100          
95 6         25 @moose_modules =
96             _modules_loaded_by_moose_sugar($document, 'extends');
97 6         19 push
98             @moose_modules,
99             _modules_loaded_by_moose_sugar($document, 'with');
100             } elsif ( $modules{'Moose::Role'} ) {
101 3         13 @moose_modules =
102             _modules_loaded_by_moose_sugar($document, 'with');
103             } # end if
104 20         70 @modules{@moose_modules} = (1) x @moose_modules;
105             } # end if
106              
107 22         175 return keys %modules;
108             } # end modules_used_in_document()
109              
110              
111             sub _create_document_from_file {
112 2     2   5 my ($source) = @_;
113              
114 2 50       54 -e $source
115             or die qq<"$source" does not exist.\n>;
116              
117 2 50       13 -r _
118             or die qq<"$source" is not readable.\n>;
119              
120 2 50       8 not -d _
121             or die qq<"$source" is a directory.\n>;
122              
123 2 50       8 if ( -z _ ) {
124             # PPI barfs on empty documents for some reason.
125 0         0 return PPI::Document->new();
126             }
127              
128 2 50       12 my $document = PPI::Document->new($source, readonly => 1)
129             or die qq, PPI::Document->errstr(), ".\n";
130              
131 2         281802 return $document;
132             } # end _create_document_from_file()
133              
134              
135             const my $QUOTE_WORDS_DELIMITER_OFFSET => length 'qw<';
136              
137             sub _modules_loaded_by_base_or_parent {
138 6     6   18 my ($statement) = @_;
139              
140 6         9 my @modules;
141              
142 6         36 my @children = $statement->schildren();
143 6         91 shift @children; # use/require/my
144 6         9 shift @children; # 'base'/'parent'
145              
146 6 100 100     60 if (@children and $children[0] =~ m< v? [\d.]+ >xms) {
147             # Skip version requirement for 'base'/'parent'. Not worrying about
148             # the potential following comma.
149 2         22 shift @children;
150             }
151              
152 6         43 foreach my $child (@children) {
153 17 100       228 if ( $child->isa('PPI::Token::Quote') ) {
    100          
154 7         41 push @modules, $child->string();
155             } elsif ( $child->isa('PPI::Token::QuoteLike::Words') ) {
156 1         8 push @modules, $child->literal();
157             } # end if
158             } # end foreach
159              
160 6         27 return @modules;
161             } # end _modules_loaded_by_base_or_parent()
162              
163              
164             sub _modules_loaded_by_moose_sugar {
165 15     15   35 my ($document, $sugar) = @_;
166              
167 15         21 my @modules;
168              
169 15         55 my $statements = $document->find( _create_wanted_moose_sugar($sugar) );
170 15 100       263 return if not $statements;
171              
172 9         44 foreach my $statement ( @{$statements} ) {
  9         27  
173 9         1496 my @children = $statement->schildren();
174 9         124 shift @children; # 'with'
175              
176 9         19 foreach my $child (@children) {
177 15 100       123 if ( $child->isa('PPI::Token::Quote') ) {
    100          
178 6         28 push @modules, $child->string();
179             } elsif ( $child->isa('PPI::Token::QuoteLike::Words') ) {
180 3         19 push @modules, $child->literal();
181             } # end if
182             } # end foreach
183             } # end foreach
184              
185 9         157 return @modules;
186             } # end _modules_loaded_by_moose_sugar()
187              
188              
189             sub _create_wanted_moose_sugar {
190 15     15   25 my ($sugar) = @_;
191              
192             # Have to return 0 for false because undef tells PPI to stop searching.
193             return sub {
194 330     330   3707 my (undef, $element) = @_;
195              
196             # Fix this once the next PPI version is released. Want only vanilla
197             # statements.
198 330 100       1147 return 0 if ref $element ne 'PPI::Statement';
199              
200 20         60 my $first_child = $element->schild(0);
201 20 50       272 return 0 if not $first_child;
202 20 100       67 return 0 if not $first_child->isa('PPI::Token::Word');
203              
204 15         49 return $first_child->content() eq $sugar;
205 15         122 }; # end closure
206             } # end _create_wanted_moose_sugar()
207              
208              
209             1; # Magic true value required at end of module.
210              
211             __END__