|
|
@@ -0,0 +1,343 @@
|
|
|
+<?php
|
|
|
+App::uses('AppShell', 'Console/Command');
|
|
|
+
|
|
|
+/**
|
|
|
+ * Misc Code Fix Tools
|
|
|
+ *
|
|
|
+ * cake Tools.Code dependencies [-p PluginName]
|
|
|
+ * - Fix missing App::uses() statements
|
|
|
+ *
|
|
|
+ * @author Mark Scherer
|
|
|
+ * @license MIT
|
|
|
+ * 2012-07-19 ms
|
|
|
+ */
|
|
|
+class CodeShell extends AppShell {
|
|
|
+
|
|
|
+ protected $_files;
|
|
|
+
|
|
|
+ protected $_paths;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * detect and fix class dependencies
|
|
|
+ * 2012-07-19 ms
|
|
|
+ */
|
|
|
+ public function dependencies() {
|
|
|
+ if ($customPath = $this->params['custom']) {
|
|
|
+ $this->_paths = array($customPath);
|
|
|
+ } elseif (!empty($this->params['plugin'])) {
|
|
|
+ $this->_paths = array(App::pluginPath($this->params['plugin']));
|
|
|
+ } else {
|
|
|
+ $this->_paths = array(APP);
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->_findFiles('php');
|
|
|
+ foreach ($this->_files as $file) {
|
|
|
+ $this->out(__d('cake_console', 'Updating %s...', $file), 1, Shell::VERBOSE);
|
|
|
+
|
|
|
+ $this->_correctFile($file);
|
|
|
+
|
|
|
+ $this->out(__d('cake_console', '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', '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)) {
|
|
|
+ $fileContent = explode(LF, $fileContent);
|
|
|
+ $inserted = array();
|
|
|
+ $pos = 1;
|
|
|
+
|
|
|
+ if (!empty($fileContent[1]) && $fileContent[1] == '/**') {
|
|
|
+ for ($i = $pos; $i < count($fileContent)-1; $i++) {
|
|
|
+ if (strpos($fileContent[$i], '*/') !== false && 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) {
|
|
|
+ if (strpos($rowValue, 'App::uses(')!== false) {
|
|
|
+ $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;
|
|
|
+ }
|
|
|
+ //FIXME
|
|
|
+ if (!empty($this->params['plugin'])) {
|
|
|
+ $type = $this->params['plugin'] . '.' . $type;
|
|
|
+ }
|
|
|
+
|
|
|
+ $inserted[] = 'App::uses(\''.$missingClass.'\', \''.$type.'\');';
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($inserted) {
|
|
|
+ array_splice($fileContent, $pos, 0, $inserted);
|
|
|
+ }
|
|
|
+ $fileContent = implode(LF, $fileContent);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($missingClasses) && empty($this->params['dry-run'])) {
|
|
|
+ file_put_contents($file, $fileContent);
|
|
|
+ $this->out(__d('cake_console', '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!
|
|
|
+ *
|
|
|
+ * 2012-01-06 ms
|
|
|
+ */
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function _filesRegexpUpdate($patterns) {
|
|
|
+ $this->_findFiles($this->params['ext']);
|
|
|
+ foreach ($this->_files as $file) {
|
|
|
+ $this->out(__d('cake_console', '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 = am($excludes, array('Plugin', 'plugins'));
|
|
|
+ }
|
|
|
+ if (empty($this->params['vendor'])) {
|
|
|
+ $excludes = am($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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function _utf8File($file, $patterns) {
|
|
|
+ $contents = $fileContent = file_get_contents($file);
|
|
|
+
|
|
|
+ foreach ($patterns as $pattern) {
|
|
|
+ $this->out(__d('cake_console', ' * Updating %s', $pattern[0]), 1, Shell::VERBOSE);
|
|
|
+ $contents = preg_replace($pattern[1], $pattern[2], $contents);
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->out(__d('cake_console', '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' => __d('cake_console', 'The plugin to update. Only the specified plugin will be updated.'),
|
|
|
+ 'default' => ''
|
|
|
+ ),
|
|
|
+ 'custom' => array(
|
|
|
+ 'short' => 'c',
|
|
|
+ 'help' => __d('cake_console', 'Custom path to update recursivly.'),
|
|
|
+ 'default' => ''
|
|
|
+ ),
|
|
|
+ 'ext' => array(
|
|
|
+ 'short' => 'e',
|
|
|
+ 'help' => __d('cake_console', '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' => __d('cake_console', 'Include vendor files, as well'),
|
|
|
+ 'boolean' => true
|
|
|
+ ),
|
|
|
+ 'dry-run'=> array(
|
|
|
+ 'short' => 'd',
|
|
|
+ 'help' => __d('cake_console', 'Dry run the update, no files will actually be modified.'),
|
|
|
+ 'boolean' => true
|
|
|
+ )
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+ return parent::getOptionParser()
|
|
|
+ ->description(__d('cake_console', "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' => __d('cake_console', 'Run multiple upgrade commands.'),
|
|
|
+ 'parser' => $subcommandParser
|
|
|
+ ))
|
|
|
+ ->addSubcommand('dependencies', array(
|
|
|
+ 'help' => __d('cake_console', 'Correct dependencies'),
|
|
|
+ 'parser' => $subcommandParser
|
|
|
+ ))
|
|
|
+ ->addSubcommand('utf8', array(
|
|
|
+ 'help' => __d('cake_console', 'Make files utf8 compliant'),
|
|
|
+ 'parser' => $subcommandParser
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+/** old stuff **/
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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;
|
|
|
+}
|