| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 | <?phpnamespace Elgg\Project;/** * Internal component to detect and fix some whitespace issues * * @access private * * @package    Elgg.Core * @subpackage Project */class CodeStyle {	const KEY_NEW_CONTENT = 'new_content';	const KEY_REMAINING = 'remaining';	const KEY_CORRECTIONS = 'corrections';	/**	 * @var string Regex pattern for file extensions to analyze	 */	protected $file_pattern = '~\.(?:php|js|css|xml|json|yml|txt|rst|md|gitignore|htaccess|mailmap|sh)$~';	/**	 * @var int The start argument such that substr(filepath, start) will return the filepath as a relative	 *          path from the project root. E.g. if the root is /path/to/Elgg, this property will be set to	 *          14. That way substr('/path/to/Elgg/foo/bar.php', 14) => foo/bar.php	 */	protected $substr_start;	/**	 * Fix problems in a directory of files and return a report.	 *	 * @param string $root    Root directory	 * @param bool   $dry_run If set to true, no files will be written	 * @return array Report of notable files	 */	public function fixDirectory($root, $dry_run = false) {		$return = array();		$this->substr_start = strlen($this->normalizePath($root)) + 1;		$files = $this->findFilesToAnalyze($root);		foreach ($files as $file) {			$report = $this->analyzeFile($file);			$key = substr($file, $this->substr_start);			if ($dry_run) {				$errors = $report[self::KEY_REMAINING];				array_splice($errors, count($errors), 0, $report[self::KEY_CORRECTIONS]);				if ($errors) {					$return[$key] = $errors;				}			} else {				if ($report[self::KEY_NEW_CONTENT] !== null) {					file_put_contents($file, $report[self::KEY_NEW_CONTENT]);				}				if ($report[self::KEY_REMAINING]) {					$return[$key][self::KEY_REMAINING] = $report[self::KEY_REMAINING];				}				if ($report[self::KEY_CORRECTIONS]) {					$return[$key][self::KEY_CORRECTIONS] = $report[self::KEY_CORRECTIONS];				}			}		}		return $return;	}	/**	 * Find files which can be analyzed/fixed by this component	 *	 * @param string $root Root directory	 * @return string[] File paths. All directory separators will be "/"	 */	public function findFilesToAnalyze($root) {		$files = array();		$this->substr_start = strlen($this->normalizePath($root)) + 1;		$this->findFiles(rtrim($root, '/\\'), $files);		return $files;	}	/**	 * Analyze a file for problems and return a report	 *	 * @param string $filepath Path of file to analyze	 * @param string $content  The file's content (optional)	 *	 * @return array Report with keys:	 *	 *     remaining_problems : string[]    Problems which could not be fixed	 *     corrections        : string[]    Problems which were fixed	 *     new_content        : string|null Null if no corrections made, otherwise the corrected content	 */	public function analyzeFile($filepath, $content = null) {		if (!is_string($content)) {			$content = file_get_contents($filepath);		}		$old = $content;		unset($content);		$return = array(			self::KEY_REMAINING => array(),			self::KEY_CORRECTIONS => array(),			self::KEY_NEW_CONTENT => null,		);		// remove WS after non-WS		$new = preg_replace('~(\S)[ \t]+(\r?\n)~', '$1$2', $old, -1, $count);		if ($count) {			$return[self::KEY_CORRECTIONS][] = "line(s) with trailing whitespace ($count)";		}		// don't risk breaking code blocks		if (!preg_match('~\.(?:rst|md)$~', $filepath)) {			// remove WS from empty lines			$new = preg_replace('~^[ \t]+$~m', '', $new, -1, $count);			if ($count) {				$return[self::KEY_CORRECTIONS][] = "empty line(s) with whitespace ($count)";			}		}		if (pathinfo($filepath, PATHINFO_EXTENSION) === 'php') {			// remove close PHP tag at file end			$new = preg_replace('~\?>\s*$~', '', $new, -1, $count);			if ($count) {				$return[self::KEY_CORRECTIONS][] = 'unnecessary close PHP tag';			}		}		if ($new !== $old) {			$return[self::KEY_NEW_CONTENT] = $new;		}		return $return;	}	/**	 * Find files within a directory (recurse for subdirectories)	 *	 * @param string $dir    Directory to search	 * @param array  &$files Reference to found files	 *	 * @return void	 */	protected function findFiles($dir, &$files) {		$d = dir($dir);		while (false !== ($entry = $d->read())) {			if ($entry === '.' || $entry === '..') {				continue;			}			$full = $this->normalizePath("{$d->path}/$entry");			$relative_path = substr($full, $this->substr_start);			if (is_dir($full)) {				if ($entry[0] === '.' || preg_match('~(?:/vendors?|/zaudio/audioplayer)$~', $full)) {					// special case					if ($entry !== '.scripts') {						continue;					}				}				if (in_array($relative_path, array('node_modules', 'docs/_build'))) {					continue;				}				$this->findFiles($full, $files);			} else {				// file				if (basename($dir) === 'languages' && $entry !== 'en.php') {					continue;				}				if ($relative_path === 'install/config/htaccess.dist' || preg_match($this->file_pattern, $entry)) {					$files[] = $full;					continue;				}			}		}		$d->close();	}	/**	 * Normalize a path	 *	 * @param string $path A file/dir path	 *	 * @return string	 */	protected function normalizePath($path) {		return str_replace('\\', '/', rtrim($path, '/\\'));	}}
 |