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