File Coverage

blib/lib/Pod/Coverage/TrustMe.pm
Criterion Covered Total %
statement 206 210 98.1
branch 61 84 72.6
condition 35 46 76.0
subroutine 39 39 100.0
pod 10 11 90.9
total 351 390 90.0


line stmt bran cond sub pod time code
1             package Pod::Coverage::TrustMe;
2 25     25   4009897 use strict;
  25         50  
  25         977  
3 25     25   133 use warnings;
  25         50  
  25         2789  
4              
5             our $VERSION = '0.002001';
6             $VERSION =~ tr/_//d;
7              
8 25     25   12737 use Pod::Coverage::TrustMe::Parser;
  25         114  
  25         1058  
9 25     25   295 use B ();
  25         60  
  25         623  
10 25     25   125 use Carp qw(croak);
  25         51  
  25         2021  
11 25 50   25   150 use constant _GVf_IMPORTED_CV => defined &B::GVf_IMPORTED_CV ? B::GVf_IMPORTED_CV() : 0x80;
  25         69  
  25         8540  
12              
13 25         51 use constant DEFAULT_PRIVATE => do {
14 25         52 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 25         50732 (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 25     25   207 };
  25         55  
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 70     70 0 1951428 my ($class, %args) = @_;
106 70 50       358 $class = ref $class
107             if ref $class;
108              
109 70         433 for my $alias (sort keys %ALIASES) {
110 140 100       506 if (exists $args{$alias}) {
111 4         13 my $to = $ALIASES{$alias};
112 4 50       15 if (exists $args{$to}) {
113 0         0 croak "$alias is an alias to $to, they can't both be specified!";
114             }
115 4         18 $args{$to} = delete $args{$alias};
116             }
117             }
118              
119             my $new = {
120 70 100       1637 map +($_ => exists $args{$_} ? $args{$_} : $DEFAULTS{$_}), keys %DEFAULTS,
121             };
122              
123 70 100 100     751 if (exists $args{private} || exists $args{also_private}) {
124             $new->{private} = [
125             map +(ref $_ ? $_ : qr/\A\Q$_\E\z/), (
126 8         73 @{ $new->{private} },
127 8 100       20 exists $args{also_private} ? @{ $args{also_private} } : (),
  5 100       214  
128             )
129             ];
130             }
131              
132             my $package = $new->{package}
133 70 50       307 or die "package is a required parameter";
134              
135 70 100       159 eval { require(__pack_to_pm($package)); 1 } or do {
  70         295  
  69         56144  
136 1         466 $new->{why_unrated} = "requiring '$package' failed: $@";
137 1         5 $new->{broken} = 1;
138             };
139              
140 70         1198 bless $new, $class;
141             }
142              
143             sub package {
144 533     533 1 1014 my $self = shift;
145 533         2129 $self->{package};
146             }
147              
148             sub symbols {
149 87     87 1 182 my $self = shift;
150             return undef
151 87 100       500 if $self->{broken};
152 86   66     396 $self->{symbols} ||= do {
153 69         283 my $package = $self->package;
154              
155 69         143 my %pods = map +( $_ => 1 ), @{ $self->_get_pods($package) };
  69         303  
156             my %symbols = map +(
157 69   100     459 $_ => ($pods{$_} || $self->_trust_method_check($_) || 0),
158             ), $self->_get_syms($package);
159              
160 69 100       415 if (!grep $_, values %symbols) {
161 8   50     43 $self->{why_unrated} ||= "no public symbols defined";
162             }
163              
164 69         653 \%symbols;
165             };
166             }
167              
168             sub coverage {
169 73     73 1 3938 my $self = shift;
170 73 100       382 my $symbols = $self->symbols
171             or return undef;
172              
173 72 100       278 my $total = scalar keys %$symbols
174             or return undef;
175 68         252 my $documented = scalar grep $_, values %$symbols;
176              
177 68         531 return $documented / $total;
178             }
179              
180             sub why_unrated {
181 5     5 1 20 my $self = shift;
182 5         22 return $self->{why_unrated};
183             }
184              
185             sub uncovered {
186 11     11 1 21 my $self = shift;
187 11 50       35 my $symbols = $self->symbols
188             or return ();
189 11         79 my @uncovered = sort grep !$symbols->{$_}, keys %$symbols;
190 11         66 return @uncovered;
191             }
192             sub naked {
193 6     6 1 4177 my $self = shift;
194 6         29 return $self->uncovered(@_);
195             }
196              
197             sub covered {
198 2     2 1 7 my $self = shift;
199 2 50       8 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 4 my $self = shift;
207 2         5 my $rating = $self->coverage;
208              
209 2 50       5 $rating = 'unrated (' . $self->why_unrated . ')'
210             unless defined $rating;
211              
212 2         3 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       4 if (@uncovered) {
216 2         3 $message .= "The following are uncovered:\n";
217             $message .= " $_\n"
218 2         7 for @uncovered;
219             }
220 2         145 return $message;
221             }
222              
223             sub print_report {
224 2     2 1 2 my $self = shift;
225 2         6 print $self->report;
226             }
227              
228             sub import {
229 9     9   94 my $class = shift;
230             return
231 9 50       13266 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 73     73   165 my $self = shift;
240 73         196 my @search = @_;
241 73 50       313 @search = ('main')
242             if !@search;
243              
244             s/\A(?:::)?(?:(?:main)?::)+//, s/(?:::)?\z/::/
245 73         832 for @search;
246              
247 73         211 my @packages;
248              
249 73         214 while (@search) {
250 21329         35530 my $search = shift @search;
251 21329         40703 push @packages, $search;
252 21329 100       41822 my $base = $search eq 'main::' ? '' : $search;
253              
254 25     25   231 no strict 'refs';
  25         70  
  25         30996  
255 21329   100     568778 my @add =
256             map $base.$_,
257             sort
258             grep /::$/ && $_ ne 'main::',
259             keys %$search;
260              
261 21329         107432 unshift @search, @add;
262             }
263              
264             s/::\z//
265 73         28814 for @packages;
266              
267 73   100     138355 return grep +(
268             $_ ne 'main'
269             && $_ ne ''
270             && $_ ne 'UNIVERSAL'
271             ), @packages;
272             }
273              
274             sub _get_roles {
275 69     69   136 my $self = shift;
276 69         266 my $package = $self->package;
277 69 50       1191 my $does
    100          
278             = $package->can('does') ? 'does'
279             : $package->can('DOES') ? 'DOES'
280             : undef;
281 69 100 66     648 if (!$does || $package->can($does) == \&UNIVERSAL::DOES) {
282 65         366 return;
283             }
284 4   100     18 return grep $_ ne $package && $package->$does($_), $self->_search_packages;
285             }
286              
287             sub _get_parents {
288 69     69   10570 my $self = shift;
289 69         194 my $package = $self->package;
290 69   100     230 return grep $_ ne $package && $package->isa($_), $self->_search_packages;
291             }
292              
293             sub __pack_to_pm {
294 159     159   423 my ($package) = @_;
295 159 50       8281 croak "Invalid package '$package'"
296             unless $package =~ /\A$PACKAGE_RE\z/;
297 159         1120 (my $mod = "$package.pm") =~ s{'|::}{/}g;
298 159         23294 return $mod;
299             }
300              
301             sub _pod_for {
302 89     89   181 my $self = shift;
303 89         269 my ($package) = @_;
304 89 50 66     381 if ($self->package eq $package && defined $self->{pod_from}) {
305 0         0 return $self->{pod_from};
306             }
307              
308 89         353 my $mod = __pack_to_pm($package);
309 89 50       529 my $full = $INC{$mod} or return;
310 89         567 (my $maybe_pod = $full) =~ s{\.pm\z}{.pod};
311 89 50       5518 my $pod
    100          
312             = -e $maybe_pod ? $maybe_pod
313             : -e $full ? $full
314             : undef
315             ;
316 89 100       398 if ($self->package eq $package) {
317 69         197 $self->{pod_from} = $pod;
318             }
319 89         568 return $pod;
320             }
321              
322             sub trusted_packages {
323 69     69 1 143 my $self = shift;
324              
325 69         168 my %seen;
326             my @trusted = sort grep !$seen{$_}++,
327             $self->package,
328 69         437 @{ $self->{trust_packages} },
329             ($self->{trust_roles} ? $self->_get_roles : ()),
330 69 50       212 ($self->{trust_parents} ? $self->_get_parents : ()),
    50          
331             ;
332              
333 69         912 return @trusted;
334             }
335              
336 89     89   1007 sub _pod_parser_class { 'Pod::Coverage::TrustMe::Parser' }
337             sub _new_pod_parser {
338 89     89   236 my $self = shift;
339              
340 89         496 my $parser = $self->_pod_parser_class->new(@_);
341 89 100       367 if ($self->{require_content}) {
342 1         4 $parser->ignore_empty(1);
343             }
344 89         260 return $parser;
345             }
346             sub _pod_parser_for {
347 89     89   211 my $self = shift;
348 89         267 my ($pack) = @_;
349 89 50       404 my $pod = $self->_pod_for($pack)
350             or return undef;
351 89         360 my $parser = $self->_new_pod_parser;
352 89         485 $parser->parse_file($pod);
353 89         4262 return $parser;
354             }
355              
356             sub _parsed {
357 95     95   364 my $self = shift;
358             return $self->{_parsed}
359 95 100       456 if $self->{_parsed};
360              
361             my %parsed = map {
362 69         297 my $pack = $_;
  89         280  
363 89         397 my $parser = $self->_pod_parser_for($pack);
364 89 50       676 $parser ? ($pack => $parser) : ();
365             } $self->trusted_packages;
366              
367 69 100       362 if ($self->{require_link}) {
368 8         45 my $package = $self->package;
369 8         18 my %allowed;
370             my %find_links = (
371 8   33     49 $package => delete $parsed{$package} || $self->_pod_parser_for($package),
372             );
373              
374 8         34 while (%find_links) {
375 11         48 @allowed{keys %find_links} = values %find_links;
376             %find_links =
377             map +(exists $parsed{$_} ? ($_ => delete $parsed{$_}) : ()),
378 11 50       34 map @{ $_->links },
  11         54  
379             values %find_links;
380             }
381              
382 8         71 %parsed = %allowed;
383             }
384              
385 69         313 $self->{_parsed} = \%parsed;
386             }
387              
388             sub _symbols_for {
389 69     69   151 my $self = shift;
390 69         236 my ($package) = @_;
391              
392 69         161 my @symbols;
393 25     25   400 no strict 'refs';
  25         92  
  25         21657  
394              
395 69 100       230 if ($self->{export_only}) {
396             @symbols = (
397 1         6 @{"${package}::EXPORT"},
398 1         2 @{"${package}::EXPORT_OK"},
  1         7  
399             );
400             }
401             else {
402             @symbols =
403             grep !(
404             $self->{ignore_imported} && $self->_imported_check($_)
405             or $self->_private_check($_)
406             ),
407             map {
408 378         662 my $sym = $_;
409 378 100       1401 utf8::decode($sym)
410             if !utf8::is_utf8($_);
411 378         1210 $sym;
412             }
413             grep !/::\z/ && defined &{$package.'::'.$_},
414 68   100     158 keys %{$package.'::'};
  68   100     876  
415             }
416              
417 69         704 return @symbols;
418             }
419              
420             sub _get_syms {
421 69     69   196 my $self = shift;
422 69   33     348 my $syms = $self->{_syms} ||= do {
423             # recurse option?
424 69         277 [ $self->_symbols_for($self->package) ];
425             };
426 69         827 return @$syms;
427             }
428              
429             sub _get_pods {
430 69     69   142 my $self = shift;
431              
432 69   33     288 $self->{_pods} ||= do {
433 69         229 my $parsed = $self->_parsed;
434              
435 69         275 my %covered = map +( $_ => 1 ), map @{ $_->covered }, values %$parsed;
  87         386  
436              
437 69         1513 [ sort keys %covered ];
438             };
439             }
440              
441             sub _trusted_from_pod {
442 50     50   80 my $self = shift;
443              
444 50   66     361 $self->{_trusted_from_pod} ||= do {
445 26         89 my $parsed = $self->_parsed;
446              
447 26         81 [ map @{ $_->trusted }, values %$parsed ];
  29         147  
448             };
449             }
450              
451             sub _private_check {
452 371     371   672 my $self = shift;
453 371         712 my ($sym) = @_;
454 371         631 return scalar grep $sym =~ /$_/, @{ $self->{private} };
  371         20578  
455             }
456              
457             # providing _trustme_check and make overriding it work for compatibility with
458             # Pod::Coverage
459             sub _trust_method_check {
460 50     50   103 my $self = shift;
461 50         148 $self->_trustme_check(@_);
462             }
463              
464             sub _trustme_check {
465 50     50   85 my $self = shift;
466 50         113 my ($sym) = @_;
467              
468             return scalar grep $sym =~ /$_/,
469 50         164 @{ $self->{trust_methods} },
470 50 50       85 ($self->{trust_pod} ? @{ $self->_trusted_from_pod } : ());
  50         157  
471             }
472              
473             sub _imported_check {
474 372     372   762 my $self = shift;
475 372         774 my ($sym) = @_;
476 372         750 my $package = $self->{package};
477 25     25   232 no strict 'refs';
  25         49  
  25         3192  
478 372         580 return !!(B::svref_2object(\*{$package.'::'.$sym})->GvFLAGS & _GVf_IMPORTED_CV);
  372         4359  
479             }
480              
481             1;
482             __END__