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