File Coverage

blib/lib/Pod/Coverage/TrustMe.pm
Criterion Covered Total %
statement 203 207 98.0
branch 59 82 71.9
condition 35 46 76.0
subroutine 39 39 100.0
pod 10 11 90.9
total 346 385 89.8


line stmt bran cond sub pod time code
1             package Pod::Coverage::TrustMe;
2 11     11   745286 use strict;
  11         108  
  11         337  
3 11     11   62 use warnings;
  11         21  
  11         603  
4              
5             our $VERSION = '0.002000';
6             $VERSION =~ tr/_//d;
7              
8 11     11   5155 use Pod::Coverage::TrustMe::Parser;
  11         44  
  11         416  
9 11     11   82 use B ();
  11         27  
  11         226  
10 11     11   57 use Carp qw(croak);
  11         24  
  11         808  
11 11 50   11   80 use constant _GVf_IMPORTED_CV => defined &B::GVf_IMPORTED_CV ? B::GVf_IMPORTED_CV() : 0x80;
  11         25  
  11         2883  
12              
13 11         23 use constant DEFAULT_PRIVATE => do {
14 11         26 my %s;
15             [
16             qr/\A_/,
17             # anything with non-word characters is not standard syntax, so exclude
18             # them. this includes overloads, which are internally stored as methods
19             # starting with '('.
20             qr/\W/,
21 11         19100 (map qr{\A\Q$_\E\z}, grep !$s{$_}++, qw(
22             import
23             unimport
24             VERSION
25              
26             can
27             isa
28             does
29             DOES
30              
31             AUTOLOAD
32              
33             DESTROY
34             CLONE
35             CLONE_SKIP
36              
37             BUILD
38             BUILDALL
39             BUILDARGS
40             FOREIGNBUILDARGS
41             DEMOLISH
42             DEMOLISHALL
43              
44             bootstrap
45              
46             TIESCALAR
47             FETCH STORE
48              
49             TIEARRAY
50             FETCH STORE FETCHSIZE STORESIZE EXTEND EXISTS
51             DELETE CLEAR PUSH POP SHIFT UNSHIFT SPLICE
52              
53             TIEHASH
54             FETCH STORE DELETE CLEAR EXISTS FIRSTKEY NEXTKEY SCALAR
55              
56             TIEHANDLE
57             OPEN BINMODE FILENO SEEK TELL WRITE PRINT PRINTF
58             READ READLINE GETC EOF CLOSE
59              
60             UNTIE
61             )),
62             qr/\A
63             (?: MODIFY | FETCH )
64             _
65             (?: REF | SCALAR | ARRAY | HASH | CODE | GLOB | FORMAT | IO )
66             _
67             ATTRIBUTES
68             \z/x,
69             ];
70 11     11   85 };
  11         24  
71             &Internals::SvREADONLY(+DEFAULT_PRIVATE, 1);
72              
73             our $PACKAGE_RE = qr{
74             (?=[^0-9'])
75             (?:
76             ::
77             |
78             \w*
79             (?:'[^\W0-9]\w*)*
80             )*
81             }x;
82             &Internals::SvREADONLY(\$PACKAGE_RE, 1);
83              
84             my %ALIASES = (
85             nonwhitespace => 'require_content',
86             trustme => 'trust_methods',
87             );
88              
89             my %DEFAULTS = (
90             trust_roles => 1,
91             trust_parents => 1,
92             trust_pod => 1,
93             require_link => 0,
94             export_only => 0,
95             ignore_imported => 1,
96             require_content => 0,
97             trust_methods => [],
98             trust_packages => [],
99             private => DEFAULT_PRIVATE,
100             pod_from => undef,
101             package => undef,
102             );
103              
104             sub new {
105 42     42 0 6212 my ($class, %args) = @_;
106 42 50       165 $class = ref $class
107             if ref $class;
108              
109 42         280 for my $alias (sort keys %ALIASES) {
110 84 100       257 if (exists $args{$alias}) {
111 4         13 my $to = $ALIASES{$alias};
112 4 50       16 if (exists $args{$to}) {
113 0         0 croak "$alias is an alias to $to, they can't both be specified!";
114             }
115 4         15 $args{$to} = delete $args{$alias};
116             }
117             }
118              
119             my $new = {
120 42 100       806 map +($_ => exists $args{$_} ? $args{$_} : $DEFAULTS{$_}), keys %DEFAULTS,
121             };
122              
123 42 100 100     323 if (exists $args{private} || exists $args{also_private}) {
124             $new->{private} = [
125             map +(ref $_ ? $_ : qr/\A\Q$_\E\z/), (
126 4         45 @{ $new->{private} },
127 4 100       10 exists $args{also_private} ? @{ $args{also_private} } : (),
  1 100       53  
128             )
129             ];
130             }
131              
132             my $package = $new->{package}
133 42 50       153 or die "package is a required parameter";
134              
135 42 100       77 eval { require(__pack_to_pm($package)); 1 } or do {
  42         137  
  41         38953  
136 1         327 $new->{why_unrated} = "requiring '$package' failed: $@";
137 1         3 $new->{broken} = 1;
138             };
139              
140 42         614 bless $new, $class;
141             }
142              
143             sub package {
144 326     326 1 570 my $self = shift;
145 326         1115 $self->{package};
146             }
147              
148             sub symbols {
149 54     54 1 103 my $self = shift;
150             return undef
151 54 100       280 if $self->{broken};
152 53   66     217 $self->{symbols} ||= do {
153 41         113 my $package = $self->package;
154              
155 41         90 my %pods = map +( $_ => 1 ), @{ $self->_get_pods($package) };
  41         132  
156             my %symbols = map +(
157 41   100     206 $_ => ($pods{$_} || $self->_trust_method_check($_) || 0),
158             ), $self->_get_syms($package);
159              
160 41 100       227 if (!grep $_, values %symbols) {
161 3   50     23 $self->{why_unrated} ||= "no public symbols defined";
162             }
163              
164 41         272 \%symbols;
165             };
166             }
167              
168             sub coverage {
169 45     45 1 2202 my $self = shift;
170 45 100       145 my $symbols = $self->symbols
171             or return undef;
172              
173 44 100       170 my $total = scalar keys %$symbols
174             or return undef;
175 43         125 my $documented = scalar grep $_, values %$symbols;
176              
177 43         353 return $documented / $total;
178             }
179              
180             sub why_unrated {
181 2     2 1 5 my $self = shift;
182 2         15 return $self->{why_unrated};
183             }
184              
185             sub uncovered {
186 7     7 1 15 my $self = shift;
187 7 50       17 my $symbols = $self->symbols
188             or return ();
189 7         46 my @uncovered = sort grep !$symbols->{$_}, keys %$symbols;
190 7         40 return @uncovered;
191             }
192             sub naked {
193 4     4 1 535 my $self = shift;
194 4         15 return $self->uncovered(@_);
195             }
196              
197             sub covered {
198 2     2 1 6 my $self = shift;
199 2 50       7 my $symbols = $self->symbols
200             or return ();
201 2         15 my @covered = sort grep $symbols->{$_}, keys %$symbols;
202 2         16 return @covered;
203             }
204              
205             sub report {
206 2     2 1 3 my $self = shift;
207 2         6 my $rating = $self->coverage;
208              
209 2 50       6 $rating = 'unrated (' . $self->why_unrated . ')'
210             unless defined $rating;
211              
212 2         5 my $message = sprintf "%s has a Pod coverage rating of %s\n", $self->package, $rating;
213              
214 2         6 my @uncovered = $self->uncovered;
215 2 50       5 if (@uncovered) {
216 2         6 $message .= "The following are uncovered:\n";
217             $message .= " $_\n"
218 2         9 for @uncovered;
219             }
220 2         105 return $message;
221             }
222              
223             sub print_report {
224 2     2 1 5 my $self = shift;
225 2         6 print $self->report;
226             }
227              
228             sub import {
229 9     9   84 my $class = shift;
230             return
231 9 50       15527 if !@_;
232              
233 0 0       0 $class->new(@_ == 1 ? (package => $_[0]) : @_)->print_report;
234 0         0 return;
235             }
236              
237              
238             sub _search_packages {
239 45     45   85 my $self = shift;
240 45         112 my @search = @_;
241 45 50       182 @search = ('main')
242             if !@search;
243              
244             s/\A(?:::)?(?:(?:main)?::)+//, s/(?:::)?\z/::/
245 45         430 for @search;
246              
247 45         94 my @packages;
248              
249 45         128 while (@search) {
250 11094         16779 my $search = shift @search;
251 11094         18035 push @packages, $search;
252 11094 100       18695 my $base = $search eq 'main::' ? '' : $search;
253              
254 11     11   110 no strict 'refs';
  11         25  
  11         11414  
255 11094   100     192360 my @add =
256             map $base.$_,
257             sort
258             grep /::$/ && $_ ne 'main::',
259             keys %$search;
260              
261 11094         34830 unshift @search, @add;
262             }
263              
264             s/::\z//
265 45         10896 for @packages;
266              
267 45   100     53612 return grep +(
268             $_ ne 'main'
269             && $_ ne ''
270             && $_ ne 'UNIVERSAL'
271             ), @packages;
272             }
273              
274             sub _get_roles {
275 41     41   83 my $self = shift;
276 41         100 my $package = $self->package;
277 41 50       658 my $does
    100          
278             = $package->can('does') ? 'does'
279             : $package->can('DOES') ? 'DOES'
280             : undef;
281 41 100 66     363 if (!$does || $package->can($does) == \&UNIVERSAL::DOES) {
282 37         176 return;
283             }
284 4   100     19 return grep $_ ne $package && $package->$does($_), $self->_search_packages;
285             }
286              
287             sub _get_parents {
288 41     41   7677 my $self = shift;
289 41         116 my $package = $self->package;
290 41   100     142 return grep $_ ne $package && $package->isa($_), $self->_search_packages;
291             }
292              
293             sub __pack_to_pm {
294 98     98   225 my ($package) = @_;
295 98 50       2629 croak "Invalid package '$package'"
296             unless $package =~ /\A$PACKAGE_RE\z/;
297 98         623 (my $mod = "$package.pm") =~ s{'|::}{/}g;
298 98         13421 return $mod;
299             }
300              
301             sub _pod_for {
302 56     56   107 my $self = shift;
303 56         117 my ($package) = @_;
304 56 50 66     171 if ($self->package eq $package && defined $self->{pod_from}) {
305 0         0 return $self->{pod_from};
306             }
307              
308 56         197 my $mod = __pack_to_pm($package);
309 56 50       223 my $full = $INC{$mod} or return;
310 56         296 (my $maybe_pod = $full) =~ s{\.pm\z}{.pod};
311 56 50       2725 my $pod
    100          
312             = -e $maybe_pod ? $maybe_pod
313             : -e $full ? $full
314             : undef
315             ;
316 56 100       279 if ($self->package eq $package) {
317 41         105 $self->{pod_from} = $pod;
318             }
319 56         224 return $pod;
320             }
321              
322             sub trusted_packages {
323 41     41 1 73 my $self = shift;
324              
325 41         78 my %seen;
326             my @trusted = sort grep !$seen{$_}++,
327             $self->package,
328 41         237 @{ $self->{trust_packages} },
329             ($self->{trust_roles} ? $self->_get_roles : ()),
330 41 50       110 ($self->{trust_parents} ? $self->_get_parents : ()),
    50          
331             ;
332              
333 41         426 return @trusted;
334             }
335              
336 56     56   414 sub _pod_parser_class { 'Pod::Coverage::TrustMe::Parser' }
337             sub _new_pod_parser {
338 56     56   152 my $self = shift;
339              
340 56         193 my $parser = $self->_pod_parser_class->new(@_);
341 56 100       196 if ($self->{require_content}) {
342 1         4 $parser->ignore_empty(1);
343             }
344 56         129 return $parser;
345             }
346             sub _pod_parser_for {
347 56     56   130 my $self = shift;
348 56         152 my ($pack) = @_;
349 56 50       173 my $pod = $self->_pod_for($pack)
350             or return undef;
351 56         178 my $parser = $self->_new_pod_parser;
352 56         271 $parser->parse_file($pod);
353 56         1389 return $parser;
354             }
355              
356             sub _parsed {
357 61     61   155 my $self = shift;
358             return $self->{_parsed}
359 61 100       182 if $self->{_parsed};
360              
361             my %parsed = map {
362 41         147 my $pack = $_;
  56         145  
363 56         199 my $parser = $self->_pod_parser_for($pack);
364 56 50       395 $parser ? ($pack => $parser) : ();
365             } $self->trusted_packages;
366              
367 41 100       190 if ($self->{require_link}) {
368 7         28 my $package = $self->package;
369 7         14 my %allowed;
370             my %find_links = (
371 7   33     35 $package => delete $parsed{$package} || $self->_pod_parser_for($package),
372             );
373              
374 7         23 while (%find_links) {
375 10         43 @allowed{keys %find_links} = values %find_links;
376             %find_links =
377             map +(exists $parsed{$_} ? ($_ => delete $parsed{$_}) : ()),
378 10 50       24 map @{ $_->links },
  10         32  
379             values %find_links;
380             }
381              
382 7         43 %parsed = %allowed;
383             }
384              
385 41         158 $self->{_parsed} = \%parsed;
386             }
387              
388             sub _symbols_for {
389 41     41   75 my $self = shift;
390 41         100 my ($package) = @_;
391              
392 41         71 my @symbols;
393 11     11   117 no strict 'refs';
  11         41  
  11         6941  
394              
395 41 100       113 if ($self->{export_only}) {
396             @symbols = (
397 1         7 @{"${package}::EXPORT"},
398 1         2 @{"${package}::EXPORT_OK"},
  1         4  
399             );
400             }
401             else {
402             @symbols =
403             grep !(
404             $self->{ignore_imported} && $self->_imported_check($_)
405             or $self->_private_check($_)
406             ),
407             grep !/::\z/ && defined &{$package.'::'.$_},
408 40   100     84 keys %{$package.'::'};
  40   100     411  
409             }
410              
411 41         227 return @symbols;
412             }
413              
414             sub _get_syms {
415 41     41   89 my $self = shift;
416 41   33     234 my $syms = $self->{_syms} ||= do {
417             # recurse option?
418 41         140 [ $self->_symbols_for($self->package) ];
419             };
420 41         334 return @$syms;
421             }
422              
423             sub _get_pods {
424 41     41   108 my $self = shift;
425              
426 41   33     142 $self->{_pods} ||= do {
427 41         116 my $parsed = $self->_parsed;
428              
429 41         167 my %covered = map +( $_ => 1 ), map @{ $_->covered }, values %$parsed;
  54         208  
430              
431 41         682 [ sort keys %covered ];
432             };
433             }
434              
435             sub _trusted_from_pod {
436 38     38   64 my $self = shift;
437              
438 38   66     244 $self->{_trusted_from_pod} ||= do {
439 20         57 my $parsed = $self->_parsed;
440              
441 20         63 [ map @{ $_->trusted }, values %$parsed ];
  23         86  
442             };
443             }
444              
445             sub _private_check {
446 205     205   382 my $self = shift;
447 205         340 my ($sym) = @_;
448 205         317 return scalar grep $sym =~ /$_/, @{ $self->{private} };
  205         6167  
449             }
450              
451             # providing _trustme_check and make overriding it work for compatibility with
452             # Pod::Coverage
453             sub _trust_method_check {
454 38     38   68 my $self = shift;
455 38         120 $self->_trustme_check(@_);
456             }
457              
458             sub _trustme_check {
459 38     38   73 my $self = shift;
460 38         79 my ($sym) = @_;
461              
462             return scalar grep $sym =~ /$_/,
463 38         111 @{ $self->{trust_methods} },
464 38 50       70 ($self->{trust_pod} ? @{ $self->_trusted_from_pod } : ());
  38         88  
465             }
466              
467             sub _imported_check {
468 202     202   400 my $self = shift;
469 202         365 my ($sym) = @_;
470 202         316 my $package = $self->{package};
471 11     11   96 no strict 'refs';
  11         41  
  11         1022  
472 202         285 return !!(B::svref_2object(\*{$package.'::'.$sym})->GvFLAGS & _GVf_IMPORTED_CV);
  202         1691  
473             }
474              
475             1;
476             __END__