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 24     24   3071651 use strict;
  24         63  
  24         897  
3 24     24   98 use warnings;
  24         45  
  24         2313  
4              
5             our $VERSION = '0.002002';
6             $VERSION =~ tr/_//d;
7              
8 24     24   10977 use Pod::Coverage::TrustMe::Parser;
  24         94  
  24         852  
9 24     24   149 use B ();
  24         36  
  24         423  
10 24     24   83 use Carp qw(croak);
  24         35  
  24         1615  
11 24 50   24   116 use constant _GVf_IMPORTED_CV => defined &B::GVf_IMPORTED_CV ? B::GVf_IMPORTED_CV() : 0x80;
  24         34  
  24         6723  
12              
13 24         39 use constant DEFAULT_PRIVATE => do {
14 24         44 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 24         37334 (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 24     24   148 };
  24         42  
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 67     67 0 1683835 my ($class, %args) = @_;
106 67 50       295 $class = ref $class
107             if ref $class;
108              
109 67         386 for my $alias (sort keys %ALIASES) {
110 134 100       368 if (exists $args{$alias}) {
111 4         37 my $to = $ALIASES{$alias};
112 4 50       14 if (exists $args{$to}) {
113 0         0 croak "$alias is an alias to $to, they can't both be specified!";
114             }
115 4         12 $args{$to} = delete $args{$alias};
116             }
117             }
118              
119             my $new = {
120 67 100       1290 map +($_ => exists $args{$_} ? $args{$_} : $DEFAULTS{$_}), keys %DEFAULTS,
121             };
122              
123 67 100 100     497 if (exists $args{private} || exists $args{also_private}) {
124             $new->{private} = [
125             map +(ref $_ ? $_ : qr/\A\Q$_\E\z/), (
126 8         49 @{ $new->{private} },
127 8 100       12 exists $args{also_private} ? @{ $args{also_private} } : (),
  5 100       120  
128             )
129             ];
130             }
131              
132             my $package = $new->{package}
133 67 50       221 or die "package is a required parameter";
134              
135 67 100       112 eval { require(__pack_to_pm($package)); 1 } or do {
  67         184  
  66         38773  
136 1         313 $new->{why_unrated} = "requiring '$package' failed: $@";
137 1         3 $new->{broken} = 1;
138             };
139              
140 67         882 bless $new, $class;
141             }
142              
143             sub package {
144 508     508 1 738 my $self = shift;
145 508         1609 $self->{package};
146             }
147              
148             sub symbols {
149 84     84 1 163 my $self = shift;
150             return undef
151 84 100       383 if $self->{broken};
152 83   66     299 $self->{symbols} ||= do {
153 66         153 my $package = $self->package;
154              
155 66         123 my %pods = map +( $_ => 1 ), @{ $self->_get_pods($package) };
  66         186  
156             my %symbols = map +(
157 66   100     314 $_ => ($pods{$_} || $self->_trust_method_check($_) || 0),
158             ), $self->_get_syms($package);
159              
160 66 100       314 if (!grep $_, values %symbols) {
161 8   50     35 $self->{why_unrated} ||= "no public symbols defined";
162             }
163              
164 66         429 \%symbols;
165             };
166             }
167              
168             sub coverage {
169 70     70 1 1841 my $self = shift;
170 70 100       218 my $symbols = $self->symbols
171             or return undef;
172              
173 69 100       196 my $total = scalar keys %$symbols
174             or return undef;
175 65         217 my $documented = scalar grep $_, values %$symbols;
176              
177 65         454 return $documented / $total;
178             }
179              
180             sub why_unrated {
181 5     5 1 16 my $self = shift;
182 5         20 return $self->{why_unrated};
183             }
184              
185             sub uncovered {
186 11     11 1 19 my $self = shift;
187 11 50       28 my $symbols = $self->symbols
188             or return ();
189 11         68 my @uncovered = sort grep !$symbols->{$_}, keys %$symbols;
190 11         76 return @uncovered;
191             }
192             sub naked {
193 6     6 1 2486 my $self = shift;
194 6         16 return $self->uncovered(@_);
195             }
196              
197             sub covered {
198 2     2 1 5 my $self = shift;
199 2 50       7 my $symbols = $self->symbols
200             or return ();
201 2         12 my @covered = sort grep $symbols->{$_}, keys %$symbols;
202 2         9 return @covered;
203             }
204              
205             sub report {
206 2     2 1 3 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         7 my @uncovered = $self->uncovered;
215 2 50       5 if (@uncovered) {
216 2         4 $message .= "The following are uncovered:\n";
217             $message .= " $_\n"
218 2         9 for @uncovered;
219             }
220 2         179 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   95 my $class = shift;
230             return
231 9 50       11281 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 70     70   105 my $self = shift;
240 70         176 my @search = @_;
241 70 50       239 @search = ('main')
242             if !@search;
243              
244             s/\A(?:::)?(?:(?:main)?::)+//, s/(?:::)?\z/::/
245 70         601 for @search;
246              
247 70         107 my @packages;
248              
249 70         154 while (@search) {
250 20465         23968 my $search = shift @search;
251 20465         27430 push @packages, $search;
252 20465 100       27864 my $base = $search eq 'main::' ? '' : $search;
253              
254 24     24   200 no strict 'refs';
  24         52  
  24         23918  
255 20465   100     353740 my @add =
256             map $base.$_,
257             sort
258             grep /::$/ && $_ ne 'main::',
259             keys %$search;
260              
261 20465         57067 unshift @search, @add;
262             }
263              
264             s/::\z//
265 70         18294 for @packages;
266              
267 70   100     86906 return grep +(
268             $_ ne 'main'
269             && $_ ne ''
270             && $_ ne 'UNIVERSAL'
271             ), @packages;
272             }
273              
274             sub _get_roles {
275 66     66   144 my $self = shift;
276 66         194 my $package = $self->package;
277 66 50       948 my $does
    100          
278             = $package->can('does') ? 'does'
279             : $package->can('DOES') ? 'DOES'
280             : undef;
281 66 100 66     488 if (!$does || $package->can($does) == \&UNIVERSAL::DOES) {
282 62         277 return;
283             }
284 4   100     15 return grep $_ ne $package && $package->$does($_), $self->_search_packages;
285             }
286              
287             sub _get_parents {
288 66     66   5698 my $self = shift;
289 66         160 my $package = $self->package;
290 66   100     188 return grep $_ ne $package && $package->isa($_), $self->_search_packages;
291             }
292              
293             sub __pack_to_pm {
294 151     151   338 my ($package) = @_;
295 151 50       6346 croak "Invalid package '$package'"
296             unless $package =~ /\A$PACKAGE_RE\z/;
297 151         918 (my $mod = "$package.pm") =~ s{'|::}{/}g;
298 151         26673 return $mod;
299             }
300              
301             sub _pod_for {
302 84     84   134 my $self = shift;
303 84         209 my ($package) = @_;
304 84 50 66     282 if ($self->package eq $package && defined $self->{pod_from}) {
305 0         0 return $self->{pod_from};
306             }
307              
308 84         246 my $mod = __pack_to_pm($package);
309 84 50       368 my $full = $INC{$mod} or return;
310 84         375 (my $maybe_pod = $full) =~ s{\.pm\z}{.pod};
311 84 50       4790 my $pod
    100          
312             = -e $maybe_pod ? $maybe_pod
313             : -e $full ? $full
314             : undef
315             ;
316 84 100       309 if ($self->package eq $package) {
317 66         146 $self->{pod_from} = $pod;
318             }
319 84         439 return $pod;
320             }
321              
322             sub trusted_packages {
323 66     66 1 101 my $self = shift;
324              
325 66         141 my %seen;
326             my @trusted = sort grep !$seen{$_}++,
327             $self->package,
328 66         357 @{ $self->{trust_packages} },
329             ($self->{trust_roles} ? $self->_get_roles : ()),
330 66 50       149 ($self->{trust_parents} ? $self->_get_parents : ()),
    50          
331             ;
332              
333 66         833 return @trusted;
334             }
335              
336 84     84   672 sub _pod_parser_class { 'Pod::Coverage::TrustMe::Parser' }
337             sub _new_pod_parser {
338 84     84   177 my $self = shift;
339              
340 84         336 my $parser = $self->_pod_parser_class->new(@_);
341 84 100       282 if ($self->{require_content}) {
342 1         4 $parser->ignore_empty(1);
343             }
344 84         146 return $parser;
345             }
346             sub _pod_parser_for {
347 84     84   142 my $self = shift;
348 84         185 my ($pack) = @_;
349 84 50       327 my $pod = $self->_pod_for($pack)
350             or return undef;
351 84         378 my $parser = $self->_new_pod_parser;
352 84         325 $parser->parse_file($pod);
353 84         3066 return $parser;
354             }
355              
356             sub _parsed {
357 91     91   142 my $self = shift;
358             return $self->{_parsed}
359 91 100       317 if $self->{_parsed};
360              
361             my %parsed = map {
362 66         211 my $pack = $_;
  84         175  
363 84         298 my $parser = $self->_pod_parser_for($pack);
364 84 50       499 $parser ? ($pack => $parser) : ();
365             } $self->trusted_packages;
366              
367 66 100       272 if ($self->{require_link}) {
368 8         27 my $package = $self->package;
369 8         14 my %allowed;
370             my %find_links = (
371 8   33     34 $package => delete $parsed{$package} || $self->_pod_parser_for($package),
372             );
373              
374 8         23 while (%find_links) {
375 11         31 @allowed{keys %find_links} = values %find_links;
376             %find_links =
377             map +(exists $parsed{$_} ? ($_ => delete $parsed{$_}) : ()),
378 11 50       24 map @{ $_->links },
  11         31  
379             values %find_links;
380             }
381              
382 8         43 %parsed = %allowed;
383             }
384              
385 66         228 $self->{_parsed} = \%parsed;
386             }
387              
388             sub _symbols_for {
389 66     66   106 my $self = shift;
390 66         142 my ($package) = @_;
391              
392 66         101 my @symbols;
393 24     24   273 no strict 'refs';
  24         114  
  24         13708  
394              
395 66 100       173 if ($self->{export_only}) {
396             @symbols = (
397 1         5 @{"${package}::EXPORT"},
398 1         1 @{"${package}::EXPORT_OK"},
  1         5  
399             );
400             }
401             else {
402             @symbols =
403             grep !(
404             $self->{ignore_imported} && $self->_imported_check($_)
405             or $self->_private_check($_)
406             ),
407             map {
408 329         422 my $sym = $_;
409 329 100       909 utf8::decode($sym)
410             if !utf8::is_utf8($_);
411 329         750 $sym;
412             }
413             grep !/::\z/ && defined &{$package.'::'.$_},
414 65   100     115 keys %{$package.'::'};
  65   100     607  
415             }
416              
417 66         431 return @symbols;
418             }
419              
420             sub _get_syms {
421 66     66   106 my $self = shift;
422 66   33     254 my $syms = $self->{_syms} ||= do {
423             # recurse option?
424 66         200 [ $self->_symbols_for($self->package) ];
425             };
426 66         494 return @$syms;
427             }
428              
429             sub _get_pods {
430 66     66   113 my $self = shift;
431              
432 66   33     201 $self->{_pods} ||= do {
433 66         197 my $parsed = $self->_parsed;
434              
435 66         211 my %covered = map +( $_ => 1 ), map @{ $_->covered }, values %$parsed;
  82         279  
436              
437 66         945 [ sort keys %covered ];
438             };
439             }
440              
441             sub _trusted_from_pod {
442 49     49   60 my $self = shift;
443              
444 49   66     246 $self->{_trusted_from_pod} ||= do {
445 25         63 my $parsed = $self->_parsed;
446              
447 25         58 [ map @{ $_->trusted }, values %$parsed ];
  28         117  
448             };
449             }
450              
451             sub _private_check {
452 324     324   509 my $self = shift;
453 324         478 my ($sym) = @_;
454 324         360 return scalar grep $sym =~ /$_/, @{ $self->{private} };
  324         12018  
455             }
456              
457             # providing _trustme_check and make overriding it work for compatibility with
458             # Pod::Coverage
459             sub _trust_method_check {
460 49     49   67 my $self = shift;
461 49         106 $self->_trustme_check(@_);
462             }
463              
464             sub _trustme_check {
465 49     49   62 my $self = shift;
466 49         78 my ($sym) = @_;
467              
468             return scalar grep $sym =~ /$_/,
469 49         107 @{ $self->{trust_methods} },
470 49 50       59 ($self->{trust_pod} ? @{ $self->_trusted_from_pod } : ());
  49         134  
471             }
472              
473             sub _imported_check {
474 323     323   480 my $self = shift;
475 323         479 my ($sym) = @_;
476 323         453 my $package = $self->{package};
477 24     24   178 no strict 'refs';
  24         39  
  24         2421  
478 323         362 return !!(B::svref_2object(\*{$package.'::'.$sym})->GvFLAGS & _GVf_IMPORTED_CV);
  323         2777  
479             }
480              
481             1;
482             __END__