File Coverage

blib/lib/CPANTS/Kwalitee/Report.pm
Criterion Covered Total %
statement 41 126 32.5
branch 0 28 0.0
condition 0 6 0.0
subroutine 14 22 63.6
pod 5 7 71.4
total 60 189 31.7


line stmt bran cond sub pod time code
1             package CPANTS::Kwalitee::Report;
2              
3             $CPANTS::Kwalitee::Report::VERSION = '0.09';
4             $CPANTS::Kwalitee::Report::AUTHORITY = 'cpan:MANWAR';
5              
6             =head1 NAME
7              
8             CPANTS::Kwalitee::Report - CPANTS Kwalitee Report.
9              
10             =head1 VERSION
11              
12             Version 0.09
13              
14             =cut
15              
16 3     3   51969 use 5.006;
  3         12  
17 3     3   1651 use Data::Dumper;
  3         25016  
  3         223  
18 3     3   1834 use File::Temp qw/tempdir/;
  3         51701  
  3         215  
19              
20 3     3   1351 use LWP::Simple;
  3         155271  
  3         23  
21 3     3   2392 use XML::RSS::Parser;
  3         18264  
  3         114  
22 3     3   1409 use Parse::CPAN::Packages;
  3         2700993  
  3         125  
23 3     3   1913 use Module::CPANTS::Analyse;
  3         35862  
  3         16  
24 3     3   26934 use Module::CPANTS::Kwalitee;
  3         10  
  3         20  
25              
26 3     3   1931 use CPANTS::Kwalitee::Report::Score;
  3         13  
  3         115  
27 3     3   1557 use CPANTS::Kwalitee::Report::Generator;
  3         14  
  3         242  
28 3     3   1596 use CPANTS::Kwalitee::Report::Indicator;
  3         17  
  3         151  
29 3     3   1595 use CPANTS::Kwalitee::Report::Distribution;
  3         14  
  3         129  
30              
31 3     3   28 use Moo;
  3         8  
  3         16  
32 3     3   7414 use namespace::clean;
  3         7  
  3         19  
33              
34             our $PAUSE_INDEX = 'http://www.cpan.org/modules/02packages.details.txt.gz';
35             our $RECENT_MODS = 'https://metacpan.org/feed/recent';
36              
37             has 'index' => (is => 'rw', default => sub { get($PAUSE_INDEX) }, lazy => 1);
38             has 'recent' => (is => 'ro', default => sub { get($RECENT_MODS) }, lazy => 1);
39             has 'rss' => (is => 'ro', default => sub { XML::RSS::Parser->new }, lazy => 1);
40             has 'kwalitee' => (is => 'ro', default => sub { Module::CPANTS::Kwalitee->new }, lazy => 1);
41             has 'verbose' => (is => 'rw', default => sub { 0 });
42             has 'parser' => (is => 'rw');
43             has 'generators' => (is => 'rw');
44             has 'indicators' => (is => 'rw');
45             has 'recent_dists' => (is => 'rw');
46              
47             =head1 DESCRIPTION
48              
49             This work is inspired by L<Module::CPANTS::Analyse> and L<Test::Kwalitee>. The
50             main objective of this module is to provide simple API to query Kwalitee scores.
51              
52             I came across a script C<kwalitee-metrics>, part of L<Test::Kwalitee>, where the
53             author wish there was an API to do what the author was doing. That prompted me to
54             begin the journey.
55              
56             This is what it would look like now, if using this module:
57              
58             use strict; use warnings;
59             use CPANTS::Kwalitee::Report;
60              
61             my $verbose = @ARGV && ($ARGV[0] eq '--verbose' || $ARGV[0] eq '-v');
62             my $report = CPANTS::Kwalitee::Report->new({ verbose => $verbose });
63              
64             print sprintf("%s\n\n", join("\n", @{$report->get_generators}));
65              
66             Interesting comparison by L<Devel::Timer> shown below:
67              
68             Devel::Timer Report -- Total time: 0.1557 secs
69             Interval Time Percent
70             ----------------------------------------------
71             00 -> 01 0.1458 93.62% INIT -> old way
72             01 -> 02 0.0099 6.38% old way -> new way
73              
74             It comes with a handy script C<kwalitee-report>, which can be used to query the
75             kwalitee scores of any distribution.
76              
77             $ kwalitee-score --dist=Map::Tube
78              
79             More detailed options shown below:
80              
81             $ kwalitee-report -h
82             USAGE: kwalitee-report [-hn] [long options...]
83              
84             --dist=String Distribution name to generate Kwalitee
85             report.
86             --metrics Show CPANTS Kwalitee metrics.
87             --recently_uploaded_dists Lookup recently uploaded distributions.
88             -n=Int Distribution count to generate Kwalitee
89             report. Default is 5.
90             --verbose Be more descriptive. Default is OFF.
91              
92             --usage show a short help message
93             -h show a compact help message
94             --help show a long help message
95             --man show the manual
96              
97             =head1 SYNOPSIS
98              
99             use strict; use warnings;
100             use CPANTS::Kwalitee::Report;
101              
102             my $report = CPANTS::Kwalitee::Report->new;
103              
104             # Individual distribution kwalitee scores.
105             print $report->scores('Map::Tube');
106              
107             # Recently uploaded last 3 distributions scores.
108             my $dists = $report->recently_uploaded_distributions(3);
109             print join("\n------\n", @$dists);
110              
111             =head1 METHODS
112              
113             =head2 kwalitee()
114              
115             Returns an object of type L<Module::CPANTS::Kwalitee>.
116              
117             =head2 get_generators()
118              
119             Returns an array ref of objects of type L<CPANTS::Kwalitee::Report::Generator>.
120              
121             =cut
122              
123             sub get_generators {
124 0     0 1   my ($self) = @_;
125              
126 0 0         unless (defined $self->{generators}) {
127 0           $self->fetch_generators;
128             }
129              
130 0           return $self->{generators};
131             }
132              
133             =head2 get_indicators()
134              
135             Returns an array ref of objects of type L<CPANTS::Kwalitee::Report::Indicator>.
136              
137             =cut
138              
139             sub get_indicators {
140 0     0 1   my ($self) = @_;
141              
142 0 0         unless (defined $self->{indicators}) {
143 0           $self->fetch_generators;
144             }
145              
146 0           return [ values %{$self->{indicators}} ];
  0            
147             }
148              
149             =head2 get_indicator($name)
150              
151             Returns an object of type L<CPANTS::Kwalitee::Report::Indicator>.
152              
153             =cut
154              
155             sub get_indicator {
156 0     0 1   my ($self, $name) = @_;
157              
158 0 0         unless (defined $self->{indicators}) {
159 0           $self->fetch_generators;
160             }
161              
162 0           return $self->{indicators}->{$name};
163             }
164              
165             =head2 recently_uploaded_distributions($count)
166              
167             Returns an array ref of objects of type L<CPANTS::Kwalitee::Report::Distribution>
168             with no more than C<$count> members.
169              
170             =cut
171              
172             sub recently_uploaded_distributions {
173 0     0 1   my ($self, $count) = @_;
174              
175 0 0         if (defined $count) {
176 0 0         if ($count < 0) {
    0          
177 0           $count = 5;
178             }
179             elsif ($count > 100) {
180 0           $count = 100;
181             }
182             }
183              
184 0           my $r_dist = [];
185 0           my $seen = {};
186 0           my $feed = $self->rss->parse_string($self->recent);
187 0           foreach my $item ($feed->query('//item')) {
188 0           my $link = $item->query('link')->text_content;
189 0           my $path = sprintf("%s.tar.gz", $link);
190 0           my $cpan = CPAN::DistnameInfo->new($path);
191 0           my $dist = $cpan->dist;
192 0 0 0       next if (exists $seen->{$dist} || exists $self->{recent_dists}->{$dist});
193              
194 0           $seen->{$dist} = 1;
195 0           $self->{recent_dists}->{$dist} = $path;
196 0           push @$r_dist, { dist => $dist, path => $path, link => $link };
197              
198 0 0 0       if (defined $count && (scalar(keys %{$self->{recent_dists}}) == $count)) {
  0            
199 0           last;
200             }
201             }
202              
203 0           my $dists = [];
204 0           foreach my $d (@$r_dist) {
205 0           push @$dists, $self->scores($d->{dist}, $d->{path}, $d->{link});
206             }
207              
208 0           return $dists;
209             }
210              
211             =head2 scores($dist_name, [$dist_path], [$dist_link])
212              
213             Returns an object of type L<CPANTS::Kwalitee::Report::Distribution>.
214              
215             =cut
216              
217             sub scores {
218 0     0 1   my ($self, $dist_name, $dist_path, $dist_link) = @_;
219              
220 0 0         die "ERROR: Missing distribution name.\n" unless (defined $dist_name);
221              
222 0           $dist_name = _format_dist_name($dist_name);
223 0 0         $dist_path = $self->get_dist_path($dist_name) unless (defined $dist_path);
224 0           my $analyser = Module::CPANTS::Analyse->new({ distdir => $dist_path, dist => tempdir(CLEANUP => 1) });
225 0           $analyser->run;
226              
227 0           my $scores = [];
228 0           foreach my $name (keys %{$analyser->d->{kwalitee}}) {
  0            
229 0           my $indicator = $self->get_indicator($name);
230 0 0         if (defined $indicator) {
231             push @$scores, CPANTS::Kwalitee::Report::Score->new(
232             {
233             indicator => $indicator,
234 0           value => $analyser->d->{kwalitee}->{$name},
235             });
236             }
237             }
238              
239 0           return CPANTS::Kwalitee::Report::Distribution->new(
240             { name => $dist_name,
241             path => $dist_path,
242             link => $dist_link,
243             scores => $scores
244             });
245             }
246              
247             #
248             #
249             # PRIVATE METHODS
250              
251             sub get_dist_path {
252 0     0 0   my ($self, $dist_name) = @_;
253              
254 0           foreach my $dist (keys %{$self->{recent_dists}}) {
  0            
255 0 0         if (exists $self->{recent_dists}->{$dist_name}) {
256 0           return $self->{recent_dists}->{$dist_name};
257             }
258             }
259              
260             # Can't find it, look into pause index file now.
261 0           my $parser = $self->parser;
262 0 0         unless (defined $parser) {
263 0           $parser = Parse::CPAN::Packages->new($self->index);
264 0           $self->parser($parser);
265             }
266              
267 0           my $dist = $parser->latest_distribution($dist_name);
268 0 0         die "ERROR: Unable to locate distribution $dist_name.\n" unless (defined $dist);
269              
270 0           $self->{recent_dists}->{$dist_name} = $dist->{prefix};
271              
272 0           return $dist->{prefix};
273             }
274              
275             sub fetch_generators {
276 0     0 0   my ($self) = @_;
277              
278 0           my $generators = [];
279 0           my $indicators = {};
280 0           my $verbose = $self->verbose;
281 0           my $kwalitee = $self->kwalitee;
282 0           foreach my $generator (@{$kwalitee->generators}) {
  0            
283 0           my $g_indicators = [];
284 0           foreach my $indicator (@{$generator->kwalitee_indicators}) {
  0            
285 0           my @types = grep { exists $indicator->{$_} } qw(is_extra is_experimental needs_db);
  0            
286 0           my $indicator_name = $indicator->{name};
287             my $object = CPANTS::Kwalitee::Report::Indicator->new(
288             {
289             name => $indicator_name,
290             types => \@types,
291             error => $indicator->{error},
292             remedy => $indicator->{remedy},
293 0           verbose => $verbose,
294             }
295             );
296              
297 0           push @$g_indicators, $object;
298 0           $indicators->{$indicator_name} = $object;
299             }
300              
301 0           push @$generators,
302             CPANTS::Kwalitee::Report::Generator->new(
303             {
304             name => $generator,
305             version => $generator->VERSION,
306             indicators => $g_indicators,
307             verbose => $verbose,
308             });
309             }
310              
311 0           $self->{generators} = $generators;
312 0           $self->{indicators} = $indicators;
313             }
314              
315             sub _format_dist_name {
316 0     0     my ($name) = @_;
317              
318 0           $name =~ s/\:\:/\-/g;
319 0           return $name;
320             }
321              
322             =head1 AUTHOR
323              
324             Mohammad S Anwar, C<< <mohammad.anwar at yahoo.com> >>
325              
326             =head1 REPOSITORY
327              
328             L<https://github.com/manwar/CPANTS-Kwalitee-Report>
329              
330             =head1 SEE ALSO
331              
332             =over 4
333              
334             =item L<Module::CPANTS::Analyse>
335              
336             =item L<Test::Kwalitee>
337              
338             =back
339              
340             =head1 BUGS
341              
342             Please report any bugs or feature requests to C<bug-cpants-kwalitee-report at rt.cpan.org>,
343             or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CPANTS-Kwalitee-Report>.
344             I will be notified and then you'll automatically be notified of progress on your
345             bug as I make changes.
346              
347             =head1 SUPPORT
348              
349             You can find documentation for this module with the perldoc command.
350              
351             perldoc CPANTS::Kwalitee::Report
352              
353             You can also look for information at:
354              
355             =over 4
356              
357             =item * RT: CPAN's request tracker (report bugs here)
358              
359             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=CPANTS-Kwalitee-Report>
360              
361             =item * AnnoCPAN: Annotated CPAN documentation
362              
363             L<http://annocpan.org/dist/CPANTS-Kwalitee-Report>
364              
365             =item * CPAN Ratings
366              
367             L<http://cpanratings.perl.org/d/CPANTS-Kwalitee-Report>
368              
369             =item * Search CPAN
370              
371             L<http://search.cpan.org/dist/CPANTS-Kwalitee-Report/>
372              
373             =back
374              
375             =head1 LICENSE AND COPYRIGHT
376              
377             Copyright (C) 2017 Mohammad S Anwar.
378              
379             This program is free software; you can redistribute it and / or modify it under
380             the terms of the the Artistic License (2.0). You may obtain a copy of the full
381             license at:
382              
383             L<http://www.perlfoundation.org/artistic_license_2_0>
384              
385             Any use, modification, and distribution of the Standard or Modified Versions is
386             governed by this Artistic License.By using, modifying or distributing the Package,
387             you accept this license. Do not use, modify, or distribute the Package, if you do
388             not accept this license.
389              
390             If your Modified Version has been derived from a Modified Version made by someone
391             other than you,you are nevertheless required to ensure that your Modified Version
392             complies with the requirements of this license.
393              
394             This license does not grant you the right to use any trademark, service mark,
395             tradename, or logo of the Copyright Holder.
396              
397             This license includes the non-exclusive, worldwide, free-of-charge patent license
398             to make, have made, use, offer to sell, sell, import and otherwise transfer the
399             Package with respect to any patent claims licensable by the Copyright Holder that
400             are necessarily infringed by the Package. If you institute patent litigation
401             (including a cross-claim or counterclaim) against any party alleging that the
402             Package constitutes direct or contributory patent infringement,then this Artistic
403             License to you shall terminate on the date that such litigation is filed.
404              
405             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
406             CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
407             WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
408             NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS
409             REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT,
410             INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE
411             OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
412              
413             =cut
414              
415             1; # End of CPANTS::Kwalitee::Report