Permalink
Browse files

code shell for app uses fixes

  • Loading branch information...
1 parent ada4dad commit 8d4a913b3173283ebc61fcd06c3b1608fc6cc49e @dereuromark committed Jul 21, 2012
Showing with 343 additions and 0 deletions.
  1. +343 −0 Console/Command/CodeShell.php
@@ -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 &uuml; 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;
+}

0 comments on commit 8d4a913

Please sign in to comment.