File Coverage

blib/lib/Test/Pod/LinkCheck/Lite.pm
Criterion Covered Total %
statement 483 526 91.8
branch 159 240 66.2
condition 30 57 52.6
subroutine 102 105 97.1
pod 15 15 100.0
total 789 943 83.6


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