#!/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(