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   942296 use strict;
  12         132  
  12         359  
4 12     12   67 use warnings;
  12         21  
  12         288  
5 12     12   269 use 5.008;
  12         40  
6              
7 12     12   6020 use Pod::Strip;
  12         390678  
  12         497  
8 12     12   16274 use Parse::RecDescent 1.967009;
  12         516907  
  12         137  
9 12     12   35819 use Module::ExtractUse::Grammar;
  12         65  
  12         1735  
10 12     12   101 use Carp;
  12         27  
  12         27421  
11             our $VERSION = '0.345';
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 259322 my $class=shift;
22 146         722 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 17262 my $self=shift;
80 383         574 my $code_to_parse=shift;
81              
82 383         544 my $podless;
83 383         1868 my $pod_parser=Pod::Strip->new;
84 383         14584 $pod_parser->output_string(\$podless);
85 383 50       31358 $pod_parser->parse_characters(1) if $pod_parser->can('parse_characters');
86 383 100       3178 if (ref($code_to_parse) eq 'SCALAR') {
87 379         1038 $pod_parser->parse_string_document($$code_to_parse);
88             }
89             else {
90 4         15 $pod_parser->parse_file($code_to_parse);
91             }
92              
93             # Strip obvious comments.
94 383         124343 $podless =~ s/(^|[\};])\s*#.*$/$1/mg;
95              
96             # Strip __(DATA|END)__ sections.
97 383         841 $podless =~ s/\n__(?:DATA|END)__\b.*$//s;
98              
99 383         597 my @statements;
100 383         2705 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         159 my $pre = $1;
104 51         122 my $eval = join('', grep { defined $_ } ($2, $3, $4, $5, $7, $9, $11));
  357         722  
105 51         185 push @statements, map { [ 0, $_ ] } split(/;/, $pre); # non-eval context
  131         291  
106 51         205 push @statements, map { [ 1, $_ ] } split(/;/, $eval); # eval context
  100         493  
107             }
108 383   100     2382 push @statements, map { [ 0, $_ ] } split(/;/, substr($podless, pos($podless) || 0)); # non-eval context
  855         2164  
109              
110 383         946 foreach my $statement_ (@statements) {
111 1086         2581 my ($eval, $statement) = @$statement_;
112 1086         4171 $statement=~s/\n+/ /gs;
113 1086         2098 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       7073 if ($statement=~m/require_module|use_module|use_package_optimistically/) {
    100          
    100          
    100          
    100          
120 69         503 $statement=~s/^(.*?)\b(\S+(?:require_module|use_module|use_package_optimistically)\([^)]*\))/$2/;
121 69 50 66     236 next if $1 && $1 =~ /->\s*$/;
122 69         120 eval {
123 69         421 my $parser=Module::ExtractUse::Grammar->new();
124 69         621 $result=$parser->token_module_runtime($statement);
125             };
126 69 100       12749 $type = $statement =~ m/require/ ? 'require' : 'use';
127             }
128             elsif ($statement=~/\buse/) {
129 289         1244 $statement=~s/^(.*?)use\b/use/;
130 289 100 100     1216 next if $1 && $1 =~ /->\s*$/;
131 286         498 eval {
132 286         1421 my $parser=Module::ExtractUse::Grammar->new();
133 286         2790 $result=$parser->token_use($statement.';');
134             };
135 286         50848 $type = 'use';
136             }
137             elsif ($statement=~/\brequire/) {
138 54         280 $statement=~s/^(.*?)require\b/require/s;
139 54 100 100     299 next if $1 && $1 =~ /->\s*$/;
140 51         91 eval {
141 51         254 my $parser=Module::ExtractUse::Grammar->new();
142 51         517 $result=$parser->token_require($statement.';');
143             };
144 51         9104 $type = 'require';
145             }
146             elsif ($statement=~/\bno/) {
147 41         181 $statement=~s/^(.*?)no\b/no/s;
148 41 50 66     174 next if $1 && $1 =~ /->\s*$/;
149 41         71 eval {
150 41         207 my $parser=Module::ExtractUse::Grammar->new();
151 41         410 $result=$parser->token_no($statement.';');
152             };
153 41         7693 $type = 'no';
154             }
155             elsif ($statement=~m/load_class|try_load_class|load_first_existing_class|load_optional_class/) {
156 73         681 $statement=~s/^(.*?)\b(\S+(?:load_class|try_load_class|load_first_existing_class|load_optional_class)\([^)]*\))/$2/;
157 73 50 66     280 next if $1 && $1 =~ /->\s*$/;
158 73         122 eval {
159 73         441 my $parser=Module::ExtractUse::Grammar->new();
160 73         753 $result = $parser->token_class_load($statement.';');
161             };
162 73         13803 $type = 'require';
163             }
164              
165 1080 100       2807 next unless $result;
166              
167 345         1564 foreach (split(/\s+/,$result)) {
168 405 100       1553 $self->_add($_, $eval, $type) if($_);
169             }
170             }
171              
172             # increment file counter
173 383         1107 $self->_inc_files;
174              
175 383         5881 return $self;
176             }
177              
178              
179              
180             sub used {
181 18     18 1 6167 my $self=shift;
182 18         42 my $key=shift;
183 18 100       133 return $self->{found}{$key} if ($key);
184 1         3 return $self->{found};
185             }
186              
187              
188             sub used_in_eval {
189 18     18 1 578 my $self=shift;
190 18         45 my $key=shift;
191 18 100       108 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 599 my $self=shift;
198 18         46 my $key=shift;
199 18 100       99 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 43 my $self=shift;
254 21   50     85 my $sep=shift || ' ';
255 21         36 return join($sep,sort keys(%{$self->{found}}));
  21         196  
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 205 return keys(%{shift->{found}})
  111         613  
275             }
276              
277              
278             sub array_in_eval {
279 111     111 1 173 return keys(%{shift->{found_in_eval}})
  111         700  
280             }
281              
282              
283             sub array_out_of_eval {
284 111     111 1 166 return keys(%{shift->{found_not_in_eval}})
  111         742  
285             }
286              
287              
288             sub arrayref {
289 110     110 1 356 my @a=shift->array;
290 110 100       425 return \@a if @a;
291 28         88 return;
292             }
293              
294              
295             sub arrayref_in_eval {
296 110     110 1 6413 my @a=shift->array_in_eval;
297 110 100       300 return \@a if @a;
298 107         320 return;
299             }
300              
301              
302             sub arrayref_out_of_eval {
303 110     110 1 327 my @a=shift->array_out_of_eval;
304 110 100       529 return \@a if @a;
305 30         81 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   658 my $self=shift;
316 402         647 my $found=shift;
317 402         574 my $eval=shift;
318 402         632 my $type=shift;
319 402         1017 $self->{found}{$found}++;
320 402         800 $self->{$type}{$found}++;
321 402 100       911 if ($eval) {
322 29         62 $self->{found_in_eval}{$found}++;
323 29         158 $self->{"${type}_in_eval"}{$found}++;
324             } else {
325 373         726 $self->{found_not_in_eval}{$found}++;
326 373         1830 $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   771 shift->{files}++
336             }
337              
338             1;
339              
340             __END__