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