File Coverage

blib/lib/MojoX/ValidateHeadLinks.pm
Criterion Covered Total %
statement 21 88 23.8
branch 0 20 0.0
condition 0 13 0.0
subroutine 7 15 46.6
pod 4 4 100.0
total 32 140 22.8


line stmt bran cond sub pod time code
1             package MojoX::ValidateHeadLinks;
2              
3 1     1   35897 use feature 'say';
  1         3  
  1         122  
4 1     1   6 use strict;
  1         2  
  1         34  
5 1     1   6 use warnings;
  1         6  
  1         29  
6              
7 1     1   924 use Hash::FieldHash ':all';
  1         2217  
  1         169  
8              
9 1     1   1202 use Log::Handler;
  1         62193  
  1         8  
10              
11 1     1   1088 use Mojo::UserAgent;
  1         400533  
  1         12  
12              
13 1     1   36 use Try::Tiny;
  1         1  
  1         1211  
14              
15             fieldhash my %doc_root => 'doc_root';
16             fieldhash my %logger => 'logger';
17             fieldhash my %maxlevel => 'maxlevel';
18             fieldhash my %minlevel => 'minlevel';
19             fieldhash my %url => 'url';
20              
21             our $VERSION = '1.03';
22              
23             # -----------------------------------------------
24              
25             sub _count
26             {
27 0     0     my($self, $want, $type, $target) = @_;
28              
29 0           $$want{$type}{count}++;
30              
31 0           my($file_name) = $self -> doc_root . $target;
32              
33 0           $self -> log(debug => sprintf('%7s: %s', "\u$type", $file_name) );
34              
35 0 0         if (! -e $file_name)
36             {
37 0           $$want{$type}{error}++;
38              
39 0           $self -> log(error => "Error: $file_name does not exist");
40             }
41              
42             } # End of _count.
43              
44             # --------------------------------------------------
45              
46             sub _init
47             {
48 0     0     my($self, $arg) = @_;
49 0   0       $$arg{doc_root} ||= ''; # Caller can set.
50 0           $$arg{logger} = Log::Handler -> new;
51 0   0       $$arg{maxlevel} ||= 'notice'; # Caller can set.
52 0   0       $$arg{minlevel} ||= 'error'; # Caller can set.
53 0   0       $$arg{url} ||= ''; # Caller can set.
54 0 0         $$arg{url} = "http://$$arg{url}" if ($$arg{url} !~ /^http/);
55 0           $self = from_hash($self, $arg);
56              
57 0           $self -> logger -> add
58             (
59             screen =>
60             {
61             maxlevel => $self -> maxlevel,
62             message_layout => '%m',
63             minlevel => $self -> minlevel,
64             newline => 1, # When running from the command line.
65             }
66             );
67              
68 0           return $self;
69              
70             } # End of _init.
71              
72             # -----------------------------------------------
73              
74             sub log
75             {
76 0     0 1   my($self, $level, $s) = @_;
77              
78 0   0       $self -> logger -> $level($s || '');
79              
80             } # End of log.
81              
82             # --------------------------------------------------
83              
84             sub new
85             {
86 0     0 1   my($class, %arg) = @_;
87 0           my($self) = bless {}, $class;
88 0           $self = $self -> _init(\%arg);
89              
90 0           return $self;
91              
92             } # End of new.
93              
94             # -----------------------------------------------
95              
96             sub quit
97             {
98 0     0 1   my($self, $s) = @_;
99              
100 0           $self -> log(error => $s);
101              
102 0           die "\n";
103              
104             } # End of quit.
105              
106             # -----------------------------------------------
107              
108             sub run
109             {
110 0     0 1   my($self) = @_;
111              
112 0           $self -> log(debug => 'URL: ' . $self -> url);
113 0 0         $self -> quit('You must provide a value for the doc_root parameter') if (! $self -> doc_root);
114 0 0         $self -> quit('You must provide a value for the url parameter') if (! $self -> url);
115              
116 0           my(%want) =
117             (
118             import =>
119             {
120             count => 0,
121             error => 0,
122             },
123             link =>
124             {
125             count => 0,
126             error => 0,
127             },
128             script =>
129             {
130             count => 0,
131             error => 0,
132             },
133             );
134 0           my($ua) = Mojo::UserAgent -> new;
135 0           my($dom) = $ua -> get($self -> url) -> res -> dom;
136              
137 0           my(@field);
138             my(@import);
139              
140 0           for my $item (@{$dom -> find('html head style')})
  0            
141             {
142 0 0         next if (! $item -> can('text') );
143              
144 0           @field = map{s/^\s+//; s/\s$//; $_} split(/;/, $item -> text);
  0            
  0            
  0            
145              
146 0 0         next if ($field[0] !~ /^\@import/);
147              
148 0           for my $field (@field)
149             {
150 0           @import = split(/\s+/, $field);
151 0           $import[1] =~ s/([\"\'])(.+)\1/$2/; # The backslashed are to help UltraEdit's syntax hiliter.
152              
153 0           $self -> _count(\%want, 'import', $import[1]);
154             }
155             }
156              
157 0           for my $item (@{$dom -> find('html head link')})
  0            
158             {
159 0           my($index);
160              
161 0           for my $i (0 .. $#{$$item{tree} })
  0            
162             {
163 0 0 0       if ( (ref $$item{tree}[$i] eq 'HASH') && exists $$item{tree}[$i]{href})
164             {
165 0           $index = $i;
166              
167 0           last;
168             }
169             }
170              
171 0 0         $self -> _count(\%want, 'link', $$item{tree}[$index]{href}) if ($index);
172             }
173              
174             # WTF: Tried $head -> can('script') and UNIVERSAL::can($head, 'script').
175              
176 0           my($can);
177              
178             try
179             {
180 0     0     my(@script) = $dom -> find('html head script');
181 0           $can = 1;
182             }
183             catch
184             {
185 0     0     $can = 0;
186 0           };
187              
188 0 0         if ($can)
189             {
190 0           for my $item (@{$dom -> find('html head script')})
  0            
191             {
192 0 0         $self -> _count(\%want, 'script', $$item{src}) if ($$item{src});
193             }
194             }
195              
196 0           for my $type (sort keys %want)
197             {
198 0           $self -> log(info => sprintf('%7s: %d. Errors: %d', "\u${type}s", $want{$type}{count}, $want{$type}{error}) );
199             }
200              
201             # Return:
202             # 0 => success.
203             # 1+ => error.
204              
205 0           return $want{link}{error} + $want{import}{error} + $want{script}{error};
206              
207             } # End of run.
208              
209             # -----------------------------------------------
210              
211             1;
212              
213             =head1 NAME
214              
215             MojoX::ValidateHeadLinks - Ensure CSS and JS links in web pages point to real files
216              
217             =head1 Synopsis
218              
219             shell> validate.head.links.pl -h
220             shell> validate.head.links.pl -d /dev/shm/html -u http://127.0.0.1/index.html
221              
222             This program calls the L method, which returns the number of errors found. Various logging options,
223             discussed under L and in the L, control the amount of output. Nothing
224             is printed by default.
225              
226             On my machine, /dev/shm/ is the directory used to access Debian's built-in RAM disk, and /dev/shm/html/ is my
227             web server's document root directory.
228              
229             Since this script ships in the bin/ directory, it is installed somewhere along your executable search path
230             when the module is installed.
231              
232             =head1 Description
233              
234             C is a pure Perl module.
235              
236             It does no more than this:
237              
238             =over 4
239              
240             =item o Download and parse a web page using L
241              
242             Hence the -url parameter to validate.head.links.pl.
243              
244             =item o Check whether the CSS and JS links point to real files
245              
246             Hence the -directory parameter to validate.head.links.pl.
247              
248             =back
249              
250             It handles the '@import' option used in some CSS links.
251              
252             =head1 Distributions
253              
254             This module is available as a Unix-style distro (*.tgz).
255              
256             See L for details.
257              
258             See L for
259             help on unpacking and installing.
260              
261             =head1 Constructor and initialization
262              
263             new(...) returns an object of type C.
264              
265             This is the class's contructor.
266              
267             Usage: C<< MojoX::ValidateHeadLinks -> new() >>.
268              
269             This method takes a hashref of options.
270              
271             Call C as C<< new({option_1 => value_1, option_2 => value_2, ...}) >>.
272              
273             Available options (which are also methods):
274              
275             =over 4
276              
277             =item o doc_root => $dir_name
278              
279             Use this to specify the doc root directory of your web server. This option is mandatory.
280              
281             Default: ''.
282              
283             =item o maxlevel => $logOption1
284              
285             This option affects L.
286              
287             See the L docs, and the L.
288              
289             Default: 'notice'. This means nothing is printed.
290              
291             For maximum details in the printed report, try:
292              
293             MojoX::ValidateHeadLinks -> new(doc_root => $d, maxlevel => 'debug', url => $u) -> run;
294              
295             =item o minlevel => $logOption2
296              
297             This option affects L.
298              
299             See the L docs.
300              
301             Default: 'error'.
302              
303             No lower levels are used.
304              
305             =item o url => $url
306              
307             Use this to specify the URL of the web page to be checked.
308              
309             Default: ''.
310              
311             If the string supplied does not start with 'http', then 'http://' is prefixed to $url automatically.
312              
313             =back
314              
315             =head1 Methods
316              
317             =head2 doc_root([$dir_name])
318              
319             Here, the [] indicate an optional parameter.
320              
321             Get or set the name of your web server's doc root directory.
322              
323             =head2 log($level => $message)
324              
325             Log the string $message at log level $level.
326              
327             The logger object is of class L.
328              
329             =head2 maxlevel([$string])
330              
331             Here, the [] indicate an optional parameter.
332              
333             Get or set the value used by the logger object.
334              
335             For more details in the printed report, try:
336              
337             MojoX::ValidateHeadLinks -> new(doc_root => $d, maxlevel => 'debug', url => $u) -> run;
338              
339             'maxlevel' is a parameter to L. See L, and the L, for details.
340              
341             =head2 minlevel([$string])
342              
343             Here, the [] indicate an optional parameter.
344              
345             Get or set the value used by the logger object.
346              
347             'minlevel' is a parameter to L. See L for details.
348              
349             =head2 new()
350              
351             See L for details on the parameters accepted by L.
352              
353             =head2 quit($message)
354              
355             Logs $message at log level I, and then dies.
356              
357             Errors can arise in these situations:
358              
359             =over 4
360              
361             =item o doc_root has no value during the call to L
362              
363             =item o url has no value during the call to L
364              
365             =back
366              
367             =head2 run()
368              
369             Does all the work.
370              
371             Returns the number of errors detected, so 0 is good and N > 0 is bad.
372              
373             =head2 url([$url])
374              
375             Here, the [] indicate an optional parameter.
376              
377             Get or set the URL of the web page your wish to check.
378              
379             =head1 FAQ
380              
381             =head2 How does bin/validate.head.links.pl differ from linkcheck.pl?
382              
383             L does not check that links
384             to non-HTML resources (CSS, JS) point to real files.
385              
386             =head2 How does the -maxlevel parameter affect the output?
387              
388             In these examples, $DR stands for the /dev/shm/html/ directory, the doc root of my dev box's web server.
389              
390             Output from a real run, where my dev web site is the same as my real web site (so -d $DR works):
391              
392             shell> validate.head.links.pl -d $DR -url http://savage.net.au/Novels-etc.html -max debug
393              
394             URL: http://savage.net.au/Novels-etc.html
395             Import: /dev/shm/html/assets/js/DataTables-1.9.4/media/css/demo_page.css
396             Import: /dev/shm/html/assets/js/DataTables-1.9.4/media/css/demo_table.css
397             Link: /dev/shm/html/assets/css/local/default.css
398             Script: /dev/shm/html/assets/js/DataTables-1.9.4/media/js/jquery.js
399             Script: /dev/shm/html/assets/js/DataTables-1.9.4/media/js/jquery.dataTables.min.js
400             Imports: 2. Errors: 0
401             Links: 1. Errors: 0
402             Scripts: 2. Errors: 0
403              
404             shell> validate.head.links.pl -d $DR -url http://savage.net.au/Novels-etc.html -max info
405              
406             Imports: 2. Errors: 0
407             Links: 1. Errors: 0
408             Scripts: 2. Errors: 0
409              
410             shell> validate.head.links.pl -d $DR -url http://savage.net.au/Novels-etc.html -max error
411              
412             (No output)
413              
414             shell> echo $?
415             0
416              
417             =head1 Support
418              
419             Email the author, or log a bug on RT:
420              
421             L.
422              
423             =head1 Author
424              
425             C was written by Ron Savage Iron@savage.net.auE> in 2012.
426              
427             Home page: L.
428              
429             =head1 Copyright
430              
431             Australian copyright (c) 2012, Ron Savage.
432              
433             All Programs of mine are 'OSI Certified Open Source Software';
434             you can redistribute them and/or modify them under the terms of
435             The Artistic License, a copy of which is available at:
436             http://www.opensource.org/licenses/index.html
437              
438             =cut