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