File Coverage

blib/lib/Module/ExtractUse.pm
Criterion Covered Total %
statement 127 161 78.8
branch 40 56 71.4
condition 15 23 65.2
subroutine 21 31 67.7
pod 21 21 100.0
total 224 292 76.7


line stmt bran cond sub pod time code
1             package Module::ExtractUse;
2              
3 12     12   978283 use strict;
  12         132  
  12         420  
4 12     12   69 use warnings;
  12         22  
  12         356  
5 12     12   277 use 5.008;
  12         40  
6              
7 12     12   6564 use Pod::Strip;
  12         397737  
  12         510  
8 12     12   16206 use Parse::RecDescent 1.967009;
  12         529537  
  12         86  
9 12     12   34261 use Module::ExtractUse::Grammar;
  12         51  
  12         1421  
10 12     12   122 use Carp;
  12         29  
  12         26799  
11             our $VERSION = '0.344';
12              
13             # ABSTRACT: Find out what modules are used
14              
15             #$::RD_TRACE=1;
16             #$::RD_HINT=1;
17              
18              
19              
20             sub new {
21 146     146 1 263058 my $class=shift;
22 146         748 return bless {
23             found=>{},
24             files=>0,
25             },$class;
26             }
27              
28              
29             # Regular expression to detect eval
30             # On newer perl, you can use named capture groups and (?&name) for recursive regex
31             # However, it requires perl newer than 5.008 declared as requirement in this module
32             my $re_block;
33             $re_block = qr {
34             ( # eval BLOCK, corresponding to the group 10 in the entire regex
35             \{
36             ((?:
37             (?> [^{}]+ ) # Non-braces without backtracking
38             |
39             (??{$re_block}) # Recurse to group 10
40             )*)
41             \}
42             )
43             }xs;
44             my $re = qr{
45             \G(.*?) # group 1
46             eval
47             (?:
48             (?:\s+
49             (?:
50             qq?\((.*?)\) # eval q(), group 2
51             |
52             qq?\[(.*?)\] # eval q[], group 3
53             |
54             qq?{(.*?)} # eval q{}, group 4
55             |
56             qq?<(.*?)> # eval q<>, group 5
57             |
58             qq?(\S)(.*?)\6 # eval q'' or so, group 6, group 7
59             )
60             )
61             |
62             (?:\s*(?:
63             (?:(['"])(.*?)\8) # eval '' or eval "", group 8, group 9
64             |
65             ( # eval BLOCK, group 10
66             \{
67             ((?: # group 11
68             (?> [^{}]+ ) # Non-braces without backtracking
69             |
70             (??{$re_block}) # Recurse to group 10
71             )*)
72             \}
73             )
74             ))
75             )
76             }xs;
77              
78             sub extract_use {
79 383     383 1 16498 my $self=shift;
80 383         710 my $code_to_parse=shift;
81              
82 383         694 my $podless;
83 383         2629 my $pod_parser=Pod::Strip->new;
84 383         17434 $pod_parser->output_string(\$podless);
85 383 50       36756 $pod_parser->parse_characters(1) if $pod_parser->can('parse_characters');
86 383 100       3555 if (ref($code_to_parse) eq 'SCALAR') {
87 379         1352 $pod_parser->parse_string_document($$code_to_parse);
88             }
89             else {
90 4         31 $pod_parser->parse_file($code_to_parse);
91             }
92              
93             # Strip obvious comments.
94 383         132776 $podless =~ s/(^|[\};])\s*#.*$/$1/mg;
95              
96             # Strip __(DATA|END)__ sections.
97 383         930 $podless =~ s/\n__(?:DATA|END)__\b.*$//s;
98              
99 383         652 my @statements;
100 383         3002 while($podless =~ /$re/gc) {
101             # to keep parsing time short, split code in statements
102             # (I know that this is not very exact, patches welcome!)
103 51         156 my $pre = $1;
104 51         111 my $eval = join('', grep { defined $_ } ($2, $3, $4, $5, $7, $9, $11));
  357         749  
105 51         166 push @statements, map { [ 0, $_ ] } split(/;/, $pre); # non-eval context
  131         260  
106 51         184 push @statements, map { [ 1, $_ ] } split(/;/, $eval); # eval context
  100         416  
107             }
108 383   100     2772 push @statements, map { [ 0, $_ ] } split(/;/, substr($podless, pos($podless) || 0)); # non-eval context
  855         2216  
109              
110 383         955 foreach my $statement_ (@statements) {
111 1086         2754 my ($eval, $statement) = @$statement_;
112 1086         4113 $statement=~s/\n+/ /gs;
113 1086         2117 my $result;
114              
115             # now that we've got some code containing 'use' or 'require',
116             # parse it! (using different entry point to save some more
117             # time)
118             my $type;
119 1086 100       8637 if ($statement=~m/require_module|use_module|use_package_optimistically/) {
    100          
    100          
    100          
    100          
120 69         651 $statement=~s/^(.*?)\b(\S+(?:require_module|use_module|use_package_optimistically)\([^)]*\))/$2/;
121 69 50 66     363 next if $1 && $1 =~ /->\s*$/;
122 69         137 eval {
123 69         716 my $parser=Module::ExtractUse::Grammar->new();
124 69         937 $result=$parser->token_module_runtime($statement);
125             };
126 69 100       21220 $type = $statement =~ m/require/ ? 'require' : 'use';
127             }
128             elsif ($statement=~/\buse/) {
129 289         1440 $statement=~s/^(.*?)use\b/use/;
130 289 100 100     1311 next if $1 && $1 =~ /->\s*$/;
131 286         506 eval {
132 286         1600 my $parser=Module::ExtractUse::Grammar->new();
133 286         3045 $result=$parser->token_use($statement.';');
134             };
135 286         60658 $type = 'use';
136             }
137             elsif ($statement=~/\brequire/) {
138 54         288 $statement=~s/^(.*?)require\b/require/s;
139 54 100 100     325 next if $1 && $1 =~ /->\s*$/;
140 51         103 eval {
141 51         273 my $parser=Module::ExtractUse::Grammar->new();
142 51         572 $result=$parser->token_require($statement.';');
143             };
144 51         10313 $type = 'require';
145             }
146             elsif ($statement=~/\bno/) {
147 41         179 $statement=~s/^(.*?)no\b/no/s;
148 41 50 66     180 next if $1 && $1 =~ /->\s*$/;
149 41         66 eval {
150 41         223 my $parser=Module::ExtractUse::Grammar->new();
151 41         394 $result=$parser->token_no($statement.';');
152             };
153 41         7742 $type = 'no';
154             }
155             elsif ($statement=~m/load_class|try_load_class|load_first_existing_class|load_optional_class/) {
156 73         658 $statement=~s/^(.*?)\b(\S+(?:load_class|try_load_class|load_first_existing_class|load_optional_class)\([^)]*\))/$2/;
157 73 50 66     268 next if $1 && $1 =~ /->\s*$/;
158 73         127 eval {
159 73         571 my $parser=Module::ExtractUse::Grammar->new();
160 73         724 $result = $parser->token_class_load($statement.';');
161             };
162 73         13815 $type = 'require';
163             }
164              
165 1080 100       3123 next unless $result;
166              
167 345         1856 foreach (split(/\s+/,$result)) {
168 405 100       1876 $self->_add($_, $eval, $type) if($_);
169             }
170             }
171              
172             # increment file counter
173 383         1368 $self->_inc_files;
174              
175 383         8043 return $self;
176             }
177              
178              
179              
180             sub used {
181 18     18 1 5972 my $self=shift;
182 18         34 my $key=shift;
183 18 100       134 return $self->{found}{$key} if ($key);
184 1         3 return $self->{found};
185             }
186              
187              
188             sub used_in_eval {
189 18     18 1 592 my $self=shift;
190 18         35 my $key=shift;
191 18 100       111 return $self->{found_in_eval}{$key} if ($key);
192 1         3 return $self->{found_in_eval};
193             }
194              
195              
196             sub used_out_of_eval {
197 18     18 1 576 my $self=shift;
198 18         37 my $key=shift;
199 18 100       96 return $self->{found_not_in_eval}{$key} if ($key);
200 1         4 return $self->{found_not_in_eval};
201             }
202              
203              
204             sub required {
205 0     0 1 0 my $self=shift;
206 0         0 my $key=shift;
207 0 0       0 return $self->{require}{$key} if ($key);
208 0         0 return $self->{require};
209             }
210              
211              
212             sub required_in_eval {
213 0     0 1 0 my $self=shift;
214 0         0 my $key=shift;
215 0 0       0 return $self->{require_in_eval}{$key} if ($key);
216 0         0 return $self->{require_in_eval};
217             }
218              
219              
220             sub required_out_of_eval {
221 0     0 1 0 my $self=shift;
222 0         0 my $key=shift;
223 0 0       0 return $self->{require_not_in_eval}{$key} if ($key);
224 0         0 return $self->{require_not_in_eval};
225             }
226              
227              
228             sub noed {
229 0     0 1 0 my $self=shift;
230 0         0 my $key=shift;
231 0 0       0 return $self->{no}{$key} if ($key);
232 0         0 return $self->{no};
233             }
234              
235              
236             sub noed_in_eval {
237 0     0 1 0 my $self=shift;
238 0         0 my $key=shift;
239 0 0       0 return $self->{no_in_eval}{$key} if ($key);
240 0         0 return $self->{no_in_eval};
241             }
242              
243              
244             sub noed_out_of_eval {
245 0     0 1 0 my $self=shift;
246 0         0 my $key=shift;
247 0 0       0 return $self->{no_not_in_eval}{$key} if ($key);
248 0         0 return $self->{no_not_in_eval};
249             }
250              
251              
252             sub string {
253 21     21 1 47 my $self=shift;
254 21   50     87 my $sep=shift || ' ';
255 21         47 return join($sep,sort keys(%{$self->{found}}));
  21         218  
256             }
257              
258              
259             sub string_in_eval {
260 0     0 1 0 my $self=shift;
261 0   0     0 my $sep=shift || ' ';
262 0         0 return join($sep,sort keys(%{$self->{found_in_eval}}));
  0         0  
263             }
264              
265              
266             sub string_out_of_eval {
267 0     0 1 0 my $self=shift;
268 0   0     0 my $sep=shift || ' ';
269 0         0 return join($sep,sort keys(%{$self->{found_not_in_eval}}));
  0         0  
270             }
271              
272              
273             sub array {
274 111     111 1 203 return keys(%{shift->{found}})
  111         798  
275             }
276              
277              
278             sub array_in_eval {
279 111     111 1 211 return keys(%{shift->{found_in_eval}})
  111         868  
280             }
281              
282              
283             sub array_out_of_eval {
284 111     111 1 237 return keys(%{shift->{found_not_in_eval}})
  111         851  
285             }
286              
287              
288             sub arrayref {
289 110     110 1 417 my @a=shift->array;
290 110 100       586 return \@a if @a;
291 28         86 return;
292             }
293              
294              
295             sub arrayref_in_eval {
296 110     110 1 5358 my @a=shift->array_in_eval;
297 110 100       353 return \@a if @a;
298 107         349 return;
299             }
300              
301              
302             sub arrayref_out_of_eval {
303 110     110 1 380 my @a=shift->array_out_of_eval;
304 110 100       607 return \@a if @a;
305 30         97 return;
306             }
307              
308              
309             sub files {
310 0     0 1 0 return shift->{files};
311             }
312              
313             # Internal Accessor Methods
314             sub _add {
315 402     402   734 my $self=shift;
316 402         657 my $found=shift;
317 402         619 my $eval=shift;
318 402         605 my $type=shift;
319 402         1073 $self->{found}{$found}++;
320 402         861 $self->{$type}{$found}++;
321 402 100       1007 if ($eval) {
322 29         72 $self->{found_in_eval}{$found}++;
323 29         176 $self->{"${type}_in_eval"}{$found}++;
324             } else {
325 373         803 $self->{found_not_in_eval}{$found}++;
326 373         1886 $self->{"${type}_not_in_eval"}{$found}++;
327             }
328             }
329              
330             sub _found {
331             return shift->{found}
332 0     0   0 }
333              
334             sub _inc_files {
335 383     383   881 shift->{files}++
336             }
337              
338             1;
339              
340             __END__