File Coverage

lib/App/CPANTS/Lint.pm
Criterion Covered Total %
statement 12 155 7.7
branch 0 86 0.0
condition 0 42 0.0
subroutine 4 16 25.0
pod 8 8 100.0
total 24 307 7.8


line stmt bran cond sub pod time code
1             package App::CPANTS::Lint;
2              
3 1     1   36093 use strict;
  1         2  
  1         51  
4 1     1   8 use warnings;
  1         2  
  1         43  
5 1     1   6 use Carp;
  1         7  
  1         93  
6 1     1   754 use Module::CPANTS::Analyse;
  1         181119  
  1         6  
7              
8             our $VERSION = '0.05';
9              
10             sub new {
11 0     0 1   my ($class, %opts) = @_;
12 0 0         $opts{no_capture} = 1 if !defined $opts{no_capture};
13 0 0 0       $opts{dump} = 1 if $opts{yaml} || $opts{json};
14 0 0         if ($opts{metrics_path}) {
15 0           Module::CPANTS::Analyse->import(@{$opts{metrics_path}});
  0            
16             }
17 0           bless {opts => \%opts}, $class;
18             }
19              
20             sub lint {
21 0     0 1   my ($self, $dist) = @_;
22              
23 0 0         croak "Cannot find $dist" unless -f $dist;
24              
25 0           my $mca = $self->{mca} = Module::CPANTS::Analyse->new({
26             dist => $dist,
27             opts => $self->{opts},
28             });
29 0           my $res = $self->{res} = {dist => $dist};
30              
31             {
32 0 0 0       if (-f $dist and my $error = $mca->unpack) {
  0            
33 0 0         warn "$dist: $error\n" and last;
34             }
35 0           $mca->analyse;
36             }
37 0           $mca->calc_kwalitee;
38              
39 0           my $kwl = $mca->d->{kwalitee};
40 0 0         my %err = %{ $mca->d->{error} || {} };
  0            
41 0           my (%fails, %passes);
42 0           for my $ind (@{$mca->mck->get_indicators}) {
  0            
43 0 0         if ($ind->{needs_db}) {
44 0   0       push @{$res->{ignored} ||= []}, $ind->{name};
  0            
45 0           next;
46             }
47 0 0 0       if ($mca->can('x_opts') && $mca->x_opts->{ignore}{$ind->{name}} && $ind->{ignorable}) {
      0        
48 0   0       push @{$res->{ignored} ||= []}, $ind->{name};
  0            
49 0           next;
50             }
51 0 0 0       next if ($kwl->{$ind->{name}} || 0) > 0;
52 0 0         my $type = $ind->{is_extra} ? 'extra' :
    0          
53             $ind->{is_experimental} ? 'experimental' :
54             'core';
55 0 0 0       next if $type eq 'experimental' && !$self->{opts}{experimental};
56 0           my $error = $err{$ind->{name}};
57 0 0 0       if ($error && ref $error) {
58 0           $error = $self->_dump($error);
59             }
60 0   0       push @{$fails{$type} ||= []}, {
  0            
61             name => $ind->{name},
62             remedy => $ind->{remedy},
63             error => $error,
64             };
65             }
66              
67 0           $res->{fails} = \%fails;
68 0           $res->{score} = $self->score(1);
69              
70 0 0 0       return $res->{ok} = (!$fails{core} and (!$fails{extra} || $self->{opts}{core_only})) ? 1 : 0;
71             }
72              
73             sub _dump {
74 0     0     my ($self, $thingy, $pretty) = @_;
75 0 0 0       if ($self->{opts}{yaml} && eval { require CPAN::Meta::YAML }) {
  0 0 0        
76 0           return CPAN::Meta::YAML::Dump($thingy);
77 0           } elsif ($self->{opts}{json} && eval { require JSON::PP }) {
78 0           my $coder = JSON::PP->new->utf8;
79 0 0         $coder->pretty if $pretty;
80 0           return $coder->encode($thingy);
81             } else {
82 0           require Data::Dumper;
83 0           my $dumper = Data::Dumper->new([$thingy])->Terse(1)->Sortkeys(1);
84 0 0         $dumper->Indent(0) unless $pretty;
85 0           $dumper->Dump;
86             }
87             }
88              
89 0     0 1   sub stash { shift->{mca}->d }
90 0     0 1   sub result { shift->{res} }
91              
92             sub score {
93 0     0 1   my ($self, $wants_detail) = @_;
94              
95 0           my $mca = $self->{mca};
96 0 0         my %fails = %{$self->{res}{fails} || {}};
  0            
97 0           my $max_core_kw = $mca->mck->available_kwalitee;
98 0           my $max_kw = $mca->mck->total_kwalitee;
99 0 0         my $total_kw = $max_kw - @{$fails{core} || []} - @{$fails{extra} || []};
  0 0          
  0            
100              
101 0           my $score = sprintf "%.2f", 100 * $total_kw/$max_core_kw;
102              
103 0 0         if ($wants_detail) {
104 0           $score .= "% ($total_kw/$max_core_kw)";
105             }
106 0           $score;
107             }
108              
109             sub report {
110 0     0 1   my $self = shift;
111              
112             # shortcut
113 0 0 0       if ($self->{opts}{dump}) {
    0          
114 0           return $self->_dump($self->stash, 'pretty');
115             } elsif ($self->{opts}{colour} && $self->_supports_colour) {
116 0           return $self->_colour;
117             }
118              
119 0   0       my $res = $self->{res} || {};
120              
121 0           my $report =
122             "Checked dist: $res->{dist}\n" .
123             "Score: $res->{score}\n";
124              
125 0 0         if ($res->{ignored}) {
126 0           $report .= "Ignored metrics: " . join(', ', @{$res->{ignored}}) . "\n";
  0            
127             }
128              
129 0 0         if ($res->{ok}) {
130 0           $report .= "Congratulations for building a 'perfect' distribution!\n";
131             }
132 0           for my $type (qw/core extra experimental/) {
133 0 0         if (my $fails = $res->{fails}{$type}) {
134 0           $report .=
135             "\n" .
136             "Failed $type Kwalitee metrics and\n" .
137             "what you can do to solve them:\n\n";
138 0           for my $fail (@$fails) {
139 0           $report .=
140             "Name: $fail->{name}\n" .
141             "Remedy: $fail->{remedy}\n";
142 0 0         if ($fail->{error}) {
143 0           $report .= "Error: $fail->{error}\n";
144             }
145 0           $report .= "\n";
146             }
147             }
148             }
149              
150 0           $report;
151             }
152              
153             sub _supports_colour {
154 0     0     my $self = shift;
155 0           eval {
156 0           require Term::ANSIColor;
157 0 0         require Win32::Console::ANSI if $^O eq 'MSWin32';
158 0           1
159             }
160             }
161              
162             sub _colour_scheme {
163 0     0     my $self = shift;
164 0           my %scheme = (
165             heading => "bright_white",
166             title => "blue",
167             fail => "bright_red",
168             pass => "bright_green",
169             warn => "bright_yellow",
170             error => "red",
171             summary => "blue",
172             );
173 0 0         if ($^O eq 'MSWin32') {
174 0           $scheme{$_} =~ s/bright_// for keys %scheme;
175             }
176 0           \%scheme;
177             }
178              
179             sub _colour {
180 0     0     my ($self) = @_;
181 0           my $scheme = $self->_colour_scheme;
182 0 0         my $icon = $^O eq 'MSWin32'
183             ? {pass => 'o', fail => 'x'}
184             : {pass => "\x{2713}", fail => "\x{2717}"};
185              
186 0           my $report = Term::ANSIColor::colored("Distribution: ", "bold $scheme->{heading}")
187             . Term::ANSIColor::colored($self->result->{dist}, "bold $scheme->{title}")
188             . "\n";
189            
190 0           my %failed;
191 0           for my $arr (values %{$self->result->{fails}}) {
  0            
192 0           for my $fail (@$arr) {
193 0           $failed{$fail->{name}} = $fail;
194             }
195             }
196            
197 0           my $core_fails = 0;
198 0           for my $type (qw/ Core Optional Experimental /) {
199 0           $report .= Term::ANSIColor::colored("\n$type\n", "bold $scheme->{heading}");
200 0           my @inds = $self->{mca}->mck->get_indicators(lc $type);
201 0           my @fails;
202 0           for my $ind (@inds) {
203 0 0         if ($failed{ $ind->{name} }) {
204 0           push @fails, $ind;
205 0 0         $core_fails++ if $type eq 'Core';
206 0           $report .= Term::ANSIColor::colored(" $icon->{fail} ", $scheme->{fail}) . $ind->{name};
207 0 0         $report .= ": " . Term::ANSIColor::colored($failed{ $ind->{name} }{error}, $scheme->{error})
208             if $failed{ $ind->{name} }{error};
209             } else {
210 0           $report .= Term::ANSIColor::colored(" $icon->{pass} ", $scheme->{pass}) . $ind->{name};
211             }
212 0           $report .= "\n";
213             }
214            
215 0           for my $fail (@fails) {
216 0           $report .= "\n"
217             . Term::ANSIColor::colored("Name: ", "bold $scheme->{summary}")
218             . Term::ANSIColor::colored("$fail->{name}\n", $scheme->{summary})
219             . Term::ANSIColor::colored("Remedy: ", "bold $scheme->{summary}")
220             . Term::ANSIColor::colored("$fail->{remedy}\n", $scheme->{summary});
221             }
222             }
223            
224 0           my $scorecolour = $scheme->{pass};
225 0 0         $scorecolour = $scheme->{warn} if keys %failed;
226 0 0         $scorecolour = $scheme->{fail} if $core_fails;
227            
228 0           $report .= "\n"
229             . Term::ANSIColor::colored("Score: ", "bold $scheme->{heading}")
230             . Term::ANSIColor::colored($self->result->{score}, "bold $scorecolour")
231             . "\n";
232            
233 0           $report;
234             }
235              
236             sub output_report {
237 0     0 1   my $self = shift;
238 0 0         if ($self->{opts}{save}) {
239 0           my $file = $self->report_file;
240 0 0         open my $fh, '>:utf8', $file or croak "Cannot write to $file: $!";
241 0           print $fh $self->report;
242             } else {
243 0           binmode(STDOUT, ':utf8');
244 0           print $self->report;
245             }
246             }
247              
248             sub report_file {
249 0     0 1   my $self = shift;
250 0   0       my $dir = $self->{opts}{dir} || '.';
251 0           my $vname = $self->{mca}->d->{vname};
252 0 0         if (!$vname) {
253 0           require File::Basename;
254 0           $vname = File::Basename::basename($self->{res}{dist});
255             }
256 0 0         my $extension =
    0          
    0          
257             $self->{opts}{yaml} ? '.yml' :
258             $self->{opts}{json} ? '.json' :
259             $self->{opts}{dump} ? '.dmp' :
260             '.txt';
261              
262 0           require File::Spec;
263 0           File::Spec->catfile($dir, "$vname$extension");
264             }
265              
266             1;
267              
268             __END__