Files
doc-gtk/scripts/classcoverage.php
2007-02-04 15:20:16 +00:00

467 lines
14 KiB
PHP

<?php
/**
* PHP-Gtk coverage analysis generator.
*
* The script is based on the php-gtk-doc coverage
* analysis script with a slightly modified output.
*
* The main work is done in the Log2Coverage class
* that takes some gen_*.log files and transforms them
* into an xml file similar to the one generated by
* the coverage.xsl script for the documentation.
*
* Usage:
* Pass the paths of the gen_*.log files to the script, e.g.
* php coverage.php ext/* /gen_*.log > classcoverage.htm
* ^ remove this space
*
* @author Christian Weiske <cweiske@php.net>
*/
/**
* Converts a gen_* logfile (e.g. ext/gtk+/gen_gtk.log) to
* a coverage xml file as generated by the
* php-gtk-doc coverage.xsl script.
*/
class Log2Coverage
{
protected static $classpattern = "/([A-Za-z0-9]+)\n~+\n((?: .*\n)+)/";
protected static $funcpattern = "/ ([a-z ]+?)\s+ \\(([0-9]+) written, ([0-9]+) skipped\\)\n/";
protected static $notgenpattern = "/Not Generated Items\\s*\n=+\n((?: .+\n)*)\n/";
protected static $notgenline = "/ ([a-z ]+)\s+(([a-zA-Z0-9]+)(?:::|->)([a-zA-Z0-9_]+):.+)/";
/**
* I know that hardcoded titles are bad, but if
* the file isn't in here the gen_*.log filename
* is shown as fallback.
*/
protected static $filetitles = array(
'gen_gtk.log' => 'Gtk',
'gen_gdk.log' => 'Gdk',
'gen_atk.log' => 'Atk',
'gen_pango.log' => 'Pango',
'gen_libglade.log' => 'Glade',
'gen_mozembed.log' => 'GtkMozEmbed',
'gen_scintilla.log' => 'GtkScintilla',
'gen_sourceview.log' => 'GtkSourceview',
);
public static function generateFromArgs()
{
$files = $GLOBALS['argv'];
array_shift($files);
return self::generate($files);
}//public static function generateFromArgs()
public static function generate($files)
{
if (count($files) == 0) {
echo "I guess you forgot to specify the gen_*.log files to parse.\n";
}
$content = '<' . '?xml version="1.0" encoding="utf-8"?' . ">\n";
$content .= '<classcoverage>';
foreach ($files as $file) {
$content .= self::splitFile($file);
}
$content .= '</classcoverage>';
return $content;
}//public static function generate($files)
protected static function splitFile($filename)
{
//match not generated items
$matches = array();
preg_match_all(self::$notgenpattern, file_get_contents($filename), $matches);
$notgen = $matches[1][0];
$arNotGen = self::splitNotGenerated($notgen);
//match statistics
$matches = array();
preg_match_all(self::$classpattern, file_get_contents($filename), $matches);
$bn = basename($filename);
$title = isset(self::$filetitles[$bn]) ? self::$filetitles[$bn] : $bn;
$content = '<classset title="' . $title . '">';
$contents = array();
foreach ($matches[1] as $id => $class) {
$notgen = isset($arNotGen[$class]) ? $arNotGen[$class] : array();
$contents[$class] = self::splitClass($class, $matches[2][$id], $notgen);
}
ksort($contents);
$content .= implode('', $contents);
$content .= '</classset>';
return $content;
}//protected static function splitFile($filename)
protected static function splitClass($class, $data, $notgen)
{
$matches = array();
preg_match_all(self::$funcpattern, $data, $matches);
$content = '<class title="' . $class . '">';
foreach ($matches[1] as $id => $name) {
$written = $matches[2][$id];
$skipped = $matches[3][$id];
if ($name[0] == 'p') {
$name = 'properties';
} else if ($name[0] == 'f') {
$name = 'methods';
}
$content .= '<type title="' . $name . '"'
. ' existing="' . ($written + $skipped) . '"'
. ' missing="' . ($skipped) . '"';
if (!isset($notgen[$name])) {
$content .= '/>';
} else {
$content .= '>';
foreach ($notgen[$name] as $name => $message) {
$content .= '<missing'
. ' name="' . htmlspecialchars($name) . '"'
. ' message="' . htmlspecialchars($message) . '"'
. '/>';
}
$content .= '</type>';
}
}
$content .= '</class>';
return $content;
}//protected static function splitClass($class, $data)
protected static function splitNotGenerated($notgen)
{
$ar = explode("\n", $notgen);
$arNotGen = array();
foreach ($ar as $line) {
$matches = array();
preg_match_all(self::$notgenline, $line, $matches);
if (!isset($matches[0][0])) {
//strange...
continue;
}
$type = trim($matches[1][0]) . 's';
$message = $matches[2][0];
$class = $matches[3][0];
$name = $matches[4][0];
if ($type[0] == 'r') {//reader for
$type = 'properties';
} else if ($type[0] == 'f') {//function
$type = 'methods';
}
$arNotGen[$class][$type][$name] = $message;
}
return $arNotGen;
}//protected static function splitNotGenerated($notgen)
}//class Log2Coverage
/**
* PHP-Gtk generator coverage analysis generator
*
* Based on the php-gtk-doc coverage generator.
*
* @author Christian Weiske <cweiske@php.net>
*/
class ClassCoverageAnalysis
{
protected $typestemplate = array(
'constructors' => array(
'missing' => 0,
'existing' => 0
),
'methods' => array(
'missing' => 0,
'existing' => 0
),
'properties' => array(
'missing' => 0,
'existing' => 0
),
);
protected $missingThings = '';
public function run()
{
$doc = simplexml_load_string(Log2Coverage::generateFromArgs());
$output = <<<EOD
<html>
<head>
<title>PHP-Gtk2 generator coverage analysis</title>
<style type="text/css">
table {
border: 1px solid black;
border-collapse: collapse;
}
td, th {
border: 1px solid grey;
}
table tr.classset td, table tr.documentation td {
font-weight:bold;
border-bottom: 2px solid black;
}
th.allpercent {
font-size: 200%;
}
</style>
</head>
<body>
<table>
<caption>
EOD;
$output .= 'PHP-Gtk2 generator coverage analysis of ' . date('Y-m-d H:i:s');
$output .= <<<EOD
</caption>
EOD;
$output .= $this->calcCoverage($doc);
$output .= <<<EOD
</table>
EOD;
$output .= $this->missingThings;
$output .= <<<EOD
</body>
</html>
EOD;
return $output;
}//public function run()
protected function calcCoverage($doc)
{
$output = '<thead>' . "\r\n"
. '<tr>'
. '<th rowspan="2">Name</th>'
. '<th colspan="2">Constructors</th>'
. '<th colspan="2">Methods</th>'
. '<th colspan="2">Properties</th>'
. '</tr>' . "\r\n"
. '<tr>'
. '<th>Done</th><th>All</th>'
. '<th>Done</th><th>All</th>'
. '<th>Done</th><th>All</th>'
. '</tr>' . "\r\n"
. '</thead>' . "\r\n"
. '<tbody>' . "\r\n";
$allMissing = 0;
$allExisting = 0;
$types = $this->typestemplate;
foreach ($doc->classset as $classset) {
list($classoutput, $existing, $missing, $settypes) = $this->calcClassset($classset);
$types = $this->addTypes($types, $settypes);
$allMissing += $missing;
$allExisting += $existing;
$output .= $classoutput;
}
$output .= '<tr class="documentation">'
. '<th rowspan="2">Coverage</th>'
. $this->getTypesDisplay($types, null)
. '</tr>'
. '<tr class="documentation">'
. $this->getTypesPercentageDisplay($types)
. '</tr>'
. "\r\n";
$percent = 100 / $allExisting * ($allExisting - $allMissing);
$output .= '<tr><th>All in all</th>'
. '<th>' . ($allExisting - $allMissing) . '</th>'
. '<th>' . $allExisting . '</th>'
. '<th class="allpercent" colspan="4" style="background-color:' . $this->getColor($percent) . '">'
. number_format($percent, 2)
. '%</th>'
. '</tr>' . "\r\n"
. '<tbody>' . "\r\n";
return $output;
}//protected function calcCoverage($doc)
public function calcClassset($classset)
{
$output = '';
$allMissing = 0;
$allExisting = 0;
$types = $this->typestemplate;
foreach ($classset->{'class'} as $class) {
list($classoutput, $existing, $missing, $classtypes) = $this->calcClass($class);
$types = $this->addTypes($types, $classtypes);
$allMissing += $missing;
$allExisting += $existing;
$output .= $classoutput;
}
$output .= '<tr class="classset">'
. '<th rowspan="2">' . htmlspecialchars((string)$classset['title']) . '</th>'
. $this->getTypesDisplay($types, null)
. '</tr>'
. '<tr class="classset">'
. $this->getTypesPercentageDisplay($types)
. '</tr>'
. "\r\n";
return array($output, $allExisting, $allMissing, $types);
}
public function calcClass($class)
{
$classname = htmlspecialchars((string)$class['title']);
$types = $this->typestemplate;
$allMissing = 0;
$allExisting = 0;
if (isset($class->type)) {
foreach ($class->type as $type) {
$title = (string)$type['title'];
$types[$title]['missing'] = (string)$type['missing'];
$types[$title]['existing'] = (string)$type['existing'];
$allMissing += $types[$title]['missing'];
$allExisting += $types[$title]['existing'];
if (isset($type->missing)) {
$this->missingThings .= '<a'
. ' name="' . $classname . '-' . $title . '">'
. '<h3>' . $classname . ' ' . $title . '</h3>'
. '</a>';
foreach ($type->missing as $missing) {
$name = (string)$missing['name'];
$message = (string)$missing['message'];
$this->missingThings .= htmlspecialchars($message) . '<br/>';
}
}
}
}
$output = '<tr><td>' . $classname . '</td>'
. $this->getTypesDisplay($types, $classname)
. '</tr>' . "\r\n";
return array($output, $allExisting, $allMissing, $types);
}//public function calcClass($class)
function getTypesDisplay($types, $class)
{
return $this->getTypeDisplay($types['constructors'], $class, 'constructors')
. $this->getTypeDisplay($types['methods'] , $class, 'methods')
. $this->getTypeDisplay($types['properties'] , $class, 'properties');
}//function getTypesDisplay($types, $class)
function getTypesPercentageDisplay($types)
{
return $this->getTypePercentageDisplay($types['constructors'])
. $this->getTypePercentageDisplay($types['methods'])
. $this->getTypePercentageDisplay($types['properties']);
}//function getTypesPercentageDisplay($types)
function getTypeDisplay($type, $class, $typename)
{
$existing = $type['existing'];
$missing = $type['missing'];
$done = $existing - $missing;
if ($existing != 0) {
$percent = intval(100 / $existing * $done);
} else {
$percent = -100;
}
$color = $this->getColor($percent);
if ($missing > 0 && $class !== null) {
$done = '<a href="#' . $class . '-' . $typename . '">' . $done . '</a>';
}
$output = '<td style="background-color:' . $color . '">' . $done . '</td>';
$output .= '<td>' . $existing . '</td>';
return $output;
}//function getTypeDisplay($type)
function getTypePercentageDisplay($type)
{
$existing = $type['existing'];
$missing = $type['missing'];
$done = $existing - $missing;
if ($existing != 0) {
$percent = number_format(100 / $existing * $done, 2);
} else {
$percent = -100;
}
$color = $this->getColor($percent);
if ($percent == -100) {
$percent = '';
} else {
$percent .= '%';
}
$output = '<td colspan="2" style="background-color:' . $color . '">' . $percent . '</td>';
return $output;
}//function getTypePercentageDisplay($type)
function getColor($percent)
{
$percent = intval($percent);
if ($percent == -100) {
$color = 'white';
} else if ($percent == 100) {
$color = 'green';
} else if ($percent >= 85) {
$color = 'yellow';
} else if ($percent >= 50) {
$color = 'orange';
} else {
$color = 'red';
}
return $color;
}//function getColor($percent)
protected function addTypes($types, $newTypes)
{
foreach ($newTypes as $name => $numbers) {
foreach ($numbers as $numtitle => $number) {
$types[$name][$numtitle] += $number;
}
}
return $types;
}//protected function addTypes($types, $newTypes)
}//class ClassCoverageAnalysis
$da = new ClassCoverageAnalysis();
echo $da->run();
?>