File Coverage

blib/lib/Test/Pod/LinkCheck/Lite.pm
Criterion Covered Total %
statement 516 545 94.6
branch 171 246 69.5
condition 37 63 58.7
subroutine 104 107 97.2
pod 16 16 100.0
total 844 977 86.3


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