File Coverage

blib/lib/App/newver/Scanner.pm
Criterion Covered Total %
statement 23 54 42.5
branch 0 14 0.0
condition 0 11 0.0
subroutine 8 10 80.0
pod 1 1 100.0
total 32 90 35.5


line stmt bran cond sub pod time code
1             package App::newver::Scanner;
2 1     1   17 use 5.016;
  1         8  
3 1     1   6 use strict;
  1         3  
  1         37  
4 1     1   12 use warnings;
  1         2  
  1         90  
5             our $VERSION = '0.02';
6              
7 1     1   7 use Exporter qw(import);
  1         2  
  1         107  
8             our @EXPORT_OK = qw(scan_version);
9              
10 1     1   1195 use HTML::TreeBuilder 5 -weak;
  1         42098  
  1         12  
11 1     1   1175 use LWP::UserAgent;
  1         67067  
  1         35  
12 1     1   7 use URI;
  1         2  
  1         20  
13              
14 1     1   487 use App::newver::Version qw(version_compare);
  1         3  
  1         512  
15              
16             our $user_agent = "newver/$VERSION (perl $^V; $^O)";
17              
18             our $MAYBE_VERSION_RX = qr/v?(?[0-9a-zA-Z._\-+~:,;]+)/;
19              
20             sub scan_version {
21              
22 0     0 1   my %params = @_;
23             my $program = $params{ program }
24 0   0       // die "required parameter 'program' missing";
25             my $version = $params{ version }
26 0   0       // die "required parameter 'version' missing";
27             my $match = $params{ match }
28 0   0       // die "required parameter 'match' missing";
29             my $page = $params{ page }
30 0   0       // die "required parameter 'page' missing";
31              
32 0 0         $match =~ s/\@VERSION\@/$MAYBE_VERSION_RX/g
33             or die "Match regex missing '\@VERSION\@'\n";
34 0           $match = qr/$match/;
35              
36 0           my $ua = LWP::UserAgent->new;
37 0           $ua->agent($user_agent);
38              
39 0           my $req = HTTP::Request->new(GET => $page);
40 0           my $res = $ua->request($req);
41 0 0         if (not $res->is_success) {
42 0           die sprintf "Error fetching %s: %s\n", $page, $res->status_line;
43             }
44              
45 0           my $content = $res->decoded_content;
46              
47 0           my $tree = HTML::TreeBuilder->new_from_content($content);
48 0     0     my @as = $tree->look_down(_tag => "a", sub { defined $_[0]->attr('href') });
  0            
49 0 0         if (!@as) {
50 0           die qq{Found no elements in $page\n};
51             }
52              
53 0           my $greatest;
54 0           for my $ae (@as) {
55 0           my $href = $ae->attr('href');
56 0 0         $href =~ $match or next;
57 0           my $ver = $+{ Version };
58 0 0 0       if (not defined $greatest or version_compare($ver, $greatest->[1]) == 1) {
59 0           $greatest = [ $href, $ver ];
60             }
61             }
62              
63 0 0         if (not defined $greatest) {
64 0           die "Found no matches in $page\n";
65             }
66              
67 0 0         if (version_compare($greatest->[1], $version) != 1) {
68 0           return undef;
69             }
70              
71             return {
72 0           program => $program,
73             version => $greatest->[1],
74             url => URI->new_abs($greatest->[0], $page)->as_string,
75             };
76              
77             }
78              
79             1;
80              
81             =head1 NAME
82              
83             App::newver::Scanner - Scan webpage for new software versions
84              
85             =head1 SYNOPSIS
86              
87             use App::newver::Scanner qw(scan_version);
88              
89             my $scan = scan_version(
90             program => 'perl',
91             version => '5.16',
92             page => 'https://github.com/Perl/perl5/tags',
93             match => 'v@VERSION@.tar.gz',
94             );
95              
96             =head1 DESCRIPTION
97              
98             B is a module that provides the C
99             subroutine for scanning software's upstream webpages for new software versions.
100             This is a private module, please consult the L manual for user
101             documentation.
102              
103             =head1 SUBROUTINES
104              
105             No subroutines are exported by default.
106              
107             =head2 \%scan = scan_version(%params)
108              
109             Scans the webpage given webpage for software versions newer than the current
110             version, and returns a hash ref of the new software version if one is found, or
111             C you're up-to-date.
112              
113             The following fields are required for C<%params>.
114              
115             =over 2
116              
117             =item program
118              
119             The name of the software.
120              
121             =item version
122              
123             The current software version.
124              
125             =item page
126              
127             The URL of the web page to scan.
128              
129             =item match
130              
131             Regex to use for matching software versoin hrefs in CaE> elements.
132             Regex must contain C<@VERSION@>, which matches the href's version component.
133              
134             =back
135              
136             =head1 GLOBAL VARIBES
137              
138             =head2 $App::newver::Scanner::user_agent
139              
140             User agent to use when fetching web pages.
141              
142             =head2 $App::newver::MAYBE_VERSION_RX
143              
144             Regex used by C<@VERSION@>.
145              
146             =head1 AUTHOR
147              
148             Written by L
149              
150             This project's source can be found on its
151             L. Comments and pull
152             requests are welcome.
153              
154             =head1 COPYRIGHT
155              
156             Copyright (C) 2025 Samuel Young.
157              
158             This program is free software; you can redistribute it and/or modify it under
159             the terms of the Artistic License 2.0.
160              
161             =head1 SEE ALSO
162              
163             L
164              
165             =cut