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   794088 use strict;
  12         120  
  12         303  
4 12     12   53 use warnings;
  12         19  
  12         261  
5 12     12   245 use 5.008;
  12         37  
6              
7 12     12   4777 use Pod::Strip;
  12         323431  
  12         447  
8 12     12   12905 use Parse::RecDescent 1.967009;
  12         440422  
  12         83  
9 12     12   29020 use Module::ExtractUse::Grammar;
  12         48  
  12         1253  
10 12     12   104 use Carp;
  12         24  
  12         21959  
11             our $VERSION = '0.343';
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 233467 my $class=shift;
22 146         1402 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 13989 my $self=shift;
80 383         500 my $code_to_parse=shift;
81              
82 383         507 my $podless;
83 383         1923 my $pod_parser=Pod::Strip->new;
84 383         13541 $pod_parser->output_string(\$podless);
85 383 50       28677 $pod_parser->parse_characters(1) if $pod_parser->can('parse_characters');
86 383 100       2885 if (ref($code_to_parse) eq 'SCALAR') {
87 379         997 $pod_parser->parse_string_document($$code_to_parse);
88             }
89             else {
90 4         21 $pod_parser->parse_file($code_to_parse);
91             }
92              
93             # Strip obvious comments.
94 383         105555 $podless =~ s/(^|[\};])\s*#.*$/$1/mg;
95              
96             # Strip __(DATA|END)__ sections.
97 383         779 $podless =~ s/\n__(?:DATA|END)__\b.*$//s;
98              
99 383         496 my @statements;
100 383         2373 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         124 my $pre = $1;
104 51         100 my $eval = join('', grep { defined $_ } ($2, $3, $4, $5, $7, $9, $11));
  357         622  
105 51         146 push @statements, map { [ 0, $_ ] } split(/;/, $pre); # non-eval context
  131         238  
106 51         162 push @statements, map { [ 1, $_ ] } split(/;/, $eval); # eval context
  100         439  
107             }
108 383   100     2126 push @statements, map { [ 0, $_ ] } split(/;/, substr($podless, pos($podless) || 0)); # non-eval context
  855         1777  
109              
110 383         1039 foreach my $statement_ (@statements) {
111 1086         2223 my ($eval, $statement) = @$statement_;
112 1086         3324 $statement=~s/\n+/ /gs;
113 1086         1737 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       6263 if ($statement=~m/require_module|use_module|use_package_optimistically/) {
    100          
    100          
    100          
    100          
120 69         419 $statement=~s/^(.*?)\b(\S+(?:require_module|use_module|use_package_optimistically)\([^)]*\))/$2/;
121 69 50 66     208 next if $1 && $1 =~ /->\s*$/;
122 69         107 eval {
123 69         333 my $parser=Module::ExtractUse::Grammar->new();
124 69         543 $result=$parser->token_module_runtime($statement);
125             };
126 69 100       10377 $type = $statement =~ m/require/ ? 'require' : 'use';
127             }
128             elsif ($statement=~/\buse/) {
129 289         1147 $statement=~s/^(.*?)use\b/use/;
130 289 100 100     1153 next if $1 && $1 =~ /->\s*$/;
131 286         413 eval {
132 286         1432 my $parser=Module::ExtractUse::Grammar->new();
133 286         2577 $result=$parser->token_use($statement.';');
134             };
135 286         47157 $type = 'use';
136             }
137             elsif ($statement=~/\brequire/) {
138 54         234 $statement=~s/^(.*?)require\b/require/s;
139 54 100 100     283 next if $1 && $1 =~ /->\s*$/;
140 51         80 eval {
141 51         235 my $parser=Module::ExtractUse::Grammar->new();
142 51         741 $result=$parser->token_require($statement.';');
143             };
144 51         7971 $type = 'require';
145             }
146             elsif ($statement=~/\bno/) {
147 41         192 $statement=~s/^(.*?)no\b/no/s;
148 41 50 66     176 next if $1 && $1 =~ /->\s*$/;
149 41         58 eval {
150 41         214 my $parser=Module::ExtractUse::Grammar->new();
151 41         377 $result=$parser->token_no($statement.';');
152             };
153 41         7258 $type = 'no';
154             }
155             elsif ($statement=~m/load_class|try_load_class|load_first_existing_class|load_optional_class/) {
156 73         538 $statement=~s/^(.*?)\b(\S+(?:load_class|try_load_class|load_first_existing_class|load_optional_class)\([^)]*\))/$2/;
157 73 50 66     240 next if $1 && $1 =~ /->\s*$/;
158 73         99 eval {
159 73         441 my $parser=Module::ExtractUse::Grammar->new();
160 73         573 $result = $parser->token_class_load($statement.';');
161             };
162 73         11436 $type = 'require';
163             }
164              
165 1080 100       2458 next unless $result;
166              
167 345         1448 foreach (split(/\s+/,$result)) {
168 405 100       1325 $self->_add($_, $eval, $type) if($_);
169             }
170             }
171              
172             # increment file counter
173 383         993 $self->_inc_files;
174              
175 383         5732 return $self;
176             }
177              
178              
179              
180             sub used {
181 18     18 1 4985 my $self=shift;
182 18         37 my $key=shift;
183 18 100       131 return $self->{found}{$key} if ($key);
184 1         4 return $self->{found};
185             }
186              
187              
188             sub used_in_eval {
189 18     18 1 479 my $self=shift;
190 18         36 my $key=shift;
191 18 100       116 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 501 my $self=shift;
198 18         34 my $key=shift;
199 18 100       95 return $self->{found_not_in_eval}{$key} if ($key);
200 1         3 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 46 my $self=shift;
254 21   50     71 my $sep=shift || ' ';
255 21         26 return join($sep,sort keys(%{$self->{found}}));
  21         142  
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 227 return keys(%{shift->{found}})
  111         635  
275             }
276              
277              
278             sub array_in_eval {
279 111     111 1 193 return keys(%{shift->{found_in_eval}})
  111         653  
280             }
281              
282              
283             sub array_out_of_eval {
284 111     111 1 164 return keys(%{shift->{found_not_in_eval}})
  111         722  
285             }
286              
287              
288             sub arrayref {
289 110     110 1 425 my @a=shift->array;
290 110 100       445 return \@a if @a;
291 28         63 return;
292             }
293              
294              
295             sub arrayref_in_eval {
296 110     110 1 4587 my @a=shift->array_in_eval;
297 110 100       323 return \@a if @a;
298 107         247 return;
299             }
300              
301              
302             sub arrayref_out_of_eval {
303 110     110 1 356 my @a=shift->array_out_of_eval;
304 110 100       419 return \@a if @a;
305 30         70 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   577 my $self=shift;
316 402         519 my $found=shift;
317 402         491 my $eval=shift;
318 402         496 my $type=shift;
319 402         869 $self->{found}{$found}++;
320 402         739 $self->{$type}{$found}++;
321 402 100       812 if ($eval) {
322 29         62 $self->{found_in_eval}{$found}++;
323 29         153 $self->{"${type}_in_eval"}{$found}++;
324             } else {
325 373         575 $self->{found_not_in_eval}{$found}++;
326 373         1610 $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   684 shift->{files}++
336             }
337              
338             1;
339              
340             __END__