#!/usr/bin/perl # Copyright 2002-2007, Paul Johnson (pjcj@cpan.org) # This software is free. It is licensed under the same terms as Perl itself. # The latest version of this software should be available from my homepage: # http://www.pjcj.net require 5.6.1; use strict; use warnings; our $VERSION = "0.61"; use Devel::Cover::DB 0.61; use Cwd (); use Getopt::Long; use Pod::Usage; use Template 2.00; # use Carp; $SIG{__DIE__} = \&Carp::confess; my $Template; my $Options = { collect => 1, directory => Cwd::cwd(), force => 0, module => [], report => "html_basic", }; sub get_options { die "Bad option" unless GetOptions($Options, # Store the options in the Options hash. qw( collect! directory=s force! help|h! info|i! module=s outputdir=s outputfile=s redo_cpancover_html! redo_html! report=s version|v! )); print "$0 version $VERSION\n" and exit 0 if $Options->{version}; pod2usage(-exitval => 0, -verbose => 0) if $Options->{help}; pod2usage(-exitval => 0, -verbose => 2) if $Options->{info}; $Options->{outputdir} ||= $Options->{directory}; $Options->{outputfile} ||= "coverage.html"; push @{$Options->{module}}, @ARGV; if (!$Options->{redo_cpancover_html} && !@{$Options->{module}}) { my $d = $Options->{directory}; opendir D, $d or die "Can't opendir $d: $!\n"; @{$Options->{module}} = grep !/^\./ && -e "$d/$_/Makefile.PL", sort readdir D or die "No module directories found\n"; closedir D or die "Can't closedir $d: $!\n"; } } sub sys { my ($command) = @_; print "$command\n"; system $command; } sub read_results { my $f = "$Options->{outputdir}/cover.results"; my %results; if (open S, "<", $f) { while () { my ($mod, $status) = split; $results{$mod} = $status; } close S or die "Can't close $f: $!\n"; } \%results } sub get_cover { my ($module) = @_; print "\n\n\n**** Checking coverage of $module ****\n\n\n"; my $d = "$Options->{directory}/$module"; chdir $d or die "Can't chdir $d: $!\n"; my $db = "$d/cover_db"; print "Already analysed\n" if -d $db; my $test = !-e "$db/runs" || $Options->{force} ? " -test" : ""; if ($test) { print "Testing $module\n"; sys "$^X Makefile.PL" unless -e "Makefile"; } my $od = "$Options->{outputdir}/$module"; my $of = $Options->{outputfile}; sys "cover$test -report $Options->{report} " . "-outputdir $od -outputfile $of" if $test || !-e "$od/$of" || $Options->{redo_html}; my $results = read_results; my $f = "$Options->{outputdir}/cover.results"; $results->{$module} = 1; open S, ">", $f or die "Can't open $f: $!\n"; for my $mod (sort keys %$results) { print S "$mod $results->{$mod}\n"; } close S or die "Can't close $f: $!\n"; } sub write_stylesheet { my $css = "$Options->{outputdir}/cpancover.css"; open CSS, ">", $css or return; print CSS <= 75% * c2 : coverage >= 90% * c3 : path covered or coverage = 100% */ .c0 { background-color: #ff9999; border: solid 1px #cc0000; } .c1 { background-color: #ffcc99; border: solid 1px #ff9933; } .c2 { background-color: #ffff99; border: solid 1px #cccc66; } .c3 { background-color: #99ff99; border: solid 1px #009900; } EOF close CSS or die "Can't close $css: $!\n"; } sub class { my ($pc) = @_; $pc eq "n/a" ? "na" : $pc < 75 ? "c0" : $pc < 90 ? "c1" : $pc < 100 ? "c2" : "c3" } sub write_html { my $d = $Options->{directory}; chdir $d or die "Can't chdir $d: $!\n"; my $results = read_results; my $f = "$Options->{outputdir}/$Options->{outputfile}"; print "\n\nWriting cpancover output to $f ...\n"; my %vals; my $vars = { title => "CPAN Coverage report", modules => [], vals => \%vals, }; for my $module (sort keys %$results) { my $dbdir = "$Options->{directory}/$module/cover_db"; next unless -d $dbdir; chdir "$Options->{directory}/$module"; print "Adding $module from $dbdir\n"; my $db = Devel::Cover::DB->new(db => $dbdir); # next unless $db->is_valid; my $criteria = $vars->{criteria} ||= [ grep(!/path|time/, $db->all_criteria) ]; $vars->{headers} ||= [ grep(!/path|time/, $db->all_criteria_short) ]; my %options = map { $_ => 1 } @$criteria; $db->calculate_summary(%options); push @{$vars->{modules}}, $module; $vals{$module}{link} = "$module/$Options->{outputfile}"; for my $criterion (@$criteria) { my $summary = $db->summary("Total", $criterion); my $pc = $summary->{percentage}; $pc = defined $pc ? sprintf "%6.2f", $pc : "n/a"; $vals{$module}{$criterion}{pc} = $pc; $vals{$module}{$criterion}{class} = class($pc); $vals{$module}{$criterion}{details} = ($summary->{covered} || 0) . " / " . ($summary->{total} || 0); } } # use Data::Dumper; print Dumper $vars; write_stylesheet; $Template->process("summary", $vars, $f) or die $Template->error(); print "done.\n"; print "\n\nWrote cpancover output to $f\n"; } sub main { get_options; $Template = Template->new ({ LOAD_TEMPLATES => [ Devel::Cover::Cpancover::Template::Provider->new({}), ], }); if ($Options->{collect}) { get_cover($_) for @{$Options->{module}}; } write_html; } package Devel::Cover::Cpancover::Template::Provider; use strict; use warnings; our $VERSION = "0.61"; use base "Template::Provider"; my %Templates; sub fetch { my $self = shift; my ($name) = @_; # print "Looking for <$name>\n"; $self->SUPER::fetch(exists $Templates{$name} ? \$Templates{$name} : $name) } $Templates{colours} = <<'EOT'; [% colours = { default => "#ffffad", text => "#000000", number => "#ffffc0", error => "#ff0000", ok => "#00ff00", } %] [% MACRO bg BLOCK -%] bgcolor="[% colours.$colour %]" [%- END %] EOT $Templates{html} = <<'EOT'; [% PROCESS colours %] [% title %] [% content %] EOT $Templates{summary} = <<'EOT'; [% WRAPPER html %]

[% title %]

[% IF modules %] [% FOREACH header = headers %] [% END %] [% END %] [% FOREACH module = modules %] [% FOREACH criterion = criteria %] [% END %] [% END %]
File [% header %]
[% module %] [% vals.$module.$criterion.pc %]
[% END %] EOT ::main __END__ =head1 NAME cpancover - report coverage statistics on CPAN modules =head1 SYNOPSIS cpancover -help -info -version =head1 DESCRIPTION =head1 OPTIONS The following command line options are supported: -h -help - show help -i -info - show documentation -v -version - show version =head1 DETAILS =head1 EXIT STATUS The following exit values are returned: 0 All operaions were completed successfully. >0 An error occurred. =head1 SEE ALSO Devel::Cover =head1 BUGS Incomplete. Undocumented. Needs to be redone properly. =head1 VERSION Version 0.61 - 10th January 2007 =head1 LICENCE Copyright 2002-2007, Paul Johnson (pjcj@cpan.org) This software is free. It is licensed under the same terms as Perl itself. The latest version of this software should be available from my homepage: http://www.pjcj.net =cut