| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # Copyright 2010, 2011, 2012, 2013 Kevin Ryde | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | # This file is part of PodLinkCheck. | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | # PodLinkCheck is free software; you can redistribute it and/or modify it | 
| 6 |  |  |  |  |  |  | # under the terms of the GNU General Public License as published by the Free | 
| 7 |  |  |  |  |  |  | # Software Foundation; either version 3, or (at your option) any later | 
| 8 |  |  |  |  |  |  | # version. | 
| 9 |  |  |  |  |  |  | # | 
| 10 |  |  |  |  |  |  | # PodLinkCheck is distributed in the hope that it will be useful, but | 
| 11 |  |  |  |  |  |  | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | 
| 12 |  |  |  |  |  |  | # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License | 
| 13 |  |  |  |  |  |  | # for more details. | 
| 14 |  |  |  |  |  |  | # | 
| 15 |  |  |  |  |  |  | # You should have received a copy of the GNU General Public License along | 
| 16 |  |  |  |  |  |  | # with PodLinkCheck.  If not, see . | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | package App::PodLinkCheck; | 
| 19 | 1 |  |  | 1 |  | 834 | use 5.006; | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 38 |  | 
| 20 | 1 |  |  | 1 |  | 6 | use strict; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 28 |  | 
| 21 | 1 |  |  | 1 |  | 5 | use warnings; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 21 |  | 
| 22 | 1 |  |  | 1 |  | 6 | use Carp; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 94 |  | 
| 23 | 1 |  |  | 1 |  | 1051 | use Locale::TextDomain ('App-PodLinkCheck'); | 
|  | 1 |  |  |  |  | 45280 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 24 |  |  |  |  |  |  |  | 
| 25 | 1 |  |  | 1 |  | 13078 | use vars '$VERSION'; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 593 |  | 
| 26 |  |  |  |  |  |  | $VERSION = 12; | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | # uncomment this to run the ### lines | 
| 29 |  |  |  |  |  |  | # use Smart::Comments; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | sub command_line { | 
| 32 | 0 |  |  | 0 | 1 | 0 | my ($self) = @_; | 
| 33 |  |  |  |  |  |  | ### command_line(): @ARGV | 
| 34 | 0 | 0 |  |  |  | 0 | ref $self or $self = $self->new; | 
| 35 |  |  |  |  |  |  |  | 
| 36 | 0 |  |  |  |  | 0 | require Getopt::Long; | 
| 37 | 0 |  |  |  |  | 0 | Getopt::Long::Configure ('permute',  # options with args, callback '<>' | 
| 38 |  |  |  |  |  |  | 'no_ignore_case', | 
| 39 |  |  |  |  |  |  | 'bundling'); | 
| 40 |  |  |  |  |  |  | Getopt::Long::GetOptions | 
| 41 | 0 |  |  | 0 |  | 0 | ('version'   => sub { $self->action_version }, | 
| 42 | 0 |  |  | 0 |  | 0 | 'help'      => sub { $self->action_help }, | 
| 43 |  |  |  |  |  |  | 'verbose:i' => \$self->{'verbose'}, | 
| 44 |  |  |  |  |  |  | 'V+'        => \$self->{'verbose'}, | 
| 45 |  |  |  |  |  |  | 'I=s'       => $self->{'extra_INC'}, | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | '<>' => sub { | 
| 48 | 0 |  |  | 0 |  | 0 | my ($value) = @_; | 
| 49 |  |  |  |  |  |  | # stringize to avoid Getopt::Long object | 
| 50 | 0 |  |  |  |  | 0 | $self->check_tree ("$value"); | 
| 51 |  |  |  |  |  |  | }, | 
| 52 | 0 |  |  |  |  | 0 | ); | 
| 53 |  |  |  |  |  |  | ### final ARGV: @ARGV | 
| 54 | 0 |  |  |  |  | 0 | $self->check_tree (@ARGV); | 
| 55 | 0 |  |  |  |  | 0 | return 0; | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | sub action_version { | 
| 59 | 0 |  |  | 0 | 0 | 0 | my ($self) = @_; | 
| 60 | 0 |  |  |  |  | 0 | print __x("PodLinkCheck version {version}\n", version => $self->VERSION); | 
| 61 | 0 | 0 |  |  |  | 0 | if ($self->{'verbose'} >= 2) { | 
| 62 | 0 |  |  |  |  | 0 | require Pod::Simple; | 
| 63 | 0 |  |  |  |  | 0 | print __x("  Perl        version {version}\n", version => $]); | 
| 64 | 0 |  |  |  |  | 0 | print __x("  Pod::Simple version {version}\n", version => Pod::Simple->VERSION); | 
| 65 |  |  |  |  |  |  | } | 
| 66 | 0 |  |  |  |  | 0 | return 0; | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | sub action_help { | 
| 70 | 0 |  |  | 0 | 0 | 0 | my ($self) = @_; | 
| 71 | 0 |  |  |  |  | 0 | require FindBin; | 
| 72 | 1 |  |  | 1 |  | 7 | no warnings 'once'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 579 |  | 
| 73 | 0 |  |  |  |  | 0 | my $progname = $FindBin::Script; | 
| 74 | 0 |  |  |  |  | 0 | print __x("Usage: $progname [--options] file-or-dir...\n"); | 
| 75 | 0 |  |  |  |  | 0 | print __x("  --help         print this message\n"); | 
| 76 | 0 |  |  |  |  | 0 | print __x("  --version      print version number (and module versions if --verbose=2)\n"); | 
| 77 | 0 |  |  |  |  | 0 | print __x("  --verbose      print diagnostic details\n"); | 
| 78 | 0 |  |  |  |  | 0 | print __x("  --verbose=2    print even more diagnostics\n"); | 
| 79 | 0 |  |  |  |  | 0 | return 0; | 
| 80 |  |  |  |  |  |  | } | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | sub new { | 
| 86 | 6 |  |  | 6 | 1 | 6108 | my ($class, @options) = @_; | 
| 87 | 6 |  |  |  |  | 52 | return bless { verbose => 0, | 
| 88 |  |  |  |  |  |  | cpan_methods => ['CPAN_SQLite','CPAN','CPANPLUS'], | 
| 89 |  |  |  |  |  |  | extra_INC => [], | 
| 90 |  |  |  |  |  |  | @options }, $class; | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | sub check_tree { | 
| 94 | 0 |  |  | 0 | 1 | 0 | my ($self, @files_or_directories) = @_; | 
| 95 |  |  |  |  |  |  | ### check_tree(): \@files_or_directories | 
| 96 |  |  |  |  |  |  |  | 
| 97 | 0 | 0 |  |  |  | 0 | my $order = eval { require Sort::Key::Natural } | 
|  | 0 |  |  |  |  | 0 |  | 
| 98 |  |  |  |  |  |  | ? \&_find_order_natural : \&_find_order_plain; | 
| 99 |  |  |  |  |  |  | ### Natural: $@ | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 0 |  |  |  |  | 0 | foreach my $filename (@files_or_directories) { | 
| 102 | 0 | 0 |  |  |  | 0 | if (-d $filename) { | 
| 103 | 0 |  |  |  |  | 0 | require File::Find::Iterator; | 
| 104 | 0 |  |  |  |  | 0 | my $finder = File::Find::Iterator->create (dir => [$filename], | 
| 105 |  |  |  |  |  |  | order => $order, | 
| 106 |  |  |  |  |  |  | filter => \&_is_perlfile); | 
| 107 | 0 |  |  |  |  | 0 | while ($filename = $finder->next) { | 
| 108 | 0 |  |  |  |  | 0 | print "$filename:\n"; | 
| 109 | 0 |  |  |  |  | 0 | $self->check_file ($filename); | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  | } else { | 
| 112 | 0 |  |  |  |  | 0 | print "$filename:\n"; | 
| 113 | 0 |  |  |  |  | 0 | $self->check_file ($filename); | 
| 114 |  |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | #         ### recurse dir: $filename | 
| 118 |  |  |  |  |  |  | #         require File::Find; | 
| 119 |  |  |  |  |  |  | #         File::Find::find ({ wanted => sub { | 
| 120 |  |  |  |  |  |  | #                               #### file: $_ | 
| 121 |  |  |  |  |  |  | #                               if (_is_perlfile()) { | 
| 122 |  |  |  |  |  |  | #                                 print "$_:\n"; | 
| 123 |  |  |  |  |  |  | #                                 $self->check_file ($_); | 
| 124 |  |  |  |  |  |  | #                               } | 
| 125 |  |  |  |  |  |  | #                             }, | 
| 126 |  |  |  |  |  |  | #                             follow_fast => 1, | 
| 127 |  |  |  |  |  |  | #                             preprocess => \&_find_sort, | 
| 128 |  |  |  |  |  |  | #                             no_chdir => 1, | 
| 129 |  |  |  |  |  |  | #                           }, | 
| 130 |  |  |  |  |  |  | #                           $filename); | 
| 131 |  |  |  |  |  |  | #       } else { | 
| 132 |  |  |  |  |  |  | #         print "$filename:\n"; | 
| 133 |  |  |  |  |  |  | #         $self->check_file ($filename); | 
| 134 |  |  |  |  |  |  | #       } | 
| 135 |  |  |  |  |  |  | #     } | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | sub _is_perlfile { | 
| 139 |  |  |  |  |  |  | ### _is_perlfile(): $_ | 
| 140 |  |  |  |  |  |  | return (! -d | 
| 141 | 0 |  | 0 | 0 |  | 0 | && ! m{/\.#}   # not emacs lockfile | 
| 142 |  |  |  |  |  |  | && /\.p([lm]|od)$/); | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | use constant::defer _HAVE_SORT_KEY_NATURAL => sub { | 
| 146 | 0 |  |  |  |  | 0 | eval { require Sort::Key::Natural; 1 } | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 147 | 1 |  |  | 1 |  | 5260 | }; | 
|  | 1 |  |  |  |  | 907 |  | 
|  | 1 |  |  |  |  | 11 |  | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | # sub _find_sort { | 
| 150 |  |  |  |  |  |  | #   # print "_find_sort\n"; | 
| 151 |  |  |  |  |  |  | #   return sort _find_order @_; | 
| 152 |  |  |  |  |  |  | # } | 
| 153 |  |  |  |  |  |  | sub _find_order_plain { | 
| 154 | 0 |  |  | 0 |  | 0 | my ($x, $y) = @_; | 
| 155 |  |  |  |  |  |  | # if $x or $y is a dangling symlink then -d is undef rather than '' false, | 
| 156 |  |  |  |  |  |  | # hence "||0" | 
| 157 | 0 |  | 0 |  |  | 0 | return (_cmp_file_before_directory($y,$x) | 
| 158 |  |  |  |  |  |  | || lc($y) cmp lc($x) | 
| 159 |  |  |  |  |  |  | || $y cmp $x); | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  | sub _find_order_natural { | 
| 162 | 0 |  |  | 0 |  | 0 | my ($x, $y) = @_; | 
| 163 |  |  |  |  |  |  | # if $x or $y is a dangling symlink then -d is undef rather than '' false, | 
| 164 |  |  |  |  |  |  | # hence "||0" | 
| 165 |  |  |  |  |  |  | return (_cmp_file_before_directory($y,$x) | 
| 166 | 0 |  | 0 |  |  | 0 | || do { | 
| 167 |  |  |  |  |  |  | $x = Sort::Key::Natural::mkkey_natural($x); | 
| 168 |  |  |  |  |  |  | $y = Sort::Key::Natural::mkkey_natural($y); | 
| 169 |  |  |  |  |  |  | lc($y) cmp lc($x) | 
| 170 |  |  |  |  |  |  | || $y cmp $x | 
| 171 |  |  |  |  |  |  | }); | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  | # $x,$y are filenames.  Return spaceship style <=> comparision 1,false,-1 | 
| 174 |  |  |  |  |  |  | # which reckons files before directories, ie. file less than directory.. | 
| 175 |  |  |  |  |  |  | sub _cmp_file_before_directory { | 
| 176 | 0 |  |  | 0 |  | 0 | my ($x, $y) = @_; | 
| 177 |  |  |  |  |  |  | # If $x or $y is a dangling symlink then -d is undef rather than '' false, | 
| 178 |  |  |  |  |  |  | # hence "||0" for the compare. | 
| 179 | 0 |  | 0 |  |  | 0 | return (-d $x || 0) <=> (-d $y || 0); | 
|  |  |  | 0 |  |  |  |  | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | sub check_file { | 
| 183 | 0 |  |  | 0 | 1 | 0 | my ($self, $filename) = @_; | 
| 184 | 0 |  |  |  |  | 0 | require App::PodLinkCheck::ParseLinks; | 
| 185 | 0 |  |  |  |  | 0 | my $parser = App::PodLinkCheck::ParseLinks->new ($self); | 
| 186 | 0 |  |  |  |  | 0 | $parser->parse_from_file ($filename); | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 0 |  |  |  |  | 0 | my $own_sections = $parser->sections_hashref; | 
| 189 |  |  |  |  |  |  | ### $own_sections | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 0 |  |  |  |  | 0 | foreach my $link (@{$parser->links_arrayref}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 192 | 0 |  |  |  |  | 0 | my ($type, $to, $section, $linenum, $column) = @$link; | 
| 193 |  |  |  |  |  |  |  | 
| 194 | 0 | 0 |  |  |  | 0 | if ($self->{'verbose'}) { | 
| 195 | 0 | 0 |  |  |  | 0 | print "Link: $type ",(defined $to ? $to : '[undef]'), | 
|  |  | 0 |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | (defined $section ? " / $section" : ""), "\n"; | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 0 | 0 |  |  |  | 0 | if ($type eq 'man') { | 
| 200 | 0 | 0 |  |  |  | 0 | if (! $self->manpage_is_known($to)) { | 
| 201 | 0 |  |  |  |  | 0 | $self->report ($filename, $linenum, $column, | 
| 202 |  |  |  |  |  |  | __x('no man page "{name}"', name => $to)); | 
| 203 |  |  |  |  |  |  | } | 
| 204 | 0 |  |  |  |  | 0 | next; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 | 0 | 0 |  |  |  | 0 | if (! defined $to) { | 
| 208 | 0 | 0 | 0 |  |  | 0 | if (defined $section | 
| 209 |  |  |  |  |  |  | && ! $own_sections->{$section}) { | 
| 210 | 0 | 0 |  |  |  | 0 | if (my $approximations | 
| 211 |  |  |  |  |  |  | = _section_approximations($section,$own_sections)) { | 
| 212 | 0 |  |  |  |  | 0 | $self->report ($filename, $linenum, $column, | 
| 213 |  |  |  |  |  |  | __x("no section \"{section}\"\n    perhaps it should be {approximations}", | 
| 214 |  |  |  |  |  |  | section => $section, | 
| 215 |  |  |  |  |  |  | approximations => $approximations)); | 
| 216 |  |  |  |  |  |  | } else { | 
| 217 | 0 |  |  |  |  | 0 | $self->report ($filename, $linenum, $column, | 
| 218 |  |  |  |  |  |  | __x('no section "{section}"', | 
| 219 |  |  |  |  |  |  | section => $section)); | 
| 220 |  |  |  |  |  |  | } | 
| 221 | 0 | 0 |  |  |  | 0 | if ($self->{'verbose'} >= 2) { | 
| 222 | 0 |  |  |  |  | 0 | print __("    available sections:\n"); | 
| 223 | 0 |  |  |  |  | 0 | foreach my $section (keys %$own_sections) { | 
| 224 | 0 |  |  |  |  | 0 | print "    $section\n"; | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  | } | 
| 228 | 0 |  |  |  |  | 0 | next; | 
| 229 |  |  |  |  |  |  | } | 
| 230 |  |  |  |  |  |  |  | 
| 231 | 0 |  | 0 |  |  | 0 | my $podfile = ($self->module_to_podfile($to) | 
| 232 |  |  |  |  |  |  | || $self->find_script($to)); | 
| 233 |  |  |  |  |  |  | ### $podfile | 
| 234 | 0 | 0 |  |  |  | 0 | if (! defined $podfile) { | 
| 235 | 0 | 0 |  |  |  | 0 | if (my $method = $self->_module_known_cpan($to)) { | 
| 236 | 0 | 0 | 0 |  |  | 0 | if (defined $section && $section ne '') { | 
| 237 | 0 |  |  |  |  | 0 | $self->report ($filename, $linenum, $column, | 
| 238 |  |  |  |  |  |  | __x('target "{name}" on cpan ({method}) but no local copy to check section "{section}"', | 
| 239 |  |  |  |  |  |  | name => $to, | 
| 240 |  |  |  |  |  |  | method => $method, | 
| 241 |  |  |  |  |  |  | section => $section)); | 
| 242 |  |  |  |  |  |  | } | 
| 243 | 0 |  |  |  |  | 0 | next; | 
| 244 |  |  |  |  |  |  | } | 
| 245 |  |  |  |  |  |  | } | 
| 246 |  |  |  |  |  |  |  | 
| 247 | 0 | 0 | 0 |  |  | 0 | if (! defined $podfile | 
|  |  |  | 0 |  |  |  |  | 
| 248 |  |  |  |  |  |  | && ! defined $section | 
| 249 |  |  |  |  |  |  | && $self->manpage_is_known($to)) { | 
| 250 |  |  |  |  |  |  | # perhaps a script or something we can't find the source but does | 
| 251 |  |  |  |  |  |  | # have a manpage -- take that as good enough | 
| 252 | 0 |  |  |  |  | 0 | next; | 
| 253 |  |  |  |  |  |  | } | 
| 254 | 0 | 0 | 0 |  |  | 0 | if (! defined $section | 
|  |  |  | 0 |  |  |  |  | 
| 255 |  |  |  |  |  |  | && _is_one_word($to) | 
| 256 |  |  |  |  |  |  | && $own_sections->{$to}) { | 
| 257 |  |  |  |  |  |  | # one-word internal section | 
| 258 | 0 | 0 |  |  |  | 0 | if (defined $podfile) { | 
| 259 |  |  |  |  |  |  | # print "$filename:$linenum:$column: target \"$to\" is both external module/program and internal section\n"; | 
| 260 |  |  |  |  |  |  | } else { | 
| 261 | 0 |  |  |  |  | 0 | $self->report ($filename, $linenum, $column, | 
| 262 |  |  |  |  |  |  | __x('internal one-word link recommend guard against ambiguity with {slash} or {quote}', | 
| 263 |  |  |  |  |  |  | slash => "L"._escape_angles($to).">", | 
| 264 |  |  |  |  |  |  | quote => "L<\""._escape_angles($to)."\">")); | 
| 265 |  |  |  |  |  |  | } | 
| 266 | 0 |  |  |  |  | 0 | next; | 
| 267 |  |  |  |  |  |  | } | 
| 268 | 0 | 0 |  |  |  | 0 | if (! defined $podfile) { | 
| 269 | 0 | 0 |  |  |  | 0 | if ($own_sections->{$to}) { | 
| 270 |  |  |  |  |  |  | # multi-word internal section | 
| 271 | 0 |  |  |  |  | 0 | return; | 
| 272 |  |  |  |  |  |  | } | 
| 273 | 0 |  |  |  |  | 0 | $self->report ($filename, $linenum, $column, | 
| 274 |  |  |  |  |  |  | "no module/program/pod \"$to\""); | 
| 275 | 0 |  |  |  |  | 0 | next; | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 0 | 0 | 0 |  |  | 0 | if (defined $section && $section ne '') { | 
| 279 | 0 |  |  |  |  | 0 | my $podfile_sections = $self->filename_to_sections ($podfile); | 
| 280 | 0 | 0 |  |  |  | 0 | if (! $podfile_sections->{$section}) { | 
| 281 | 0 | 0 |  |  |  | 0 | if (my $approximations | 
| 282 |  |  |  |  |  |  | = _section_approximations($section,$podfile_sections)) { | 
| 283 | 0 |  |  |  |  | 0 | $self->report ($filename, $linenum, $column, | 
| 284 |  |  |  |  |  |  | __x("no section \"{section}\" in \"{name}\" (file {filename})\n    perhaps it should be {approximations}", | 
| 285 |  |  |  |  |  |  | name => $to, | 
| 286 |  |  |  |  |  |  | section => $section, | 
| 287 |  |  |  |  |  |  | filename => $podfile, | 
| 288 |  |  |  |  |  |  | approximations => $approximations)); | 
| 289 |  |  |  |  |  |  | } else { | 
| 290 | 0 |  |  |  |  | 0 | $self->report ($filename, $linenum, $column, | 
| 291 |  |  |  |  |  |  | __x('no section "{section}" in "{name}" (file {filename})', | 
| 292 |  |  |  |  |  |  | name => $to, | 
| 293 |  |  |  |  |  |  | section => $section, | 
| 294 |  |  |  |  |  |  | filename => $podfile)); | 
| 295 |  |  |  |  |  |  | } | 
| 296 | 0 | 0 |  |  |  | 0 | if ($self->{'verbose'} >= 2) { | 
| 297 | 0 |  |  |  |  | 0 | print __("    available sections:\n"); | 
| 298 | 0 |  |  |  |  | 0 | foreach my $section (keys %$podfile_sections) { | 
| 299 | 0 |  |  |  |  | 0 | print "    $section\n"; | 
| 300 |  |  |  |  |  |  | } | 
| 301 |  |  |  |  |  |  | } | 
| 302 |  |  |  |  |  |  | } | 
| 303 |  |  |  |  |  |  | } | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  | } | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | sub report { | 
| 308 | 0 |  |  | 0 | 0 | 0 | my ($self, $filename, $linenum, $column, $message) = @_; | 
| 309 | 0 |  |  |  |  | 0 | print "$filename:$linenum:$column: $message\n"; | 
| 310 |  |  |  |  |  |  | } | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | # return a string of close matches of $section in the keys of %$hashref | 
| 313 |  |  |  |  |  |  | sub _section_approximations { | 
| 314 | 0 |  |  | 0 |  | 0 | my ($section, $hashref) = @_; | 
| 315 | 0 |  |  |  |  | 0 | $section = _section_approximation_crunch($section); | 
| 316 | 0 |  |  |  |  | 0 | return join(' or ', | 
| 317 | 0 |  |  |  |  | 0 | map {"\"$_\""} | 
| 318 | 0 |  |  |  |  | 0 | grep {_section_approximation_crunch($_) eq $section} | 
| 319 |  |  |  |  |  |  | keys %$hashref); | 
| 320 |  |  |  |  |  |  | } | 
| 321 |  |  |  |  |  |  | sub _section_approximation_crunch { | 
| 322 | 0 |  |  | 0 |  | 0 | my ($section) = @_; | 
| 323 | 0 |  |  |  |  | 0 | $section =~ s/(\W|_)+//g; | 
| 324 | 0 |  |  |  |  | 0 | return lc($section); | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | sub _is_one_word { | 
| 328 | 0 |  |  | 0 |  | 0 | my ($link) = @_; | 
| 329 | 0 |  |  |  |  | 0 | return ($link !~ /\W/); | 
| 330 |  |  |  |  |  |  | } | 
| 331 |  |  |  |  |  |  | sub _escape_angles { | 
| 332 | 3 |  |  | 3 |  | 1717 | my ($str) = @_; | 
| 333 | 3 |  |  |  |  | 15 | $str =~ s{([<>])} | 
| 334 | 4 | 100 |  |  |  | 20 | { 'E<'.($1 eq '<' ? 'lt' : 'gt').'>' }ge; | 
| 335 | 3 |  |  |  |  | 19 | return $str; | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | sub module_to_podfile { | 
| 339 | 0 |  |  | 0 | 0 | 0 | my ($self, $module) = @_; | 
| 340 | 0 |  |  |  |  | 0 | require Pod::Find; | 
| 341 | 0 |  |  |  |  | 0 | return Pod::Find::pod_where ({ '-dirs' => $self->{'extra_INC'}, | 
| 342 |  |  |  |  |  |  | '-inc' => 1, | 
| 343 |  |  |  |  |  |  | }, | 
| 344 |  |  |  |  |  |  | $module); | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | # return hashref | 
| 348 |  |  |  |  |  |  | sub filename_to_sections { | 
| 349 | 0 |  |  | 0 | 0 | 0 | my ($self, $filename) = @_; | 
| 350 | 0 |  | 0 |  |  | 0 | return ($self->{'sections_cache'}->{$filename} ||= do { | 
| 351 |  |  |  |  |  |  | ### parse file for sections: $filename | 
| 352 | 0 |  |  |  |  | 0 | my $parser = App::PodLinkCheck::ParseSections->new; | 
| 353 | 0 |  |  |  |  | 0 | $parser->parse_file ($filename); | 
| 354 |  |  |  |  |  |  | ### file sections: $parser->sections_hashref | 
| 355 | 0 |  |  |  |  | 0 | $parser->sections_hashref; | 
| 356 |  |  |  |  |  |  | }); | 
| 357 |  |  |  |  |  |  | } | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 360 |  |  |  |  |  |  | # CPAN | 
| 361 |  |  |  |  |  |  | # | 
| 362 |  |  |  |  |  |  | # cf CPAN::API::HOWTO | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | sub _module_known_cpan { | 
| 365 | 0 |  |  | 0 |  | 0 | my ($self, $module) = @_; | 
| 366 | 0 |  |  |  |  | 0 | foreach my $method (@{$self->{'cpan_methods'}}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 367 | 0 |  |  |  |  | 0 | my $fullmethod = "_module_known_$method"; | 
| 368 | 0 | 0 |  |  |  | 0 | if ($self->$fullmethod ($module)) { | 
| 369 | 0 |  |  |  |  | 0 | return $method; | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  | } | 
| 372 | 0 |  |  |  |  | 0 | return 0; | 
| 373 |  |  |  |  |  |  | } | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | use constant::defer _CPAN_config => sub { | 
| 376 | 1 |  |  |  |  | 19 | my $result = 0; | 
| 377 | 1 | 50 |  |  |  | 3 | eval { | 
| 378 | 1 |  |  |  |  | 5853 | require CPAN; | 
| 379 |  |  |  |  |  |  | # not sure how far back this will work, maybe only 5.8.0 up | 
| 380 | 1 | 50 | 33 |  |  | 362731 | if (! $CPAN::Config_loaded | 
| 381 |  |  |  |  |  |  | && CPAN::HandleConfig->can('load')) { | 
| 382 |  |  |  |  |  |  | # fake $loading to avoid running the CPAN::FirstTime dialog -- is | 
| 383 |  |  |  |  |  |  | # this the right way to do that? | 
| 384 | 1 |  |  |  |  | 11 | local $CPAN::HandleConfig::loading = 1; | 
| 385 | 1 |  |  |  |  | 22 | print __x("PodLinkCheck: {module} configs\n", | 
| 386 |  |  |  |  |  |  | module => 'CPAN'); | 
| 387 | 1 |  |  |  |  | 1055 | CPAN::HandleConfig->load; | 
| 388 |  |  |  |  |  |  | } | 
| 389 | 1 |  |  |  |  | 469 | $result = 1; | 
| 390 |  |  |  |  |  |  | } | 
| 391 |  |  |  |  |  |  | or print "CPAN.pm config error: $@\n"; | 
| 392 | 1 |  |  |  |  | 18 | return $result; | 
| 393 | 1 |  |  | 1 |  | 2778 | }; | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 10 |  | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | sub _module_known_CPAN_SQLite { | 
| 396 | 3 |  |  | 3 |  | 869 | my ($self, $module) = @_; | 
| 397 |  |  |  |  |  |  |  | 
| 398 | 3 | 100 |  |  |  | 26 | if (! defined $self->{'cpan_sqlite'}) { | 
| 399 | 1 |  |  |  |  | 4 | $self->{'cpan_sqlite'} = 0; | 
| 400 |  |  |  |  |  |  |  | 
| 401 | 1 | 50 |  |  |  | 9 | if ($self->_CPAN_config) { | 
| 402 | 1 |  |  |  |  | 94 | print __x("PodLinkCheck: loading {module} for module existence checking\n", | 
| 403 |  |  |  |  |  |  | module => 'CPAN::SQLite'); | 
| 404 | 1 | 50 |  |  |  | 410 | if (! eval { require CPAN::SQLite }) { | 
|  | 1 |  |  |  |  | 394 |  | 
| 405 | 1 |  |  |  |  | 17 | print __x("Cannot load {module}, skipping -- {error}\n", | 
| 406 |  |  |  |  |  |  | module => 'CPAN::SQLite', | 
| 407 |  |  |  |  |  |  | error => $@); | 
| 408 | 1 |  |  |  |  | 243 | return 0; | 
| 409 |  |  |  |  |  |  | } | 
| 410 | 0 | 0 |  |  |  | 0 | if (! eval { | 
| 411 |  |  |  |  |  |  | # fake $loading to avoid running the CPAN::FirstTime dialog -- is | 
| 412 |  |  |  |  |  |  | # this the right way to do that? | 
| 413 | 0 |  |  |  |  | 0 | local $CPAN::HandleConfig::loading = 1; | 
| 414 | 0 |  |  |  |  | 0 | $self->{'cpan_sqlite'} = CPAN::SQLite->new (update_indices => 0); | 
| 415 |  |  |  |  |  |  | }) { | 
| 416 | 0 |  |  |  |  | 0 | print __x("{module} error: {error}\n", | 
| 417 |  |  |  |  |  |  | module => 'CPAN::SQLite', | 
| 418 |  |  |  |  |  |  | error => $@); | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  | } | 
| 422 |  |  |  |  |  |  |  | 
| 423 | 2 |  | 50 |  |  | 26 | my $cpan_sqlite = $self->{'cpan_sqlite'} || return 0; | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | # Have struck errors from cpantesters creating db tables.  Not sure if it | 
| 426 |  |  |  |  |  |  | # might happen in a real run.  Guard with an eval. | 
| 427 |  |  |  |  |  |  | # | 
| 428 | 0 |  |  |  |  | 0 | my $result; | 
| 429 | 0 | 0 |  |  |  | 0 | if (! eval { $result = $cpan_sqlite->query (mode => 'module', | 
|  | 0 |  |  |  |  | 0 |  | 
| 430 |  |  |  |  |  |  | name => $module); | 
| 431 | 0 |  |  |  |  | 0 | 1 }) { | 
| 432 | 0 |  |  |  |  | 0 | print __x("{module} error, disabling -- {error}\n", | 
| 433 |  |  |  |  |  |  | module => 'CPAN::SQLite', | 
| 434 |  |  |  |  |  |  | error  => $@); | 
| 435 | 0 |  |  |  |  | 0 | $self->{'cpan_sqlite'} = 0; | 
| 436 | 0 |  |  |  |  | 0 | return 0; | 
| 437 |  |  |  |  |  |  | } | 
| 438 | 0 |  |  |  |  | 0 | return $result; | 
| 439 |  |  |  |  |  |  | } | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | my $use_CPAN; | 
| 442 |  |  |  |  |  |  | sub _module_known_CPAN { | 
| 443 | 3 |  |  | 3 |  | 1099 | my ($self, $module) = @_; | 
| 444 |  |  |  |  |  |  | ### _module_known_CPAN(): $module | 
| 445 |  |  |  |  |  |  |  | 
| 446 | 3 | 100 |  |  |  | 11 | if (! defined $use_CPAN) { | 
| 447 | 1 |  |  |  |  | 2 | $use_CPAN = 0; | 
| 448 |  |  |  |  |  |  |  | 
| 449 | 1 | 50 |  |  |  | 4 | if ($self->_CPAN_config) { | 
| 450 | 1 | 50 |  |  |  | 2 | eval { | 
| 451 | 1 |  |  |  |  | 4 | print __x("PodLinkCheck: load {module} for module existence checking\n", | 
| 452 |  |  |  |  |  |  | module => 'CPAN'); | 
| 453 |  |  |  |  |  |  |  | 
| 454 | 1 | 50 | 33 |  |  | 1731 | if (defined $CPAN::META && %$CPAN::META) { | 
|  |  | 50 |  |  |  |  |  | 
| 455 | 0 |  |  |  |  | 0 | $use_CPAN = 1; | 
| 456 |  |  |  |  |  |  | } elsif (! CPAN::Index->can('read_metadata_cache')) { | 
| 457 | 0 |  |  |  |  | 0 | print __("PodLinkCheck: no Metadata cache in this CPAN.pm\n"); | 
| 458 |  |  |  |  |  |  | } else { | 
| 459 |  |  |  |  |  |  | # try the .cpan/Metadata even if CPAN::SQLite is installed, just in | 
| 460 |  |  |  |  |  |  | # case the SQLite is not up-to-date or has not been used yet | 
| 461 | 1 |  |  |  |  | 4 | local $CPAN::Config->{use_sqlite} = 0; | 
| 462 | 1 |  |  |  |  | 11 | CPAN::Index->read_metadata_cache; | 
| 463 | 1 | 50 | 33 |  |  | 2787071 | if (defined $CPAN::META && %$CPAN::META) { | 
| 464 | 1 |  |  |  |  | 5 | $use_CPAN = 1; | 
| 465 |  |  |  |  |  |  | } else { | 
| 466 | 0 |  |  |  |  | 0 | print __("PodLinkCheck: empty Metadata cache\n"); | 
| 467 |  |  |  |  |  |  | } | 
| 468 |  |  |  |  |  |  | } | 
| 469 | 1 |  |  |  |  | 7 | 1; | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  | or print "CPAN.pm error: $@\n"; | 
| 472 |  |  |  |  |  |  | } | 
| 473 |  |  |  |  |  |  | } | 
| 474 |  |  |  |  |  |  |  | 
| 475 | 3 |  | 66 |  |  | 40 | return ($use_CPAN | 
| 476 |  |  |  |  |  |  | && exists($CPAN::META->{'readwrite'}->{'CPAN::Module'}->{$module})); | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | sub _module_known_CPANPLUS { | 
| 480 | 3 |  |  | 3 |  | 914 | my ($self, $module) = @_; | 
| 481 |  |  |  |  |  |  | ### _module_known_CPANPLUS(): $module | 
| 482 |  |  |  |  |  |  |  | 
| 483 | 3 | 100 |  |  |  | 11 | if (! defined $self->{'cpanplus'}) { | 
| 484 | 1 |  |  |  |  | 7 | print __x("PodLinkCheck: load {module} for module existence checking\n", | 
| 485 |  |  |  |  |  |  | module => 'CPANPLUS'); | 
| 486 | 1 | 50 |  |  |  | 225 | if (! eval { require CPANPLUS::Backend; | 
|  | 1 |  |  |  |  | 412 |  | 
| 487 | 0 |  |  |  |  | 0 | require CPANPLUS::Configure; | 
| 488 |  |  |  |  |  |  | }) { | 
| 489 | 1 |  |  |  |  | 9 | $self->{'cpanplus'} = 0; | 
| 490 | 1 |  |  |  |  | 6 | print __x("Cannot load {module}, skipping -- {error}\n", | 
| 491 |  |  |  |  |  |  | module => 'CPANPLUS', | 
| 492 |  |  |  |  |  |  | error => $@); | 
| 493 | 1 |  |  |  |  | 292 | return 0; | 
| 494 |  |  |  |  |  |  | } | 
| 495 | 0 |  |  |  |  | 0 | my $conf = CPANPLUS::Configure->new; | 
| 496 | 0 |  |  |  |  | 0 | $conf->set_conf (verbose => 1); | 
| 497 | 0 |  |  |  |  | 0 | $conf->set_conf (no_update => 1); | 
| 498 | 0 |  |  |  |  | 0 | $self->{'cpanplus'} = CPANPLUS::Backend->new ($conf); | 
| 499 |  |  |  |  |  |  | } | 
| 500 |  |  |  |  |  |  |  | 
| 501 | 2 |  | 50 |  |  | 12 | my $cpanplus = $self->{'cpanplus'} || return 0; | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | # module_tree() returns false '' for not found. | 
| 504 |  |  |  |  |  |  | # | 
| 505 |  |  |  |  |  |  | # Struck an error from module_tree() somehow relating to | 
| 506 |  |  |  |  |  |  | # CPANPLUS::Internals::Source::SQLite on cpantesters at one time, so guard | 
| 507 |  |  |  |  |  |  | # with an eval. | 
| 508 |  |  |  |  |  |  | # | 
| 509 | 0 |  |  |  |  | 0 | my $result; | 
| 510 | 0 | 0 |  |  |  | 0 | if (! eval { $result = $cpanplus->module_tree($module); 1 }) { | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 511 | 0 |  |  |  |  | 0 | print __x("{module} error, disabling -- {error}\n", | 
| 512 |  |  |  |  |  |  | module => 'CPANPLUS', | 
| 513 |  |  |  |  |  |  | error  => $@); | 
| 514 | 0 |  |  |  |  | 0 | $self->{'cpanplus'} = 0; | 
| 515 | 0 |  |  |  |  | 0 | return 0; | 
| 516 |  |  |  |  |  |  | } | 
| 517 | 0 |  |  |  |  | 0 | return $result; | 
| 518 |  |  |  |  |  |  | } | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 521 |  |  |  |  |  |  | # PATH | 
| 522 |  |  |  |  |  |  |  | 
| 523 |  |  |  |  |  |  | sub find_script { | 
| 524 | 0 |  |  | 0 | 0 | 0 | my ($self, $name) = @_; | 
| 525 | 0 |  |  |  |  | 0 | foreach my $dir ($self->PATH_list) { | 
| 526 | 0 |  |  |  |  | 0 | my $filename = File::Spec->catfile($dir,$name); | 
| 527 |  |  |  |  |  |  | #### $filename | 
| 528 | 0 | 0 |  |  |  | 0 | if (-e $filename) { | 
| 529 | 0 |  |  |  |  | 0 | return $filename; | 
| 530 |  |  |  |  |  |  | } | 
| 531 |  |  |  |  |  |  | } | 
| 532 | 0 |  |  |  |  | 0 | return undef; | 
| 533 |  |  |  |  |  |  | } | 
| 534 |  |  |  |  |  |  |  | 
| 535 |  |  |  |  |  |  | # return list of directories | 
| 536 |  |  |  |  |  |  | sub PATH_list { | 
| 537 | 0 |  |  | 0 | 0 | 0 | my ($self) = @_; | 
| 538 | 0 |  |  |  |  | 0 | require Config; | 
| 539 | 0 |  |  |  |  | 0 | return split /\Q$Config::Config{'path_sep'}/o, $self->PATH; | 
| 540 |  |  |  |  |  |  | } | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | # return string | 
| 543 |  |  |  |  |  |  | sub PATH { | 
| 544 | 0 |  |  | 0 | 0 | 0 | my ($self) = @_; | 
| 545 | 0 | 0 |  |  |  | 0 | if (defined $self->{'PATH'}) { | 
| 546 | 0 |  |  |  |  | 0 | return $self->{'PATH'}; | 
| 547 |  |  |  |  |  |  | } else { | 
| 548 | 0 |  |  |  |  | 0 | return $ENV{'PATH'}; | 
| 549 |  |  |  |  |  |  | } | 
| 550 |  |  |  |  |  |  | } | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 553 |  |  |  |  |  |  | # man | 
| 554 |  |  |  |  |  |  |  | 
| 555 |  |  |  |  |  |  | # return bool | 
| 556 |  |  |  |  |  |  | sub manpage_is_known { | 
| 557 | 6 |  |  | 6 | 0 | 2954 | my ($self, $name) = @_; | 
| 558 | 6 |  |  |  |  | 11 | my @manargs; | 
| 559 | 6 |  |  |  |  | 10 | my $section = ''; | 
| 560 | 6 | 100 |  |  |  | 32 | if ($name =~ s/\s*\((.+)\)$//) { | 
| 561 | 2 |  |  |  |  | 6 | $section = $1; | 
| 562 | 2 |  |  |  |  | 6 | @manargs = ($section); | 
| 563 |  |  |  |  |  |  | } | 
| 564 |  |  |  |  |  |  |  | 
| 565 | 6 |  |  |  |  | 33 | my $r = \$self->{'manpage_is_known'}->{$section}->{$name}; | 
| 566 | 6 | 50 |  |  |  | 22 | if (defined $$r) { | 
| 567 | 0 |  |  |  |  | 0 | return $$r; | 
| 568 |  |  |  |  |  |  | } | 
| 569 | 6 |  |  |  |  | 13 | push @manargs, $name; | 
| 570 |  |  |  |  |  |  | ### man: \@manargs | 
| 571 |  |  |  |  |  |  |  | 
| 572 | 6 | 50 |  |  |  | 29 | return ($$r = ($self->_man_has_location_option() | 
| 573 |  |  |  |  |  |  | ? $self->_manpage_is_known_by_location(@manargs) | 
| 574 |  |  |  |  |  |  | : $self->_manpage_is_known_by_output(@manargs))); | 
| 575 |  |  |  |  |  |  | } | 
| 576 |  |  |  |  |  |  |  | 
| 577 |  |  |  |  |  |  | # --location is not in posix, | 
| 578 |  |  |  |  |  |  | # http://www.opengroup.org/onlinepubs/009695399/utilities/man.html | 
| 579 |  |  |  |  |  |  | # Is it man-db specific, or does it have a chance of working elsewhere? | 
| 580 |  |  |  |  |  |  | # | 
| 581 |  |  |  |  |  |  | use constant::defer _man_has_location_option => sub { | 
| 582 | 1 |  |  |  |  | 19 | my ($self) = @_; | 
| 583 |  |  |  |  |  |  | ### _man_has_location_option() ... | 
| 584 | 1 |  |  |  |  | 1631 | require IPC::Run; | 
| 585 | 1 |  |  |  |  | 54803 | require File::Spec; | 
| 586 | 1 |  |  |  |  | 4 | my $str = ''; | 
| 587 | 1 |  |  |  |  | 2 | eval { | 
| 588 | 1 |  |  |  |  | 23 | IPC::Run::run (['man','--help'], | 
| 589 |  |  |  |  |  |  | '<', \undef, | 
| 590 |  |  |  |  |  |  | '>', \$str, | 
| 591 |  |  |  |  |  |  | '2>', File::Spec->devnull); | 
| 592 |  |  |  |  |  |  | }; | 
| 593 | 1 |  |  |  |  | 1437 | my $ret = ($str =~ /--location\b/); | 
| 594 | 1 | 50 |  |  |  | 10 | if ($self->{'verbose'} >= 2) { | 
| 595 | 0 | 0 |  |  |  | 0 | if ($ret) { | 
| 596 | 0 |  |  |  |  | 0 | print __("man \"--location\" option is available\n"); | 
| 597 |  |  |  |  |  |  | } else { | 
| 598 | 0 |  |  |  |  | 0 | print __("man \"--location\" option not available (not in its \"--help\")\n"); | 
| 599 |  |  |  |  |  |  | } | 
| 600 |  |  |  |  |  |  | } | 
| 601 |  |  |  |  |  |  | ### $ret | 
| 602 | 1 |  |  |  |  | 6 | return $ret; | 
| 603 | 1 |  |  | 1 |  | 1549 | }; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 7 |  | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | sub _manpage_is_known_by_location { | 
| 606 | 0 |  |  | 0 |  | 0 | my ($self, @manargs) = @_; | 
| 607 |  |  |  |  |  |  | ### _manpage_is_known_by_location() run: \@manargs | 
| 608 | 0 |  |  |  |  | 0 | require IPC::Run; | 
| 609 | 0 |  |  |  |  | 0 | my $str; | 
| 610 | 0 | 0 |  |  |  | 0 | if (! eval { | 
| 611 | 0 |  |  |  |  | 0 | IPC::Run::run (['man', '--location', @manargs], | 
| 612 |  |  |  |  |  |  | '<', \undef,  # stdin | 
| 613 |  |  |  |  |  |  | '>', \$str,  # stdout | 
| 614 |  |  |  |  |  |  | '2>', File::Spec->devnull); | 
| 615 | 0 |  |  |  |  | 0 | 1; | 
| 616 |  |  |  |  |  |  | }) { | 
| 617 | 0 |  |  |  |  | 0 | my $err = $@; | 
| 618 | 0 |  |  |  |  | 0 | $err =~ s/\s+$//; | 
| 619 | 0 |  |  |  |  | 0 | print __x("PodLinkCheck: error running 'man': {error}\n", error => $err); | 
| 620 | 0 |  |  |  |  | 0 | return 0; | 
| 621 |  |  |  |  |  |  | } | 
| 622 |  |  |  |  |  |  | ### _manpage_is_known_by_location() output: $str | 
| 623 | 0 | 0 |  |  |  | 0 | return ($str =~ /\n/ ? 1 : 0); | 
| 624 |  |  |  |  |  |  | } | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | sub _manpage_is_known_by_output { | 
| 627 | 6 |  |  | 6 |  | 67 | my ($self, @manargs) = @_; | 
| 628 |  |  |  |  |  |  | ### _manpage_is_known_by_output() run: \@manargs | 
| 629 | 6 |  |  |  |  | 37 | require IPC::Run; | 
| 630 | 6 |  |  |  |  | 3733 | require File::Temp; | 
| 631 | 6 |  |  |  |  | 13984 | my $fh = File::Temp->new (TEMPLATE => 'PodLinkCheck-man-XXXXXX', | 
| 632 |  |  |  |  |  |  | TMPDIR => 1); | 
| 633 | 6 | 50 |  |  |  | 2820 | if (! eval { | 
| 634 | 6 |  |  |  |  | 61 | IPC::Run::run (['man', @manargs], | 
| 635 |  |  |  |  |  |  | '<', \undef,  # stdin | 
| 636 |  |  |  |  |  |  | '>', $fh,     # stdout | 
| 637 |  |  |  |  |  |  | '2>', File::Spec->devnull); | 
| 638 | 0 |  |  |  |  | 0 | 1; | 
| 639 |  |  |  |  |  |  | }) { | 
| 640 | 6 |  |  |  |  | 9113 | my $err = $@; | 
| 641 | 6 |  |  |  |  | 42 | $err =~ s/\s+$//; | 
| 642 | 6 |  |  |  |  | 29 | print __x("PodLinkCheck: error running 'man': {error}\n", error => $err); | 
| 643 | 6 |  |  |  |  | 1863 | return 0; | 
| 644 |  |  |  |  |  |  | } | 
| 645 |  |  |  |  |  |  |  | 
| 646 | 0 |  |  |  |  |  | seek $fh, 0, 0; | 
| 647 | 0 |  |  |  |  |  | foreach (1 .. 5) { | 
| 648 | 0 | 0 |  |  |  |  | if (! defined (readline $fh)) { | 
| 649 | 0 |  |  |  |  |  | return 0; | 
| 650 |  |  |  |  |  |  | } | 
| 651 |  |  |  |  |  |  | } | 
| 652 | 0 |  |  |  |  |  | return 1; | 
| 653 |  |  |  |  |  |  | } | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | 1; | 
| 656 |  |  |  |  |  |  | __END__ |