File Coverage

blib/lib/Pod/Coverage/TrustMe.pm
Criterion Covered Total %
statement 201 206 97.5
branch 58 82 70.7
condition 29 37 78.3
subroutine 36 36 100.0
pod 10 11 90.9
total 334 372 89.7


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