File Coverage

blib/lib/OrePAN2/Auditor.pm
Criterion Covered Total %
statement 72 84 85.7
branch 5 10 50.0
condition 0 3 0.0
subroutine 19 20 95.0
pod 2 3 66.6
total 98 120 81.6


line stmt bran cond sub pod time code
1             package OrePAN2::Auditor;
2              
3 1     1   6348 use Moo;
  1         7362  
  1         6  
4              
5 1     1   1229 use feature qw( say state );
  1         2  
  1         103  
6 1     1   320 use version;
  1         1674  
  1         7  
7              
8 1     1   75 use Carp qw( croak );
  1         3  
  1         41  
9 1     1   1013 use List::Compare ();
  1         18734  
  1         27  
10 1     1   689 use MooX::Options;
  1         5545  
  1         6  
11 1     1   120037 use Parse::CPAN::Packages::Fast 0.09 ();
  1         39815  
  1         24  
12 1     1   773 use Path::Tiny ();
  1         10213  
  1         30  
13 1     1   577 use Type::Params qw( signature );
  1         103180  
  1         10  
14 1     1   685 use Types::Self qw( Self );
  1         6298  
  1         10  
15 1     1   239 use Types::Standard qw( ArrayRef Bool Enum InstanceOf Str );
  1         2  
  1         3  
16 1     1   2975 use Types::URI qw( Uri );
  1         121851  
  1         8  
17 1     1   1316 use LWP::UserAgent ();
  1         43874  
  1         39  
18              
19 1     1   423 use namespace::clean -except => [qw( _options_data _options_config )];
  1         10150  
  1         10  
20              
21             option cpan => (
22             is => 'ro',
23             isa => Uri,
24             format => 's',
25             required => 1,
26             coerce => 1,
27             doc => 'the path to a CPAN 02packages file',
28             );
29              
30             option darkpan => (
31             is => 'ro',
32             isa => Uri,
33             format => 's',
34             required => 1,
35             coerce => 1,
36             doc => 'the path to your DarkPan 02packages file',
37             );
38              
39             option show => (
40             is => 'ro',
41             isa =>
42             Enum [qw( cpan-only-modules darkpan-only-modules outdated-modules )],
43             format => 's',
44             );
45              
46             option verbose => (
47             is => 'ro',
48             isa => Bool,
49             default => 0,
50             );
51              
52             has cpan_modules => (
53             is => 'ro',
54             isa => ArrayRef [Str],
55             lazy => 1,
56             default => sub {
57             my $self = shift;
58             return $self->_modules_from_parser( $self->_cpan_parser );
59             },
60             );
61              
62             has darkpan_modules => (
63             is => 'ro',
64             isa => ArrayRef [Str],
65             lazy => 1,
66             default => sub {
67             my $self = shift;
68             return $self->_modules_from_parser( $self->_darkpan_parser );
69             },
70             );
71              
72             has cpan_only_modules => (
73             is => 'ro',
74             isa => ArrayRef [Str],
75             lazy => 1,
76             default => sub {
77             return [ shift->_list_compare->get_complement ];
78             },
79             );
80              
81             has darkpan_only_modules => (
82             is => 'ro',
83             isa => ArrayRef [Str],
84             lazy => 1,
85             default => sub {
86             return [ shift->_list_compare->get_unique ];
87             },
88             );
89              
90             has outdated_modules => (
91             is => 'ro',
92             isa => ArrayRef [Str],
93             lazy => 1,
94             builder => '_build_outdated_modules',
95             );
96              
97             has ua => (
98             is => 'ro',
99             isa => InstanceOf ['LWP::UserAgent'],
100             default => sub {
101             return LWP::UserAgent->new();
102             },
103             );
104              
105             has _cpan_parser => (
106             is => 'ro',
107             isa => InstanceOf ['Parse::CPAN::Packages::Fast'],
108             lazy => 1,
109             default => sub {
110             my $self = shift;
111             return $self->_parser_for_url( $self->cpan );
112             },
113             );
114              
115             has _darkpan_parser => (
116             is => 'ro',
117             isa => InstanceOf ['Parse::CPAN::Packages::Fast'],
118             lazy => 1,
119             default => sub {
120             my $self = shift;
121             return $self->_parser_for_url( $self->darkpan );
122             },
123             );
124              
125             has _list_compare => (
126             is => 'ro',
127             isa => InstanceOf ['List::Compare'],
128             lazy => 1,
129             default => sub {
130             my $self = shift;
131             return List::Compare->new(
132             $self->darkpan_modules,
133             $self->cpan_modules
134             );
135             },
136             );
137              
138             sub run {
139 0     0 0 0 my $self = shift;
140              
141 0         0 my $method = $self->show;
142 0         0 $method =~ s{-}{_}g;
143              
144 0         0 my $modules = $self->$method;
145              
146 0 0 0     0 if ( $method eq 'outdated_modules' && $self->verbose ) {
147 0         0 foreach my $module ( @{$modules} ) {
  0         0  
148 0         0 my @row = (
149             $module,
150             $self->darkpan_module($module)->distribution->distvname,
151             $self->cpan_module($module)->distribution->distvname,
152              
153             sprintf(
154             'https://metacpan.org/changes/distribution/%s',
155             $self->cpan_module($module)->distribution->dist
156             ),
157             );
158 0         0 say join "\t", @row;
159             }
160 0         0 return;
161             }
162              
163 0         0 say $_ for @{$modules};
  0         0  
164             }
165              
166             sub cpan_module {
167 1     1 1 593 state $signature = signature( method => Self, positional => [Str] );
168 1         56133 my ( $self, $module ) = $signature->(@_);
169              
170 1         36 return $self->_cpan_parser->package($module);
171             }
172              
173             sub darkpan_module {
174 1     1 1 2265 state $signature = signature( method => Self, positional => [Str] );
175 1         2202 my ( $self, $module ) = $signature->(@_);
176              
177 1         36 return $self->_darkpan_parser->package($module);
178             }
179              
180             sub _build_outdated_modules {
181 1     1   782 my $self = shift;
182              
183 1         30 my $darkpan = $self->_darkpan_parser;
184 1         30 my $cpan = $self->_cpan_parser;
185              
186 1         9 my @outdated;
187 1         23 for my $module ( $self->_list_compare->get_intersection ) {
188 1 50       37 if ( version->parse( $darkpan->package($module)->version )
189             < version->parse( $cpan->package($module)->version ) ) {
190 1         80 push @outdated, $module;
191             }
192             }
193 1         74 return \@outdated;
194             }
195              
196             sub _modules_from_parser {
197 2     2   1255 my $self = shift;
198 2         3 my $parser = shift;
199              
200 2         8 return [ sort { $a cmp $b } $parser->packages ];
  4         54  
201             }
202              
203             sub _parser_for_url {
204 2     2   5 my $self = shift;
205 2         5 my $url = shift;
206              
207 2 100       14 $url->scheme('file') if !$url->scheme;
208              
209 2         242 my $res = $self->ua->get($url);
210 2 50       22933 croak "could not fetch $url" if !$res->is_success;
211              
212             # dumb hack to avoid having to uncompress this ourselves
213 2         22 my @path_segments = $url->path_segments;
214              
215 2         169 my $err = <<"EOF";
216             Path invalid for $url Please provide full path to 02packages file.
217             EOF
218 2 50       14 croak $err if !@path_segments;
219              
220 2         18 my $tempdir = Path::Tiny->tempdir;
221 2         9768 my $child = $tempdir->child( pop @path_segments );
222 2         70 $child->spew_raw( $res->content );
223              
224 2         1253 return Parse::CPAN::Packages::Fast->new( $child->stringify );
225             }
226              
227             1;
228              
229             __END__
230              
231             =pod
232              
233             =head1 SYNOPSIS
234              
235             my $auditor = OrePAN2::Auditor->new(
236             cpan => 'https://cpan.metacpan.org/modules/02packages.details.txt',
237             darkpan => '/full/path/to/darkpan/02packages.details.txt'
238             );
239              
240             # ArrayRef of module names
241             my $outdated_modules = $auditor->outdated_modules;
242              
243             =head1 DESCRIPTION
244              
245             If you have a local DarkPAN or MiniCPAN or something which has its own
246             C<02packages.txt> file, it can be helpful to know which files are outdated or
247             which files exist in your DarkPAN, but not on CPAN (or vice versa). This
248             module makes this easy for you.
249              
250             Think of it as a way of diffing C<02packages> files.
251              
252             =head2 new
253              
254             my $auditor = OrePAN2::Auditor->new(
255             cpan => 'https://cpan.metacpan.org/modules/02packages.details.txt',
256             darkpan => '/full/path/to/darkpan/02packages.details.txt'
257             );
258              
259             The C<cpan> and C<darkpan> args are the only required arguments. These can
260             either be a path on your filesystem or a full URL to the 02packages files which
261             you'd like to diff.
262              
263             =head2 cpan_modules
264              
265             An C<ArrayRef> of module names which exist currently on CPAN.
266              
267             =head2 cpan_only_modules
268              
269             An C<ArrayRef> of module names which exist currently on CPAN but not in your DarkPAN.
270              
271             =head2 darkpan_modules
272              
273             An C<ArrayRef> of module names which exist currently on your DarkPAN.
274              
275             =head2 darkpan_only_modules
276              
277             An C<ArrayRef> of module names which exist currently on your DarkPAN but not in CPAN.
278              
279             =head2 outdated_modules
280              
281             An C<ArrayRef> of module names which exist currently on both your DarkPAN and
282             on CPAN and for which the module in your DarkPAN has a lower version number.
283              
284             =head2 cpan_module( $module_name )
285              
286             my $module = $auditor->cpan_module( 'HTML::Restrict' );
287              
288             Returns a L<Parse::CPAN::Packages::Fast::Package> object.
289              
290             =head2 darkpan_module( $module_name )
291              
292             my $module = $auditor->cpan_module( 'HTML::Restrict' );
293              
294             Returns a L<Parse::CPAN::Packages::Fast::Package> object.
295              
296             =cut