File Coverage

blib/lib/OrePAN2/Auditor.pm
Criterion Covered Total %
statement 74 86 86.0
branch 5 10 50.0
condition 0 3 0.0
subroutine 19 20 95.0
pod 2 3 66.6
total 100 122 81.9


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