vendor/symfony/src/Symfony/Component/Console/Application.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     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\Input\InputInterface;
       
    15 use Symfony\Component\Console\Input\ArgvInput;
       
    16 use Symfony\Component\Console\Input\ArrayInput;
       
    17 use Symfony\Component\Console\Input\InputDefinition;
       
    18 use Symfony\Component\Console\Input\InputOption;
       
    19 use Symfony\Component\Console\Input\InputArgument;
       
    20 use Symfony\Component\Console\Output\OutputInterface;
       
    21 use Symfony\Component\Console\Output\Output;
       
    22 use Symfony\Component\Console\Output\ConsoleOutput;
       
    23 use Symfony\Component\Console\Command\Command;
       
    24 use Symfony\Component\Console\Command\HelpCommand;
       
    25 use Symfony\Component\Console\Command\ListCommand;
       
    26 use Symfony\Component\Console\Helper\HelperSet;
       
    27 use Symfony\Component\Console\Helper\FormatterHelper;
       
    28 use Symfony\Component\Console\Helper\DialogHelper;
       
    29 
       
    30 /**
       
    31  * An Application is the container for a collection of commands.
       
    32  *
       
    33  * It is the main entry point of a Console application.
       
    34  *
       
    35  * This class is optimized for a standard CLI environment.
       
    36  *
       
    37  * Usage:
       
    38  *
       
    39  *     $app = new Application('myapp', '1.0 (stable)');
       
    40  *     $app->add(new SimpleCommand());
       
    41  *     $app->run();
       
    42  *
       
    43  * @author Fabien Potencier <fabien@symfony.com>
       
    44  *
       
    45  * @api
       
    46  */
       
    47 class Application
       
    48 {
       
    49     private $commands;
       
    50     private $wantHelps = false;
       
    51     private $runningCommand;
       
    52     private $name;
       
    53     private $version;
       
    54     private $catchExceptions;
       
    55     private $autoExit;
       
    56     private $definition;
       
    57     private $helperSet;
       
    58 
       
    59     /**
       
    60      * Constructor.
       
    61      *
       
    62      * @param string  $name    The name of the application
       
    63      * @param string  $version The version of the application
       
    64      *
       
    65      * @api
       
    66      */
       
    67     public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
       
    68     {
       
    69         $this->name = $name;
       
    70         $this->version = $version;
       
    71         $this->catchExceptions = true;
       
    72         $this->autoExit = true;
       
    73         $this->commands = array();
       
    74         $this->helperSet = new HelperSet(array(
       
    75             new FormatterHelper(),
       
    76             new DialogHelper(),
       
    77         ));
       
    78 
       
    79         $this->add(new HelpCommand());
       
    80         $this->add(new ListCommand());
       
    81 
       
    82         $this->definition = new InputDefinition(array(
       
    83             new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
       
    84 
       
    85             new InputOption('--help',           '-h', InputOption::VALUE_NONE, 'Display this help message.'),
       
    86             new InputOption('--quiet',          '-q', InputOption::VALUE_NONE, 'Do not output any message.'),
       
    87             new InputOption('--verbose',        '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'),
       
    88             new InputOption('--version',        '-V', InputOption::VALUE_NONE, 'Display this program version.'),
       
    89             new InputOption('--ansi',           '',   InputOption::VALUE_NONE, 'Force ANSI output.'),
       
    90             new InputOption('--no-ansi',        '',   InputOption::VALUE_NONE, 'Disable ANSI output.'),
       
    91             new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'),
       
    92         ));
       
    93     }
       
    94 
       
    95     /**
       
    96      * Runs the current application.
       
    97      *
       
    98      * @param InputInterface  $input  An Input instance
       
    99      * @param OutputInterface $output An Output instance
       
   100      *
       
   101      * @return integer 0 if everything went fine, or an error code
       
   102      *
       
   103      * @throws \Exception When doRun returns Exception
       
   104      *
       
   105      * @api
       
   106      */
       
   107     public function run(InputInterface $input = null, OutputInterface $output = null)
       
   108     {
       
   109         if (null === $input) {
       
   110             $input = new ArgvInput();
       
   111         }
       
   112 
       
   113         if (null === $output) {
       
   114             $output = new ConsoleOutput();
       
   115         }
       
   116 
       
   117         try {
       
   118             $statusCode = $this->doRun($input, $output);
       
   119         } catch (\Exception $e) {
       
   120             if (!$this->catchExceptions) {
       
   121                 throw $e;
       
   122             }
       
   123 
       
   124             $this->renderException($e, $output);
       
   125             $statusCode = $e->getCode();
       
   126 
       
   127             $statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
       
   128         }
       
   129 
       
   130         if ($this->autoExit) {
       
   131             if ($statusCode > 255) {
       
   132                 $statusCode = 255;
       
   133             }
       
   134             // @codeCoverageIgnoreStart
       
   135             exit($statusCode);
       
   136             // @codeCoverageIgnoreEnd
       
   137         }
       
   138 
       
   139         return $statusCode;
       
   140     }
       
   141 
       
   142     /**
       
   143      * Runs the current application.
       
   144      *
       
   145      * @param InputInterface  $input  An Input instance
       
   146      * @param OutputInterface $output An Output instance
       
   147      *
       
   148      * @return integer 0 if everything went fine, or an error code
       
   149      */
       
   150     public function doRun(InputInterface $input, OutputInterface $output)
       
   151     {
       
   152         $name = $this->getCommandName($input);
       
   153 
       
   154         if (true === $input->hasParameterOption(array('--ansi'))) {
       
   155             $output->setDecorated(true);
       
   156         } elseif (true === $input->hasParameterOption(array('--no-ansi'))) {
       
   157             $output->setDecorated(false);
       
   158         }
       
   159 
       
   160         if (true === $input->hasParameterOption(array('--help', '-h'))) {
       
   161             if (!$name) {
       
   162                 $name = 'help';
       
   163                 $input = new ArrayInput(array('command' => 'help'));
       
   164             } else {
       
   165                 $this->wantHelps = true;
       
   166             }
       
   167         }
       
   168 
       
   169         if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
       
   170             $input->setInteractive(false);
       
   171         }
       
   172 
       
   173         if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
       
   174             $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
       
   175         } elseif (true === $input->hasParameterOption(array('--verbose', '-v'))) {
       
   176             $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
       
   177         }
       
   178 
       
   179         if (true === $input->hasParameterOption(array('--version', '-V'))) {
       
   180             $output->writeln($this->getLongVersion());
       
   181 
       
   182             return 0;
       
   183         }
       
   184 
       
   185         if (!$name) {
       
   186             $name = 'list';
       
   187             $input = new ArrayInput(array('command' => 'list'));
       
   188         }
       
   189 
       
   190         // the command name MUST be the first element of the input
       
   191         $command = $this->find($name);
       
   192 
       
   193         $this->runningCommand = $command;
       
   194         $statusCode = $command->run($input, $output);
       
   195         $this->runningCommand = null;
       
   196 
       
   197         return is_numeric($statusCode) ? $statusCode : 0;
       
   198     }
       
   199 
       
   200     /**
       
   201      * Set a helper set to be used with the command.
       
   202      *
       
   203      * @param HelperSet $helperSet The helper set
       
   204      *
       
   205      * @api
       
   206      */
       
   207     public function setHelperSet(HelperSet $helperSet)
       
   208     {
       
   209         $this->helperSet = $helperSet;
       
   210     }
       
   211 
       
   212     /**
       
   213      * Get the helper set associated with the command.
       
   214      *
       
   215      * @return HelperSet The HelperSet instance associated with this command
       
   216      *
       
   217      * @api
       
   218      */
       
   219     public function getHelperSet()
       
   220     {
       
   221         return $this->helperSet;
       
   222     }
       
   223 
       
   224     /**
       
   225      * Gets the InputDefinition related to this Application.
       
   226      *
       
   227      * @return InputDefinition The InputDefinition instance
       
   228      */
       
   229     public function getDefinition()
       
   230     {
       
   231         return $this->definition;
       
   232     }
       
   233 
       
   234     /**
       
   235      * Gets the help message.
       
   236      *
       
   237      * @return string A help message.
       
   238      */
       
   239     public function getHelp()
       
   240     {
       
   241         $messages = array(
       
   242             $this->getLongVersion(),
       
   243             '',
       
   244             '<comment>Usage:</comment>',
       
   245             sprintf("  [options] command [arguments]\n"),
       
   246             '<comment>Options:</comment>',
       
   247         );
       
   248 
       
   249         foreach ($this->getDefinition()->getOptions() as $option) {
       
   250             $messages[] = sprintf('  %-29s %s %s',
       
   251                 '<info>--'.$option->getName().'</info>',
       
   252                 $option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : '  ',
       
   253                 $option->getDescription()
       
   254             );
       
   255         }
       
   256 
       
   257         return implode("\n", $messages);
       
   258     }
       
   259 
       
   260     /**
       
   261      * Sets whether to catch exceptions or not during commands execution.
       
   262      *
       
   263      * @param Boolean $boolean Whether to catch exceptions or not during commands execution
       
   264      *
       
   265      * @api
       
   266      */
       
   267     public function setCatchExceptions($boolean)
       
   268     {
       
   269         $this->catchExceptions = (Boolean) $boolean;
       
   270     }
       
   271 
       
   272     /**
       
   273      * Sets whether to automatically exit after a command execution or not.
       
   274      *
       
   275      * @param Boolean $boolean Whether to automatically exit after a command execution or not
       
   276      *
       
   277      * @api
       
   278      */
       
   279     public function setAutoExit($boolean)
       
   280     {
       
   281         $this->autoExit = (Boolean) $boolean;
       
   282     }
       
   283 
       
   284     /**
       
   285      * Gets the name of the application.
       
   286      *
       
   287      * @return string The application name
       
   288      *
       
   289      * @api
       
   290      */
       
   291     public function getName()
       
   292     {
       
   293         return $this->name;
       
   294     }
       
   295 
       
   296     /**
       
   297      * Sets the application name.
       
   298      *
       
   299      * @param string $name The application name
       
   300      *
       
   301      * @api
       
   302      */
       
   303     public function setName($name)
       
   304     {
       
   305         $this->name = $name;
       
   306     }
       
   307 
       
   308     /**
       
   309      * Gets the application version.
       
   310      *
       
   311      * @return string The application version
       
   312      *
       
   313      * @api
       
   314      */
       
   315     public function getVersion()
       
   316     {
       
   317         return $this->version;
       
   318     }
       
   319 
       
   320     /**
       
   321      * Sets the application version.
       
   322      *
       
   323      * @param string $version The application version
       
   324      *
       
   325      * @api
       
   326      */
       
   327     public function setVersion($version)
       
   328     {
       
   329         $this->version = $version;
       
   330     }
       
   331 
       
   332     /**
       
   333      * Returns the long version of the application.
       
   334      *
       
   335      * @return string The long application version
       
   336      *
       
   337      * @api
       
   338      */
       
   339     public function getLongVersion()
       
   340     {
       
   341         if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
       
   342             return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
       
   343         }
       
   344 
       
   345         return '<info>Console Tool</info>';
       
   346     }
       
   347 
       
   348     /**
       
   349      * Registers a new command.
       
   350      *
       
   351      * @param string $name The command name
       
   352      *
       
   353      * @return Command The newly created command
       
   354      *
       
   355      * @api
       
   356      */
       
   357     public function register($name)
       
   358     {
       
   359         return $this->add(new Command($name));
       
   360     }
       
   361 
       
   362     /**
       
   363      * Adds an array of command objects.
       
   364      *
       
   365      * @param Command[] $commands An array of commands
       
   366      *
       
   367      * @api
       
   368      */
       
   369     public function addCommands(array $commands)
       
   370     {
       
   371         foreach ($commands as $command) {
       
   372             $this->add($command);
       
   373         }
       
   374     }
       
   375 
       
   376     /**
       
   377      * Adds a command object.
       
   378      *
       
   379      * If a command with the same name already exists, it will be overridden.
       
   380      *
       
   381      * @param Command $command A Command object
       
   382      *
       
   383      * @return Command The registered command
       
   384      *
       
   385      * @api
       
   386      */
       
   387     public function add(Command $command)
       
   388     {
       
   389         $command->setApplication($this);
       
   390 
       
   391         $this->commands[$command->getName()] = $command;
       
   392 
       
   393         foreach ($command->getAliases() as $alias) {
       
   394             $this->commands[$alias] = $command;
       
   395         }
       
   396 
       
   397         return $command;
       
   398     }
       
   399 
       
   400     /**
       
   401      * Returns a registered command by name or alias.
       
   402      *
       
   403      * @param string $name The command name or alias
       
   404      *
       
   405      * @return Command A Command object
       
   406      *
       
   407      * @throws \InvalidArgumentException When command name given does not exist
       
   408      *
       
   409      * @api
       
   410      */
       
   411     public function get($name)
       
   412     {
       
   413         if (!isset($this->commands[$name])) {
       
   414             throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
       
   415         }
       
   416 
       
   417         $command = $this->commands[$name];
       
   418 
       
   419         if ($this->wantHelps) {
       
   420             $this->wantHelps = false;
       
   421 
       
   422             $helpCommand = $this->get('help');
       
   423             $helpCommand->setCommand($command);
       
   424 
       
   425             return $helpCommand;
       
   426         }
       
   427 
       
   428         return $command;
       
   429     }
       
   430 
       
   431     /**
       
   432      * Returns true if the command exists, false otherwise.
       
   433      *
       
   434      * @param string $name The command name or alias
       
   435      *
       
   436      * @return Boolean true if the command exists, false otherwise
       
   437      *
       
   438      * @api
       
   439      */
       
   440     public function has($name)
       
   441     {
       
   442         return isset($this->commands[$name]);
       
   443     }
       
   444 
       
   445     /**
       
   446      * Returns an array of all unique namespaces used by currently registered commands.
       
   447      *
       
   448      * It does not returns the global namespace which always exists.
       
   449      *
       
   450      * @return array An array of namespaces
       
   451      */
       
   452     public function getNamespaces()
       
   453     {
       
   454         $namespaces = array();
       
   455         foreach ($this->commands as $command) {
       
   456             $namespaces[] = $this->extractNamespace($command->getName());
       
   457 
       
   458             foreach ($command->getAliases() as $alias) {
       
   459                 $namespaces[] = $this->extractNamespace($alias);
       
   460             }
       
   461         }
       
   462 
       
   463         return array_values(array_unique(array_filter($namespaces)));
       
   464     }
       
   465 
       
   466     /**
       
   467      * Finds a registered namespace by a name or an abbreviation.
       
   468      *
       
   469      * @param string $namespace A namespace or abbreviation to search for
       
   470      *
       
   471      * @return string A registered namespace
       
   472      *
       
   473      * @throws \InvalidArgumentException When namespace is incorrect or ambiguous
       
   474      */
       
   475     public function findNamespace($namespace)
       
   476     {
       
   477         $allNamespaces = array();
       
   478         foreach ($this->getNamespaces() as $n) {
       
   479             $allNamespaces[$n] = explode(':', $n);
       
   480         }
       
   481 
       
   482         $found = array();
       
   483         foreach (explode(':', $namespace) as $i => $part) {
       
   484             $abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $allNamespaces)))));
       
   485 
       
   486             if (!isset($abbrevs[$part])) {
       
   487                 throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
       
   488             }
       
   489 
       
   490             if (count($abbrevs[$part]) > 1) {
       
   491                 throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace])));
       
   492             }
       
   493 
       
   494             $found[] = $abbrevs[$part][0];
       
   495         }
       
   496 
       
   497         return implode(':', $found);
       
   498     }
       
   499 
       
   500     /**
       
   501      * Finds a command by name or alias.
       
   502      *
       
   503      * Contrary to get, this command tries to find the best
       
   504      * match if you give it an abbreviation of a name or alias.
       
   505      *
       
   506      * @param  string $name A command name or a command alias
       
   507      *
       
   508      * @return Command A Command instance
       
   509      *
       
   510      * @throws \InvalidArgumentException When command name is incorrect or ambiguous
       
   511      *
       
   512      * @api
       
   513      */
       
   514     public function find($name)
       
   515     {
       
   516         // namespace
       
   517         $namespace = '';
       
   518         $searchName = $name;
       
   519         if (false !== $pos = strrpos($name, ':')) {
       
   520             $namespace = $this->findNamespace(substr($name, 0, $pos));
       
   521             $searchName = $namespace.substr($name, $pos);
       
   522         }
       
   523 
       
   524         // name
       
   525         $commands = array();
       
   526         foreach ($this->commands as $command) {
       
   527             if ($this->extractNamespace($command->getName()) == $namespace) {
       
   528                 $commands[] = $command->getName();
       
   529             }
       
   530         }
       
   531 
       
   532         $abbrevs = static::getAbbreviations(array_unique($commands));
       
   533         if (isset($abbrevs[$searchName]) && 1 == count($abbrevs[$searchName])) {
       
   534             return $this->get($abbrevs[$searchName][0]);
       
   535         }
       
   536 
       
   537         if (isset($abbrevs[$searchName]) && count($abbrevs[$searchName]) > 1) {
       
   538             $suggestions = $this->getAbbreviationSuggestions($abbrevs[$searchName]);
       
   539 
       
   540             throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
       
   541         }
       
   542 
       
   543         // aliases
       
   544         $aliases = array();
       
   545         foreach ($this->commands as $command) {
       
   546             foreach ($command->getAliases() as $alias) {
       
   547                 if ($this->extractNamespace($alias) == $namespace) {
       
   548                     $aliases[] = $alias;
       
   549                 }
       
   550             }
       
   551         }
       
   552 
       
   553         $abbrevs = static::getAbbreviations(array_unique($aliases));
       
   554         if (!isset($abbrevs[$searchName])) {
       
   555             throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $name));
       
   556         }
       
   557 
       
   558         if (count($abbrevs[$searchName]) > 1) {
       
   559             throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($abbrevs[$searchName])));
       
   560         }
       
   561 
       
   562         return $this->get($abbrevs[$searchName][0]);
       
   563     }
       
   564 
       
   565     /**
       
   566      * Gets the commands (registered in the given namespace if provided).
       
   567      *
       
   568      * The array keys are the full names and the values the command instances.
       
   569      *
       
   570      * @param  string  $namespace A namespace name
       
   571      *
       
   572      * @return array An array of Command instances
       
   573      *
       
   574      * @api
       
   575      */
       
   576     public function all($namespace = null)
       
   577     {
       
   578         if (null === $namespace) {
       
   579             return $this->commands;
       
   580         }
       
   581 
       
   582         $commands = array();
       
   583         foreach ($this->commands as $name => $command) {
       
   584             if ($namespace === $this->extractNamespace($name)) {
       
   585                 $commands[$name] = $command;
       
   586             }
       
   587         }
       
   588 
       
   589         return $commands;
       
   590     }
       
   591 
       
   592     /**
       
   593      * Returns an array of possible abbreviations given a set of names.
       
   594      *
       
   595      * @param array $names An array of names
       
   596      *
       
   597      * @return array An array of abbreviations
       
   598      */
       
   599     static public function getAbbreviations($names)
       
   600     {
       
   601         $abbrevs = array();
       
   602         foreach ($names as $name) {
       
   603             for ($len = strlen($name) - 1; $len > 0; --$len) {
       
   604                 $abbrev = substr($name, 0, $len);
       
   605                 if (!isset($abbrevs[$abbrev])) {
       
   606                     $abbrevs[$abbrev] = array($name);
       
   607                 } else {
       
   608                     $abbrevs[$abbrev][] = $name;
       
   609                 }
       
   610             }
       
   611         }
       
   612 
       
   613         // Non-abbreviations always get entered, even if they aren't unique
       
   614         foreach ($names as $name) {
       
   615             $abbrevs[$name] = array($name);
       
   616         }
       
   617 
       
   618         return $abbrevs;
       
   619     }
       
   620 
       
   621     /**
       
   622      * Returns a text representation of the Application.
       
   623      *
       
   624      * @param string $namespace An optional namespace name
       
   625      *
       
   626      * @return string A string representing the Application
       
   627      */
       
   628     public function asText($namespace = null)
       
   629     {
       
   630         $commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
       
   631 
       
   632         $messages = array($this->getHelp(), '');
       
   633         if ($namespace) {
       
   634             $messages[] = sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $namespace);
       
   635         } else {
       
   636             $messages[] = '<comment>Available commands:</comment>';
       
   637         }
       
   638 
       
   639         $width = 0;
       
   640         foreach ($commands as $command) {
       
   641             $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
       
   642         }
       
   643         $width += 2;
       
   644 
       
   645         // add commands by namespace
       
   646         foreach ($this->sortCommands($commands) as $space => $commands) {
       
   647             if (!$namespace && '_global' !== $space) {
       
   648                 $messages[] = '<comment>'.$space.'</comment>';
       
   649             }
       
   650 
       
   651             foreach ($commands as $name => $command) {
       
   652                 $messages[] = sprintf("  <info>%-${width}s</info> %s", $name, $command->getDescription());
       
   653             }
       
   654         }
       
   655 
       
   656         return implode("\n", $messages);
       
   657     }
       
   658 
       
   659     /**
       
   660      * Returns an XML representation of the Application.
       
   661      *
       
   662      * @param string  $namespace An optional namespace name
       
   663      * @param Boolean $asDom     Whether to return a DOM or an XML string
       
   664      *
       
   665      * @return string|DOMDocument An XML string representing the Application
       
   666      */
       
   667     public function asXml($namespace = null, $asDom = false)
       
   668     {
       
   669         $commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
       
   670 
       
   671         $dom = new \DOMDocument('1.0', 'UTF-8');
       
   672         $dom->formatOutput = true;
       
   673         $dom->appendChild($xml = $dom->createElement('symfony'));
       
   674 
       
   675         $xml->appendChild($commandsXML = $dom->createElement('commands'));
       
   676 
       
   677         if ($namespace) {
       
   678             $commandsXML->setAttribute('namespace', $namespace);
       
   679         } else {
       
   680             $namespacesXML = $dom->createElement('namespaces');
       
   681             $xml->appendChild($namespacesXML);
       
   682         }
       
   683 
       
   684         // add commands by namespace
       
   685         foreach ($this->sortCommands($commands) as $space => $commands) {
       
   686             if (!$namespace) {
       
   687                 $namespaceArrayXML = $dom->createElement('namespace');
       
   688                 $namespacesXML->appendChild($namespaceArrayXML);
       
   689                 $namespaceArrayXML->setAttribute('id', $space);
       
   690             }
       
   691 
       
   692             foreach ($commands as $name => $command) {
       
   693                 if ($name !== $command->getName()) {
       
   694                     continue;
       
   695                 }
       
   696 
       
   697                 if (!$namespace) {
       
   698                     $commandXML = $dom->createElement('command');
       
   699                     $namespaceArrayXML->appendChild($commandXML);
       
   700                     $commandXML->appendChild($dom->createTextNode($name));
       
   701                 }
       
   702 
       
   703                 $node = $command->asXml(true)->getElementsByTagName('command')->item(0);
       
   704                 $node = $dom->importNode($node, true);
       
   705 
       
   706                 $commandsXML->appendChild($node);
       
   707             }
       
   708         }
       
   709 
       
   710         return $asDom ? $dom : $dom->saveXml();
       
   711     }
       
   712 
       
   713     /**
       
   714      * Renders a catched exception.
       
   715      *
       
   716      * @param Exception       $e      An exception instance
       
   717      * @param OutputInterface $output An OutputInterface instance
       
   718      */
       
   719     public function renderException($e, $output)
       
   720     {
       
   721         $strlen = function ($string)
       
   722         {
       
   723             return function_exists('mb_strlen') ? mb_strlen($string, mb_detect_encoding($string)) : strlen($string);
       
   724         };
       
   725 
       
   726         do {
       
   727             $title = sprintf('  [%s]  ', get_class($e));
       
   728             $len = $strlen($title);
       
   729             $lines = array();
       
   730             foreach (explode("\n", $e->getMessage()) as $line) {
       
   731                 $lines[] = sprintf('  %s  ', $line);
       
   732                 $len = max($strlen($line) + 4, $len);
       
   733             }
       
   734 
       
   735             $messages = array(str_repeat(' ', $len), $title.str_repeat(' ', $len - $strlen($title)));
       
   736 
       
   737             foreach ($lines as $line) {
       
   738                 $messages[] = $line.str_repeat(' ', $len - $strlen($line));
       
   739             }
       
   740 
       
   741             $messages[] = str_repeat(' ', $len);
       
   742 
       
   743             $output->writeln("\n");
       
   744             foreach ($messages as $message) {
       
   745                 $output->writeln('<error>'.$message.'</error>');
       
   746             }
       
   747             $output->writeln("\n");
       
   748 
       
   749             if (OutputInterface::VERBOSITY_VERBOSE === $output->getVerbosity()) {
       
   750                 $output->writeln('<comment>Exception trace:</comment>');
       
   751 
       
   752                 // exception related properties
       
   753                 $trace = $e->getTrace();
       
   754                 array_unshift($trace, array(
       
   755                     'function' => '',
       
   756                     'file'     => $e->getFile() != null ? $e->getFile() : 'n/a',
       
   757                     'line'     => $e->getLine() != null ? $e->getLine() : 'n/a',
       
   758                     'args'     => array(),
       
   759                 ));
       
   760 
       
   761                 for ($i = 0, $count = count($trace); $i < $count; $i++) {
       
   762                     $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
       
   763                     $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
       
   764                     $function = $trace[$i]['function'];
       
   765                     $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
       
   766                     $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
       
   767 
       
   768                     $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line));
       
   769                 }
       
   770 
       
   771                 $output->writeln("\n");
       
   772             }
       
   773         } while ($e = $e->getPrevious());
       
   774 
       
   775         if (null !== $this->runningCommand) {
       
   776             $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
       
   777             $output->writeln("\n");
       
   778         }
       
   779     }
       
   780 
       
   781     /**
       
   782      * Gets the name of the command based on input.
       
   783      *
       
   784      * @param InputInterface $input The input interface
       
   785      *
       
   786      * @return string The command name
       
   787      */
       
   788     protected function getCommandName(InputInterface $input)
       
   789     {
       
   790         return $input->getFirstArgument('command');
       
   791     }
       
   792 
       
   793     /**
       
   794      * Sorts commands in alphabetical order.
       
   795      *
       
   796      * @param array $commands An associative array of commands to sort
       
   797      *
       
   798      * @return array A sorted array of commands
       
   799      */
       
   800     private function sortCommands($commands)
       
   801     {
       
   802         $namespacedCommands = array();
       
   803         foreach ($commands as $name => $command) {
       
   804             $key = $this->extractNamespace($name, 1);
       
   805             if (!$key) {
       
   806                 $key = '_global';
       
   807             }
       
   808 
       
   809             $namespacedCommands[$key][$name] = $command;
       
   810         }
       
   811         ksort($namespacedCommands);
       
   812 
       
   813         foreach ($namespacedCommands as &$commands) {
       
   814             ksort($commands);
       
   815         }
       
   816 
       
   817         return $namespacedCommands;
       
   818     }
       
   819 
       
   820     /**
       
   821      * Returns abbreviated suggestions in string format.
       
   822      *
       
   823      * @param array $abbrevs Abbreviated suggestions to convert
       
   824      *
       
   825      * @return string A formatted string of abbreviated suggestions
       
   826      */
       
   827     private function getAbbreviationSuggestions($abbrevs)
       
   828     {
       
   829         return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
       
   830     }
       
   831 
       
   832     private function extractNamespace($name, $limit = null)
       
   833     {
       
   834         $parts = explode(':', $name);
       
   835         array_pop($parts);
       
   836 
       
   837         return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
       
   838     }
       
   839 }