File Coverage

blib/lib/CPANTS/Kwalitee/Report.pm
Criterion Covered Total %
statement 41 131 31.3
branch 0 28 0.0
condition 0 6 0.0
subroutine 14 27 51.8
pod 5 7 71.4
total 60 199 30.1


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