File Coverage

lib/App/CPANTS/Lint.pm
Criterion Covered Total %
statement 12 152 7.8
branch 0 84 0.0
condition 0 42 0.0
subroutine 4 16 25.0
pod 8 8 100.0
total 24 302 7.9


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