line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test::Pod::Links; |
2
|
|
|
|
|
|
|
|
3
|
12
|
|
|
12
|
|
802833
|
use 5.006; |
|
12
|
|
|
|
|
142
|
|
4
|
12
|
|
|
12
|
|
70
|
use strict; |
|
12
|
|
|
|
|
25
|
|
|
12
|
|
|
|
|
299
|
|
5
|
12
|
|
|
12
|
|
62
|
use warnings; |
|
12
|
|
|
|
|
34
|
|
|
12
|
|
|
|
|
553
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.003'; |
8
|
|
|
|
|
|
|
|
9
|
12
|
|
|
12
|
|
86
|
use Carp (); |
|
12
|
|
|
|
|
23
|
|
|
12
|
|
|
|
|
310
|
|
10
|
12
|
|
|
12
|
|
8209
|
use HTTP::Tiny 0.014 (); |
|
12
|
|
|
|
|
582886
|
|
|
12
|
|
|
|
|
390
|
|
11
|
12
|
|
|
12
|
|
7097
|
use Pod::Simple::Search (); |
|
12
|
|
|
|
|
71582
|
|
|
12
|
|
|
|
|
297
|
|
12
|
12
|
|
|
12
|
|
4746
|
use Pod::Simple::SimpleTree (); |
|
12
|
|
|
|
|
344981
|
|
|
12
|
|
|
|
|
299
|
|
13
|
12
|
|
|
12
|
|
98
|
use Scalar::Util (); |
|
12
|
|
|
|
|
23
|
|
|
12
|
|
|
|
|
198
|
|
14
|
12
|
|
|
12
|
|
66
|
use Test::Builder (); |
|
12
|
|
|
|
|
30
|
|
|
12
|
|
|
|
|
160
|
|
15
|
12
|
|
|
12
|
|
6028
|
use Test::XTFiles (); |
|
12
|
|
|
|
|
213003
|
|
|
12
|
|
|
|
|
13792
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
my $TEST = Test::Builder->new(); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# - Do not use subtests because subtests cannot be tested with |
20
|
|
|
|
|
|
|
# Test::Builder:Tester. |
21
|
|
|
|
|
|
|
# - Do not use a plan because a method that sets a plan cannot be tested |
22
|
|
|
|
|
|
|
# with Test::Builder:Tester. |
23
|
|
|
|
|
|
|
# - Do not call done_testing in a method that should be tested by |
24
|
|
|
|
|
|
|
# Test::Builder::Tester because TBT cannot test them. |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub all_pod_files_ok { |
27
|
6
|
|
|
6
|
1
|
31
|
my $self = shift; |
28
|
|
|
|
|
|
|
|
29
|
6
|
|
|
|
|
86
|
my @files = Test::XTFiles->new->all_files(); |
30
|
6
|
100
|
|
|
|
44266
|
if ( !@files ) { |
31
|
1
|
|
|
|
|
18
|
$TEST->skip_all("No files found\n"); |
32
|
1
|
|
|
|
|
11
|
return 1; |
33
|
|
|
|
|
|
|
} |
34
|
|
|
|
|
|
|
|
35
|
5
|
|
|
|
|
82
|
my @pod_files = grep { Pod::Simple::Search->new->contains_pod($_) } @files; |
|
6
|
|
|
|
|
191
|
|
36
|
5
|
100
|
|
|
|
662
|
if ( !@pod_files ) { |
37
|
2
|
|
|
|
|
12
|
$TEST->skip_all("No files with Pod found\n"); |
38
|
2
|
|
|
|
|
24
|
return 1; |
39
|
|
|
|
|
|
|
} |
40
|
|
|
|
|
|
|
|
41
|
3
|
|
|
|
|
8
|
my $rc = 1; |
42
|
3
|
|
|
|
|
11
|
for my $file (@pod_files) { |
43
|
4
|
100
|
|
|
|
35
|
if ( !$self->pod_file_ok($file) ) { |
44
|
1
|
|
|
|
|
9
|
$rc = 0; |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
3
|
|
|
|
|
29
|
$TEST->done_testing; |
49
|
|
|
|
|
|
|
|
50
|
3
|
100
|
|
|
|
23
|
return 1 if $rc; |
51
|
1
|
|
|
|
|
8
|
return; |
52
|
|
|
|
|
|
|
} |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
sub new { |
55
|
37
|
|
|
37
|
1
|
51891
|
my $class = shift; |
56
|
|
|
|
|
|
|
|
57
|
37
|
100
|
|
|
|
258
|
Carp::croak 'Odd number of arguments' if @_ % 2; |
58
|
36
|
|
|
|
|
100
|
my %args = @_; |
59
|
|
|
|
|
|
|
|
60
|
36
|
|
|
|
|
91
|
my $self = bless {}, $class; |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# |
63
|
36
|
|
|
|
|
300
|
$self->{_cache} = {}; |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# |
66
|
36
|
|
66
|
|
|
246
|
$self->_ua( $args{ua} || HTTP::Tiny->new ); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# |
69
|
35
|
|
|
|
|
61
|
my @ignores; |
70
|
35
|
100
|
|
|
|
98
|
if ( exists $args{ignore} ) { |
71
|
7
|
|
|
|
|
16
|
my $ignore = $args{ignore}; |
72
|
7
|
100
|
|
|
|
33
|
if ( ref $ignore eq ref [] ) { |
73
|
2
|
|
|
|
|
4
|
@ignores = @{$ignore}; |
|
2
|
|
|
|
|
6
|
|
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
else { |
76
|
5
|
|
|
|
|
13
|
@ignores = $ignore; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# |
81
|
35
|
|
|
|
|
59
|
my @ignores_match; |
82
|
35
|
100
|
|
|
|
106
|
if ( exists $args{ignore_match} ) { |
83
|
8
|
|
|
|
|
17
|
my $ignore_match = $args{ignore_match}; |
84
|
8
|
100
|
|
|
|
26
|
if ( ref $ignore_match eq ref [] ) { |
85
|
4
|
|
|
|
|
6
|
@ignores_match = @{$ignore_match}; |
|
4
|
|
|
|
|
9
|
|
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
else { |
88
|
4
|
|
|
|
|
10
|
@ignores_match = $ignore_match; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
## no critic (RegularExpressions::RequireDotMatchAnything) |
93
|
|
|
|
|
|
|
## no critic (RegularExpressions::RequireExtendedFormatting) |
94
|
|
|
|
|
|
|
## no critic (RegularExpressions::RequireLineBoundaryMatching) |
95
|
35
|
|
|
|
|
105
|
my $ignore_regex = join q{|}, @ignores_match, map { qr{^\Q$_\E$} } @ignores; |
|
8
|
|
|
|
|
104
|
|
96
|
35
|
100
|
|
|
|
368
|
$self->_ignore_regex( $ignore_regex ne q{} ? qr{$ignore_regex} : undef ); |
97
|
|
|
|
|
|
|
## use critic |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
KEY: |
100
|
35
|
|
|
|
|
118
|
for my $key ( keys %args ) { |
101
|
29
|
100
|
|
|
|
70
|
next KEY if $key eq 'ignore'; |
102
|
22
|
100
|
|
|
|
52
|
next KEY if $key eq 'ignore_match'; |
103
|
14
|
100
|
|
|
|
37
|
next KEY if $key eq 'ua'; |
104
|
|
|
|
|
|
|
|
105
|
1
|
|
|
|
|
118
|
Carp::croak "new() knows nothing about argument '$key'"; |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
|
108
|
34
|
|
|
|
|
151
|
return $self; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub pod_file_ok { |
112
|
16
|
|
|
16
|
1
|
24281
|
my ( $self, $file ) = @_; |
113
|
|
|
|
|
|
|
|
114
|
16
|
100
|
100
|
|
|
406
|
Carp::croak 'usage: pod_file_ok(FILE)' if @_ != 2 || !defined $file; |
115
|
|
|
|
|
|
|
|
116
|
13
|
|
|
|
|
30
|
my $parse_msg = "Parse Pod ($file)"; |
117
|
|
|
|
|
|
|
|
118
|
13
|
100
|
|
|
|
253
|
if ( !-f $file ) { |
119
|
1
|
|
|
|
|
6
|
$TEST->ok( 0, $parse_msg ); |
120
|
1
|
|
|
|
|
1059
|
$TEST->diag("\n"); |
121
|
1
|
|
|
|
|
238
|
$TEST->diag("File $file does not exist or is not a file"); |
122
|
1
|
|
|
|
|
230
|
return; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
12
|
|
|
|
|
112
|
my $pod = Pod::Simple::SimpleTree->new->parse_file($file); |
126
|
|
|
|
|
|
|
|
127
|
12
|
100
|
|
|
|
25416
|
if ( $pod->any_errata_seen ) { |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# Pod contains errors |
130
|
1
|
|
|
|
|
11
|
$TEST->ok( 0, $parse_msg ); |
131
|
1
|
|
|
|
|
1006
|
return; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
11
|
|
|
|
|
94
|
$TEST->ok( 1, $parse_msg ); |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
my @links = |
137
|
32
|
50
|
|
|
|
189
|
grep { defined && m{ ^ http(?:s)? :// }xsmi } |
138
|
32
|
|
|
|
|
46
|
map { ${ $_->{to} }[2] } |
|
32
|
|
|
|
|
59
|
|
139
|
11
|
|
|
|
|
2859
|
grep { $_->{type} eq 'url' } $self->_extract_links_from_pod( $pod->root ); |
|
39
|
|
|
|
|
77
|
|
140
|
|
|
|
|
|
|
|
141
|
11
|
|
|
|
|
31
|
my $ignore_regex = $self->_ignore_regex; |
142
|
11
|
100
|
|
|
|
24
|
if ( defined $ignore_regex ) { |
143
|
3
|
|
|
|
|
5
|
@links = grep { $_ !~ $ignore_regex } @links; |
|
15
|
|
|
|
|
63
|
|
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
11
|
|
|
|
|
19
|
my $rc = 1; |
147
|
11
|
|
|
|
|
23
|
my $ua = $self->_ua; |
148
|
11
|
|
|
|
|
17
|
my %url_checked_in_this_file; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
LINK: |
151
|
11
|
|
|
|
|
21
|
for my $link (@links) { |
152
|
26
|
100
|
|
|
|
534
|
next LINK if exists $url_checked_in_this_file{$link}; |
153
|
24
|
|
|
|
|
43
|
$url_checked_in_this_file{$link} = 1; |
154
|
|
|
|
|
|
|
|
155
|
24
|
100
|
|
|
|
57
|
if ( !exists $self->{_cache}->{$link} ) { |
156
|
21
|
|
|
|
|
59
|
$self->{_cache}->{$link} = $ua->head($link); |
157
|
|
|
|
|
|
|
} |
158
|
24
|
|
|
|
|
225
|
my $res = $self->{_cache}->{$link}; |
159
|
|
|
|
|
|
|
|
160
|
24
|
|
|
|
|
105
|
$TEST->ok( $res->{success}, "$link ($file)" ); |
161
|
|
|
|
|
|
|
|
162
|
24
|
100
|
|
|
|
7170
|
if ( !$res->{success} ) { |
163
|
2
|
|
|
|
|
5
|
$rc = 0; |
164
|
2
|
|
|
|
|
7
|
$TEST->diag("\n"); |
165
|
2
|
|
|
|
|
470
|
$TEST->diag( $res->{reason} ); |
166
|
2
|
|
|
|
|
542
|
$TEST->diag("\n"); |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
11
|
100
|
|
|
|
138
|
return 1 if $rc; |
171
|
2
|
|
|
|
|
30
|
return; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub _extract_links_from_pod { |
175
|
142
|
|
|
142
|
|
9653
|
my ( $self, $node_ref ) = @_; |
176
|
|
|
|
|
|
|
|
177
|
142
|
100
|
100
|
|
|
808
|
Carp::croak 'usage: _extract_links_from_pod([ elementname, \%attributes, ...subnodes... ])' if @_ != 2 || ref $node_ref ne ref [] || scalar @{$node_ref} < 2; |
|
139
|
|
100
|
|
|
392
|
|
178
|
|
|
|
|
|
|
|
179
|
138
|
|
|
|
|
188
|
my @links; |
180
|
138
|
|
|
|
|
172
|
my ( $elem_name, $attr_ref, @subnodes ) = @{$node_ref}; |
|
138
|
|
|
|
|
281
|
|
181
|
|
|
|
|
|
|
|
182
|
138
|
100
|
|
|
|
249
|
if ( $elem_name eq 'L' ) { |
183
|
48
|
|
|
|
|
71
|
push @links, $attr_ref; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
SUBNODE: |
187
|
138
|
|
|
|
|
197
|
for my $subnode (@subnodes) { |
188
|
285
|
100
|
|
|
|
581
|
next SUBNODE if ref $subnode ne ref []; |
189
|
|
|
|
|
|
|
|
190
|
124
|
|
|
|
|
220
|
push @links, $self->_extract_links_from_pod($subnode); |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
138
|
|
|
|
|
297
|
return @links; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub _ignore_regex { |
197
|
88
|
|
|
88
|
|
3878
|
my $self = shift; |
198
|
|
|
|
|
|
|
|
199
|
88
|
100
|
|
|
|
228
|
if (@_) { |
200
|
37
|
|
|
|
|
60
|
my $ignore_regex = shift; |
201
|
37
|
|
|
|
|
84
|
$self->{_ignore_regex} = $ignore_regex; |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
88
|
|
|
|
|
480
|
return $self->{_ignore_regex}; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub _ua { |
208
|
53
|
|
|
53
|
|
4329
|
my $self = shift; |
209
|
|
|
|
|
|
|
|
210
|
53
|
100
|
|
|
|
139
|
if (@_) { |
211
|
40
|
|
|
|
|
64
|
my $ua = shift; |
212
|
40
|
100
|
100
|
|
|
841
|
Carp::croak q{ua must have method 'head'} if !Scalar::Util::blessed($ua) || !$ua->can('head'); |
213
|
37
|
|
|
|
|
106
|
$self->{_ua} = $ua; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
50
|
|
|
|
|
111
|
return $self->{_ua}; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
1; |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
__END__ |