comparison vendor/symfony/console/Application.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Console;
13
14 use Symfony\Component\Console\Exception\ExceptionInterface;
15 use Symfony\Component\Console\Formatter\OutputFormatter;
16 use Symfony\Component\Console\Helper\DebugFormatterHelper;
17 use Symfony\Component\Console\Helper\ProcessHelper;
18 use Symfony\Component\Console\Helper\QuestionHelper;
19 use Symfony\Component\Console\Input\InputInterface;
20 use Symfony\Component\Console\Input\StreamableInputInterface;
21 use Symfony\Component\Console\Input\ArgvInput;
22 use Symfony\Component\Console\Input\ArrayInput;
23 use Symfony\Component\Console\Input\InputDefinition;
24 use Symfony\Component\Console\Input\InputOption;
25 use Symfony\Component\Console\Input\InputArgument;
26 use Symfony\Component\Console\Input\InputAwareInterface;
27 use Symfony\Component\Console\Output\OutputInterface;
28 use Symfony\Component\Console\Output\ConsoleOutput;
29 use Symfony\Component\Console\Output\ConsoleOutputInterface;
30 use Symfony\Component\Console\Command\Command;
31 use Symfony\Component\Console\Command\HelpCommand;
32 use Symfony\Component\Console\Command\ListCommand;
33 use Symfony\Component\Console\Helper\HelperSet;
34 use Symfony\Component\Console\Helper\FormatterHelper;
35 use Symfony\Component\Console\Event\ConsoleCommandEvent;
36 use Symfony\Component\Console\Event\ConsoleExceptionEvent;
37 use Symfony\Component\Console\Event\ConsoleTerminateEvent;
38 use Symfony\Component\Console\Exception\CommandNotFoundException;
39 use Symfony\Component\Console\Exception\LogicException;
40 use Symfony\Component\Debug\Exception\FatalThrowableError;
41 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
42
43 /**
44 * An Application is the container for a collection of commands.
45 *
46 * It is the main entry point of a Console application.
47 *
48 * This class is optimized for a standard CLI environment.
49 *
50 * Usage:
51 *
52 * $app = new Application('myapp', '1.0 (stable)');
53 * $app->add(new SimpleCommand());
54 * $app->run();
55 *
56 * @author Fabien Potencier <fabien@symfony.com>
57 */
58 class Application
59 {
60 private $commands = array();
61 private $wantHelps = false;
62 private $runningCommand;
63 private $name;
64 private $version;
65 private $catchExceptions = true;
66 private $autoExit = true;
67 private $definition;
68 private $helperSet;
69 private $dispatcher;
70 private $terminal;
71 private $defaultCommand;
72 private $singleCommand;
73
74 /**
75 * @param string $name The name of the application
76 * @param string $version The version of the application
77 */
78 public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
79 {
80 $this->name = $name;
81 $this->version = $version;
82 $this->terminal = new Terminal();
83 $this->defaultCommand = 'list';
84 $this->helperSet = $this->getDefaultHelperSet();
85 $this->definition = $this->getDefaultInputDefinition();
86
87 foreach ($this->getDefaultCommands() as $command) {
88 $this->add($command);
89 }
90 }
91
92 public function setDispatcher(EventDispatcherInterface $dispatcher)
93 {
94 $this->dispatcher = $dispatcher;
95 }
96
97 /**
98 * Runs the current application.
99 *
100 * @param InputInterface $input An Input instance
101 * @param OutputInterface $output An Output instance
102 *
103 * @return int 0 if everything went fine, or an error code
104 *
105 * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
106 */
107 public function run(InputInterface $input = null, OutputInterface $output = null)
108 {
109 putenv('LINES='.$this->terminal->getHeight());
110 putenv('COLUMNS='.$this->terminal->getWidth());
111
112 if (null === $input) {
113 $input = new ArgvInput();
114 }
115
116 if (null === $output) {
117 $output = new ConsoleOutput();
118 }
119
120 $this->configureIO($input, $output);
121
122 try {
123 $e = null;
124 $exitCode = $this->doRun($input, $output);
125 } catch (\Exception $x) {
126 $e = $x;
127 } catch (\Throwable $x) {
128 $e = new FatalThrowableError($x);
129 }
130
131 if (null !== $e) {
132 if (!$this->catchExceptions) {
133 throw $x;
134 }
135
136 if ($output instanceof ConsoleOutputInterface) {
137 $this->renderException($e, $output->getErrorOutput());
138 } else {
139 $this->renderException($e, $output);
140 }
141
142 $exitCode = $e->getCode();
143 if (is_numeric($exitCode)) {
144 $exitCode = (int) $exitCode;
145 if (0 === $exitCode) {
146 $exitCode = 1;
147 }
148 } else {
149 $exitCode = 1;
150 }
151 }
152
153 if ($this->autoExit) {
154 if ($exitCode > 255) {
155 $exitCode = 255;
156 }
157
158 exit($exitCode);
159 }
160
161 return $exitCode;
162 }
163
164 /**
165 * Runs the current application.
166 *
167 * @param InputInterface $input An Input instance
168 * @param OutputInterface $output An Output instance
169 *
170 * @return int 0 if everything went fine, or an error code
171 */
172 public function doRun(InputInterface $input, OutputInterface $output)
173 {
174 if (true === $input->hasParameterOption(array('--version', '-V'), true)) {
175 $output->writeln($this->getLongVersion());
176
177 return 0;
178 }
179
180 $name = $this->getCommandName($input);
181 if (true === $input->hasParameterOption(array('--help', '-h'), true)) {
182 if (!$name) {
183 $name = 'help';
184 $input = new ArrayInput(array('command_name' => $this->defaultCommand));
185 } else {
186 $this->wantHelps = true;
187 }
188 }
189
190 if (!$name) {
191 $name = $this->defaultCommand;
192 $input = new ArrayInput(array('command' => $this->defaultCommand));
193 }
194
195 $this->runningCommand = null;
196 // the command name MUST be the first element of the input
197 $command = $this->find($name);
198
199 $this->runningCommand = $command;
200 $exitCode = $this->doRunCommand($command, $input, $output);
201 $this->runningCommand = null;
202
203 return $exitCode;
204 }
205
206 /**
207 * Set a helper set to be used with the command.
208 *
209 * @param HelperSet $helperSet The helper set
210 */
211 public function setHelperSet(HelperSet $helperSet)
212 {
213 $this->helperSet = $helperSet;
214 }
215
216 /**
217 * Get the helper set associated with the command.
218 *
219 * @return HelperSet The HelperSet instance associated with this command
220 */
221 public function getHelperSet()
222 {
223 return $this->helperSet;
224 }
225
226 /**
227 * Set an input definition to be used with this application.
228 *
229 * @param InputDefinition $definition The input definition
230 */
231 public function setDefinition(InputDefinition $definition)
232 {
233 $this->definition = $definition;
234 }
235
236 /**
237 * Gets the InputDefinition related to this Application.
238 *
239 * @return InputDefinition The InputDefinition instance
240 */
241 public function getDefinition()
242 {
243 if ($this->singleCommand) {
244 $inputDefinition = $this->definition;
245 $inputDefinition->setArguments();
246
247 return $inputDefinition;
248 }
249
250 return $this->definition;
251 }
252
253 /**
254 * Gets the help message.
255 *
256 * @return string A help message
257 */
258 public function getHelp()
259 {
260 return $this->getLongVersion();
261 }
262
263 /**
264 * Gets whether to catch exceptions or not during commands execution.
265 *
266 * @return bool Whether to catch exceptions or not during commands execution
267 */
268 public function areExceptionsCaught()
269 {
270 return $this->catchExceptions;
271 }
272
273 /**
274 * Sets whether to catch exceptions or not during commands execution.
275 *
276 * @param bool $boolean Whether to catch exceptions or not during commands execution
277 */
278 public function setCatchExceptions($boolean)
279 {
280 $this->catchExceptions = (bool) $boolean;
281 }
282
283 /**
284 * Gets whether to automatically exit after a command execution or not.
285 *
286 * @return bool Whether to automatically exit after a command execution or not
287 */
288 public function isAutoExitEnabled()
289 {
290 return $this->autoExit;
291 }
292
293 /**
294 * Sets whether to automatically exit after a command execution or not.
295 *
296 * @param bool $boolean Whether to automatically exit after a command execution or not
297 */
298 public function setAutoExit($boolean)
299 {
300 $this->autoExit = (bool) $boolean;
301 }
302
303 /**
304 * Gets the name of the application.
305 *
306 * @return string The application name
307 */
308 public function getName()
309 {
310 return $this->name;
311 }
312
313 /**
314 * Sets the application name.
315 *
316 * @param string $name The application name
317 */
318 public function setName($name)
319 {
320 $this->name = $name;
321 }
322
323 /**
324 * Gets the application version.
325 *
326 * @return string The application version
327 */
328 public function getVersion()
329 {
330 return $this->version;
331 }
332
333 /**
334 * Sets the application version.
335 *
336 * @param string $version The application version
337 */
338 public function setVersion($version)
339 {
340 $this->version = $version;
341 }
342
343 /**
344 * Returns the long version of the application.
345 *
346 * @return string The long application version
347 */
348 public function getLongVersion()
349 {
350 if ('UNKNOWN' !== $this->getName()) {
351 if ('UNKNOWN' !== $this->getVersion()) {
352 return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
353 }
354
355 return $this->getName();
356 }
357
358 return 'Console Tool';
359 }
360
361 /**
362 * Registers a new command.
363 *
364 * @param string $name The command name
365 *
366 * @return Command The newly created command
367 */
368 public function register($name)
369 {
370 return $this->add(new Command($name));
371 }
372
373 /**
374 * Adds an array of command objects.
375 *
376 * If a Command is not enabled it will not be added.
377 *
378 * @param Command[] $commands An array of commands
379 */
380 public function addCommands(array $commands)
381 {
382 foreach ($commands as $command) {
383 $this->add($command);
384 }
385 }
386
387 /**
388 * Adds a command object.
389 *
390 * If a command with the same name already exists, it will be overridden.
391 * If the command is not enabled it will not be added.
392 *
393 * @param Command $command A Command object
394 *
395 * @return Command|null The registered command if enabled or null
396 */
397 public function add(Command $command)
398 {
399 $command->setApplication($this);
400
401 if (!$command->isEnabled()) {
402 $command->setApplication(null);
403
404 return;
405 }
406
407 if (null === $command->getDefinition()) {
408 throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
409 }
410
411 $this->commands[$command->getName()] = $command;
412
413 foreach ($command->getAliases() as $alias) {
414 $this->commands[$alias] = $command;
415 }
416
417 return $command;
418 }
419
420 /**
421 * Returns a registered command by name or alias.
422 *
423 * @param string $name The command name or alias
424 *
425 * @return Command A Command object
426 *
427 * @throws CommandNotFoundException When given command name does not exist
428 */
429 public function get($name)
430 {
431 if (!isset($this->commands[$name])) {
432 throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
433 }
434
435 $command = $this->commands[$name];
436
437 if ($this->wantHelps) {
438 $this->wantHelps = false;
439
440 $helpCommand = $this->get('help');
441 $helpCommand->setCommand($command);
442
443 return $helpCommand;
444 }
445
446 return $command;
447 }
448
449 /**
450 * Returns true if the command exists, false otherwise.
451 *
452 * @param string $name The command name or alias
453 *
454 * @return bool true if the command exists, false otherwise
455 */
456 public function has($name)
457 {
458 return isset($this->commands[$name]);
459 }
460
461 /**
462 * Returns an array of all unique namespaces used by currently registered commands.
463 *
464 * It does not return the global namespace which always exists.
465 *
466 * @return string[] An array of namespaces
467 */
468 public function getNamespaces()
469 {
470 $namespaces = array();
471 foreach ($this->all() as $command) {
472 $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
473
474 foreach ($command->getAliases() as $alias) {
475 $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
476 }
477 }
478
479 return array_values(array_unique(array_filter($namespaces)));
480 }
481
482 /**
483 * Finds a registered namespace by a name or an abbreviation.
484 *
485 * @param string $namespace A namespace or abbreviation to search for
486 *
487 * @return string A registered namespace
488 *
489 * @throws CommandNotFoundException When namespace is incorrect or ambiguous
490 */
491 public function findNamespace($namespace)
492 {
493 $allNamespaces = $this->getNamespaces();
494 $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
495 $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
496
497 if (empty($namespaces)) {
498 $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
499
500 if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
501 if (1 == count($alternatives)) {
502 $message .= "\n\nDid you mean this?\n ";
503 } else {
504 $message .= "\n\nDid you mean one of these?\n ";
505 }
506
507 $message .= implode("\n ", $alternatives);
508 }
509
510 throw new CommandNotFoundException($message, $alternatives);
511 }
512
513 $exact = in_array($namespace, $namespaces, true);
514 if (count($namespaces) > 1 && !$exact) {
515 throw new CommandNotFoundException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
516 }
517
518 return $exact ? $namespace : reset($namespaces);
519 }
520
521 /**
522 * Finds a command by name or alias.
523 *
524 * Contrary to get, this command tries to find the best
525 * match if you give it an abbreviation of a name or alias.
526 *
527 * @param string $name A command name or a command alias
528 *
529 * @return Command A Command instance
530 *
531 * @throws CommandNotFoundException When command name is incorrect or ambiguous
532 */
533 public function find($name)
534 {
535 $allCommands = array_keys($this->commands);
536 $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
537 $commands = preg_grep('{^'.$expr.'}', $allCommands);
538
539 if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) {
540 if (false !== $pos = strrpos($name, ':')) {
541 // check if a namespace exists and contains commands
542 $this->findNamespace(substr($name, 0, $pos));
543 }
544
545 $message = sprintf('Command "%s" is not defined.', $name);
546
547 if ($alternatives = $this->findAlternatives($name, $allCommands)) {
548 if (1 == count($alternatives)) {
549 $message .= "\n\nDid you mean this?\n ";
550 } else {
551 $message .= "\n\nDid you mean one of these?\n ";
552 }
553 $message .= implode("\n ", $alternatives);
554 }
555
556 throw new CommandNotFoundException($message, $alternatives);
557 }
558
559 // filter out aliases for commands which are already on the list
560 if (count($commands) > 1) {
561 $commandList = $this->commands;
562 $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
563 $commandName = $commandList[$nameOrAlias]->getName();
564
565 return $commandName === $nameOrAlias || !in_array($commandName, $commands);
566 });
567 }
568
569 $exact = in_array($name, $commands, true);
570 if (count($commands) > 1 && !$exact) {
571 $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
572
573 throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions), array_values($commands));
574 }
575
576 return $this->get($exact ? $name : reset($commands));
577 }
578
579 /**
580 * Gets the commands (registered in the given namespace if provided).
581 *
582 * The array keys are the full names and the values the command instances.
583 *
584 * @param string $namespace A namespace name
585 *
586 * @return Command[] An array of Command instances
587 */
588 public function all($namespace = null)
589 {
590 if (null === $namespace) {
591 return $this->commands;
592 }
593
594 $commands = array();
595 foreach ($this->commands as $name => $command) {
596 if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
597 $commands[$name] = $command;
598 }
599 }
600
601 return $commands;
602 }
603
604 /**
605 * Returns an array of possible abbreviations given a set of names.
606 *
607 * @param array $names An array of names
608 *
609 * @return array An array of abbreviations
610 */
611 public static function getAbbreviations($names)
612 {
613 $abbrevs = array();
614 foreach ($names as $name) {
615 for ($len = strlen($name); $len > 0; --$len) {
616 $abbrev = substr($name, 0, $len);
617 $abbrevs[$abbrev][] = $name;
618 }
619 }
620
621 return $abbrevs;
622 }
623
624 /**
625 * Renders a caught exception.
626 *
627 * @param \Exception $e An exception instance
628 * @param OutputInterface $output An OutputInterface instance
629 */
630 public function renderException(\Exception $e, OutputInterface $output)
631 {
632 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
633
634 do {
635 $title = sprintf(
636 ' [%s%s] ',
637 get_class($e),
638 $output->isVerbose() && 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''
639 );
640
641 $len = $this->stringWidth($title);
642
643 $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
644 // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
645 if (defined('HHVM_VERSION') && $width > 1 << 31) {
646 $width = 1 << 31;
647 }
648 $lines = array();
649 foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
650 foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
651 // pre-format lines to get the right string length
652 $lineLength = $this->stringWidth($line) + 4;
653 $lines[] = array($line, $lineLength);
654
655 $len = max($lineLength, $len);
656 }
657 }
658
659 $messages = array();
660 $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
661 $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))));
662 foreach ($lines as $line) {
663 $messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
664 }
665 $messages[] = $emptyLine;
666 $messages[] = '';
667
668 $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
669
670 if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
671 $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
672
673 // exception related properties
674 $trace = $e->getTrace();
675 array_unshift($trace, array(
676 'function' => '',
677 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
678 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
679 'args' => array(),
680 ));
681
682 for ($i = 0, $count = count($trace); $i < $count; ++$i) {
683 $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
684 $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
685 $function = $trace[$i]['function'];
686 $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
687 $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
688
689 $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
690 }
691
692 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
693 }
694 } while ($e = $e->getPrevious());
695
696 if (null !== $this->runningCommand) {
697 $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
698 $output->writeln('', OutputInterface::VERBOSITY_QUIET);
699 }
700 }
701
702 /**
703 * Tries to figure out the terminal width in which this application runs.
704 *
705 * @return int|null
706 *
707 * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
708 */
709 protected function getTerminalWidth()
710 {
711 @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);
712
713 return $this->terminal->getWidth();
714 }
715
716 /**
717 * Tries to figure out the terminal height in which this application runs.
718 *
719 * @return int|null
720 *
721 * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
722 */
723 protected function getTerminalHeight()
724 {
725 @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);
726
727 return $this->terminal->getHeight();
728 }
729
730 /**
731 * Tries to figure out the terminal dimensions based on the current environment.
732 *
733 * @return array Array containing width and height
734 *
735 * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
736 */
737 public function getTerminalDimensions()
738 {
739 @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);
740
741 return array($this->terminal->getWidth(), $this->terminal->getHeight());
742 }
743
744 /**
745 * Sets terminal dimensions.
746 *
747 * Can be useful to force terminal dimensions for functional tests.
748 *
749 * @param int $width The width
750 * @param int $height The height
751 *
752 * @return $this
753 *
754 * @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead.
755 */
756 public function setTerminalDimensions($width, $height)
757 {
758 @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), E_USER_DEPRECATED);
759
760 putenv('COLUMNS='.$width);
761 putenv('LINES='.$height);
762
763 return $this;
764 }
765
766 /**
767 * Configures the input and output instances based on the user arguments and options.
768 *
769 * @param InputInterface $input An InputInterface instance
770 * @param OutputInterface $output An OutputInterface instance
771 */
772 protected function configureIO(InputInterface $input, OutputInterface $output)
773 {
774 if (true === $input->hasParameterOption(array('--ansi'), true)) {
775 $output->setDecorated(true);
776 } elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) {
777 $output->setDecorated(false);
778 }
779
780 if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) {
781 $input->setInteractive(false);
782 } elseif (function_exists('posix_isatty')) {
783 $inputStream = null;
784
785 if ($input instanceof StreamableInputInterface) {
786 $inputStream = $input->getStream();
787 }
788
789 // This check ensures that calling QuestionHelper::setInputStream() works
790 // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream)
791 if (!$inputStream && $this->getHelperSet()->has('question')) {
792 $inputStream = $this->getHelperSet()->get('question')->getInputStream(false);
793 }
794
795 if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
796 $input->setInteractive(false);
797 }
798 }
799
800 if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) {
801 $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
802 $input->setInteractive(false);
803 } else {
804 if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || $input->getParameterOption('--verbose', false, true) === 3) {
805 $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
806 } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || $input->getParameterOption('--verbose', false, true) === 2) {
807 $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
808 } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
809 $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
810 }
811 }
812 }
813
814 /**
815 * Runs the current command.
816 *
817 * If an event dispatcher has been attached to the application,
818 * events are also dispatched during the life-cycle of the command.
819 *
820 * @param Command $command A Command instance
821 * @param InputInterface $input An Input instance
822 * @param OutputInterface $output An Output instance
823 *
824 * @return int 0 if everything went fine, or an error code
825 */
826 protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
827 {
828 foreach ($command->getHelperSet() as $helper) {
829 if ($helper instanceof InputAwareInterface) {
830 $helper->setInput($input);
831 }
832 }
833
834 if (null === $this->dispatcher) {
835 return $command->run($input, $output);
836 }
837
838 // bind before the console.command event, so the listeners have access to input options/arguments
839 try {
840 $command->mergeApplicationDefinition();
841 $input->bind($command->getDefinition());
842 } catch (ExceptionInterface $e) {
843 // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
844 }
845
846 $event = new ConsoleCommandEvent($command, $input, $output);
847 $e = null;
848
849 try {
850 $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
851
852 if ($event->commandShouldRun()) {
853 $exitCode = $command->run($input, $output);
854 } else {
855 $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
856 }
857 } catch (\Exception $e) {
858 } catch (\Throwable $e) {
859 }
860 if (null !== $e) {
861 $x = $e instanceof \Exception ? $e : new FatalThrowableError($e);
862 $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode());
863 $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
864
865 if ($x !== $event->getException()) {
866 $e = $event->getException();
867 }
868 $exitCode = $e->getCode();
869 }
870
871 $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
872 $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
873
874 if (null !== $e) {
875 throw $e;
876 }
877
878 return $event->getExitCode();
879 }
880
881 /**
882 * Gets the name of the command based on input.
883 *
884 * @param InputInterface $input The input interface
885 *
886 * @return string The command name
887 */
888 protected function getCommandName(InputInterface $input)
889 {
890 return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
891 }
892
893 /**
894 * Gets the default input definition.
895 *
896 * @return InputDefinition An InputDefinition instance
897 */
898 protected function getDefaultInputDefinition()
899 {
900 return new InputDefinition(array(
901 new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
902
903 new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
904 new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
905 new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
906 new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
907 new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
908 new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
909 new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
910 ));
911 }
912
913 /**
914 * Gets the default commands that should always be available.
915 *
916 * @return Command[] An array of default Command instances
917 */
918 protected function getDefaultCommands()
919 {
920 return array(new HelpCommand(), new ListCommand());
921 }
922
923 /**
924 * Gets the default helper set with the helpers that should always be available.
925 *
926 * @return HelperSet A HelperSet instance
927 */
928 protected function getDefaultHelperSet()
929 {
930 return new HelperSet(array(
931 new FormatterHelper(),
932 new DebugFormatterHelper(),
933 new ProcessHelper(),
934 new QuestionHelper(),
935 ));
936 }
937
938 /**
939 * Returns abbreviated suggestions in string format.
940 *
941 * @param array $abbrevs Abbreviated suggestions to convert
942 *
943 * @return string A formatted string of abbreviated suggestions
944 */
945 private function getAbbreviationSuggestions($abbrevs)
946 {
947 return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
948 }
949
950 /**
951 * Returns the namespace part of the command name.
952 *
953 * This method is not part of public API and should not be used directly.
954 *
955 * @param string $name The full name of the command
956 * @param string $limit The maximum number of parts of the namespace
957 *
958 * @return string The namespace of the command
959 */
960 public function extractNamespace($name, $limit = null)
961 {
962 $parts = explode(':', $name);
963 array_pop($parts);
964
965 return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
966 }
967
968 /**
969 * Finds alternative of $name among $collection,
970 * if nothing is found in $collection, try in $abbrevs.
971 *
972 * @param string $name The string
973 * @param array|\Traversable $collection The collection
974 *
975 * @return string[] A sorted array of similar string
976 */
977 private function findAlternatives($name, $collection)
978 {
979 $threshold = 1e3;
980 $alternatives = array();
981
982 $collectionParts = array();
983 foreach ($collection as $item) {
984 $collectionParts[$item] = explode(':', $item);
985 }
986
987 foreach (explode(':', $name) as $i => $subname) {
988 foreach ($collectionParts as $collectionName => $parts) {
989 $exists = isset($alternatives[$collectionName]);
990 if (!isset($parts[$i]) && $exists) {
991 $alternatives[$collectionName] += $threshold;
992 continue;
993 } elseif (!isset($parts[$i])) {
994 continue;
995 }
996
997 $lev = levenshtein($subname, $parts[$i]);
998 if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
999 $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
1000 } elseif ($exists) {
1001 $alternatives[$collectionName] += $threshold;
1002 }
1003 }
1004 }
1005
1006 foreach ($collection as $item) {
1007 $lev = levenshtein($name, $item);
1008 if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
1009 $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
1010 }
1011 }
1012
1013 $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
1014 asort($alternatives);
1015
1016 return array_keys($alternatives);
1017 }
1018
1019 /**
1020 * Sets the default Command name.
1021 *
1022 * @param string $commandName The Command name
1023 * @param bool $isSingleCommand Set to true if there is only one command in this application
1024 *
1025 * @return self
1026 */
1027 public function setDefaultCommand($commandName, $isSingleCommand = false)
1028 {
1029 $this->defaultCommand = $commandName;
1030
1031 if ($isSingleCommand) {
1032 // Ensure the command exist
1033 $this->find($commandName);
1034
1035 $this->singleCommand = true;
1036 }
1037
1038 return $this;
1039 }
1040
1041 private function stringWidth($string)
1042 {
1043 if (false === $encoding = mb_detect_encoding($string, null, true)) {
1044 return strlen($string);
1045 }
1046
1047 return mb_strwidth($string, $encoding);
1048 }
1049
1050 private function splitStringByWidth($string, $width)
1051 {
1052 // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
1053 // additionally, array_slice() is not enough as some character has doubled width.
1054 // we need a function to split string not by character count but by string width
1055 if (false === $encoding = mb_detect_encoding($string, null, true)) {
1056 return str_split($string, $width);
1057 }
1058
1059 $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
1060 $lines = array();
1061 $line = '';
1062 foreach (preg_split('//u', $utf8String) as $char) {
1063 // test if $char could be appended to current line
1064 if (mb_strwidth($line.$char, 'utf8') <= $width) {
1065 $line .= $char;
1066 continue;
1067 }
1068 // if not, push current line to array and make new line
1069 $lines[] = str_pad($line, $width);
1070 $line = $char;
1071 }
1072 if ('' !== $line) {
1073 $lines[] = count($lines) ? str_pad($line, $width) : $line;
1074 }
1075
1076 mb_convert_variables($encoding, 'utf8', $lines);
1077
1078 return $lines;
1079 }
1080
1081 /**
1082 * Returns all namespaces of the command name.
1083 *
1084 * @param string $name The full name of the command
1085 *
1086 * @return string[] The namespaces of the command
1087 */
1088 private function extractAllNamespaces($name)
1089 {
1090 // -1 as third argument is needed to skip the command short name when exploding
1091 $parts = explode(':', $name, -1);
1092 $namespaces = array();
1093
1094 foreach ($parts as $part) {
1095 if (count($namespaces)) {
1096 $namespaces[] = end($namespaces).':'.$part;
1097 } else {
1098 $namespaces[] = $part;
1099 }
1100 }
1101
1102 return $namespaces;
1103 }
1104 }