Skip to content

Commit

Permalink
code shell for app uses fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
euromark committed Jul 21, 2012
1 parent ada4dad commit 8d4a913
Showing 1 changed file with 343 additions and 0 deletions.
343 changes: 343 additions & 0 deletions Console/Command/CodeShell.php
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 &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.