File Coverage

lib/Code/Statistics/Reporter.pm
Criterion Covered Total %
statement 168 169 99.4
branch 13 18 72.2
condition 4 6 66.6
subroutine 31 31 100.0
pod 1 1 100.0
total 217 225 96.4


line stmt bran cond sub pod time code
1 1     1   6 use strict;
  1         2  
  1         41  
2 1     1   6 use warnings;
  1         2  
  1         70  
3              
4             package Code::Statistics::Reporter;
5             {
6             $Code::Statistics::Reporter::VERSION = '1.112980';
7             }
8              
9             # ABSTRACT: creates reports statistics and outputs them
10              
11 1     1   26 use 5.004;
  1         4  
  1         31  
12              
13 1     1   6 use Moose;
  1         2  
  1         8  
14 1     1   7044 use MooseX::HasDefaults::RO;
  1         3  
  1         8  
15 1     1   5209 use Code::Statistics::MooseTypes;
  1         2  
  1         26  
16 1     1   6 use Code::Statistics::Metric;
  1         1  
  1         9  
17              
18 1     1   13 use Carp 'confess';
  1         2  
  1         72  
19 1     1   5 use JSON 'from_json';
  1         3  
  1         9  
20 1     1   132 use File::Slurp 'read_file';
  1         2  
  1         45  
21 1     1   6 use List::Util qw( reduce max sum min );
  1         3  
  1         76  
22 1     1   1042 use Data::Section -setup;
  1         16651  
  1         10  
23 1     1   4174 use Template;
  1         32399  
  1         51  
24 1     1   14 use List::MoreUtils qw( uniq );
  1         2  
  1         83  
25 1     1   9 use Clone qw( clone );
  1         2  
  1         2470  
26              
27             has quiet => ( isa => 'Bool' );
28              
29             has file_ignore => (
30             isa => 'CS::InputList',
31             coerce => 1,
32             default => sub {[]},
33             );
34              
35             has screen_width => ( isa => 'Int', default => 80 );
36             has min_path_width => ( isa => 'Int', default => 12 );
37             has table_length => ( isa => 'Int', default => 10 );
38              
39              
40             sub report {
41 1     1 1 457 my ( $self ) = @_;
42              
43 1         8 my $stats = from_json read_file('codestat.out');
44              
45 1         315 $stats->{files} = $self->_strip_ignored_files( @{ $stats->{files} } );
  1         14  
46 1         8 $stats->{target_types} = $self->_prepare_target_types( $stats->{files} );
47              
48 1         3 $_->{metrics} = $self->_process_target_type( $_, $stats->{metrics} ) for @{$stats->{target_types}};
  1         7  
49              
50 1         3 my $output;
51 1         8 my $tmpl = $self->section_data( 'dos_template' );
52 1         2639 my $tt = Template->new( STRICT => 1 );
53             $tt->process(
54             $tmpl,
55             {
56             targets => $stats->{target_types},
57             truncate_front => sub {
58 128     128   189049 my ( $string, $length ) = @_;
59 128 50       602 return $string if $length >= length $string;
60 0         0 return substr $string, 0-$length, $length;
61             },
62             },
63 1 50       56126 \$output
64             ) or confess $tt->error;
65              
66 1 50       1219 print $output if !$self->quiet;
67              
68 1         28 return $output;
69             }
70              
71             sub _strip_ignored_files {
72 1     1   4 my ( $self, @files ) = @_;
73              
74 1         2 my @ignore_regexes = grep { $_ } @{ $self->file_ignore };
  2         4  
  1         34  
75              
76 1         4 for my $re ( @ignore_regexes ) {
77 1         3 @files = grep { $_->{path} !~ $re } @files;
  4         31  
78             }
79              
80 1         4 return \@files;
81             }
82              
83             sub _sort_columns {
84 10     10   38 my ( $self, %widths ) = @_;
85              
86             # get all columns in the right order
87 10         18 my @start_columns = qw( path line col );
88 10         15 my %end_columns = ( 'deviation' => 1 );
89 10         50 my @columns = uniq grep { !$end_columns{$_} } @start_columns, sort keys %widths;
  110         219  
90 10         36 push @columns, keys %end_columns;
91              
92 10         14 @columns = grep { $widths{$_} } @columns; # remove the ones that have no data
  80         126  
93              
94             # expand the rest
95 10         29 @columns = map $self->_make_col_hash( $_, \%widths ), @columns;
96              
97             # calculate the width left over for the first column
98 10         47 my $used_width = sum( values %widths ) - $columns[0]{width};
99 10         367 my $first_col_width = $self->screen_width - $used_width;
100              
101             # special treatment for the first column
102 10         22 for ( @columns[0..0] ) {
103 10         283 $_->{width} = max( $self->min_path_width, $first_col_width );
104 10         34 $_->{printname} = substr $_->{printname}, 1;
105             }
106              
107 10         48 return \@columns;
108             }
109              
110             sub _make_col_hash {
111 80     80   106 my ( $self, $col, $widths ) = @_;
112              
113 80         128 my $short_name = $self->_col_short_name($_);
114 80         268 my $col_hash = {
115             name => $_,
116             width => $widths->{$_},
117             printname => " $short_name",
118             };
119              
120 80         218 return $col_hash;
121             }
122              
123             sub _prepare_target_types {
124 1     1   3 my ( $self, $files ) = @_;
125              
126 1         3 my %target_types;
127              
128 1         2 for my $file ( @{$files} ) {
  1         3  
129 3         4 for my $target_type ( keys %{$file->{measurements}} ) {
  3         7  
130 7         8 for my $target ( @{$file->{measurements}{$target_type}} ) {
  7         10  
131 37         41 $target->{path} = $file->{path};
132 37         29 push @{ $target_types{$target_type}->{list} }, $target;
  37         60  
133             }
134             }
135             }
136              
137 1         7 $target_types{$_}->{type} = $_ for keys %target_types;
138              
139 1         6 return [ values %target_types ];
140             }
141              
142             sub _process_target_type {
143 3     3   4 my ( $self, $target_type, $metrics ) = @_;
144              
145 3         4 my @metric = map $self->_process_metric( $target_type, $_ ), @{$metrics};
  3         10  
146              
147 3         14 return \@metric;
148             }
149              
150             sub _process_metric {
151 24     24   35 my ( $self, $target_type, $metric ) = @_;
152              
153 24 100       116 return if "Code::Statistics::Metric::$metric"->is_insignificant;
154 12 50 33     41 return if !$target_type->{list} or !@{$target_type->{list}};
  12         30  
155 12 50       35 return if !exists $target_type->{list}[0]{$metric};
156              
157 12         12 my @list = reverse sort { $a->{$metric} <=> $b->{$metric} } @{$target_type->{list}};
  373         474  
  12         40  
158              
159 12         31 my $metric_data = { type => $metric };
160              
161 12         28 $metric_data->{avg} = $self->_calc_average( $metric, @list );
162              
163 12 100 100     75 $self->_prepare_metric_tables( $metric_data, @list ) if $metric_data->{avg} and $metric_data->{avg} != 1;
164              
165 12         39 return $metric_data;
166             }
167              
168             sub _prepare_metric_tables {
169 10     10   25 my ( $self, $metric_data, @list ) = @_;
170              
171 10         23 $metric_data->{top} = $self->_get_top( @list );
172 10         58 $metric_data->{bottom} = $self->_get_bottom( @list );
173 10         31 $self->_calc_deviation( $_, $metric_data ) for ( @{$metric_data->{top}}, @{$metric_data->{bottom}} );
  10         16  
  10         32  
174 10         22 $metric_data->{widths} = $self->_calc_widths( $metric_data );
175 10         11 $metric_data->{columns} = $self->_sort_columns( %{ $metric_data->{widths} } );
  10         49  
176              
177 10         34 return;
178             }
179              
180             sub _calc_deviation {
181 128     128   149 my ( $self, $line, $metric_data ) = @_;
182              
183 128         146 my $avg = $metric_data->{avg};
184 128         129 my $type = $metric_data->{type};
185              
186 128         211 my $deviation = $line->{$type} / $avg;
187 128         483 $line->{deviation} = sprintf '%.2f', $deviation;
188              
189 128         316 return;
190             }
191              
192             sub _calc_widths {
193 10     10   10 my ( $self, $metric_data ) = @_;
194              
195 10         10 my @entries = @{$metric_data->{top}};
  10         73  
196 10         16 @entries = ( @entries, @{$metric_data->{bottom}} );
  10         30  
197              
198 10         17 my @columns = keys %{$entries[0]};
  10         42  
199              
200 10         20 my %widths;
201 10         14 for my $col ( @columns ) {
202 80         100 my @lengths = map { length $_->{$col} } @entries;
  1024         1568  
203 80         172 push @lengths, length $self->_col_short_name($col);
204 80         187 my $max = max @lengths;
205 80         188 $widths{$col} = $max;
206             }
207              
208 10         48 $_++ for values %widths;
209              
210 10         43 return \%widths;
211             }
212              
213             sub _calc_average {
214 12     12   29 my ( $self, $metric, @list ) = @_;
215              
216 12     148   69 my $sum = reduce { $a + $b->{$metric} } 0, @list;
  148         162  
217 12         39 my $average = $sum / @list;
218              
219 12         41 return $average;
220             }
221              
222             sub _get_top {
223 10     10   37 my ( $self, @list ) = @_;
224              
225 10         340 my $slice_end = min( $#list, $self->table_length - 1 );
226 10         38 my @top = grep { defined } @list[ 0 .. $slice_end ];
  79         119  
227              
228 10         882 return clone \@top;
229             }
230              
231             sub _get_bottom {
232 10     10   27 my ( $self, @list ) = @_;
233              
234 10 100       288 return [] if @list < $self->table_length;
235              
236 7         11 @list = reverse @list;
237 7         183 my $slice_end = min( $#list, $self->table_length - 1 );
238 7         24 my @bottom = @list[ 0 .. $slice_end ];
239              
240 7         183 my $bottom_size = @list - $self->table_length;
241 7 100       181 @bottom = splice @bottom, 0, $bottom_size if $bottom_size < $self->table_length;
242              
243 7         450 return clone \@bottom;
244             }
245              
246             sub _col_short_name {
247 160     160   192 my ( $self, $col ) = @_;
248 160         560 return ucfirst "Code::Statistics::Metric::$col"->short_name;
249             }
250              
251             1;
252              
253              
254              
255             =pod
256              
257             =head1 NAME
258              
259             Code::Statistics::Reporter - creates reports statistics and outputs them
260              
261             =head1 VERSION
262              
263             version 1.112980
264              
265             =head2 reports
266             Creates a report on given code statistics and outputs it in some way.
267              
268             =head1 AUTHOR
269              
270             Christian Walde <mithaldu@yahoo.de>
271              
272             =head1 COPYRIGHT AND LICENSE
273              
274             This software is Copyright (c) 2010 by Christian Walde.
275              
276             This is free software, licensed under:
277              
278             DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE, Version 2, December 2004
279              
280             =cut
281              
282              
283             __DATA__
284             __[ dos_template ]__
285              
286             ================================================================================
287             ============================ Code Statistics Report ============================
288             ================================================================================
289              
290             [% FOR target IN targets %]
291             ================================================================================
292              
293             [%- " " FILTER repeat( ( 80 - target.type.length ) / 2 ) %][% target.type %]
294             ================================================================================
295              
296              
297             [%- "averages" %]
298              
299             [%- FOR metric IN target.metrics %]
300             [%- metric.type %]: [% metric.avg %]
301              
302             [%- END %]
303              
304             [%- FOR metric IN target.metrics %]
305             [%- NEXT IF !metric.defined( 'top' ) and !metric.defined( 'bottom' ) %]
306              
307             [%- " " FILTER repeat( ( 80 - metric.type.length ) / 2 ) %][% metric.type %]
308              
309             [%- FOR table_mode IN [ 'top', 'bottom' ] %]
310             [%- NEXT IF !metric.$table_mode.size -%]
311             [%- table_mode %] ten
312              
313             [%- FOR column IN metric.columns -%]
314             [%- column.printname FILTER format("%-${column.width}s") -%]
315             [%- END %]
316             --------------------------------------------------------------------------------
317              
318             [%- FOR line IN metric.$table_mode -%]
319             [%- FOR column IN metric.columns -%]
320             [%- IF column.name == 'path' # align to the left and truncate -%]
321             [%- truncate_front( line.${column.name}, column.width ) FILTER format("%-${column.width}s") -%]
322             [%- ELSE # align to the right -%]
323             [%- line.${column.name} FILTER format("%${column.width}s") -%]
324             [%- END -%]
325             [%- END %]
326              
327             [%- END -%]
328             --------------------------------------------------------------------------------
329              
330              
331             [%- END %]
332             [%- END -%]
333             [%- END -%]