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   152680 use 5.008;
  6         47  
4              
5 6     2   31 use strict; # Core since 5.0
  6         40  
  6         70  
6 2     2   11 use warnings; # Core since 5.6.0
  2         3  
  2         66  
7              
8 2     2   1394 use utf8; # Core since 5.6.0
  2         31  
  2         10  
9              
10 2     2   1075 use B::Keywords (); # Not core
  2         3521  
  2         47  
11 2     2   14 use Carp (); # Core since 5.0
  2         4  
  2         36  
12 2     2   12 use Exporter (); # Core since 5.0
  2         5  
  2         34  
13 2     2   16 use File::Find (); # Core since 5.0
  2         4  
  2         39  
14 2     2   12 use File::Spec; # Core since 5.4.5
  2         6  
  2         43  
15 2     2   1153 use HTTP::Tiny; # Core since 5.13.9
  3         47754  
  3         72  
16 3     2   776 use IPC::Cmd (); # Core since 5.9.5
  2         45052  
  2         54  
17 2     2   17 use Module::Load::Conditional (); # Core since 5.9.5
  2         4  
  2         31  
18 2     2   1394 use Pod::Perldoc (); # Core since 5.8.1
  2         57992  
  2         58  
19 2     2   1319 use Pod::Simple (); # Core since 5.9.3
  2         60691  
  2         77  
20 2     2   23 use Pod::Simple::LinkSection; # Core since 5.9.3 (part of Pod::Simple)
  2         5  
  2         52  
21 2     2   13 use Scalar::Util (); # Core since 5.7.3
  2         22  
  2         30  
22 2     2   717 use Storable (); # Core since 5.7.3
  2         3276  
  2         49  
23 2     2   13 use Test::Builder (); # Core since 5.6.2
  2         4  
  2         403  
24              
25             our $VERSION = '0.011';
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   16 use constant ON_DARWIN => 'darwin' eq $^O;
  2         4  
  2         161  
39 2     2   13 use constant ON_VMS => 'VMS' eq $^O;
  2         4  
  2         234  
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   14 use constant ARRAY_REF => ref [];
  2         10  
  2         130  
48 2     2   12 use constant CODE_REF => ref sub {};
  2         4  
  2         117  
49 2     2   12 use constant HASH_REF => ref {};
  2         5  
  2         120  
50 2     2   13 use constant NON_REF => ref 0;
  2         4  
  2         146  
51 2     2   13 use constant REGEXP_REF => ref qrsmx;
  2         5  
  2         113  
52 2     2   12 use constant SCALAR_REF => ref \0;
  2         4  
  2         153  
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   14 use constant NEED_MAN_FIX => Pod::Simple->VERSION lt '3.24';
  2         10  
  2         496  
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   16 };
  2         4  
  2         398  
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   18 };
  2         3  
  2         126  
88              
89 2     2   12 use constant USER_AGENT_CLASS => 'HTTP::Tiny';
  2         4  
  2         13321  
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 68575 my ( $class, %arg ) = @_;
100 25   33     138 my $self = bless {}, ref $class || $class;
101 25         93 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   60 my ( $self, %arg ) = @_;
116 25         122 foreach my $key ( keys %dflt ) {
117             exists $arg{$key}
118 275 100       814 or $arg{$key} = $dflt{$key}->();
119             }
120 25         124 foreach my $name ( keys %arg ) {
121 275 50       954 if ( my $code = $self->can( "_init_$name" ) ) {
    0          
122 275         656 $code->( $self, $name, $arg{$name} );
123             } elsif ( defined $arg{$name} ) {
124 0         0 Carp::croak( "Unknown argument $name" );
125             }
126             }
127 25         142 return $self;
128             }
129             }
130              
131             sub _default_allow_man_spaces {
132 25     25   65 return 0;
133             }
134              
135             sub _default_check_external_sections {
136 24     24   56 return 1;
137             }
138              
139             sub _default_cache_url_response {
140 25     25   83 return 1;
141             }
142              
143             sub _default_check_url {
144 23     23   57 return 1;
145             }
146              
147             sub _default_ignore_url {
148 17     17   50 return [];
149             }
150              
151             {
152             my $checked;
153             my $rslt;
154              
155             sub _default_man {
156 24 100   24   63 unless ( $checked ) {
157 2         4 $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       13 $rslt = IPC::Cmd::can_run( 'man' )
174             or $TEST->diag(
175             q );
176             }
177             }
178 24         206590 return $rslt;
179             }
180             }
181              
182             sub _default_module_index {
183 23     23   47 my @handlers;
184 23         513 foreach ( keys %Test::Pod::LinkCheck::Lite:: ) {
185 1949 100 66     3969 m/ \A _get_module_index_ ( .+ ) /smx
186             and __PACKAGE__->can( $_ )
187             or next;
188 46         155 push @handlers, $1;
189             }
190 23         196 @handlers = sort @handlers;
191 23         85 return \@handlers;
192             }
193              
194             sub _default_prohibit_redirect {
195 21     21   58 return 0;
196             }
197              
198             sub _default_require_installed {
199 24     24   64 return 0;
200             }
201              
202             sub _default_skip_server_errors {
203 23     23   56 return 1;
204             }
205              
206             sub _default_user_agent {
207 25     25   78 return USER_AGENT_CLASS;
208             }
209              
210             sub _init_allow_man_spaces {
211 25     25   53 my ( $self, $name, $value ) = @_;
212 25 50       66 $self->{$name} = $value ? 1 : 0;
213 25         51 return;
214             }
215              
216             sub _init_cache_url_response {
217 25     25   53 my ( $self, $name, $value ) = @_;
218 25 50       65 $self->{$name} = $value ? 1 : 0;
219 25         50 return;
220             }
221              
222             sub _init_check_external_sections {
223 25     25   55 my ( $self, $name, $value ) = @_;
224 25 100       68 $self->{$name} = $value ? 1 : 0;
225 25         53 return;
226             }
227              
228             sub _init_check_url {
229 25     25   56 my ( $self, $name, $value ) = @_;
230 25 100       71 $self->{$name} = $value ? 1 : 0;
231 25         51 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   63 my ( $self, $name, $value ) = @_;
275              
276 25         65 my $spec = $self->{$name} = {};
277 25 50       44 eval {
278 25         100 $handler{ ref $value }->( $spec, $value );
279 25         69 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         56 return;
283             }
284             }
285              
286             sub _init_man {
287 25     25   53 my ( $self, $name, $value ) = @_;
288 25 50       75 $self->{$name} = $value ? 1 : 0;
289 25         52 return;
290             }
291              
292             sub _init_module_index {
293 25     25   61 my ( $self, $name, $value ) = @_;
294 48         392 my @val = map { split qr{ \s* , \s* }smx } ARRAY_REF eq ref $value ?
295 25 100       77 @{ $value } : $value;
  23         53  
296 25         55 my @handlers;
297 25         79 foreach my $mi ( @val ) {
298 48 50       200 my $code = $self->can( "_get_module_index_$mi" )
299             or Carp::croak( "Invalid module_index value '$mi'" );
300 48         129 push @handlers, $code;
301             }
302 25         78 $self->{$name} = \@val;
303 25         85 $self->{"_$name"} = \@handlers;
304 25         64 return;
305             }
306              
307             sub _init_prohibit_redirect {
308 25     25   56 my ( $self, $name, $value ) = @_;
309 25 100       72 if ( CODE_REF eq ref $value ) {
    100          
310 2         9 $self->{$name} = $self->{"_$name"} = $value;
311             } elsif ( $value ) {
312 1         3 $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         7 };
317             } else {
318 22         48 $self->{$name} = 0;
319             $self->{"_$name"} = sub {
320 5     5   16 return 0;
321 22         113 };
322             }
323 25         57 return;
324             }
325              
326             sub _init_require_installed {
327 25     25   59 my ( $self, $name, $value ) = @_;
328 25 100       61 $self->{$name} = $value ? 1 : 0;
329 25         52 return;
330             }
331              
332             sub _init_skip_server_errors {
333 25     25   104 my ( $self, $name, $value ) = @_;
334 25 100       72 $self->{$name} = $value ? 1 : 0;
335 25         52 return;
336             }
337              
338             sub _init_user_agent {
339 25     25   58 my ( $self, $name, $value ) = @_;
340 25 50       61 defined $name
341             or $name = USER_AGENT_CLASS;
342 25 50       49 eval {
343 25         162 $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         65 $self->{$name} = $value;
347 25 50       54 if ( ref $value ) {
348 0         0 $self->{_user_agent} = $value;
349             } else {
350             # Probably unnecessary, but I'm paranoid.
351 25         65 delete $self->{_user_agent};
352             }
353 25         55 return;
354             }
355              
356             sub agent {
357 1     1 1 4 my ( $self ) = @_;
358             defined $self->{agent}
359 1 50       8 or $self->{agent} = $self->_user_agent()->agent();
360 1         7 return $self->{agent};
361             }
362              
363             sub all_pod_files_ok {
364 1     1 1 387 my ( $self, @dir ) = @_;
365              
366             @dir
367 1 50       5 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         8 $TEST->note( "Begin $note" );
373              
374 1         285 my ( $fail, $pass, $skip ) = ( 0 ) x 3;
375              
376             File::Find::find( {
377             no_chdir => 1,
378             wanted => sub {
379 13 100   13   45 if ( $self->_is_perl_file( $_ ) ) {
380 12         80 $TEST->note( "Checking POD links in $File::Find::name" );
381 12         3614 my ( $f, $p, $s ) = $self->pod_file_ok( $_ );
382 12         25 $fail += $f;
383 12         20 $pass += $p;
384 12         18 $skip += $s;
385             }
386 13         246 return;
387             },
388             },
389 1         137 @dir,
390             );
391              
392 1         13 $TEST->note( "End $note" );
393              
394 1 50       290 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         6 }
401              
402             sub cache_url_response {
403 14     14 1 28 my ( $self ) = @_;
404             return $self->{cache_url_response}
405 14         36 }
406              
407             sub check_external_sections {
408 6     6 1 136 my ( $self ) = @_;
409             return $self->{check_external_sections}
410 6         26 }
411              
412             sub check_url {
413 17     17 1 30 my ( $self ) = @_;
414             return $self->{check_url}
415 17         50 }
416              
417             sub configuration {
418 1     1 1 351 my ( $self, $leader ) = @_;
419              
420 1 50       6 defined $leader
421             or $leader = '';
422 1         7 $leader =~ s/ (?<= \S ) \z / /smx;
423              
424 1         6 my ( $ignore_url ) = $TEST->explain( scalar $self->ignore_url() );
425 1         6962 chomp $ignore_url;
426              
427 1         4 return <<"EOD";
428 1         13 ${leader}'agent' is '@{[ $self->agent() ]}'
429 1         5 ${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         5 ${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         4 ${leader}'man' is @{[ _Boolean( $self->man() ) ]}
438 1         5 ${leader}'module_index' is ( @{[ join ', ', map { "'$_'" }
  2         11  
439             $self->module_index() ]} )
440 1         6 ${leader}'prohibit_redirect' is @{[ _Boolean( $self->prohibit_redirect() ) ]}
441 1         5 ${leader}'require_installed' is @{[ _Boolean( $self->require_installed() ) ]}
442 1         5 ${leader}'skip_server_errors' is @{[ _Boolean( $self->skip_server_errors() ) ]}
443             EOD
444             }
445              
446             sub _Boolean {
447 8     8   16 my ( $value ) = @_;
448 8 100       44 return $value ? 'true' : 'false';
449             }
450              
451             sub ignore_url {
452 1     1 1 3 my ( $self ) = @_;
453 1         6 my $spec = $self->__ignore_url();
454             my @rslt = (
455 1 50       9 sort keys %{ $spec->{ ( NON_REF ) } || {} },
456 1 50       5 @{ $spec->{ ( REGEXP_REF ) } || [] },
457 1 50       3 @{ $spec->{ ( CODE_REF ) } || [] },
  1         7  
458             );
459 1 50       10 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   61 my ( $self, $url ) = @_;
468             @_ > 1
469 22 100       72 or return $self->{ignore_url};
470 15         33 my $spec = $self->{ignore_url};
471 15 50       43 $spec->{ NON_REF() }{$url}
472             and return 1;
473 15         26 foreach my $re ( @{ $spec->{ REGEXP_REF() } } ) {
  15         41  
474 1 50       11 $url =~ $re
475             and return 1;
476             }
477 14         28 local $_ = $url;
478 14         20 foreach my $code ( @{ $spec->{ CODE_REF() } } ) {
  14         34  
479 1 50       5 $code->()
480             and return 1;
481             }
482 13         39 return 0;
483             }
484              
485             sub man {
486 3     3 1 664 my ( $self ) = @_;
487 3         12 return $self->{man};
488             }
489              
490             sub module_index {
491 2     2 1 5 my ( $self ) = @_;
492             wantarray
493 2 50       7 and return @{ $self->{module_index} };
  2         9  
494 0         0 local $" = ',';
495 0         0 return "@{ $self->{module_index} }";
  0         0  
496             }
497              
498             sub pod_file_ok {
499 46     46 1 13705 my ( $self, $file ) = @_;
500              
501 46         106 delete $self->{_section};
502             $self->{_test} = {
503 46         236 pass => 0,
504             fail => 0,
505             skip => 0,
506             };
507              
508 46 100       862 if ( SCALAR_REF eq ref $file ) {
    100          
509 1 50       3 $self->{_file_name} = ${ $file } =~ m/ \n /smx ?
  1         5  
510             "String $file" :
511 1         4 "String '${ $file }'";
512             } elsif ( -f $file ) {
513 44         194 $self->{_file_name} = "File $file";
514             } else {
515 1         5 $self->{_file_name} = "File $file";
516 1         5 $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         239 ( $self->{_section}, $self->{_links} ) = My_Parser->new()->run(
522             $file, \&_any_errata_seen, $self );
523              
524 45         114 @{ $self->{_links} }
525 45 100       305 or do {
526 7         22 $self->_pass();
527 7 100       85 return wantarray ? ( 0, 1, 0 ) : 0;
528             };
529              
530 38         61 my $errors = 0;
531              
532 38         61 foreach my $link ( @{ $self->{_links} } ) {
  38         92  
533 43 50       204 my $code = $self->can( "_handle_$link->[1]{type}" )
534             or Carp::confess(
535             "TODO - link type $link->[1]{type} not supported" );
536 43         123 $errors += $code->( $self, $link );
537             }
538              
539             $errors
540 38 100       156 or $self->_pass();
541             return wantarray ?
542 14         55 ( @{ $self->{_test} }{ qw{ fail pass skip } } ) :
543 38 100       268 $self->{_test}{fail};
544             }
545              
546             sub prohibit_redirect {
547 1     1 1 3 my ( $self ) = @_;
548 1         4 return $self->{prohibit_redirect};
549             }
550              
551             sub require_installed {
552 6     6 1 17 my ( $self ) = @_;
553 6         29 return $self->{require_installed};
554             }
555              
556             sub skip_server_errors {
557 3     3 1 6 my ( $self ) = @_;
558 3         21 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   44 my ( $self ) = @_;
570 19   66     69 return( $self->{_user_agent} ||= do {
571 13         22 my @arg;
572             defined $self->{agent}
573 13 50       41 and push @arg, agent => $self->{agent};
574 13         74 $self->{user_agent}->new( @arg );
575             }
576             );
577             }
578              
579             sub _pass {
580 37     37   77 my ( $self, @msg ) = @_;
581             @msg
582 37 50       106 or @msg = ( 'contains no broken links' );
583 37         80 local $Test::Builder::Level = _nest_depth();
584 37         90 $TEST->ok( 1, $self->__build_test_msg( @msg ) );
585 37         11462 $self->{_test}{pass}++;
586 37         84 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         28 $TEST->ok( 0, $self->__build_test_msg( @msg ) );
593 10         13387 $self->{_test}{fail}++;
594 10         43 return 1;
595             }
596              
597             sub _skip {
598 5     5   18 my ( $self, @msg ) = @_;
599 5         14 local $Test::Builder::Level = _nest_depth();
600 5         18 $TEST->skip( $self->__build_test_msg( @msg ) );
601 5         2052 $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   119 my ( $self, @msg ) = @_;
616 55         123 my @prefix = ( $self->{_file_name} );
617 55 100       155 if ( ARRAY_REF eq ref $msg[0] ) {
618 16         33 my $link = shift @msg;
619             my $text = defined $link->[1]{raw} ?
620 16 50       63 "link L<$link->[1]{raw}>" :
621             'Link L<>';
622             defined $link->[1]{line_number}
623 16 100       59 and push @prefix, "line $link->[1]{line_number}";
624 16         36 push @prefix, $text;
625             }
626 55         375 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   22 my ( undef, $module ) = @_;
636 10         64 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         5764 local *STDERR;
643 10         458 open STDERR, '>', File::Spec->devnull(); ## no critic (RequireCheckedOpen)
644              
645             # NOTE that grand_search_init() is undocumented.
646 10         83 my ( $path ) = $pd->grand_search_init( [ $module ] );
647              
648 10         31569 close STDERR;
649              
650 10 100       111 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       35 Module::Load::Conditional::check_install( module => $module )
658             and return {
659             file => $path,
660             undocumented => 1,
661             };
662              
663 5         2227 return;
664             }
665              
666             # POD link handlers
667              
668             # Handle a 'man' link.
669              
670             sub _handle_man {
671 1     1   4 my ( $self, $link ) = @_;
672              
673 1 50       5 $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   56 my ( $self, $link ) = @_;
705              
706 26 100       92 if ( $link->[1]{to} ) {
    100          
707 16         420 return $self->_check_external_pod_info( $link )
708              
709             } elsif ( my $section = $link->[1]{section} ) {
710 9         237 $section = "$section"; # Stringify object
711             # Internal links (no {to})
712 9 100       141 $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         2 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         4 return $self->_fail( $link, 'links to unknown section' );
729              
730             } else {
731             # Links to nowhere: L<...|> or L<...|/>
732 1         24 return $self->_fail( $link, 'links to nothing' );
733             }
734 0         0 return 0;
735             }
736              
737             sub _check_external_pod_info {
738 16     16   34 my ( $self, $link ) = @_;
739              
740             # Stringify overloaded objects
741 16 50       35 my $module = $link->[1]{to} ? "$link->[1]{to}" : undef;
742 16 100       552 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       197 unless ( $section ) {
747 11 100       28 $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     35 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     26 \&_any_errata_seen, $self, "File $data->{file}" );
778              
779 4 100       36 $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       32 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     31 $self->{_cache}{uninstalled} ||= $self->_get_module_index();
794              
795 4         17 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         9 map { $_->( $self ) } @{ $self->{_module_index} };
  6         21  
  4         13  
803 4 50       14 if ( @inxes ) {
804 4         9 my $modinx = $inxes[-1][0];
805             return sub {
806 4     4   10 my ( $self, $link ) = @_;
807 4         9 my $module = $link->[1]{to};
808 4 50       9 $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         34 };
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   6 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         148 push @dir_list, File::HomeDir->my_home();
849             }
850              
851             $ENV{HOME}
852 3 50       88 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     12 $ENV{HOMEPATH} );
857             $ENV{USERPROFILE}
858 3 50       11 and push @dir_list, $ENV{USERPROFILE};
859             $ENV{'SYS$LOGIN'}
860 3 50       10 and push @dir_list, $ENV{'SYS$LOGIN'};
861              
862             # The preceding code reproduces
863             # CPAN::HandleConfig::cpan_home_dir_candidates()
864              
865 3         9 foreach my $dir ( @dir_list ) {
866 5 50       15 defined $dir
867             or next;
868 5         49 my $path = File::Spec->catfile( $dir, $DOT_CPAN, 'Metadata' );
869 5 100       84 -e $path
870             or next;
871 2         9 my $rev = ( stat _ )[9];
872 2 50       20 my $hash = Storable::retrieve( $path )
873             or return;
874 2         214 $hash = $hash->{'CPAN::Module'};
875             return [
876 2     2   11 sub { return $hash->{$_[0]} },
877 2         19 $rev,
878             ];
879             }
880              
881 1         6 return;
882             }
883              
884             sub _get_module_index_cpan_meta_db {
885 3     3   11 my ( $self ) = @_;
886              
887 3         14 my $user_agent = $self->_user_agent();
888              
889 3         6 my %hash;
890              
891             return [
892             sub {
893             exists $hash{$_[0]}
894 2 50   2   12 and return $hash{$_[0]};
895 2         44 my $resp = $user_agent->head(
896             "https://cpanmetadb.plackperl.org/v1.0/package/$_[0]" );
897 2         10 return ( $hash{$_[0]} = $resp->{success} );
898             },
899 3         35 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   33 my ( $self, $link ) = @_;
907              
908 16 100       55 $self->check_url()
909             or return $self->_skip( $link, 'not checked; url checks disabled' );
910              
911 15         36 my $user_agent = $self->_user_agent();
912              
913 15 50       52 my $url = "$link->[1]{to}" # Stringify object
914             or return $self->_fail( $link, 'contains no url' );
915              
916 15 100       287 if ( $url =~ m/ \A https : /smxi ) {
917 11         46 my ( $ok, $why ) = USER_AGENT_CLASS->can_ssl();
918 11 50       32 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       47 $self->__ignore_url( $url )
928             and return $self->_skip( $link, 'not checked; explicitly ignored' );
929              
930 13         18 my $resp;
931 13 50       31 if ( $self->cache_url_response() ) {
932 13   33     64 $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         20 my $code = $self->{_prohibit_redirect};
941 11         34 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         26 return 0;
947              
948             } else {
949              
950             $self->skip_server_errors()
951 2 100 66     8 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         7 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   9 my ( $module, $version ) = @_;
966              
967 3 100       11 unless ( exists $checked{$module} ) {
968 1         2 local $@ = undef;
969 1         7 ( my $fn = "$module.pm" ) =~ s| :: |/|smxg;
970             eval {
971 1         557 require $fn;
972 1         5504 $checked{$module} = 1;
973 1         7 1;
974 1 50       4 } or do {
975 0         0 $checked{$module} = 0;
976             };
977             }
978              
979 3 50       12 $checked{$module}
980             or return;
981              
982 3 50       9 if ( defined $version ) {
983 3         7 my $rslt = 1;
984 3     0   28 local $SIG{__DIE__} = sub { $rslt = undef };
  0         0  
985 3         55 $module->VERSION( $version );
986 3         27 return $rslt;
987             }
988              
989 0         0 return 1;
990             }
991             }
992              
993             sub _is_perl_file {
994 20     20   1851 my ( undef, $path ) = @_;
995 20 100 100     1385 -e $path
996             and -T _
997             or return;
998 17 100       188 $path =~ m/ [.] (?: (?i: pl ) | pm | pod | t ) \z /smx
999             and return 1;
1000 1 50       41 open my $fh, '<', $path
1001             or return;
1002 1   50     24 local $_ = <$fh> || '';
1003 1         15 close $fh;
1004 1         12 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     36 map { $_ => 1 } @B::Keywords::Functions, @B::Keywords::Barewords };
  287         627  
1014 11         56 return $bareword->{$word};
1015             }
1016             }
1017              
1018             {
1019             my %ignore;
1020             BEGIN {
1021 2     2   10 %ignore = map { $_ => 1 } __PACKAGE__, qw{ DB File::Find };
  6         198  
1022             }
1023              
1024             sub _nest_depth {
1025 52     52   72 my $nest = 0;
1026 52   50     419 $nest++ while $ignore{ caller( $nest ) || '' };
1027 52         109 return $nest;
1028             }
1029             }
1030              
1031             package My_Parser; ## no critic (ProhibitMultiplePackages)
1032              
1033 2     2   1227 use Pod::Simple::PullParser; # Core since 5.9.3 (part of Pod::Simple)
  2         13044  
  2         1669  
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   118 my ( $class ) = @_;
1041 48         164 my $self = $class->SUPER::new();
1042 48         1792 $self->preserve_whitespace( 1 );
1043 48         481 return $self;
1044             }
1045              
1046             sub run {
1047 48     48   123 my ( $self, $source, $err, @err_arg ) = @_;
1048 48 50       202 defined $source
1049             and $self->set_source( $source );
1050 48         3108 my $attr = $self->_attr();
1051 48         113 @{ $attr }{ qw{ line links sections ignore_tag } } = ( 1, [], {}, [] );
  48         145  
1052 48         139 while ( my $token = $self->get_token() ) {
1053 656 50       74568 if ( my $code = $self->can( '__token_' . $token->type() ) ) {
1054 656         3302 $code->( $self, $token );
1055             }
1056             }
1057             $err
1058 48 50 33     1009 and $self->any_errata_seen()
1059             and $err->( @err_arg );
1060             return wantarray ?
1061             ( $attr->{sections}, $attr->{links} ) :
1062 48 100       515 $attr->{sections};
1063             }
1064              
1065             sub _attr {
1066 704     704   1129 my ( $self ) = @_;
1067 704   100     1832 return $self->{ ( __PACKAGE__ ) } ||= {};
1068             }
1069              
1070             sub _normalize_text {
1071 75     75   663 my ( $text ) = @_;
1072 75 50       153 defined $text
1073             or $text = '';
1074 75         213 $text =~ s/ \A \s+ //smx;
1075 75         190 $text =~ s/ \s+ \z //smx;
1076 75         165 $text =~ s/ \s+ / /smxg;
1077 75         208 return $text;
1078             }
1079              
1080             sub __token_start {
1081 219     219   377 my ( $self, $token ) = @_;
1082 219         375 my $attr = $self->_attr();
1083 219 100       490 if ( defined( my $line = $token->attr( 'start_line' ) ) ) {
1084 154         1099 $attr->{line} = $line;
1085             }
1086 219         787 my $tag = $token->tag();
1087 219 100       1443 if ( 'L' eq $tag ) {
    100          
    100          
1088 43         137 $token->attr( line_number => $self->{My_Parser}{line} );
1089 43         338 foreach my $name ( qw{ section to } ) {
1090 86 100       424 my $sect = $token->attr( $name )
1091             or next;
1092 47         1521 @{ $sect }[ 2 .. $#$sect ] = ( _normalize_text( "$sect" ) );
  47         157  
1093             }
1094 43         119 push @{ $attr->{links} }, [ @{ $token }[ 1 .. $#$token ] ];
  43         91  
  43         108  
1095             } elsif ( 'X' eq $tag ) {
1096 2         3 push @{ $attr->{ignore_tag} }, $tag;
  2         7  
1097             } elsif ( $section_tag{$tag} ) {
1098 28         55 $attr->{text} = '';
1099             }
1100 219         806 return;
1101             }
1102              
1103             sub __token_text {
1104 218     218   379 my ( $self, $token ) = @_;
1105 218         439 my $attr = $self->_attr();
1106 218 100       334 return if @{ $attr->{ignore_tag} };
  218         444  
1107 216         449 my $text = $token->text();
1108 216         904 $attr->{line} += $text =~ tr/\n//;
1109 216         458 $attr->{text} .= $text;
1110 216         671 return;
1111             }
1112              
1113             sub __token_end {
1114 219     219   358 my ( $self, $token ) = @_;
1115 219         356 my $attr = $self->_attr();
1116 219         453 my $tag = $token->tag();
1117 219 100 66     1229 if ( $section_tag{$tag} ) {
    100          
1118 28         73 $attr->{sections}{ _normalize_text( delete $attr->{text} ) } = 1;
1119 191         513 } elsif( @{ $attr->{ignore_tag} } && $tag eq $attr->{ignore_tag}[-1] ) {
1120 2         5 pop @{ $attr->{ignore_tag} };
  2         5  
1121             }
1122 219         652 return;
1123             }
1124              
1125             1;
1126              
1127             __END__