File Coverage

lib/CPAN/Audit.pm
Criterion Covered Total %
statement 35 161 21.7
branch 0 58 0.0
condition 0 26 0.0
subroutine 12 24 50.0
pod 0 9 0.0
total 47 278 16.9


line stmt bran cond sub pod time code
1             package CPAN::Audit;
2 1     1   1740 use v5.10.1;
  1         6  
3 1     1   5 use strict;
  1         15  
  1         23  
4 1     1   5 use warnings;
  1         3  
  1         24  
5 1     1   397 use version;
  1         1695  
  1         4  
6              
7 1     1   73 use Carp qw(carp);
  1         2  
  1         51  
8 1     1   4072 use Module::CoreList;
  1         136171  
  1         748  
9              
10 1     1   877 use CPAN::Audit::Installed;
  1         3  
  1         33  
11 1     1   367 use CPAN::Audit::Discover;
  1         3  
  1         29  
12 1     1   335 use CPAN::Audit::Filter;
  1         2  
  1         36  
13 1     1   379 use CPAN::Audit::Version;
  1         2  
  1         33  
14 1     1   352 use CPAN::Audit::Query;
  1         4  
  1         28  
15 1     1   6 use CPAN::Audit::DB;
  1         2  
  1         1796  
16              
17             our $VERSION = '20230601.002';
18              
19             sub new {
20 0     0 0   my( $class, %params ) = @_;
21              
22 0           my @allowed_keys = qw(ascii db exclude exclude_file include_perl interactive no_corelist quiet verbose version);
23              
24 0           my %args = map { $_, $params{$_} } @allowed_keys;
  0            
25 0           my $self = bless \%args, $class;
26              
27 0 0         $self->_handle_exclude_file if $self->{exclude_file};
28              
29 0           $self->{db} = CPAN::Audit::DB->db;
30              
31 0           $self->{filter} = CPAN::Audit::Filter->new( exclude => $args{exclude} );
32 0           $self->{query} = CPAN::Audit::Query->new( db => $self->{db} );
33 0           $self->{discover} = CPAN::Audit::Discover->new( db => $self->{db} );
34              
35 0           return $self;
36             }
37              
38             sub _handle_exclude_file {
39 0     0     my( $self ) = @_;
40              
41 0           foreach my $file (@{$self->{exclude_file}}) {
  0            
42 0           my $fh;
43 0 0         unless( open $fh, "<", $file ) {
44 0           carp "unable to open exclude_file [$file]: $!\n";
45 0           return;
46             }
47             my @excludes =
48 0           grep { !/^\s*$/ } # no blank lines
49 0           map { s{^\s+|\s+$}{}g; $_ } # strip leading/trailing whitespace
  0            
50 0           map { s{#.*}{}; $_ } # strip comments
  0            
  0            
51             <$fh>;
52 0           push @{$self->{exclude}}, @excludes;
  0            
53             }
54             }
55              
56             sub command_module {
57 0     0 0   my ( $self, $dists, $module, $version_range ) = @_;
58 0 0         return "Usage: module [version-range]" unless $module;
59              
60 0           my $distname = $self->{db}->{module2dist}->{$module};
61              
62 0 0         if ( !$distname ) {
63 0           return "Module '$module' is not in database";
64             }
65              
66 0   0       $dists->{$distname} = $version_range // '';
67              
68 0           return;
69             }
70              
71             sub command_release {
72 0     0 0   my ( $self, $dists, $distname, $version_range ) = @_;
73 0 0         return "Usage: dist|release [version-range]"
74             unless $distname;
75              
76 0 0         if ( !$self->{db}->{dists}->{$distname} ) {
77 0           return "Distribution '$distname' is not in database";
78             }
79              
80 0   0       $dists->{$distname} = $version_range // '';
81              
82 0           return;
83             }
84              
85             sub command_show {
86 0     0 0   my ( $self, $dists, $advisory_id ) = @_;
87 0 0         return "Usage: show " unless $advisory_id;
88              
89 0           my ($release) = $advisory_id =~ m/^CPANSA-(.*?)-(\d+)-(\d+)$/;
90 0 0         return "Invalid advisory id" unless $release;
91              
92 0           my $dist = $self->{db}->{dists}->{$release};
93 0 0         return "Unknown advisory id" unless $dist;
94              
95             my ($advisory) =
96 0           grep { $_->{id} eq $advisory_id } @{ $dist->{advisories} };
  0            
  0            
97 0 0         return "Unknown advisory id" unless $advisory;
98              
99 0   0       my $distname = $advisory->{distribution} // 'Unknown distribution name';
100 0           $dists->{$distname}{advisories} = [ $advisory ];
101 0           $dists->{$distname}{version} = 'Any';
102              
103 0           return;
104             }
105              
106             sub command_modules {
107 0     0 0   my ($self, $dists, @modules) = @_;
108 0 0         return "Usage: modules '[,version-range]' '[,version-range]'" unless @modules;
109              
110 0           foreach my $module ( @modules ) {
111 0           my ($name, $version) = split /;/, $module;
112              
113 0   0       my $failed = $self->command_module( $dists, $name, $version // '' );
114              
115 0 0         if ( $failed ) {
116 0           $self->verbose( $failed );
117 0           next;
118             }
119             }
120              
121 0           return;
122             }
123              
124             sub command_deps {
125 0     0 0   my ($self, $dists, $dir) = @_;
126 0 0         $dir = '.' unless defined $dir;
127              
128 0 0         return "Usage: deps " unless -d $dir;
129              
130 0           my @deps = $self->{discover}->discover($dir);
131              
132 0           $self->verbose( sprintf 'Discovered %d dependencies', scalar(@deps) );
133              
134 0           foreach my $dep (@deps) {
135             my $dist = $dep->{dist}
136 0   0       || $self->{db}->{module2dist}->{ $dep->{module} };
137 0 0         next unless $dist;
138              
139 0           $dists->{$dist} = $dep->{version};
140             }
141              
142 0           return;
143             }
144              
145             sub command_installed {
146 0     0 0   my ($self, $dists, @args) = @_;
147              
148 0           $self->verbose('Collecting all installed modules. This can take a while...');
149              
150             my $verbose_callback = sub {
151 0     0     my ($info) = @_;
152 0           $self->verbose( sprintf '%s: %s-%s', $info->{path}, $info->{distname}, $info->{version} );
153 0           };
154              
155             my @deps = CPAN::Audit::Installed->new(
156             db => $self->{db},
157             include_perl => $self->{include_perl},
158 0 0         ( $self->{verbose} ? ( cb => $verbose_callback ) : () ),
159             )->find(@args);
160              
161 0           foreach my $dep (@deps) {
162             my $dist = $dep->{dist}
163 0   0       || $self->{db}->{module2dist}->{ $dep->{module} };
164 0 0         next unless $dist;
165              
166 0           $dists->{ $dep->{dist} } = $dep->{version};
167             }
168              
169 0           return;
170             }
171              
172             sub command {
173 0     0 0   state $command_table = {
174             dependencies => 'command_deps',
175             deps => 'command_deps',
176             installed => 'command_installed',
177             module => 'command_module',
178             modules => 'command_modules',
179             release => 'command_release',
180             dist => 'command_release',
181             show => 'command_show',
182             };
183              
184 0           my( $self, $command, @args ) = @_;
185              
186 0           my %report = (
187             meta => {
188             command => $command,
189             args => [ @args ],
190             cpan_audit => { version => $VERSION },
191             total_advisories => 0,
192             },
193             errors => [],
194             dists => {},
195             );
196 0           my $dists = $report{dists};
197              
198 0 0 0       if (!$self->{no_corelist}
      0        
199             && ( $command eq 'dependencies'
200             || $command eq 'deps'
201             || $command eq 'installed' )
202             )
203             {
204             # Find core modules for this perl version first.
205             # This way explictly installed versions will overwrite.
206 0 0         if ( my $core = $Module::CoreList::version{$]} ) {
207 0           while ( my ( $mod, $ver ) = each %$core ) {
208 0 0         my $dist = $self->{db}{module2dist}{$mod} or next;
209 0 0 0       $dists->{$dist} = $ver if( ! defined $dists->{$dist} or version->parse($ver) > $dists->{$dist} );
210             }
211             }
212             }
213              
214 0 0         if ( exists $command_table->{$command} ) {
215 0           my $method = $command_table->{$command};
216 0           push @{ $report{errors} }, $self->$method( $dists, @args );
  0            
217 0 0         return \%report if $command eq 'show';
218             }
219             else {
220 0           push @{ $report{errors} }, "unknown command: $command. See -h";
  0            
221             }
222              
223 0 0         if (%$dists) {
224 0           my $query = $self->{query};
225              
226 0           foreach my $distname ( keys %$dists ) {
227 0           my $version_range = $dists->{$distname};
228             my @advisories =
229 0           grep { ! $self->{filter}->excludes($_) }
  0            
230             $query->advisories_for( $distname, $version_range );
231              
232 0 0 0       $version_range = 'Any'
233             if $version_range eq '' || $version_range eq '0';
234              
235 0           $report{meta}{total_advisories} += @advisories;
236              
237 0 0         if ( @advisories ) {
238 0           $dists->{$distname} = {
239             advisories => \@advisories,
240             version => $version_range,
241             };
242             }
243             else {
244 0           delete $dists->{$distname}
245             }
246             }
247             }
248              
249 0           return \%report;
250             }
251              
252             sub verbose {
253 0     0 0   my ( $self, $message ) = @_;
254 0 0         return if $self->{quiet};
255 0           $self->_print( *STDERR, $message );
256             }
257              
258              
259             sub _print {
260 0     0     my ( $self, $fh, $message ) = @_;
261              
262 0 0         if ( $self->{no_color} ) {
263 0           $message =~ s{__BOLD__}{}g;
264 0           $message =~ s{__GREEN__}{}g;
265 0           $message =~ s{__RED__}{}g;
266 0           $message =~ s{__RESET__}{}g;
267             }
268             else {
269 0           $message =~ s{__BOLD__}{\e[39;1m}g;
270 0           $message =~ s{__GREEN__}{\e[32m}g;
271 0           $message =~ s{__RED__}{\e[31m}g;
272 0           $message =~ s{__RESET__}{\e[0m}g;
273              
274 0 0         $message .= "\e[0m" if length $message;
275             }
276              
277 0           print $fh "$message\n";
278             }
279              
280             1;
281             __END__