Chris@0: #!/usr/bin/env php Chris@0: Chris@0: * @author Greg Sherwood Chris@0: * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) Chris@0: * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence Chris@0: * @link http://pear.php.net/package/PHP_CodeSniffer Chris@0: */ Chris@0: Chris@0: if (is_file(dirname(__FILE__).'/../CodeSniffer/CLI.php') === true) { Chris@0: include_once dirname(__FILE__).'/../CodeSniffer/CLI.php'; Chris@0: } else { Chris@0: include_once 'PHP/CodeSniffer/CLI.php'; Chris@0: } Chris@0: Chris@0: define('PHP_CODESNIFFER_SVNLOOK', '/usr/bin/svnlook'); Chris@0: Chris@0: Chris@0: /** Chris@0: * A class to process command line options. Chris@0: * Chris@0: * @category PHP Chris@0: * @package PHP_CodeSniffer Chris@0: * @author Jack Bates Chris@0: * @author Greg Sherwood Chris@0: * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) Chris@0: * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence Chris@0: * @version Release: @package_version@ Chris@0: * @link http://pear.php.net/package/PHP_CodeSniffer Chris@0: */ Chris@0: class PHP_CodeSniffer_SVN_Hook extends PHP_CodeSniffer_CLI Chris@0: { Chris@0: Chris@0: Chris@0: /** Chris@0: * Get a list of default values for all possible command line arguments. Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public function getDefaults() Chris@0: { Chris@0: $defaults = parent::getDefaults(); Chris@0: Chris@0: $defaults['svnArgs'] = array(); Chris@0: return $defaults; Chris@0: Chris@0: }//end getDefaults() Chris@0: Chris@0: Chris@0: /** Chris@0: * Processes an unknown command line argument. Chris@0: * Chris@0: * Assumes all unknown arguments are files and folders to check. Chris@0: * Chris@0: * @param string $arg The command line argument. Chris@0: * @param int $pos The position of the argument on the command line. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: public function processUnknownArgument($arg, $pos) Chris@0: { Chris@0: $this->values['svnArgs'][] = escapeshellarg($arg); Chris@0: Chris@0: }//end processUnknownArgument() Chris@0: Chris@0: Chris@0: /** Chris@0: * Runs PHP_CodeSniffer over files are directories. Chris@0: * Chris@0: * @param array $values An array of values determined from CLI args. Chris@0: * Chris@0: * @return int The number of error and warning messages shown. Chris@0: * @see getCommandLineValues() Chris@0: */ Chris@0: public function process($values=array()) Chris@0: { Chris@0: if (empty($values) === true) { Chris@0: $values = $this->getCommandLineValues(); Chris@0: } else { Chris@0: $values = array_merge($this->getDefaults(), $values); Chris@0: $this->values = $values; Chris@0: } Chris@0: Chris@0: // Get list of files in this transaction. Chris@0: $command = PHP_CODESNIFFER_SVNLOOK.' changed '.implode(' ', $values['svnArgs']); Chris@0: $handle = popen($command, 'r'); Chris@0: if ($handle === false) { Chris@0: echo 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL; Chris@0: exit(2); Chris@0: } Chris@0: Chris@0: $contents = stream_get_contents($handle); Chris@0: fclose($handle); Chris@0: Chris@0: // Do not check deleted paths. Chris@0: $contents = preg_replace('/^D.*/m', null, $contents); Chris@0: Chris@0: // Drop the four characters representing the action which precede the path on Chris@0: // each line. Chris@0: $contents = preg_replace('/^.{4}/m', null, $contents); Chris@0: Chris@0: $values['standard'] = $this->validateStandard($values['standard']); Chris@0: foreach ($values['standard'] as $standard) { Chris@0: if (PHP_CodeSniffer::isInstalledStandard($standard) === false) { Chris@0: // They didn't select a valid coding standard, so help them Chris@0: // out by letting them know which standards are installed. Chris@0: echo 'ERROR: the "'.$standard.'" coding standard is not installed. '; Chris@0: $this->printInstalledStandards(); Chris@0: exit(2); Chris@0: } Chris@0: } Chris@0: Chris@0: $phpcs = new PHP_CodeSniffer( Chris@0: $values['verbosity'], Chris@0: $values['tabWidth'], Chris@0: $values['encoding'] Chris@0: ); Chris@0: Chris@0: // Set file extensions if they were specified. Otherwise, Chris@0: // let PHP_CodeSniffer decide on the defaults. Chris@0: if (empty($values['extensions']) === false) { Chris@0: $phpcs->setAllowedFileExtensions($values['extensions']); Chris@0: } else { Chris@0: $phpcs->setAllowedFileExtensions(array_keys($phpcs->defaultFileExtensions)); Chris@0: } Chris@0: Chris@0: // Set ignore patterns if they were specified. Chris@0: if (empty($values['ignored']) === false) { Chris@0: $phpcs->setIgnorePatterns($values['ignored']); Chris@0: } Chris@0: Chris@0: // Set some convenience member vars. Chris@0: if ($values['errorSeverity'] === null) { Chris@0: $this->errorSeverity = PHPCS_DEFAULT_ERROR_SEV; Chris@0: } else { Chris@0: $this->errorSeverity = $values['errorSeverity']; Chris@0: } Chris@0: Chris@0: if ($values['warningSeverity'] === null) { Chris@0: $this->warningSeverity = PHPCS_DEFAULT_WARN_SEV; Chris@0: } else { Chris@0: $this->warningSeverity = $values['warningSeverity']; Chris@0: } Chris@0: Chris@0: if (empty($values['reports']) === true) { Chris@0: $this->values['reports']['full'] = $values['reportFile']; Chris@0: } Chris@0: Chris@0: // Initialize PHP_CodeSniffer listeners but don't process any files. Chris@0: $phpcs->setCli($this); Chris@0: $phpcs->initStandard($values['standard'], $values['sniffs']); Chris@0: Chris@0: // Need double quotes around the following regex beause the vertical whitespace Chris@0: // char is not always treated correctly for whatever reason. Chris@0: foreach (preg_split("/\v|\n/", $contents, -1, PREG_SPLIT_NO_EMPTY) as $path) { Chris@0: // No need to process folders as each changed file is checked. Chris@0: if (substr($path, -1) === '/') { Chris@0: continue; Chris@0: } Chris@0: Chris@0: // We need to check ignore rules ourself because they are Chris@0: // not checked when processing a single file. Chris@0: if ($phpcs->shouldProcessFile($path, dirname($path)) === false) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: // Get the contents of each file, as it would be after this transaction. Chris@0: $command = PHP_CODESNIFFER_SVNLOOK.' cat '.implode(' ', $values['svnArgs']).' '.escapeshellarg($path); Chris@0: $handle = popen($command, 'r'); Chris@0: if ($handle === false) { Chris@0: echo 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL; Chris@0: exit(2); Chris@0: } Chris@0: Chris@0: $contents = stream_get_contents($handle); Chris@0: fclose($handle); Chris@0: Chris@0: $phpcs->processFile($path, $contents); Chris@0: }//end foreach Chris@0: Chris@0: return $this->printErrorReport( Chris@0: $phpcs, Chris@0: $values['reports'], Chris@0: $values['showSources'], Chris@0: $values['reportFile'], Chris@0: $values['reportWidth'] Chris@0: ); Chris@0: Chris@0: }//end process() Chris@0: Chris@0: Chris@0: /** Chris@0: * Prints out the usage information for this script. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: public function printUsage() Chris@0: { Chris@0: parent::printUsage(); Chris@0: Chris@0: echo PHP_EOL; Chris@0: echo ' Each additional argument is passed to the `svnlook changed ...`'.PHP_EOL; Chris@0: echo ' and `svnlook cat ...` commands. The report is printed on standard output,'.PHP_EOL; Chris@0: echo ' however Subversion displays only standard error to the user, so in a'.PHP_EOL; Chris@0: echo ' pre-commit hook, this script should be invoked as follows:'.PHP_EOL; Chris@0: echo PHP_EOL; Chris@0: echo ' '.basename($_SERVER['argv'][0]).' ... "$REPOS" -t "$TXN" >&2 || exit 1'.PHP_EOL; Chris@0: Chris@0: }//end printUsage() Chris@0: Chris@0: Chris@0: }//end class Chris@0: Chris@0: $phpcs = new PHP_CodeSniffer_SVN_Hook(); Chris@0: Chris@0: PHP_CodeSniffer_Reporting::startTiming(); Chris@0: $phpcs->checkRequirements(); Chris@0: Chris@0: $numErrors = $phpcs->process(); Chris@0: if ($numErrors !== 0) { Chris@0: exit(1); Chris@0: }