File Coverage

blib/lib/Test/Pod/LinkCheck/Lite.pm
Criterion Covered Total %
statement 461 500 92.2
branch 146 218 66.9
condition 28 54 51.8
subroutine 101 104 97.1
pod 15 15 100.0
total 751 891 84.2


line stmt bran cond sub pod time code
1             package Test::Pod::LinkCheck::Lite;
2              
3 6     2   130956 use 5.008;
  6         31  
4              
5 6     2   30 use strict; # Core since 5.0
  6         43  
  6         59  
6 2     2   8 use warnings; # Core since 5.6.0
  2         3  
  2         51  
7              
8 2     2   1210 use utf8; # Core since 5.6.0
  2         25  
  2         8  
9              
10 2     2   919 use B::Keywords (); # Not core
  2         3104  
  2         43  
11 2     2   11 use Carp (); # Core since 5.0
  2         4  
  2         22  
12 2     2   7 use Exporter (); # Core since 5.0
  2         3  
  2         36  
13 2     2   9 use File::Find (); # Core since 5.0
  2         4  
  2         36  
14 2     2   12 use File::Spec; # Core since 5.4.5
  2         4  
  2         38  
15 2     2   978 use HTTP::Tiny; # Core since 5.13.9
  3         42898  
  3         64  
16 3     2   703 use IPC::Cmd (); # Core since 5.9.5
  2         39653  
  2         46  
17 2     2   14 use Module::Load::Conditional (); # Core since 5.9.5
  2         4  
  2         26  
18 2     2   1306 use Pod::Perldoc (); # Core since 5.8.1
  2         50264  
  2         52  
19 2     2   1149 use Pod::Simple (); # Core since 5.9.3
  2         52358  
  2         77  
20 2     2   20 use Pod::Simple::LinkSection; # Core since 5.9.3 (part of Pod::Simple)
  2         4  
  2         49  
21 2     2   16 use Scalar::Util (); # Core since 5.7.3
  2         25  
  2         24  
22 2     2   789 use Storable (); # Core since 5.7.3
  2         3026  
  2         45  
23 2     2   10 use Test::Builder (); # Core since 5.6.2
  2         3  
  2         373  
24              
25             our $VERSION = '0.010';
26              
27             our @ISA = qw{ Exporter };
28              
29             our @EXPORT_OK = qw{
30             ALLOW_REDIRECT_TO_INDEX
31             MAYBE_IGNORE_GITHUB
32             };
33              
34             our %EXPORT_TAGS = (
35             const => [ grep { m/ \A [[:upper:]_]+ \z /smx } @EXPORT_OK ],
36             );
37              
38 2     2   14 use constant ON_DARWIN => 'darwin' eq $^O;
  2         3  
  2         152  
39 2     2   9 use constant ON_VMS => 'VMS' eq $^O;
  2         3  
  2         186  
40              
41             our $DIRECTORY_LEADER; # FOR TESTING ONLY -- may be retracted without notice
42             defined $DIRECTORY_LEADER
43             or $DIRECTORY_LEADER = ON_VMS ? '_' : '.';
44              
45             my $DOT_CPAN = "${DIRECTORY_LEADER}cpan";
46              
47 2     2   10 use constant ARRAY_REF => ref [];
  2         4  
  2         116  
48 2     2   11 use constant CODE_REF => ref sub {};
  2         4  
  2         99  
49 2     2   10 use constant HASH_REF => ref {};
  2         30  
  2         113  
50 2     2   11 use constant NON_REF => ref 0;
  2         4  
  2         132  
51 2     2   12 use constant REGEXP_REF => ref qrsmx;
  2         4  
  2         95  
52 2     2   9 use constant SCALAR_REF => ref \0;
  2         5  
  2         124  
53              
54             # Pod::Simple versions earlier than this were too restrictive in
55             # recognizing 'man' links, so some valid ones ended up classified as
56             # 'pod'. We conditionalize fix-up code on this constant so that, if the
57             # fix-up is not needed, the optimizer ditches it.
58 2     2   12 use constant NEED_MAN_FIX => Pod::Simple->VERSION lt '3.24';
  2         10  
  2         444  
59              
60             use constant ALLOW_REDIRECT_TO_INDEX => sub {
61 0         0 my ( undef, $resp, $url ) = @_;
62             # Does not apply to non-hierarchical URLs. This list is derived from
63             # the URI distribution, and represents those classes that do not
64             # inherit from URI::_generic.
65             $url =~ m/ \A (?: data | mailto | urn ) : /smxi
66 0 50       0 and return $resp->{url} ne $url;
67             $url =~ m| / \z |smx
68 0 50       0 or return $resp->{url} ne $url;
69 0         0 ( my $resp_url = $resp->{url} ) =~ s| (?<= / ) [^/]* \z ||smx;
70 0         0 return $resp_url ne $url;
71 2     2   12 };
  2         4  
  2         357  
72              
73             use constant MAYBE_IGNORE_GITHUB => sub {
74 0 0       0 m< \A https://github\.com \b >smx
75             or return;
76 0   0     0 my $git_dir = $ENV{GIT_DIR} || '.git';
77 0 0       0 -d $git_dir
78             or return 1;
79 0 50       0 open my $fh, '-|', qw{ git remote --verbose } ## no critic (RequireBriefOpen)
80             or return 1;
81 0         0 local $_ = undef; # while (<>) ... does not localize $_.
82 0         0 while ( <$fh> ) {
83 0 50       0 m< \b https://github\.com \b >smx
84             and return;
85             }
86 0         0 return 1;
87 2     2   14 };
  2         2  
  2         11658  
88              
89              
90             # NOTE that Test::Builder->new() gets us a singleton. For this reason I
91             # use $Test::Builder::Level (localized) to get tests reported relative
92             # to the correct file and line, rather than setting the 'level'
93             # attribute.
94             my $TEST = Test::Builder->new();
95              
96             sub new {
97 25     25 1 9160 my ( $class, %arg ) = @_;
98 25   33     113 my $self = bless {}, ref $class || $class;
99 25         79 return _init( $self, %arg );
100             }
101              
102             {
103             my %dflt;
104             local $_ = undef;
105             foreach ( keys %Test::Pod::LinkCheck::Lite:: ) {
106             m/ \A _default_ ( .+ ) /smx
107             and my $code = __PACKAGE__->can( $_ )
108             or next;
109             $dflt{$1} = $code;
110             }
111              
112             sub _init {
113 25     25   49 my ( $self, %arg ) = @_;
114 25         94 foreach my $key ( keys %dflt ) {
115             exists $arg{$key}
116 275 100       834 or $arg{$key} = $dflt{$key}->();
117             }
118 25         101 foreach my $name ( keys %arg ) {
119 275 50       813 if ( my $code = $self->can( "_init_$name" ) ) {
    0          
120 275         533 $code->( $self, $name, $arg{$name} );
121             } elsif ( defined $arg{$name} ) {
122 0         0 Carp::croak( "Unknown argument $name" );
123             }
124             }
125 25         115 return $self;
126             }
127             }
128              
129             sub _default_agent {
130 25     25   101 return HTTP::Tiny->new()->agent();
131             }
132              
133             sub _default_allow_man_spaces {
134 25     25   58 return 0;
135             }
136              
137             sub _default_check_external_sections {
138 24     24   46 return 1;
139             }
140              
141             sub _default_cache_url_response {
142 25     25   60 return 1;
143             }
144              
145             sub _default_check_url {
146 23     23   51 return 1;
147             }
148              
149             sub _default_ignore_url {
150 17     17   37 return [];
151             }
152              
153             {
154             my $checked;
155             my $rslt;
156              
157             sub _default_man {
158 24 100   24   44 unless ( $checked ) {
159 2         3 $checked = 1;
160             # I had hoped that just feeling around for an executable
161             # 'man' would be adequate, but ReactOS (which identifies
162             # itself as MSWin32) has a MAN.EXE that will not work. If
163             # the user has customized the system he or she can always
164             # specify man => 1. The hash is in case I find other OSes
165             # that have this problem. OpenVMS might end up here, but I
166             # have no access to it to see.
167 2 50       23 if ( {
168             DOS => 1,
169             MSWin32 => 1,
170             }->{$^O}
171             ) {
172 0         0 $rslt = 0;
173 0         0 $TEST->diag( "Can not check man pages by default under $^O" );
174             } else {
175 2 50       10 $rslt = IPC::Cmd::can_run( 'man' )
176             or $TEST->diag(
177             q );
178             }
179             }
180 24         168243 return $rslt;
181             }
182             }
183              
184             sub _default_module_index {
185 23     23   37 my @handlers;
186 23         400 foreach ( keys %Test::Pod::LinkCheck::Lite:: ) {
187 1926 100 66     3329 m/ \A _get_module_index_ ( .+ ) /smx
188             and __PACKAGE__->can( $_ )
189             or next;
190 46         125 push @handlers, $1;
191             }
192 23         154 @handlers = sort @handlers;
193 23         67 return \@handlers;
194             }
195              
196             sub _default_prohibit_redirect {
197 21     21   40 return 0;
198             }
199              
200             sub _default_require_installed {
201 24     24   50 return 0;
202             }
203              
204             sub _default_skip_server_errors {
205 23     23   46 return 1;
206             }
207              
208             sub _init_agent {
209 25     25   48 my ( $self, $name, $value ) = @_;
210 25         45 $self->{$name} = $value;
211 25         47 return;
212             }
213              
214             sub _init_allow_man_spaces {
215 25     25   47 my ( $self, $name, $value ) = @_;
216 25 50       60 $self->{$name} = $value ? 1 : 0;
217 25         43 return;
218             }
219              
220             sub _init_cache_url_response {
221 25     25   41 my ( $self, $name, $value ) = @_;
222 25 50       54 $self->{$name} = $value ? 1 : 0;
223 25         43 return;
224             }
225              
226             sub _init_check_external_sections {
227 25     25   46 my ( $self, $name, $value ) = @_;
228 25 100       64 $self->{$name} = $value ? 1 : 0;
229 25         47 return;
230             }
231              
232             sub _init_check_url {
233 25     25   53 my ( $self, $name, $value ) = @_;
234 25 100       62 $self->{$name} = $value ? 1 : 0;
235 25         45 return;
236             }
237              
238             {
239             my %handler;
240              
241             %handler = (
242             ARRAY_REF, sub {
243             my ( $spec, $value ) = @_;
244             $handler{ ref $_ }->( $spec, $_ ) for @{ $value };
245             return;
246             },
247             CODE_REF, sub {
248             my ( $spec, $value ) = @_;
249             push @{ $spec->{ CODE_REF() } }, $value;
250             return;
251             },
252             HASH_REF, sub {
253             my ( $spec, $value ) = @_;
254             $spec->{ NON_REF() }{$_} = 1 for
255             grep { $value->{$_} } keys %{ $value };
256             return;
257             },
258             NON_REF, sub {
259             my ( $spec, $value ) = @_;
260             defined $value
261             or return;
262             $spec->{ NON_REF() }->{$value} = 1;
263             return;
264             },
265             REGEXP_REF, sub {
266             my ( $spec, $value ) = @_;
267             push @{ $spec->{ REGEXP_REF() } }, $value;
268             return;
269             },
270             SCALAR_REF, sub {
271             my ( $spec, $value ) = @_;
272             $spec->{ NON_REF() }->{$$value} = 1;
273             return;
274             },
275             );
276              
277             sub _init_ignore_url {
278 25     25   58 my ( $self, $name, $value ) = @_;
279              
280 25         53 my $spec = $self->{$name} = {};
281 25 50       40 eval {
282 25         79 $handler{ ref $value }->( $spec, $value );
283 25         52 1;
284             } or Carp::confess(
285             "Invalid ignore_url value '$value': must be scalar, regexp, array ref, hash ref, code ref, or undef" );
286 25         44 return;
287             }
288             }
289              
290             sub _init_man {
291 25     25   49 my ( $self, $name, $value ) = @_;
292 25 50       54 $self->{$name} = $value ? 1 : 0;
293 25         43 return;
294             }
295              
296             sub _init_module_index {
297 25     25   48 my ( $self, $name, $value ) = @_;
298 48         353 my @val = map { split qr{ \s* , \s* }smx } ARRAY_REF eq ref $value ?
299 25 100       58 @{ $value } : $value;
  23         45  
300 25         43 my @handlers;
301 25         39 foreach my $mi ( @val ) {
302 48 50       170 my $code = $self->can( "_get_module_index_$mi" )
303             or Carp::croak( "Invalid module_index value '$mi'" );
304 48         91 push @handlers, $code;
305             }
306 25         55 $self->{$name} = \@val;
307 25         85 $self->{"_$name"} = \@handlers;
308 25         57 return;
309             }
310              
311             sub _init_prohibit_redirect {
312 25     25   49 my ( $self, $name, $value ) = @_;
313 25 100       61 if ( CODE_REF eq ref $value ) {
    100          
314 2         5 $self->{$name} = $self->{"_$name"} = $value;
315             } elsif ( $value ) {
316 1         3 $self->{$name} = 1;
317             $self->{"_$name"} = sub {
318 2     2   6 my ( undef, $resp, $url ) = @_;
319 2         6 return $resp->{url} ne $url;
320 1         5 };
321             } else {
322 22         37 $self->{$name} = 0;
323             $self->{"_$name"} = sub {
324 5     5   14 return 0;
325 22         107 };
326             }
327 25         49 return;
328             }
329              
330             sub _init_require_installed {
331 25     25   58 my ( $self, $name, $value ) = @_;
332 25 100       58 $self->{$name} = $value ? 1 : 0;
333 25         40 return;
334             }
335              
336             sub _init_skip_server_errors {
337 25     25   42 my ( $self, $name, $value ) = @_;
338 25 100       58 $self->{$name} = $value ? 1 : 0;
339 25         41 return;
340             }
341              
342             sub agent {
343 13     13 1 22 my ( $self ) = @_;
344 13         68 return $self->{agent};
345             }
346              
347             sub all_pod_files_ok {
348 1     1 1 315 my ( $self, @dir ) = @_;
349              
350             @dir
351 1 50       5 or push @dir, 'blib';
352              
353             my $note = sprintf 'all_pod_files_ok( %s )',
354 1         4 join ', ', map { "'$_'" } @dir;
  1         7  
355              
356 1         5 $TEST->note( "Begin $note" );
357              
358 1         236 my ( $fail, $pass, $skip ) = ( 0 ) x 3;
359              
360             File::Find::find( {
361             no_chdir => 1,
362             wanted => sub {
363 13 100   13   42 if ( $self->_is_perl_file( $_ ) ) {
364 12         68 $TEST->note( "Checking POD links in $File::Find::name" );
365 12         3045 my ( $f, $p, $s ) = $self->pod_file_ok( $_ );
366 12         18 $fail += $f;
367 12         19 $pass += $p;
368 12         16 $skip += $s;
369             }
370 13         196 return;
371             },
372             },
373 1         132 @dir,
374             );
375              
376 1         11 $TEST->note( "End $note" );
377              
378 1 50       239 return wantarray ? ( $fail, $pass, $skip ) : $fail;
379             }
380              
381             sub allow_man_spaces {
382 1     1 1 3 my ( $self ) = @_;
383             return $self->{allow_man_spaces}
384 1         4 }
385              
386             sub cache_url_response {
387 14     14 1 21 my ( $self ) = @_;
388             return $self->{cache_url_response}
389 14         31 }
390              
391             sub check_external_sections {
392 6     6 1 107 my ( $self ) = @_;
393             return $self->{check_external_sections}
394 6         21 }
395              
396             sub check_url {
397 17     17 1 28 my ( $self ) = @_;
398             return $self->{check_url}
399 17         39 }
400              
401             sub configuration {
402 1     1 1 312 my ( $self, $leader ) = @_;
403              
404 1 50       4 defined $leader
405             or $leader = '';
406 1         7 $leader =~ s/ (?<= \S ) \z / /smx;
407              
408 1         5 my ( $ignore_url ) = $TEST->explain( scalar $self->ignore_url() );
409 1         6080 chomp $ignore_url;
410              
411 1         3 return <<"EOD";
412 1         5 ${leader}'agent' is '@{[ $self->agent() ]}'
413 1         4 ${leader}'allow_man_spaces' is @{[ _Boolean(
414             $self->allow_man_spaces() ) ]}
415 1         4 ${leader}'cache_url_response' is @{[ _Boolean(
416             $self->cache_url_response() ) ]}
417 1         3 ${leader}'check_external_sections' is @{[ _Boolean(
418             $self->check_external_sections() ) ]}
419 1         3 ${leader}'check_url' is @{[ _Boolean( $self->check_url() ) ]}
420             ${leader}'ignore_url' is $ignore_url
421 1         4 ${leader}'man' is @{[ _Boolean( $self->man() ) ]}
422 1         4 ${leader}'module_index' is ( @{[ join ', ', map { "'$_'" }
  2         11  
423             $self->module_index() ]} )
424 1         3 ${leader}'prohibit_redirect' is @{[ _Boolean( $self->prohibit_redirect() ) ]}
425 1         3 ${leader}'require_installed' is @{[ _Boolean( $self->require_installed() ) ]}
426 1         3 ${leader}'skip_server_errors' is @{[ _Boolean( $self->skip_server_errors() ) ]}
427             EOD
428             }
429              
430             sub _Boolean {
431 8     8   12 my ( $value ) = @_;
432 8 100       41 return $value ? 'true' : 'false';
433             }
434              
435             sub ignore_url {
436 1     1 1 2 my ( $self ) = @_;
437 1         9 my $spec = $self->__ignore_url();
438             my @rslt = (
439 1 50       6 sort keys %{ $spec->{ ( NON_REF ) } || {} },
440 1 50       7 @{ $spec->{ ( REGEXP_REF ) } || [] },
441 1 50       2 @{ $spec->{ ( CODE_REF ) } || [] },
  1         5  
442             );
443 1 50       8 return wantarray ? @rslt : \@rslt;
444             }
445              
446             # This method returns the internal value of the ignore_url attribute. It
447             # is PRIVATE to this package, and may be changed or revoked at any time.
448             # If called with an argument, it returns a true value if that argument
449             # is a URL that is to be ignored, and false otherwise.
450             sub __ignore_url {
451 22     22   46 my ( $self, $url ) = @_;
452             @_ > 1
453 22 100       58 or return $self->{ignore_url};
454 15         29 my $spec = $self->{ignore_url};
455 15 50       33 $spec->{ NON_REF() }{$url}
456             and return 1;
457 15         20 foreach my $re ( @{ $spec->{ REGEXP_REF() } } ) {
  15         35  
458 1 50       9 $url =~ $re
459             and return 1;
460             }
461 14         24 local $_ = $url;
462 14         16 foreach my $code ( @{ $spec->{ CODE_REF() } } ) {
  14         26  
463 1 50       4 $code->()
464             and return 1;
465             }
466 13         27 return 0;
467             }
468              
469             sub man {
470 3     3 1 515 my ( $self ) = @_;
471 3         8 return $self->{man};
472             }
473              
474             sub module_index {
475 2     2 1 5 my ( $self ) = @_;
476             wantarray
477 2 50       7 and return @{ $self->{module_index} };
  2         7  
478 0         0 local $" = ',';
479 0         0 return "@{ $self->{module_index} }";
  0         0  
480             }
481              
482             sub pod_file_ok {
483 46     46 1 11477 my ( $self, $file ) = @_;
484              
485 46         86 delete $self->{_section};
486             $self->{_test} = {
487 46         145 pass => 0,
488             fail => 0,
489             skip => 0,
490             };
491              
492 46 100       731 if ( SCALAR_REF eq ref $file ) {
    100          
493 1 50       2 $self->{_file_name} = ${ $file } =~ m/ \n /smx ?
  1         3  
494             "String $file" :
495 1         3 "String '${ $file }'";
496             } elsif ( -f $file ) {
497 44         192 $self->{_file_name} = "File $file";
498             } else {
499 1         5 $self->{_file_name} = "File $file";
500 1         5 $self->_fail(
501             'does not exist, or is not a normal file' );
502 1 50       6 return wantarray ? ( 1, 0, 0 ) : 1;
503             }
504              
505 45         206 ( $self->{_section}, $self->{_links} ) = My_Parser->new()->run(
506             $file, \&_any_errata_seen, $self );
507              
508 45         93 @{ $self->{_links} }
509 45 100       276 or do {
510 7         18 $self->_pass();
511 7 100       54 return wantarray ? ( 0, 1, 0 ) : 0;
512             };
513              
514 38         53 my $errors = 0;
515              
516 38         43 foreach my $link ( @{ $self->{_links} } ) {
  38         71  
517 43 50       180 my $code = $self->can( "_handle_$link->[1]{type}" )
518             or Carp::confess(
519             "TODO - link type $link->[1]{type} not supported" );
520 43         95 $errors += $code->( $self, $link );
521             }
522              
523             $errors
524 38 100       125 or $self->_pass();
525             return wantarray ?
526 14         46 ( @{ $self->{_test} }{ qw{ fail pass skip } } ) :
527 38 100       203 $self->{_test}{fail};
528             }
529              
530             sub prohibit_redirect {
531 1     1 1 3 my ( $self ) = @_;
532 1         3 return $self->{prohibit_redirect};
533             }
534              
535             sub require_installed {
536 6     6 1 16 my ( $self ) = @_;
537 6         19 return $self->{require_installed};
538             }
539              
540             sub skip_server_errors {
541 3     3 1 7 my ( $self ) = @_;
542 3         27 return $self->{skip_server_errors};
543             }
544              
545             sub _user_agent {
546 18     18   31 my ( $self ) = @_;
547 18   66     53 return( $self->{_user_agent} ||= HTTP::Tiny->new(
548             agent => $self->agent(),
549             ) );
550             }
551              
552             sub _pass {
553 37     37   62 my ( $self, @msg ) = @_;
554             @msg
555 37 50       93 or @msg = ( 'contains no broken links' );
556 37         70 local $Test::Builder::Level = _nest_depth();
557 37         78 $TEST->ok( 1, $self->__build_test_msg( @msg ) );
558 37         9415 $self->{_test}{pass}++;
559 37         74 return 0;
560             }
561              
562             sub _fail {
563 10     10   24 my ( $self, @msg ) = @_;
564 10         19 local $Test::Builder::Level = _nest_depth();
565 10         27 $TEST->ok( 0, $self->__build_test_msg( @msg ) );
566 10         11008 $self->{_test}{fail}++;
567 10         38 return 1;
568             }
569              
570             sub _skip {
571 5     5   12 my ( $self, @msg ) = @_;
572 5         10 local $Test::Builder::Level = _nest_depth();
573 5         14 $TEST->skip( $self->__build_test_msg( @msg ) );
574 5         1655 $self->{_test}{skip}++;
575 5         17 return 0;
576             }
577              
578             sub _any_errata_seen {
579 0     0   0 my ( $self, $file ) = @_;
580 0 0       0 $file = defined $file ? "File $file" : $self->{_file_name};
581 0         0 $TEST->diag( "$file contains POD errors" );
582 0         0 return;
583             }
584              
585             # This method formats test messages. It is PRIVATE to this package, and
586             # can be changed or revoked without notice.
587             sub __build_test_msg {
588 55     55   99 my ( $self, @msg ) = @_;
589 55         96 my @prefix = ( $self->{_file_name} );
590 55 100       131 if ( ARRAY_REF eq ref $msg[0] ) {
591 16         27 my $link = shift @msg;
592             my $text = defined $link->[1]{raw} ?
593 16 50       55 "link L<$link->[1]{raw}>" :
594             'Link L<>';
595             defined $link->[1]{line_number}
596 16 100       78 and push @prefix, "line $link->[1]{line_number}";
597 16         28 push @prefix, $text;
598             }
599 55         313 return join ' ', @prefix, join '', @msg;
600             }
601              
602             # Get the information on installed documentation. If the doc is found
603             # the return is a reference to a hash containing key {file}, value the
604             # path name to the file containing the documentation. This works both
605             # for module documentation (whether in the .pm or a separate .pod), or
606             # regular .pod documentation (e.g. perldelta.pod).
607             sub _get_installed_doc_info {
608 10     10   20 my ( undef, $module ) = @_;
609 10         53 my $pd = Pod::Perldoc->new();
610              
611             # Pod::Perldoc writes to STDERR if the module (or whatever) is not
612             # installed, so we localize STDERR and reopen it to the null device.
613             # The reopen of STDERR is unchecked because if it fails we still
614             # want to run the tests. They just may be noisy.
615 10         4825 local *STDERR;
616 10         400 open STDERR, '>', File::Spec->devnull(); ## no critic (RequireCheckedOpen)
617              
618             # NOTE that grand_search_init() is undocumented.
619 10         75 my ( $path ) = $pd->grand_search_init( [ $module ] );
620              
621 10         29335 close STDERR;
622              
623 10 100       93 defined $path
624             and return {
625             file => $path,
626             };
627              
628             # See the comment above (just below where _get_installed_doc_info is
629             # called) for why this check is done.
630 5 50       28 Module::Load::Conditional::check_install( module => $module )
631             and return {
632             file => $path,
633             undocumented => 1,
634             };
635              
636 5         1859 return;
637             }
638              
639             # POD link handlers
640              
641             # Handle a 'man' link.
642              
643             sub _handle_man {
644 1     1   4 my ( $self, $link ) = @_;
645              
646 1 50       4 $self->man()
647             or return $self->_skip( $link, 'not checked; man checks disabled' );
648              
649             $link->[1]{to}
650 0 0       0 or return $self->_fail( $link, 'no man page specified' );
651              
652 0 0       0 my ( $page, $sect ) = $link->[1]{to} =~ m/
653             ( [^(]+ ) (?: [(] ( [^)]+ ) [)] )? /smx
654             or return $self->_fail( $link, 'not recognized as man page spec' );
655              
656 0         0 $page =~ s/ \s+ \z //smx;
657              
658 0 0 0     0 $page =~ m/ \s /smx
659             and not $self->allow_man_spaces()
660             and return $self->_fail( $link, 'contains embedded spaces' );
661              
662 0 0       0 my @pg = (
663             $sect ? $sect : (),
664             $page,
665             );
666              
667 0 0 0     0 ( $self->{_cache}{man}{"@pg"} ||= IPC::Cmd::run( COMMAND => [
      0        
668             qw{ man -w }, @pg ] ) || 0 )
669             and return 0;
670              
671 0         0 return $self->_fail( $link, 'refers to unknown man page' );
672             }
673              
674             # Handle pod links. This is pretty much everything, except for 'man'
675             # (see above) or 'url' (see below).
676             sub _handle_pod {
677 26     26   49 my ( $self, $link ) = @_;
678              
679 26 100       74 if ( $link->[1]{to} ) {
    100          
680 16         341 return $self->_check_external_pod_info( $link )
681              
682             } elsif ( my $section = $link->[1]{section} ) {
683 9         197 $section = "$section"; # Stringify object
684             # Internal links (no {to})
685 9 100       110 $self->{_section}{$section}
686             and return 0;
687              
688             # Before 3.24, Pod::Simple was too restrictive in parsing 'man'
689             # links, and they end up here. The regex is verbatim from
690             # Pod::Simple 3.24.
691 1         2 if ( NEED_MAN_FIX && $section =~ m{^[^/|]+[(][-a-zA-Z0-9]+[)]$}s ) {
692             # The misparse left the actual link text in {section}, but
693             # an honest-to-God Pod link has it in {to}.
694             $link->[1]{to} = delete $link->[1]{section};
695             # While we're at it, we might as well make it an actual
696             # 'man' link.
697             $link->[1]{type} = 'man';
698             goto &_handle_man;
699             }
700              
701 1         4 return $self->_fail( $link, 'links to unknown section' );
702              
703             } else {
704             # Links to nowhere: L<...|> or L<...|/>
705 1         17 return $self->_fail( $link, 'links to nothing' );
706             }
707 0         0 return 0;
708             }
709              
710             sub _check_external_pod_info {
711 16     16   26 my ( $self, $link ) = @_;
712              
713             # Stringify overloaded objects
714 16 50       32 my $module = $link->[1]{to} ? "$link->[1]{to}" : undef;
715 16 100       465 my $section = $link->[1]{section} ? "$link->[1]{section}" : undef;
716              
717             # If there is no section info it might be a Perl builtin. Return
718             # success if it is.
719 16 100       155 unless ( $section ) {
720 11 100       24 $self->_is_perl_function( $module )
721             and return 0;
722             }
723              
724             # If it is installed, handle it
725 14 100 100     59 if ( my $data = $self->{_cache}{installed}{$module} ||=
726             $self->_get_installed_doc_info( $module ) ) {
727              
728             # This check is the result of an Andreas J. König (ANDK) test
729             # failure under Perl 5.8.9. That version ships with Pod::Perldoc
730             # 3.14, which is undocumented. Previously the unfound
731             # documentation caused us to fall through to the 'uninstalled'
732             # code, which succeeded because all it was doing was looking for
733             # the existence of the module, and _assuming_ that it was
734             # documented.
735             $data->{undocumented}
736 9 50       19 and return $self->_fail( $link,
737             "$module is installed but undocumented" );
738              
739             # If we get this far it is an installed module with
740             # documentation. We can return success at this point unless the
741             # link specifies a section AND we are checking them. We test the
742             # link rather than the section name because the latter could be
743             # '0'.
744             $link->[1]{section}
745 9 100 100     32 and $self->check_external_sections()
746             or return 0;
747              
748             # Find and parse the section info if needed.
749             $data->{section} ||= My_Parser->new()->run( $data->{file},
750 4   66     22 \&_any_errata_seen, $self, "File $data->{file}" );
751              
752 4 100       31 $data->{section}{$section}
753             and return 0;
754              
755 1         4 return $self->_fail( $link, 'links to unknown section' );
756             }
757              
758             # If we're requiring links to be to installed modules, flunk now.
759             $self->require_installed()
760 5 100       19 and return $self->_fail( $link,
761             'links to module that is not installed' );
762              
763             # It's not installed on this system, but it may be out there
764             # somewhere
765              
766 4   33     24 $self->{_cache}{uninstalled} ||= $self->_get_module_index();
767              
768 4         12 return $self->{_cache}{uninstalled}->( $self, $link );
769              
770             }
771              
772             sub _get_module_index {
773 4     4   10 my ( $self ) = @_;
774 1         7 my @inxes = sort { $a->[1] <=> $b->[1] }
775 4         7 map { $_->( $self ) } @{ $self->{_module_index} };
  6         17  
  4         12  
776 4 50       15 if ( @inxes ) {
777 4         7 my $modinx = $inxes[-1][0];
778             return sub {
779 4     4   9 my ( $self, $link ) = @_;
780 4         7 my $module = $link->[1]{to};
781 4 50       9 $modinx->( $module )
782             or return $self->_fail( $link, 'links to unknown module' );
783             $link->[1]{section}
784 4 50       73 or return 0;
785 0         0 return $self->_skip( $link, 'not checked; ',
786             'module exists, but unable to check sections of ',
787             'uninstalled modules' );
788 4         31 };
789             } else {
790             return sub {
791 0     0   0 my ( $self, $link ) = @_;
792 0         0 return $self->_skip( $link, 'not checked; ',
793             'not found on this system' );
794 0         0 };
795             }
796             }
797              
798             # In all of the module index getters, the return is either nothing at
799             # all (for inability to use this indexing mechanism) or a refererence to
800             # an array. Element [0] of the array is a reference a piece of code that
801             # takes the module name as its only argument, and returns a true value
802             # if that module exists and a false value otherwise. Element [1] of the
803             # array is a Perl time that is characteristic of the information in the
804             # index (typically the revision date of the underlying file if that's
805             # the way the index works).
806              
807             # NOTE that Test::Pod::LinkCheck loads CPAN and then messes with it to
808             # try to prevent it from initializing itself. After trying this and
809             # thinking about it, I decided to go after the metadata directly.
810             sub _get_module_index_cpan {
811             # my ( $self ) = @_;
812              
813             # The following code reproduces
814             # CPAN::HandleConfig::cpan_home_dir_candidates()
815             # as of CPAN::HandleConfig version 5.5011.
816 3     3   4 my @dir_list;
817              
818 3 50       11 if ( _has_usable( 'File::HomeDir', 0.52 ) ) {
819 3         15 ON_DARWIN
820             or push @dir_list, File::HomeDir->my_data();
821 3         122 push @dir_list, File::HomeDir->my_home();
822             }
823              
824             $ENV{HOME}
825 3 50       73 and push @dir_list, $ENV{HOME};
826             $ENV{HOMEDRIVE}
827             and $ENV{HOMEPATH}
828             and push @dir_list, File::Spec->catpath( $ENV{HOMEDRIVE},
829 3 0 33     11 $ENV{HOMEPATH} );
830             $ENV{USERPROFILE}
831 3 50       9 and push @dir_list, $ENV{USERPROFILE};
832             $ENV{'SYS$LOGIN'}
833 3 50       11 and push @dir_list, $ENV{'SYS$LOGIN'};
834              
835             # The preceding code reproduces
836             # CPAN::HandleConfig::cpan_home_dir_candidates()
837              
838 3         9 foreach my $dir ( @dir_list ) {
839 5 50       16 defined $dir
840             or next;
841 5         49 my $path = File::Spec->catfile( $dir, $DOT_CPAN, 'Metadata' );
842 5 100       1465 -e $path
843             or next;
844 2         10 my $rev = ( stat _ )[9];
845 2 50       11 my $hash = Storable::retrieve( $path )
846             or return;
847 2         199 $hash = $hash->{'CPAN::Module'};
848             return [
849 2     2   10 sub { return $hash->{$_[0]} },
850 2         17 $rev,
851             ];
852             }
853              
854 1         5 return;
855             }
856              
857             sub _get_module_index_cpan_meta_db {
858 3     3   8 my ( $self ) = @_;
859              
860 3         13 my $user_agent = $self->_user_agent();
861              
862 3         6 my %hash;
863              
864             return [
865             sub {
866             exists $hash{$_[0]}
867 2 50   2   10 and return $hash{$_[0]};
868 2         48 my $resp = $user_agent->head(
869             "https://cpanmetadb.plackperl.org/v1.0/package/$_[0]" );
870 2         8 return ( $hash{$_[0]} = $resp->{success} );
871             },
872 3         29 time - 86400 * 7,
873             ];
874             }
875              
876             # Handle url links. This is something like L or
877             # L<...|http://...>.
878             sub _handle_url {
879 16     16   27 my ( $self, $link ) = @_;
880              
881 16 100       33 $self->check_url()
882             or return $self->_skip( $link, 'not checked; url checks disabled' );
883              
884 15         31 my $user_agent = $self->_user_agent();
885              
886 15 50       42 my $url = "$link->[1]{to}" # Stringify object
887             or return $self->_fail( $link, 'contains no url' );
888              
889 15 100       213 $self->__ignore_url( $url )
890             and return $self->_skip( $link, 'not checked; explicitly ignored' );
891              
892 13         19 my $resp;
893 13 50       26 if ( $self->cache_url_response() ) {
894 13   33     53 $resp = $self->{_cache_url_response}{$url} ||=
895             $user_agent->head( $url );
896             } else {
897 0         0 $resp = $user_agent->head( $url );
898             }
899              
900 13 100       26 if ( $resp->{success} ) {
901              
902 11         18 my $code = $self->{_prohibit_redirect};
903 11         25 while ( $code = $code->( $self, $resp, $url ) ) {
904 6 100       29 CODE_REF eq ref $code
905             or return $self->_fail( $link, "redirected to $resp->{url}" );
906             }
907              
908 7         19 return 0;
909              
910             } else {
911              
912             $self->skip_server_errors()
913 2 100 66     6 and $resp->{status} =~ m/ \A 5 /smx
914             and return $self->_skip( $link,
915             "not checked: server error $resp->{status} $resp->{reason}" );
916              
917 1         6 return $self->_fail( $link,
918             "broken: $resp->{status} $resp->{reason}" );
919              
920             }
921             }
922              
923             {
924             my %checked;
925              
926             sub _has_usable {
927 3     3   8 my ( $module, $version ) = @_;
928              
929 3 100       11 unless ( exists $checked{$module} ) {
930 1         2 local $@ = undef;
931 1         7 ( my $fn = "$module.pm" ) =~ s| :: |/|smxg;
932             eval {
933 1         1134 require $fn;
934 1         4925 $checked{$module} = 1;
935 1         5 1;
936 1 50       2 } or do {
937 0         0 $checked{$module} = 0;
938             };
939             }
940              
941 3 50       8 $checked{$module}
942             or return;
943              
944 3 50       9 if ( defined $version ) {
945 3         6 my $rslt = 1;
946 3     0   23 local $SIG{__DIE__} = sub { $rslt = undef };
  0         0  
947 3         56 $module->VERSION( $version );
948 3         22 return $rslt;
949             }
950              
951 0         0 return 1;
952             }
953             }
954              
955             sub _is_perl_file {
956 20     20   1539 my ( undef, $path ) = @_;
957 20 100 100     1245 -e $path
958             and -T _
959             or return;
960 17 100       173 $path =~ m/ [.] (?: (?i: pl ) | pm | pod | t ) \z /smx
961             and return 1;
962 1 50       35 open my $fh, '<', $path
963             or return;
964 1   50     23 local $_ = <$fh> || '';
965 1         13 close $fh;
966 1         11 return m/ perl /smx;
967             }
968              
969             {
970             my $bareword;
971              
972             sub _is_perl_function {
973 11     11   22 my ( undef, $word ) = @_;
974             $bareword ||= {
975 11   100     29 map { $_ => 1 } @B::Keywords::Functions, @B::Keywords::Barewords };
  287         527  
976 11         45 return $bareword->{$word};
977             }
978             }
979              
980             {
981             my %ignore;
982             BEGIN {
983 2     2   9 %ignore = map { $_ => 1 } __PACKAGE__, qw{ DB File::Find };
  6         240  
984             }
985              
986             sub _nest_depth {
987 52     52   64 my $nest = 0;
988 52   50     351 $nest++ while $ignore{ caller( $nest ) || '' };
989 52         82 return $nest;
990             }
991             }
992              
993             package My_Parser; ## no critic (ProhibitMultiplePackages)
994              
995 2     2   1191 use Pod::Simple::PullParser; # Core since 5.9.3 (part of Pod::Simple)
  2         13650  
  2         1320  
996              
997             @My_Parser::ISA = qw{ Pod::Simple::PullParser };
998              
999             my %section_tag = map { $_ => 1 } qw{ head1 head2 head3 head4 item-text };
1000              
1001             sub new {
1002 48     48   90 my ( $class ) = @_;
1003 48         140 my $self = $class->SUPER::new();
1004 48         1513 $self->preserve_whitespace( 1 );
1005 48         369 return $self;
1006             }
1007              
1008             sub run {
1009 48     48   103 my ( $self, $source, $err, @err_arg ) = @_;
1010 48 50       170 defined $source
1011             and $self->set_source( $source );
1012 48         2728 my $attr = $self->_attr();
1013 48         95 @{ $attr }{ qw{ line links sections } } = ( 1, [], {} );
  48         138  
1014 48         123 while ( my $token = $self->get_token() ) {
1015 628 50       57871 if ( my $code = $self->can( '__token_' . $token->type() ) ) {
1016 628         2608 $code->( $self, $token );
1017             }
1018             }
1019             $err
1020 48 50 33     793 and $self->any_errata_seen()
1021             and $err->( @err_arg );
1022             return wantarray ?
1023             ( $attr->{sections}, $attr->{links} ) :
1024 48 100       402 $attr->{sections};
1025             }
1026              
1027             sub _attr {
1028 676     676   879 my ( $self ) = @_;
1029 676   100     1352 return $self->{ ( __PACKAGE__ ) } ||= {};
1030             }
1031              
1032             sub _normalize_text {
1033 75     75   535 my ( $text ) = @_;
1034 75 50       136 defined $text
1035             or $text = '';
1036 75         186 $text =~ s/ \A \s+ //smx;
1037 75         161 $text =~ s/ \s+ \z //smx;
1038 75         139 $text =~ s/ \s+ / /smxg;
1039 75         178 return $text;
1040             }
1041              
1042             sub __token_start {
1043 211     211   302 my ( $self, $token ) = @_;
1044 211         326 my $attr = $self->_attr();
1045 211 100       420 if ( defined( my $line = $token->attr( 'start_line' ) ) ) {
1046 152         885 $attr->{line} = $line;
1047             }
1048 211         649 my $tag = $token->tag();
1049 211 100       1111 if ( 'L' eq $tag ) {
    100          
1050 43         104 $token->attr( line_number => $self->{My_Parser}{line} );
1051 43         291 foreach my $name ( qw{ section to } ) {
1052 86 100       290 my $sect = $token->attr( $name )
1053             or next;
1054 47         1241 @{ $sect }[ 2 .. $#$sect ] = ( _normalize_text( "$sect" ) );
  47         114  
1055             }
1056 43         106 push @{ $attr->{links} }, [ @{ $token }[ 1 .. $#$token ] ];
  43         74  
  43         94  
1057             } elsif ( $section_tag{$tag} ) {
1058 28         43 $attr->{text} = '';
1059             }
1060 211         602 return;
1061             }
1062              
1063             sub __token_text {
1064 206     206   285 my ( $self, $token ) = @_;
1065 206         276 my $attr = $self->_attr();
1066 206         386 my $text = $token->text();
1067 206         678 $attr->{line} += $text =~ tr/\n//;
1068 206         366 $attr->{text} .= $text;
1069 206         480 return;
1070             }
1071              
1072             sub __token_end {
1073 211     211   299 my ( $self, $token ) = @_;
1074 211         283 my $attr = $self->_attr();
1075 211         357 my $tag = $token->tag();
1076 211 100       955 if ( $section_tag{$tag} ) {
1077 28         53 $attr->{sections}{ _normalize_text( delete $attr->{text} ) } = 1;
1078             }
1079 211         483 return;
1080             }
1081              
1082             1;
1083              
1084             __END__