File Coverage

blib/lib/WWW/LinkRot.pm
Criterion Covered Total %
statement 33 144 22.9
branch 0 46 0.0
condition 0 3 0.0
subroutine 11 15 73.3
pod 4 4 100.0
total 48 212 22.6


line stmt bran cond sub pod time code
1             package WWW::LinkRot;
2 1     1   720 use warnings;
  1         1  
  1         33  
3 1     1   5 use strict;
  1         2  
  1         17  
4 1     1   4 use Carp;
  1         2  
  1         52  
5 1     1   5 use utf8;
  1         2  
  1         4  
6             require Exporter;
7             our @ISA = qw(Exporter);
8             our @EXPORT_OK = qw/
9             check_links
10             get_links
11             html_report
12             replace
13             /;
14             our %EXPORT_TAGS = (
15             all => \@EXPORT_OK,
16             );
17 1     1   836 use LWP::UserAgent;
  1         44749  
  1         34  
18 1     1   521 use HTML::Make;
  1         31353  
  1         40  
19 1     1   478 use HTML::Make::Page 'make_page';
  1         1001  
  1         85  
20 1     1   507 use File::Slurper qw!read_text write_text!;
  1         2214  
  1         63  
21 1     1   455 use JSON::Create 'write_json';
  1         1260  
  1         60  
22 1     1   16 use JSON::Parse 'read_json';
  1         4  
  1         51  
23 1     1   511 use Convert::Moji 'make_regex';
  1         1992  
  1         1411  
24              
25             our $VERSION = '0.01';
26              
27             sub get_links
28             {
29 0     0 1   my ($files) = @_;
30 0           my %links;
31 0           for my $file (@$files) {
32 0 0         if (! -f $file) {
33 0           carp "Can't find file '$file'";
34 0           next;
35             }
36 0           my $text = read_text ($file);
37             # Remove comments so that commented-out links don't appear in
38             # the results.
39 0           $text =~ s///gsm;
40 0           while ($text =~ /href=["'](.*?)["']/g) {
41 0           my $link = $1;
42 0           push @{$links{$link}}, $file;
  0            
43             }
44             }
45 0           return \%links;
46             }
47              
48             sub check_links
49             {
50 0     0 1   my ($links, %options) = @_;
51 0           my $out = $options{out};
52 0           my $verbose = $options{verbose};
53 0           my $nook = $options{nook};
54 0           my $tempfile = "$out-temp.json";
55 0           my %skip;
56 0           my $ua = LWP::UserAgent->new (
57             agent => __PACKAGE__,
58             );
59 0           $ua->max_redirect (0);
60             # Time out after five seconds (dead sites etc.)
61 0           $ua->timeout (5);
62              
63 0 0         if (-f $out) {
64 0           my $old = read_json ($out);
65 0           for my $link (@$old) {
66 0 0         if ($link->{status} =~ /200/) {
67 0           $skip{$link->{link}} = $link;
68             }
69             }
70             }
71 0           my $count = 0;
72 0           my @checks;
73 0           for my $link (sort keys %$links) {
74 0 0         if ($nook) {
75 0 0         if ($skip{$link}) {
76 0 0         if ($verbose) {
77 0           print "$link was OK last time, skipping\n";
78             }
79             # Keep a copy of this link in the output.
80 0           push @checks, $skip{$link};
81 0           next;
82             }
83             }
84             my %r = (
85             link => $link,
86 0           files => $links->{$link},
87             );
88 0 0         if ($verbose) {
89 0           print "Getting $link...\n";
90             }
91 0           my $res = $ua->get ($link);
92 0           $r{status} = $res->status_line ();
93 0 0         if ($r{status} =~ m!^30[12]!) {
94 0           $r{location} = $res->header ('location');
95             }
96 0           push @checks, \%r;
97 0           $count++;
98 0 0         if ($count % 5 == 0) {
99 0           write_json ($tempfile, \@checks, indent => 1, sort => 1);
100             }
101             }
102 0 0         unlink ($tempfile) or carp "Error unlinking $tempfile: $!";
103 0           write_json ($out, \@checks, indent => 1, sort => 1);
104             }
105              
106             sub html_report
107             {
108 0     0 1   my (%options) = @_;
109 0           my $links = read_json ($options{in});
110 0           my $title = $options{title};
111 0 0         if (! $title) {
112 0           $title = 'WWW::LinkRot link report';
113             }
114 0           my $style = <
115             .error {
116             background: gold;
117             }
118              
119             .moved {
120             background: pink;
121             }
122             EOF
123 0           my ($html, $body) = make_page (
124             title => $title,
125             style => $style,
126             );
127 0           $body->push ('h1', text => $title);
128 0           my $table = $body->push ('table');
129 0           for my $xlink (@$links) {
130 0           my $status = $xlink->{status};
131 0           my $class = 'OK';
132 0 0         if ($status =~ /30.*/) {
    0          
133 0           $class = 'moved';
134             }
135             elsif ($status =~ /^[45].*/) {
136 0           $class = 'error';
137             }
138 0           my $row = $table->push ('tr', class => $class,);
139 0           my $link = $row->push ('td');
140 0           my $text = $xlink->{link};
141 0 0         if (length ($text) > 100) {
142 0           $text = substr ($text, 0, 100);
143             }
144 0           my $h = $xlink->{link};
145 0           $link->push (
146             'a',
147             attr => {
148             target => '_blank',
149             href => $h,
150             },
151             text => $text,
152             );
153 0           my $archive = "https://web.archive.org/web/*/$h";
154 0           $link->push (
155             'a',
156             attr => {
157             href => $archive,
158             target => '_blank',
159             },
160             text => '[archive]',
161             );
162 0           my $statcell = $row->push ('td', text => $xlink->{status});
163 0 0         if ($class eq 'moved') {
164 0           my $loc = $xlink->{location};
165 0 0         if ($loc) {
166 0           my $hs = $h;
167 0           $hs =~ s!http!https!;
168 0 0         if ($hs eq $loc) {
169 0           $statcell->add_text (' (HTTPS)');
170             }
171             else {
172 0           $statcell->push ('a', href => $loc, text => $loc);
173             }
174             }
175             }
176 0           my $files = $row->push ('td');
177 0           my $filelist = $xlink->{files};
178 0 0         if ($filelist) {
179 0           my $nfiles = scalar (@$filelist);
180 0           my $maxfiles = $nfiles;
181 0 0         if ($nfiles > 5) {
182 0           $maxfiles = 5;
183             }
184 0           my $filen = 0;
185 0           for my $file (@$filelist) {
186 0           $filen++;
187 0 0         if ($filen > $maxfiles) {
188 0           last;
189             }
190 0 0         if ($options{strip}) {
191 0           $file =~ s!$options{strip}!!;
192             }
193 0           my $href = "$options{url}/$file";
194 0           $files->push (
195             'a',
196             attr => {target => '_blank', href => $href},
197             text => $file
198             );
199             }
200             }
201             }
202 0           write_text ($options{out}, $html->text ());
203             }
204              
205             sub replace
206             {
207 0     0 1   my ($links, $files) = @_;
208 0           my @moved;
209 0           for my $l (keys %$links) {
210             # print "$l\n";
211 0           my $link = $links->{$l};
212 0 0 0       if ($link->{status} =~ m!^30! && $link->{location}) {
213 0           push @moved, $l;
214             # print "$l\n";
215             }
216             }
217             # print "@moved\n";
218 0           my $re = make_regex (@moved);
219             # print "$re\n";
220 0           for my $file (@$files) {
221 0           my $text = read_text ($file);
222 0 0         if ($text =~ s!($re)!$links->{$1}{location}!g) {
223 0           print "$file changed $1 $links->{$1}{location}\n";
224 0           write_text ($file, $text);
225             }
226             else {
227             # print "$file unchanged\n";
228             }
229             }
230             }
231              
232             1;