#!/usr/bin/env php * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file dev/tools/apstats.php * \brief Script to report Advanced Statistics on a coding project */ $sapi_type = php_sapi_name(); $script_file = basename(__FILE__); $path = dirname(__FILE__) . '/'; // Test si mode batch $sapi_type = php_sapi_name(); if (substr($sapi_type, 0, 3) == 'cgi') { echo "Error: You are using PHP for CGI. To execute " . $script_file . " from command line, you must use PHP for CLI mode.\n"; exit(); } error_reporting(E_ALL & ~E_DEPRECATED); define('PRODUCT', "apstats"); define('VERSION', "1.0"); print '***** '.constant('PRODUCT').' - '.constant('VERSION').' *****'."\n"; if (empty($argv[1])) { print 'You must run this tool being into the root of the project.'."\n"; print 'Usage: '.constant('PRODUCT').'.php pathto/outputfile.html [--dir-scc=pathtoscc] [--dir-phpstan=pathtophpstan]'."\n"; print 'Example: '.constant('PRODUCT').'.php documents/apstats/index.html --dir-scc=/snap/bin --dir-phpstan=~/git/phpstan/htdocs/includes/bin'; exit(0); } $outputpath = $argv[1]; $outputdir = dirname($outputpath); $outputfile = basename($outputpath); if (!is_dir($outputdir)) { print 'Error: dir '.$outputdir.' does not exists or is not writable'."\n"; exit(1); } $dirscc = ''; $dirphpstan = ''; $i = 0; while ($i < $argc) { $reg = array(); if (preg_match('/--dir-scc=(.*)$/', $argv[$i], $reg)) { $dirscc = $reg[1]; } if (preg_match('/--dir-phpstan=(.*)$/', $argv[$i], $reg)) { $dirphpstan = $reg[1]; } $i++; } // Count lines of code of Dolibarr itself /* $commandcheck = 'cloc . --exclude-dir=includes --exclude-dir=custom --ignore-whitespace --vcs=git'; $resexec = shell_exec($commandcheck); $resexec = (int) (empty($resexec) ? 0 : trim($resexec)); // Count lines of code of external dependencies $commandcheck = 'cloc htdocs/includes --ignore-whitespace --vcs=git'; $resexec = shell_exec($commandcheck); $resexec = (int) (empty($resexec) ? 0 : trim($resexec)); */ // Count lines of code of application $commandcheck = ($dirscc ? $dirscc.'/' : '').'scc . --exclude-dir=includes,custom'; print 'Execute SCC to count lines of code in project: '.$commandcheck."\n"; $output_arrproj = array(); $resexecproj = 0; exec($commandcheck, $output_arrproj, $resexecproj); // Count lines of code of dependencies $commandcheck = ($dirscc ? $dirscc.'/' : '').'scc htdocs/includes'; print 'Execute SCC to count lines of code in dependencies: '.$commandcheck."\n"; $output_arrdep = array(); $resexecdep = 0; exec($commandcheck, $output_arrdep, $resexecdep); // Get technical debt $commandcheck = ($dirphpstan ? $dirphpstan.'/' : '').'phpstan -v analyze -a build/phpstan/bootstrap.php --memory-limit 5G --error-format=github'; print 'Execute PHPStan to get the technical debt: '.$commandcheck."\n"; $output_arrtd = array(); $resexectd = 0; exec($commandcheck, $output_arrtd, $resexectd); $arrayoflineofcode = array(); $arraycocomo = array(); $arrayofmetrics = array( 'proj' => array('Bytes' => 0, 'Files' => 0, 'Lines' => 0, 'Blanks' => 0, 'Comments' => 0, 'Code' => 0, 'Complexity' => 0), 'dep' => array('Bytes' => 0, 'Files' => 0, 'Lines' => 0, 'Blanks' => 0, 'Comments' => 0, 'Code' => 0, 'Complexity' => 0) ); // Analyse $output_arrproj foreach (array('proj', 'dep') as $source) { print 'Analyze SCC result for lines of code for '.$source."\n"; if ($source == 'proj') { $output_arr = &$output_arrproj; } elseif ($source == 'dep') { $output_arr = &$output_arrdep; } else { print 'Bad value for $source'; die(); } foreach ($output_arr as $line) { if (preg_match('/^(───|Language|Total)/', $line)) { continue; } //print $line."
\n"; if (preg_match('/^Estimated Cost.*\$(.*)/i', $line, $reg)) { $arraycocomo[$source]['currency'] = preg_replace('/[^\d\.]/', '', str_replace(array(',', ' '), array('', ''), $reg[1])); } if (preg_match('/^Estimated Schedule Effort.*\s([\d\s,]+)/i', $line, $reg)) { $arraycocomo[$source]['effort'] = str_replace(array(',', ' '), array('.', ''), $reg[1]); } if (preg_match('/^Estimated People.*\s([\d\s,]+)/i', $line, $reg)) { $arraycocomo[$source]['people'] = str_replace(array(',', ' '), array('.', ''), $reg[1]); } if (preg_match('/^Processed\s(\d+)\s/i', $line, $reg)) { $arrayofmetrics[$source]['Bytes'] = $reg[1]; } if (preg_match('/^(.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/', $line, $reg)) { $arrayoflineofcode[$source][$reg[1]]['Files'] = $reg[2]; $arrayoflineofcode[$source][$reg[1]]['Lines'] = $reg[3]; $arrayoflineofcode[$source][$reg[1]]['Blanks'] = $reg[4]; $arrayoflineofcode[$source][$reg[1]]['Comments'] = $reg[5]; $arrayoflineofcode[$source][$reg[1]]['Code'] = $reg[6]; $arrayoflineofcode[$source][$reg[1]]['Complexity'] = $reg[7]; } } if (!empty($arrayoflineofcode[$source])) { foreach ($arrayoflineofcode[$source] as $key => $val) { $arrayofmetrics[$source]['Files'] += $val['Files']; $arrayofmetrics[$source]['Lines'] += $val['Lines']; $arrayofmetrics[$source]['Blanks'] += $val['Blanks']; $arrayofmetrics[$source]['Comments'] += $val['Comments']; $arrayofmetrics[$source]['Code'] += $val['Code']; $arrayofmetrics[$source]['Complexity'] += $val['Complexity']; } } } /* * View */ $html = ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ''."\n"; $html .= ' '."\n"; $html .= ''."\n"; $html .= '
'."\n"; $html .= '

Advanced Project Statistics

'."\n"; $currentDate = date("Y-m-d H:i:s"); // Format: Year-Month-Day Hour:Minute:Second $html .= 'Generated on '.$currentDate.''."\n"; $html .= '
'."\n"; $html .= '
'."\n"; $html .= '

Lines of code

'."\n"; $html .= '
'."\n"; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; //$html .= ''; $html .= ''; foreach (array('proj', 'dep') as $source) { $html .= ''; if ($source == 'proj') { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; if (!empty($arrayoflineofcode[$source])) { foreach ($arrayoflineofcode[$source] as $key => $val) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; //$html .= ''; $html .= ''; $html .= ''; } } } $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; //$html .= ''; $html .= ''; $html .= ''; $html .= '
LanguageBytesFilesLinesBlanksCommentsCode'.$val['Complexity'].'
All files from project only'; } elseif ($source == 'dep') { $html .= 'All files from dependencies'; } $html .= '     (See detail per file type...)'; $html .= ''.formatNumber($arrayofmetrics[$source]['Bytes']).''.formatNumber($arrayofmetrics[$source]['Files']).''.formatNumber($arrayofmetrics[$source]['Lines']).''.formatNumber($arrayofmetrics[$source]['Blanks']).''.formatNumber($arrayofmetrics[$source]['Comments']).''.formatNumber($arrayofmetrics[$source]['Code']).'
Total'.formatNumber($arrayofmetrics['proj']['Bytes'] + $arrayofmetrics['dep']['Bytes']).''.formatNumber($arrayofmetrics['proj']['Files'] + $arrayofmetrics['dep']['Files']).''.formatNumber($arrayofmetrics['proj']['Lines'] + $arrayofmetrics['dep']['Lines']).''.formatNumber($arrayofmetrics['proj']['Blanks'] + $arrayofmetrics['dep']['Blanks']).''.formatNumber($arrayofmetrics['proj']['Comments'] + $arrayofmetrics['dep']['Comments']).''.formatNumber($arrayofmetrics['proj']['Code'] + $arrayofmetrics['dep']['Code']).''.$arrayofmetrics['Complexity'].'
'; $html .= '
'; $html .= '
'."\n"; $html .= '
'."\n"; $html .= '

Project value


'."\n"; $html .= '
'; $html .= 'COCOMO (Basic organic model) value:
'; $html .= '$'.formatNumber((empty($arraycocomo['proj']['currency']) ? 0 : $arraycocomo['proj']['currency']) + (empty($arraycocomo['dep']['currency']) ? 0 : $arraycocomo['dep']['currency']), 2).''; $html .= '
'; $html .= '
'; $html .= 'COCOMO (Basic organic model) effort
'; $html .= ''.formatNumber($arraycocomo['proj']['people'] * $arraycocomo['proj']['effort'] + $arraycocomo['dep']['people'] * $arraycocomo['dep']['effort']); $html .= ' monthes people
'; $html .= '
'."\n"; $html .= '
'."\n"; $html .= '

Technical debt ('.count($output_arrtd).')


'."\n"; $html .= '
'."\n"; $html .= ''."\n"; $html .= ''."\n"; foreach ($output_arrtd as $line) { $reg = array(); //print $line."\n"; preg_match('/^::error file=(.*),line=(\d+),col=(\d+)::(.*)$/', $line, $reg); if (!empty($reg[1])) { $html .= ''."\n"; } } $html .= '
FileLineType
'.$reg[1].''.$reg[2].''.$reg[4].'
'; $html .= '
'; $html .= '
'."\n"; $html .= ' '; $html .= ''; $html .= ''; $fh = fopen($outputpath, 'w'); if ($fh) { fwrite($fh, $html); fclose($fh); print 'Generation of output file '.$outputfile.' done.'."\n"; } else { print 'Failed to open '.$outputfile.' for ouput.'."\n"; } /** * function to format a number * * @param string|int $number Number to format * @param int $nbdec Number of decimal digits * @return string Formated string */ function formatNumber($number, $nbdec = 0) { return number_format($number, 0, '.', ' '); }