| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- <?php
- App::uses('AppShell', 'Console/Command');
- if (!defined('LF')) {
- define('LF', PHP_EOL); # use PHP to detect default linebreak
- }
- /**
- * Misc Code Fix Tools
- *
- * cake Tools.Code dependencies [-p PluginName] [-c /custom/path]
- * - Fix missing App::uses() statements
- *
- * @author Mark Scherer
- * @license MIT
- */
- class CodeShell extends AppShell {
- protected $_files;
- protected $_paths;
- /**
- * Detect and fix class dependencies
- *
- * @return void
- */
- public function dependencies() {
- if ($customPath = $this->params['custom']) {
- $this->_paths = array($customPath);
- } elseif (!empty($this->params['plugin'])) {
- $this->_paths = array(CakePlugin::path($this->params['plugin']));
- } else {
- $this->_paths = array(APP);
- }
- $this->_findFiles('php');
- foreach ($this->_files as $file) {
- $this->out(sprintf('Updating %s...', $file), 1, Shell::VERBOSE);
- $this->_correctFile($file);
- $this->out(sprintf('Done updating %s', $file), 1, Shell::VERBOSE);
- }
- }
- protected function _correctFile($file) {
- $fileContent = $content = file_get_contents($file);
- preg_match_all('/class \w+ extends (.+)\s*{/', $fileContent, $matches);
- if (empty($matches)) {
- continue;
- }
- $excludes = array('Fixture', 'Exception', 'TestSuite', 'CakeTestModel');
- $missingClasses = array();
- foreach ($matches[1] as $match) {
- $match = trim($match);
- preg_match('/\bApp\:\:uses\(\'' . $match . '\'/', $fileContent, $usesMatches);
- if (!empty($usesMatches)) {
- continue;
- }
- preg_match('/class ' . $match . '\s*(w+)?{/', $fileContent, $existingMatches);
- if (!empty($existingMatches)) {
- continue;
- }
- if (in_array($match, $missingClasses)) {
- continue;
- }
- $break = false;
- foreach ($excludes as $exclude) {
- if (strposReverse($match, $exclude) === 0) {
- $break = true;
- break;
- }
- }
- if ($break) {
- continue;
- }
- $missingClasses[] = $match;
- }
- if (empty($missingClasses)) {
- return;
- }
- $fileContent = explode(LF, $fileContent);
- $inserted = array();
- $pos = 1;
- if (!empty($fileContent[1]) && $fileContent[1] === '/**') {
- $count = count($fileContent);
- for ($i = $pos; $i < $count - 1; $i++) {
- if (strpos($fileContent[$i], '*/') !== false) {
- if (strpos($fileContent[$i + 1], 'class ') !== 0) {
- $pos = $i + 1;
- }
- break;
- }
- }
- }
- // try to find the best position to insert app uses statements
- foreach ($fileContent as $row => $rowValue) {
- preg_match('/^App\:\:uses\(/', $rowValue, $matches);
- if ($matches) {
- $pos = $row;
- break;
- }
- }
- foreach ($missingClasses as $missingClass) {
- $classes = array(
- 'Controller' => 'Controller',
- 'Component' => 'Controller/Component',
- 'Shell' => 'Console/Command',
- 'Model' => 'Model',
- 'Behavior' => 'Model/Behavior',
- 'Datasource' => 'Model/Datasource',
- 'Task' => 'Console/Command/Task',
- 'View' => 'View',
- 'Helper' => 'View/Helper',
- );
- $type = null;
- foreach ($classes as $class => $namespace) {
- if (($t = strposReverse($missingClass, $class)) === 0) {
- $type = $namespace;
- break;
- }
- }
- if (empty($type)) {
- $this->err($missingClass . ' (' . $file . ') could not be matched');
- continue;
- }
- if ($class === 'Model') {
- $missingClassName = $missingClass;
- } else {
- $missingClassName = substr($missingClass, 0, strlen($missingClass) - strlen($class));
- }
- $objects = App::objects(($this->params['plugin'] ? $this->params['plugin'] . '.' : '') . $class);
- //FIXME: correct result for plugin classes
- if ($missingClass === 'Component') {
- $type = 'Controller';
- } elseif ($missingClass === 'Helper') {
- $type = 'View';
- } elseif ($missingClass === 'ModelBehavior') {
- $type = 'Model';
- } elseif (!empty($this->params['plugin']) && ($location = App::location($missingClass))) {
- $type = $location;
- } elseif (in_array($missingClass, $objects)) {
- $type = ($this->params['plugin'] ? ($this->params['plugin'] . '.') : '') . $type;
- }
- $inserted[] = 'App::uses(\'' . $missingClass . '\', \'' . $type . '\');';
- }
- if (!$inserted) {
- return;
- }
- array_splice($fileContent, $pos, 0, $inserted);
- $fileContent = implode(LF, $fileContent);
- if (empty($this->params['dry-run'])) {
- file_put_contents($file, $fileContent);
- $this->out(sprintf('Correcting %s', $file), 1, Shell::VERBOSE);
- }
- }
- /**
- * Make sure all files are properly encoded (ü instead of ü etc)
- * FIXME: non-utf8 files to utf8 files error on windows!
- *
- * @return void
- */
- public function utf8() {
- $this->_paths = array(APP . 'View' . DS);
- $this->params['ext'] = 'php|ctp';
- //$this->out('found: '.count($this->_files));
- $patterns = array(
- );
- $umlauts = array('ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', 'ß');
- foreach ($umlauts as $umlaut) {
- $patterns[] = array(
- ent($umlaut) . ' => ' . $umlaut,
- '/' . ent($umlaut) . '/',
- $umlaut,
- );
- }
- $this->_filesRegexpUpdate($patterns);
- }
- /**
- * CodeShell::_filesRegexpUpdate()
- *
- * @param mixed $patterns
- * @return void
- */
- protected function _filesRegexpUpdate($patterns) {
- $this->_findFiles($this->params['ext']);
- foreach ($this->_files as $file) {
- $this->out(sprintf('Updating %s...', $file), 1, Shell::VERBOSE);
- $this->_utf8File($file, $patterns);
- }
- }
- /**
- * Searches the paths and finds files based on extension.
- *
- * @param string $extensions
- * @return void
- */
- protected function _findFiles($extensions = '') {
- $this->_files = array();
- foreach ($this->_paths as $path) {
- if (!is_dir($path)) {
- continue;
- }
- $Iterator = new RegexIterator(
- new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)),
- '/^.+\.(' . $extensions . ')$/i',
- RegexIterator::MATCH
- );
- foreach ($Iterator as $file) {
- $excludes = array('Config');
- //Iterator processes plugins even if not asked to
- if (empty($this->params['plugin'])) {
- $excludes = array_merge($excludes, array('Plugin', 'plugins'));
- }
- if (empty($this->params['vendor'])) {
- $excludes = array_merge($excludes, array('Vendor', 'vendors'));
- }
- if (!empty($excludes)) {
- $isIllegalPluginPath = false;
- foreach ($excludes as $exclude) {
- if (strpos($file, $path . $exclude . DS) === 0) {
- $isIllegalPluginPath = true;
- break;
- }
- }
- if ($isIllegalPluginPath) {
- continue;
- }
- }
- if ($file->isFile()) {
- $this->_files[] = $file->getPathname();
- }
- }
- }
- }
- /**
- * CodeShell::_utf8File()
- *
- * @param mixed $file
- * @param mixed $patterns
- * @return void
- */
- protected function _utf8File($file, $patterns) {
- $contents = $fileContent = file_get_contents($file);
- foreach ($patterns as $pattern) {
- $this->out(sprintf(' * Updating %s', $pattern[0]), 1, Shell::VERBOSE);
- $contents = preg_replace($pattern[1], $pattern[2], $contents);
- }
- $this->out(sprintf('Done updating %s', $file), 1, Shell::VERBOSE);
- if (!$this->params['dry-run'] && $contents !== $fileContent) {
- if (file_exists($file)) {
- unlink($file);
- }
- if (WINDOWS) {
- //$fileContent = utf8_decode($fileContent);
- }
- file_put_contents($file, $contents);
- }
- }
- public function getOptionParser() {
- $subcommandParser = array(
- 'options' => array(
- 'plugin' => array(
- 'short' => 'p',
- 'help' => 'The plugin to update. Only the specified plugin will be updated.',
- 'default' => ''
- ),
- 'custom' => array(
- 'short' => 'c',
- 'help' => 'Custom path to update recursivly.',
- 'default' => ''
- ),
- 'ext' => array(
- 'short' => 'e',
- 'help' => 'The extension(s) to search. A pipe delimited list, or a preg_match compatible subpattern',
- 'default' => 'php|ctp|thtml|inc|tpl'
- ),
- 'vendor' => array(
- 'short' => 'e',
- 'help' => 'Include vendor files, as well',
- 'boolean' => true
- ),
- 'dry-run' => array(
- 'short' => 'd',
- 'help' => 'Dry run the update, no files will actually be modified.',
- 'boolean' => true
- )
- )
- );
- return parent::getOptionParser()
- ->description("A shell to help automate code cleanup. \n" .
- "Be sure to have a backup of your application before running these commands.")
- ->addSubcommand('group', array(
- 'help' => 'Run multiple commands.',
- 'parser' => $subcommandParser
- ))
- ->addSubcommand('dependencies', array(
- 'help' => 'Correct dependencies',
- 'parser' => $subcommandParser
- ))
- ->addSubcommand('utf8', array(
- 'help' => 'Make files utf8 compliant',
- 'parser' => $subcommandParser
- ));
- }
- /**
- * Shell tasks
- *
- * @var array
- */
- public $tasks = array(
- 'CodeConvention',
- 'CodeWhitespace'
- );
- /**
- * Main execution function
- *
- * @return void
- */
- public function group() {
- if (!empty($this->args)) {
- if (!empty($this->args[1])) {
- $this->args[1] = constant($this->args[1]);
- } else {
- $this->args[1] = APP;
- }
- $this->{'Code' . ucfirst($this->args[0])}->execute($this->args[1]);
- } else {
- $this->out('Usage: cake code type');
- $this->out('');
- $this->out('type should be space-separated');
- $this->out('list of any combination of:');
- $this->out('');
- $this->out('convention');
- $this->out('whitespace');
- }
- }
- }
- function strposReverse($str, $search) {
- $str = strrev($str);
- $search = strrev($search);
- $posRev = strpos($str, $search);
- return $posRev;
- }
|