"Fossies" - the Fresh Open Source Software Archive

Member "grav/vendor/symfony/console/Application.php" (1 Sep 2020, 41211 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Application.php" see the Fossies "Dox" file reference documentation.

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