PageRenderTime 58ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/wwwroot/phpbb/vendor/symfony/console/Symfony/Component/Console/Application.php

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