1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18:
19: App::uses('TaskCollection', 'Console');
20: App::uses('ConsoleOutput', 'Console');
21: App::uses('ConsoleInput', 'Console');
22: App::uses('ConsoleInputSubcommand', 'Console');
23: App::uses('ConsoleOptionParser', 'Console');
24: App::uses('File', 'Utility');
25:
26: 27: 28: 29: 30:
31: class Shell extends Object {
32:
33: 34: 35:
36: const VERBOSE = 2;
37: const NORMAL = 1;
38: const QUIET = 0;
39:
40: 41: 42: 43: 44:
45: public $OptionParser;
46:
47: 48: 49: 50: 51:
52: public $interactive = true;
53:
54: 55: 56: 57: 58:
59: public $params = array();
60:
61: 62: 63: 64: 65:
66: public $command;
67:
68: 69: 70: 71: 72:
73: public $args = array();
74:
75: 76: 77: 78: 79:
80: public $name = null;
81:
82: 83: 84: 85: 86: 87:
88: public $plugin = null;
89:
90: 91: 92: 93: 94: 95:
96: public $tasks = array();
97:
98: 99: 100: 101: 102:
103: public $taskNames = array();
104:
105: 106: 107: 108: 109: 110:
111: public $uses = array();
112:
113: 114: 115: 116: 117:
118: public $Tasks;
119:
120: 121: 122: 123: 124:
125: protected $_taskMap = array();
126:
127: 128: 129: 130: 131:
132: public $stdout;
133:
134: 135: 136: 137: 138:
139: public $stderr;
140:
141: 142: 143: 144: 145:
146: public $stdin;
147:
148: 149: 150: 151: 152: 153: 154: 155:
156: public function __construct($stdout = null, $stderr = null, $stdin = null) {
157: if ($this->name == null) {
158: $this->name = Inflector::camelize(str_replace(array('Shell', 'Task'), '', get_class($this)));
159: }
160: $this->Tasks = new TaskCollection($this);
161:
162: $this->stdout = $stdout;
163: $this->stderr = $stderr;
164: $this->stdin = $stdin;
165: if ($this->stdout == null) {
166: $this->stdout = new ConsoleOutput('php://stdout');
167: }
168: if ($this->stderr == null) {
169: $this->stderr = new ConsoleOutput('php://stderr');
170: }
171: if ($this->stdin == null) {
172: $this->stdin = new ConsoleInput('php://stdin');
173: }
174: $this->_useLogger();
175: $parent = get_parent_class($this);
176: if ($this->tasks !== null && $this->tasks !== false) {
177: $this->_mergeVars(array('tasks'), $parent, true);
178: }
179: if ($this->uses !== null && $this->uses !== false) {
180: $this->_mergeVars(array('uses'), $parent, false);
181: }
182: }
183:
184: 185: 186: 187: 188: 189: 190: 191:
192: public function initialize() {
193: $this->_loadModels();
194: }
195:
196: 197: 198: 199: 200: 201: 202: 203: 204: 205:
206: public function startup() {
207: $this->_welcome();
208: }
209:
210: 211: 212: 213: 214:
215: protected function _welcome() {
216: $this->out();
217: $this->out(__d('cake_console', '<info>Welcome to CakePHP %s Console</info>', 'v' . Configure::version()));
218: $this->hr();
219: $this->out(__d('cake_console', 'App : %s', APP_DIR));
220: $this->out(__d('cake_console', 'Path: %s', APP));
221: $this->hr();
222: }
223:
224: 225: 226: 227: 228: 229: 230: 231:
232: protected function _loadModels() {
233: if ($this->uses === null || $this->uses === false) {
234: return;
235: }
236: App::uses('ClassRegistry', 'Utility');
237:
238: if ($this->uses !== true && !empty($this->uses)) {
239: $uses = is_array($this->uses) ? $this->uses : array($this->uses);
240:
241: $modelClassName = $uses[0];
242: if (strpos($uses[0], '.') !== false) {
243: list($plugin, $modelClassName) = explode('.', $uses[0]);
244: }
245: $this->modelClass = $modelClassName;
246:
247: foreach ($uses as $modelClass) {
248: list($plugin, $modelClass) = pluginSplit($modelClass, true);
249: $this->{$modelClass} = ClassRegistry::init($plugin . $modelClass);
250: }
251: return true;
252: }
253: return false;
254: }
255:
256: 257: 258: 259: 260:
261: public function loadTasks() {
262: if ($this->tasks === true || empty($this->tasks) || empty($this->Tasks)) {
263: return true;
264: }
265: $this->_taskMap = TaskCollection::normalizeObjectArray((array)$this->tasks);
266: foreach ($this->_taskMap as $task => $properties) {
267: $this->taskNames[] = $task;
268: }
269: return true;
270: }
271:
272: 273: 274: 275: 276: 277: 278:
279: public function hasTask($task) {
280: return isset($this->_taskMap[Inflector::camelize($task)]);
281: }
282:
283: 284: 285: 286: 287: 288: 289:
290: public function hasMethod($name) {
291: try {
292: $method = new ReflectionMethod($this, $name);
293: if (!$method->isPublic() || substr($name, 0, 1) === '_') {
294: return false;
295: }
296: if ($method->getDeclaringClass()->name == 'Shell') {
297: return false;
298: }
299: return true;
300: } catch (ReflectionException $e) {
301: return false;
302: }
303: }
304:
305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325:
326: public function dispatchShell() {
327: $args = func_get_args();
328: if (is_string($args[0]) && count($args) == 1) {
329: $args = explode(' ', $args[0]);
330: }
331:
332: $Dispatcher = new ShellDispatcher($args, false);
333: return $Dispatcher->dispatch();
334: }
335:
336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354:
355: public function runCommand($command, $argv) {
356: $isTask = $this->hasTask($command);
357: $isMethod = $this->hasMethod($command);
358: $isMain = $this->hasMethod('main');
359:
360: if ($isTask || $isMethod && $command !== 'execute') {
361: array_shift($argv);
362: }
363:
364: $this->OptionParser = $this->getOptionParser();
365: try {
366: list($this->params, $this->args) = $this->OptionParser->parse($argv, $command);
367: } catch (ConsoleException $e) {
368: $this->out($this->OptionParser->help($command));
369: return false;
370: }
371:
372: if (!empty($this->params['quiet'])) {
373: $this->_useLogger(false);
374: }
375: if (!empty($this->params['plugin'])) {
376: CakePlugin::load($this->params['plugin']);
377: }
378: $this->command = $command;
379: if (!empty($this->params['help'])) {
380: return $this->_displayHelp($command);
381: }
382:
383: if (($isTask || $isMethod || $isMain) && $command !== 'execute') {
384: $this->startup();
385: }
386:
387: if ($isTask) {
388: $command = Inflector::camelize($command);
389: return $this->{$command}->runCommand('execute', $argv);
390: }
391: if ($isMethod) {
392: return $this->{$command}();
393: }
394: if ($isMain) {
395: return $this->main();
396: }
397: $this->out($this->OptionParser->help($command));
398: return false;
399: }
400:
401: 402: 403: 404: 405: 406:
407: protected function _displayHelp($command) {
408: $format = 'text';
409: if (!empty($this->args[0]) && $this->args[0] == 'xml') {
410: $format = 'xml';
411: $this->stdout->outputAs(ConsoleOutput::RAW);
412: } else {
413: $this->_welcome();
414: }
415: return $this->out($this->OptionParser->help($command, $format));
416: }
417:
418: 419: 420: 421: 422: 423: 424:
425: public function getOptionParser() {
426: $name = ($this->plugin ? $this->plugin . '.' : '') . $this->name;
427: $parser = new ConsoleOptionParser($name);
428: return $parser;
429: }
430:
431: 432: 433: 434: 435: 436:
437: public function __get($name) {
438: if (empty($this->{$name}) && in_array($name, $this->taskNames)) {
439: $properties = $this->_taskMap[$name];
440: $this->{$name} = $this->Tasks->load($properties['class'], $properties['settings']);
441: $this->{$name}->args =& $this->args;
442: $this->{$name}->params =& $this->params;
443: $this->{$name}->initialize();
444: $this->{$name}->loadTasks();
445: }
446: return $this->{$name};
447: }
448:
449: 450: 451: 452: 453: 454: 455: 456: 457:
458: public function in($prompt, $options = null, $default = null) {
459: if (!$this->interactive) {
460: return $default;
461: }
462: $originalOptions = $options;
463: $in = $this->_getInput($prompt, $originalOptions, $default);
464:
465: if ($options && is_string($options)) {
466: if (strpos($options, ',')) {
467: $options = explode(',', $options);
468: } elseif (strpos($options, '/')) {
469: $options = explode('/', $options);
470: } else {
471: $options = array($options);
472: }
473: }
474: if (is_array($options)) {
475: $options = array_merge(
476: array_map('strtolower', $options),
477: array_map('strtoupper', $options),
478: $options
479: );
480: while ($in === '' || !in_array($in, $options)) {
481: $in = $this->_getInput($prompt, $originalOptions, $default);
482: }
483: }
484: return $in;
485: }
486:
487: 488: 489: 490: 491: 492: 493: 494:
495: protected function _getInput($prompt, $options, $default) {
496: if (!is_array($options)) {
497: $printOptions = '';
498: } else {
499: $printOptions = '(' . implode('/', $options) . ')';
500: }
501:
502: if ($default === null) {
503: $this->stdout->write('<question>' . $prompt . '</question>' . " $printOptions \n" . '> ', 0);
504: } else {
505: $this->stdout->write('<question>' . $prompt . '</question>' . " $printOptions \n" . "[$default] > ", 0);
506: }
507: $result = $this->stdin->read();
508:
509: if ($result === false) {
510: $this->_stop(1);
511: }
512: $result = trim($result);
513:
514: if ($default !== null && ($result === '' || $result === null)) {
515: return $default;
516: }
517: return $result;
518: }
519:
520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535:
536: public function wrapText($text, $options = array()) {
537: return String::wrap($text, $options);
538: }
539:
540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556:
557: public function out($message = null, $newlines = 1, $level = Shell::NORMAL) {
558: $currentLevel = Shell::NORMAL;
559: if (!empty($this->params['verbose'])) {
560: $currentLevel = Shell::VERBOSE;
561: }
562: if (!empty($this->params['quiet'])) {
563: $currentLevel = Shell::QUIET;
564: }
565: if ($level <= $currentLevel) {
566: return $this->stdout->write($message, $newlines);
567: }
568: return true;
569: }
570:
571: 572: 573: 574: 575: 576: 577: 578: 579:
580: public function err($message = null, $newlines = 1) {
581: $this->stderr->write($message, $newlines);
582: }
583:
584: 585: 586: 587: 588: 589: 590:
591: public function nl($multiplier = 1) {
592: return str_repeat(ConsoleOutput::LF, $multiplier);
593: }
594:
595: 596: 597: 598: 599: 600: 601: 602:
603: public function hr($newlines = 0, $width = 63) {
604: $this->out(null, $newlines);
605: $this->out(str_repeat('-', $width));
606: $this->out(null, $newlines);
607: }
608:
609: 610: 611: 612: 613: 614: 615: 616: 617:
618: public function error($title, $message = null) {
619: $this->err(__d('cake_console', '<error>Error:</error> %s', $title));
620:
621: if (!empty($message)) {
622: $this->err($message);
623: }
624: $this->_stop(1);
625: }
626:
627: 628: 629: 630: 631: 632:
633: public function clear() {
634: if (empty($this->params['noclear'])) {
635: if (DS === '/') {
636: passthru('clear');
637: } else {
638: passthru('cls');
639: }
640: }
641: }
642:
643: 644: 645: 646: 647: 648: 649: 650:
651: public function createFile($path, $contents) {
652: $path = str_replace(DS . DS, DS, $path);
653:
654: $this->out();
655:
656: if (is_file($path) && $this->interactive === true) {
657: $this->out(__d('cake_console', '<warning>File `%s` exists</warning>', $path));
658: $key = $this->in(__d('cake_console', 'Do you want to overwrite?'), array('y', 'n', 'q'), 'n');
659:
660: if (strtolower($key) == 'q') {
661: $this->out(__d('cake_console', '<error>Quitting</error>.'), 2);
662: $this->_stop();
663: } elseif (strtolower($key) != 'y') {
664: $this->out(__d('cake_console', 'Skip `%s`', $path), 2);
665: return false;
666: }
667: } else {
668: $this->out(__d('cake_console', 'Creating file %s', $path));
669: }
670:
671: $File = new File($path, true);
672: if ($File->exists() && $File->writable()) {
673: $data = $File->prepare($contents);
674: $File->write($data);
675: $this->out(__d('cake_console', '<success>Wrote</success> `%s`', $path));
676: return true;
677: } else {
678: $this->err(__d('cake_console', '<error>Could not write to `%s`</error>.', $path), 2);
679: return false;
680: }
681: }
682:
683: 684: 685: 686: 687:
688: protected function _checkUnitTest() {
689: if (class_exists('PHPUnit_Framework_TestCase')) {
690: return true;
691:
692: } elseif (@include 'PHPUnit' . DS . 'Autoload.php') {
693:
694: return true;
695: } elseif (App::import('Vendor', 'phpunit', array('file' => 'PHPUnit' . DS . 'Autoload.php'))) {
696: return true;
697: }
698:
699: $prompt = __d('cake_console', 'PHPUnit is not installed. Do you want to bake unit test files anyway?');
700: $unitTest = $this->in($prompt, array('y', 'n'), 'y');
701: $result = strtolower($unitTest) == 'y' || strtolower($unitTest) == 'yes';
702:
703: if ($result) {
704: $this->out();
705: $this->out(__d('cake_console', 'You can download PHPUnit from %s', 'http://phpunit.de'));
706: }
707: return $result;
708: }
709:
710: 711: 712: 713: 714: 715: 716:
717: public function shortPath($file) {
718: $shortPath = str_replace(ROOT, null, $file);
719: $shortPath = str_replace('..' . DS, '', $shortPath);
720: return str_replace(DS . DS, DS, $shortPath);
721: }
722:
723: 724: 725: 726: 727: 728:
729: protected function _controllerPath($name) {
730: return Inflector::underscore($name);
731: }
732:
733: 734: 735: 736: 737: 738:
739: protected function _controllerName($name) {
740: return Inflector::pluralize(Inflector::camelize($name));
741: }
742:
743: 744: 745: 746: 747: 748:
749: protected function _modelName($name) {
750: return Inflector::camelize(Inflector::singularize($name));
751: }
752:
753: 754: 755: 756: 757: 758:
759: protected function _modelKey($name) {
760: return Inflector::underscore($name) . '_id';
761: }
762:
763: 764: 765: 766: 767: 768:
769: protected function _modelNameFromKey($key) {
770: return Inflector::camelize(str_replace('_id', '', $key));
771: }
772:
773: 774: 775: 776: 777: 778:
779: protected function _singularName($name) {
780: return Inflector::variable(Inflector::singularize($name));
781: }
782:
783: 784: 785: 786: 787: 788:
789: protected function _pluralName($name) {
790: return Inflector::variable(Inflector::pluralize($name));
791: }
792:
793: 794: 795: 796: 797: 798:
799: protected function _singularHumanName($name) {
800: return Inflector::humanize(Inflector::underscore(Inflector::singularize($name)));
801: }
802:
803: 804: 805: 806: 807: 808:
809: protected function _pluralHumanName($name) {
810: return Inflector::humanize(Inflector::underscore($name));
811: }
812:
813: 814: 815: 816: 817: 818:
819: protected function _pluginPath($pluginName) {
820: if (CakePlugin::loaded($pluginName)) {
821: return CakePlugin::path($pluginName);
822: }
823: return current(App::path('plugins')) . $pluginName . DS;
824: }
825:
826: 827: 828: 829: 830: 831: 832: 833:
834: protected function _useLogger($enable = true) {
835: if (!$enable) {
836: CakeLog::drop('stdout');
837: CakeLog::drop('stderr');
838: return;
839: }
840: CakeLog::config('stdout', array(
841: 'engine' => 'ConsoleLog',
842: 'types' => array('notice', 'info'),
843: 'stream' => $this->stdout,
844: ));
845: CakeLog::config('stderr', array(
846: 'engine' => 'ConsoleLog',
847: 'types' => array('emergency', 'alert', 'critical', 'error', 'warning', 'debug'),
848: 'stream' => $this->stderr,
849: ));
850: }
851: }
852: