File Coverage

blib/lib/App/plmetrics.pm
Criterion Covered Total %
statement 124 159 77.9
branch 25 40 62.5
condition 4 4 100.0
subroutine 24 27 88.8
pod 3 3 100.0
total 180 233 77.2


line stmt bran cond sub pod time code
1             package App::plmetrics;
2 13     13   1075951 use strict;
  13         36  
  13         558  
3 13     13   75 use warnings;
  13         27  
  13         407  
4 13     13   18384 use Module::Path;
  13         11425  
  13         890  
5 13     13   14572 use Perl::Metrics::Lite;
  13         3891664  
  13         450  
6 13     13   14060 use Statistics::Swoop;
  13         41398  
  13         493  
7 13     13   117 use Text::ASCIITable;
  13         27  
  13         43694  
8              
9             our $VERSION = '0.06';
10              
11             my %VIEWER = (
12             qr/^modules?$/i => sub { $_[0]->_view_module($_[1]) },
13             qr/^methods?$/i => sub { $_[0]->_view_methods($_[1]) },
14             qr/^cc$/i => sub { $_[0]->_view_cc_lines($_[1], 'cc') },
15             qr/^lines?$/i => sub { $_[0]->_view_cc_lines($_[1], 'lines') },
16             qr/^files?$/i => sub { $_[0]->_view_files($_[1]) },
17             );
18              
19             sub new {
20 13     13 1 2982 my ($class, $opt) = @_;
21 13         73 bless +{ opt => $opt } => $class;
22             }
23              
24 84     84 1 914 sub opt { $_[0]->{opt} }
25              
26             sub run {
27 10     10 1 15505 my $self = shift;
28              
29 10         52 my ($targets, $base_path) = $self->_get_targets;
30 10         60 my $stats = $self->_get_stats($targets, $base_path);
31 10         58 $self->_view($stats);
32             }
33              
34             sub _view {
35 10     10   26 my ($self, $stats) = @_;
36              
37 10   100     34 my $result_opt = $self->opt->{'--result'} || 'module';
38              
39 10         9353 for my $regex (keys %VIEWER) {
40 39 100       1435 if ($result_opt =~ $regex) {
41 9         721 $VIEWER{$regex}->($self, $stats);
42 9         59282 return;
43             }
44             }
45 1         149 print STDERR "wrong option: --result $result_opt\nsee the --help\n";
46             }
47              
48             sub _view_cc_lines {
49 2     2   6 my ($self, $stats, $label) = @_;
50              
51 2         290 print "$label\n";
52 2         26 my $t = Text::ASCIITable->new;
53 2         94 my @metrics_keys = keys %{$stats};
  2         9  
54 2 50       8 for my $pl ( $self->opt->{'--sort'} ? sort @metrics_keys : @metrics_keys ) {
55 2         9 $t->setCols($self->_header);
56 2         304 $t->addRow( $pl, $self->_row($stats->{$pl}{$label}) );
57             }
58 2         977 print $t. "\n";
59             }
60              
61 5     5   41 sub _header { ('', qw/avg max min range sum methods/) }
62              
63             sub _row {
64 8     8   16 my ($self, $list) = @_;
65              
66 8         60 my $stats = Statistics::Swoop->new($list);
67 8         1078 return( $self->_round($stats->avg), $stats->max, $stats->min,
68             $stats->range, $stats->sum, $stats->count );
69             }
70              
71             sub _view_module {
72 4     4   9 my ($self, $stats) = @_;
73              
74 4         8 my @metrics_keys = keys %{$stats};
  4         17  
75 4 50       16 for my $pl ( $self->opt->{'--sort'} ? sort @metrics_keys : @metrics_keys ) {
76 3         345 print "$pl\n";
77 3         40 my $t = Text::ASCIITable->new;
78 3         155 $t->setCols($self->_header);
79 3         464 $t->addRow( 'cc', $self->_row($stats->{$pl}{cc}) );
80 3         1418 $t->addRow( 'lines', $self->_row($stats->{$pl}{lines}) );
81 3         1090 print $t. "\n";
82             }
83             }
84              
85             sub _view_files {
86 1     1   3 my ($self, $stats) = @_;
87              
88 1         117 print "files\n";
89 1         11 my $t = Text::ASCIITable->new;
90 1         57 $t->setCols(qw/file lines methods packages/);
91 1         237 my @metrics_keys = keys %{$stats};
  1         35  
92 1 50       6 for my $pl ( $self->opt->{'--sort'} ? sort @metrics_keys : @metrics_keys ) {
93 26         6455 $t->addRow(
94             $pl,
95             $stats->{$pl}{file_stats}{lines},
96             $stats->{$pl}{file_stats}{methods},
97             $stats->{$pl}{file_stats}{packages},
98             );
99             }
100 1         243 print $t. "\n";
101             }
102              
103 8     8   193 sub _round { sprintf("%.2f", $_[1]) }
104              
105             sub _view_methods {
106 2     2   6 my ($self, $stats) = @_;
107              
108 2         3 my @metrics_keys = keys %{$stats};
  2         9  
109 2 100       10 for my $pl ( $self->opt->{'--sort'} ? sort @metrics_keys : @metrics_keys ) {
110 2         319 print "$pl\n";
111 2         28 my $t = Text::ASCIITable->new;
112 2         109 $t->setCols('', 'cc', 'lines');
113 2         337 my $ref = $stats->{$pl}{method};
114 2         3 my @methods_keys = keys %{$ref};
  2         34  
115 2 100       11 for my $method ( $self->opt->{'--sort'} ? sort @methods_keys : @methods_keys ) {
116 76         15062 $t->addRow($method, $ref->{$method}{cc}, $ref->{$method}{lines});
117             }
118 2         345 print $t. "\n";
119             }
120             }
121              
122             sub _get_stats {
123 10     10   26 my ($self, $targets, $base_path) = @_;
124              
125 10         118 my $m = Perl::Metrics::Lite->new;
126 10         482 my $analysis = $m->analyze_files(@{$targets});
  10         59  
127              
128 10 100 100     46229703 my $stats = ( ($self->opt->{'--result'} || '') =~ m!^files?$!i )
129             ? $self->_get_file_stats($analysis, $base_path)
130             : $self->_get_sub_stats($analysis, $base_path);
131 10         282 return $stats;
132             }
133              
134             sub _get_sub_stats {
135 9     9   26 my ($self, $analysis, $base_path) = @_;
136              
137 9         24 my %stats;
138 9         21 for my $full_path (keys %{($analysis->sub_stats)}) {
  9         55  
139 8         1280 for my $sub (@{$analysis->sub_stats->{$full_path}}) {
  8         35  
140 270 100       1731 $sub->{path} =~ s!$base_path/!! if $base_path;
141 270         406 my $cc = $sub->{mccabe_complexity};
142 270         363 my $lines = $sub->{lines};
143 270         1198 $stats{$sub->{path}}->{method}{$sub->{name}} = +{
144             cc => $cc,
145             lines => $lines,
146             };
147 270         314 push @{ $stats{$sub->{path}}->{cc} }, $cc;
  270         628  
148 270         287 push @{ $stats{$sub->{path}}->{lines} }, $lines;
  270         788  
149             }
150             }
151 9         67 return \%stats;
152             }
153              
154             sub _get_file_stats {
155 1     1   3 my ($self, $analysis, $base_path) = @_;
156              
157 1         3 my %stats;
158 1         2 for my $f (@{$analysis->file_stats}) {
  1         7  
159 26 50       151 $f->{path} =~ s!$base_path/!! if $base_path;
160 26         167 $stats{$f->{path}}->{file_stats} = +{
161             packages => $f->{main_stats}{packages},
162             lines => $f->{main_stats}{lines},
163             methods => $f->{main_stats}{number_of_methods},
164             };
165             }
166 1         5 return \%stats;
167             }
168              
169             sub _get_targets {
170 10     10   20 my $self = shift;
171              
172 10 50       45 return $self->opt->{'--dir'} ? $self->_target_dir
    100          
    50          
    50          
    100          
173             : $self->opt->{'--tar'} ? $self->_target_tar
174             : $self->opt->{'--git'} ? $self->_target_git
175             : $self->opt->{'--file'} ? $self->_target_file
176             : $self->opt->{'--module'} ? $self->_target_module : [];
177             }
178              
179             sub _target_module {
180 7     7   15 my $self = shift;
181              
182 7         20 my $path = Module::Path::module_path($self->opt->{'--module'});
183 7         8629 my @targets;
184 7 100       28 if ($path) {
185 6         20 push @targets, $path;
186             }
187             else {
188 1         5 print STDERR "No such module: ". $self->opt->{'--module'}. "\n";
189             }
190 7         32 return(\@targets, '');
191             }
192              
193             sub _target_file {
194 1     1   2 my $self = shift;
195              
196 1         2 my @targets;
197 1 50       5 push @targets, $self->opt->{'--file'} if -f $self->opt->{'--file'};
198              
199 1         4 return(\@targets, '');
200             }
201              
202             sub _target_dir {
203 2     2   4 my $self = shift;
204              
205 2         3 my @targets;
206 2         7 my $dir = $self->opt->{'--dir'};
207 2 50       36 push @targets, $dir if -d $dir;
208              
209 2         8 return(\@targets, $dir);
210             }
211              
212             sub _target_git {
213 0     0     my $self = shift;
214              
215 0           $self->_load_or_recommend(qw/
216             File::Temp
217             Path::Class
218             Git::Repository
219             /);
220              
221 0           my $work_dir = File::Temp::tempdir(CLEANUP => 1);
222 0           my $repo_dir = Path::Class::dir($work_dir);
223              
224 0           Git::Repository->run(
225             clone => $self->opt->{'--git'},
226             $repo_dir->stringify,
227             );
228              
229 0           my @targets;
230 0           for my $dir (qw/lib script bin/) {
231 0           my $dir_path = "$repo_dir/$dir";
232 0 0         push @targets, $dir_path if -d $dir_path;
233             }
234              
235 0           return(\@targets, $repo_dir);
236             }
237              
238             sub _target_tar {
239 0     0     my $self = shift;
240              
241 0           $self->_load_or_recommend(qw/
242             File::Temp
243             LWP::Simple
244             Archive::Tar
245             /);
246              
247 0           my $work_dir = File::Temp::tempdir(CLEANUP => 1);
248 0           my ($fh, $filename) = File::Temp::tempfile(
249             DIR => $work_dir,
250             SUFFIX => '.tar.gz',
251             );
252              
253 0           my $tar_url = $self->opt->{'--tar'};
254 0           my ($module_dir) = ($tar_url =~ m!/([^/]+)\.tar\.gz!);
255              
256 0           LWP::Simple::getstore($tar_url => $filename);
257              
258 0           my $tar = Archive::Tar->new;
259 0           $tar->read($filename);
260 0           $tar->setcwd($work_dir);
261 0           $tar->extract;
262              
263 0           my @targets;
264 0           for my $dir (qw/lib script bin/) {
265 0           my $dir_path = "$work_dir/$module_dir/$dir";
266 0 0         push @targets, $dir_path if -d $dir_path;
267             }
268              
269 0           return(\@targets, "$work_dir/$module_dir");
270             }
271              
272             sub _load_or_recommend {
273 0     0     my ($self, @modules) = @_;
274              
275 0           for my $module (@modules) {
276 0           eval {
277 0           my $file = $module;
278 0           $file =~ s!::!/!g;
279 0           require "$file.pm"; ## no critic
280             };
281 0 0         if (my $e = $@) {
282 0           die <<"_MESSAGE_";
283             ERROR: This system does NOT have [$module] for executing this task.
284             Would you mind installing $module?
285              
286             \$ cpanm $module
287              
288             _MESSAGE_
289             }
290             else {
291 0           $module->import;
292             }
293             }
294             }
295              
296             1;
297              
298             __END__