#!/usr/bin/perl -w use strict; use Devel::Cover::DB; use Text::Diff::Parser; use POSIX qw(strftime); use Getopt::Long; my $DIFF = 'diff'; my $COVER_DB = 'cover_db'; my @PREFIX; my $SIMPLIFY=0; my $HELP=0; my $STRIP=0; my $ret = GetOptions( 'diff=s' => \$DIFF, 'cover-db=s' => \$COVER_DB, 'prefix=s' => \@PREFIX, 'simplify' => \$SIMPLIFY, 'strip=i' => \$STRIP, 'help' => \$HELP ); unless( $ret ) { usage(); exit 3; } if( $HELP ) { usage(); exit 0; } my $OUTPUT = "$COVER_DB/diff.html"; ######################################################### my $diff = Text::Diff::Parser->new( File => $DIFF, Simplify => $SIMPLIFY, Strip => $STRIP ); my $db = Devel::Cover::DB->new( db=>$COVER_DB ); my $cover = $db->cover; my $currentfile = ''; my %LAST; my %WARNED; my $lastline; my $href; my $totals; my @report; foreach my $change ( $diff->changes ) { my( $file, $c ); ($file, $c)= ( '', ''); PREFIX: foreach my $dir ( @PREFIX ) { foreach my $tf ( join( '/', $dir, $change->filename2 ), join( '/', 'blib', $dir, $change->filename2 ), join( '/', 'blib', 'lib', $dir, $change->filename2 ) ) { $c = $cover->file( $tf ); next unless $c; $file = $tf; last PREFIX; } } unless( $c ) { my $file = $change->filename2; warn "$file not in cover_db\n" if $file =~ /\.p[ml]$/ and not $WARNED{$file}++; next; } my $crit = $c->criterion( 'statement' ); my $last = $LAST{ $file } ||= ( sort { $b<=>$a } $crit->items )[0]; if( $currentfile ne $file ) { $href = $file; $href =~ s/\W/-/g; $href .= ".html"; push @report, ['html_newfile', $file, $href ]; $currentfile = $file; undef( $lastline ); } my $line = $change->line2; my $size = $change->size; if( $lastline and not ($line <= $lastline+1 and $lastline <= $line+$size)) { push @report, ['html_newchunk']; } for( my $n =0; $n < $size ; $n++ ) { push @report, ['html_line', {href=>$href, line=>$line+$n}]; my $text = $change->text( $n ); my $check = $line+$n; $check = 0 if $line + $n > $last; # past end of coverage -> POD? $check = 0 unless $text =~ /\S/; # empty line $check = 0 if $text =~ /^\s*#/; # comments $check = 0 unless $change->type; # null operation # we can't have run a line that was removed, so we just make sure # that the equiv of the first line that currently exists was run. $check = $change->line2 if $change->type eq 'REMOVE'; my $class = ''; if( $check ) { my $l = $crit->location( $check ); if( $l ) { if( $l->[0]->covered ) { $class = 'c3'; $totals->{$file}{good}++; } else { $class = 'c0'; $totals->{$file}{bad}++; } } } $report[-1][1]{text} = $text; $report[-1][1]{class} = $class; $report[-1][1]{type} = $change->type; } $lastline = $line+$size; } ######################################################### open OUT, ">$OUTPUT" or die "Unable to create $OUTPUT: $!\n"; print OUT html_preamble(); print OUT html_report( $totals ); print OUT qq(\n); foreach my $line ( @report ) { my( $func, @args ) = @$line; print OUT 'main'->can($func)->( @args ); } print OUT qq(
\n); print OUT html_postamble(); close OUT; patch_css(); print "Report created in $OUTPUT\n"; ######################################################### sub html_preamble { my $diff_age = strftime "%Y/%m/%d %H:%M:%S %Z", localtime((stat $DIFF)[9]); my $db_age = strftime "%Y/%m/%d %H:%M:%S %Z", localtime((stat "$COVER_DB/cover.12")[9]); return < Change Coverage: $DIFF

Change Coverage

Database: $COVER_DB Generated $db_age
DIFF: $DIFF Generated $diff_age

HTML } ######################################################### sub html_report { my( $totals ) = @_; my @ret; my $total = 0; my $covered = 0; push @ret, qq(\n); foreach my $file ( sort keys %$totals ) { $totals->{$file}{good} ||= 0; my $stotal = $totals->{$file}{good} + ($totals->{$file}{bad}||0); $covered += $totals->{$file}{good}; $total += $stotal; push @ret, qq(), html_percent( $totals->{$file}{good} / $stotal ), qq(\n); } push @ret, qq(), html_percent( $total ? ($covered / $total) : 0 ), qq(
filecovered
$file
Total
\n
\n); return @ret; } ######################################################### sub html_percent { my( $percent ) = @_; $percent = sprintf "%.1f", $percent*100; my $class = 'c0'; $class = 'c1' if $percent > 75; $class = 'c2' if $percent > 90; $class = 'c3' if $percent > 99; return qq($percent); } ######################################################### sub html_newfile { my( $filename, $href ) = @_; return <$filename HTML } ######################################################### sub html_newchunk { return qq(
\n); } ######################################################### sub html_line { my( $bits ) = @_; $bits->{text} =~ s/$bits->{line} return <$bits->{line} $bits->{type} $bits->{text} HTML } ######################################################### sub html_postamble { return < HTML } ######################################################### sub patch_css { local @ARGV = join '/', $COVER_DB, 'cover.css'; local $^I = '.bk'; my $once; while( <> ) { unless( $once ) { $once = 1; print <