-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
euromark
committed
Jul 21, 2012
1 parent
ada4dad
commit 8d4a913
Showing
1 changed file
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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; | |||
} |