File manager - Edit - /home/autoph/public_html/projects/Rating-AutoHub/public/css/console.tar
Back
Color.php 0000644 00000011774 15025056541 0006350 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * @author Fabien Potencier <fabien@symfony.com> */ final class Color { private const COLORS = [ 'black' => 0, 'red' => 1, 'green' => 2, 'yellow' => 3, 'blue' => 4, 'magenta' => 5, 'cyan' => 6, 'white' => 7, 'default' => 9, ]; private const BRIGHT_COLORS = [ 'gray' => 0, 'bright-red' => 1, 'bright-green' => 2, 'bright-yellow' => 3, 'bright-blue' => 4, 'bright-magenta' => 5, 'bright-cyan' => 6, 'bright-white' => 7, ]; private const AVAILABLE_OPTIONS = [ 'bold' => ['set' => 1, 'unset' => 22], 'underscore' => ['set' => 4, 'unset' => 24], 'blink' => ['set' => 5, 'unset' => 25], 'reverse' => ['set' => 7, 'unset' => 27], 'conceal' => ['set' => 8, 'unset' => 28], ]; private string $foreground; private string $background; private array $options = []; public function __construct(string $foreground = '', string $background = '', array $options = []) { $this->foreground = $this->parseColor($foreground); $this->background = $this->parseColor($background, true); foreach ($options as $option) { if (!isset(self::AVAILABLE_OPTIONS[$option])) { throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS)))); } $this->options[$option] = self::AVAILABLE_OPTIONS[$option]; } } public function apply(string $text): string { return $this->set().$text.$this->unset(); } public function set(): string { $setCodes = []; if ('' !== $this->foreground) { $setCodes[] = $this->foreground; } if ('' !== $this->background) { $setCodes[] = $this->background; } foreach ($this->options as $option) { $setCodes[] = $option['set']; } if (0 === \count($setCodes)) { return ''; } return sprintf("\033[%sm", implode(';', $setCodes)); } public function unset(): string { $unsetCodes = []; if ('' !== $this->foreground) { $unsetCodes[] = 39; } if ('' !== $this->background) { $unsetCodes[] = 49; } foreach ($this->options as $option) { $unsetCodes[] = $option['unset']; } if (0 === \count($unsetCodes)) { return ''; } return sprintf("\033[%sm", implode(';', $unsetCodes)); } private function parseColor(string $color, bool $background = false): string { if ('' === $color) { return ''; } if ('#' === $color[0]) { $color = substr($color, 1); if (3 === \strlen($color)) { $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2]; } if (6 !== \strlen($color)) { throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color)); } return ($background ? '4' : '3').$this->convertHexColorToAnsi(hexdec($color)); } if (isset(self::COLORS[$color])) { return ($background ? '4' : '3').self::COLORS[$color]; } if (isset(self::BRIGHT_COLORS[$color])) { return ($background ? '10' : '9').self::BRIGHT_COLORS[$color]; } throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS))))); } private function convertHexColorToAnsi(int $color): string { $r = ($color >> 16) & 255; $g = ($color >> 8) & 255; $b = $color & 255; // see https://github.com/termstandard/colors/ for more information about true color support if ('truecolor' !== getenv('COLORTERM')) { return (string) $this->degradeHexColorToAnsi($r, $g, $b); } return sprintf('8;2;%d;%d;%d', $r, $g, $b); } private function degradeHexColorToAnsi(int $r, int $g, int $b): int { if (0 === round($this->getSaturation($r, $g, $b) / 50)) { return 0; } return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255); } private function getSaturation(int $r, int $g, int $b): int { $r = $r / 255; $g = $g / 255; $b = $b / 255; $v = max($r, $g, $b); if (0 === $diff = $v - min($r, $g, $b)) { return 0; } return (int) $diff * 100 / $v; } } DependencyInjection/AddConsoleCommandPass.php 0000644 00000012135 15025056541 0015344 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\DependencyInjection; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\LazyCommand; use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; /** * Registers console commands. * * @author Grégoire Pineau <lyrixx@lyrixx.info> */ class AddConsoleCommandPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $commandServices = $container->findTaggedServiceIds('console.command', true); $lazyCommandMap = []; $lazyCommandRefs = []; $serviceIds = []; foreach ($commandServices as $id => $tags) { $definition = $container->getDefinition($id); $definition->addTag('container.no_preload'); $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (isset($tags[0]['command'])) { $aliases = $tags[0]['command']; } else { if (!$r = $container->getReflectionClass($class)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); } $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); } $aliases = explode('|', $aliases ?? ''); $commandName = array_shift($aliases); if ($isHidden = '' === $commandName) { $commandName = array_shift($aliases); } if (null === $commandName) { if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) { $commandId = 'console.command.public_alias.'.$id; $container->setAlias($commandId, $id)->setPublic(true); $id = $commandId; } $serviceIds[] = $id; continue; } $description = $tags[0]['description'] ?? null; unset($tags[0]); $lazyCommandMap[$commandName] = $id; $lazyCommandRefs[$id] = new TypedReference($id, $class); foreach ($aliases as $alias) { $lazyCommandMap[$alias] = $id; } foreach ($tags as $tag) { if (isset($tag['command'])) { $aliases[] = $tag['command']; $lazyCommandMap[$tag['command']] = $id; } $description = $description ?? $tag['description'] ?? null; } $definition->addMethodCall('setName', [$commandName]); if ($aliases) { $definition->addMethodCall('setAliases', [$aliases]); } if ($isHidden) { $definition->addMethodCall('setHidden', [true]); } if (!$description) { if (!$r = $container->getReflectionClass($class)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); } $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); } if ($description) { $definition->addMethodCall('setDescription', [$description]); $container->register('.'.$id.'.lazy', LazyCommand::class) ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy'); } } $container ->register('console.command_loader', ContainerCommandLoader::class) ->setPublic(true) ->addTag('container.no_preload') ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); $container->setParameter('console.command.ids', $serviceIds); } } Logger/ConsoleLogger.php 0000644 00000010231 15025056541 0011236 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Logger; use Psr\Log\AbstractLogger; use Psr\Log\InvalidArgumentException; use Psr\Log\LogLevel; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * PSR-3 compliant console logger. * * @author Kévin Dunglas <dunglas@gmail.com> * * @see https://www.php-fig.org/psr/psr-3/ */ class ConsoleLogger extends AbstractLogger { public const INFO = 'info'; public const ERROR = 'error'; private $output; private array $verbosityLevelMap = [ LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, ]; private array $formatLevelMap = [ LogLevel::EMERGENCY => self::ERROR, LogLevel::ALERT => self::ERROR, LogLevel::CRITICAL => self::ERROR, LogLevel::ERROR => self::ERROR, LogLevel::WARNING => self::INFO, LogLevel::NOTICE => self::INFO, LogLevel::INFO => self::INFO, LogLevel::DEBUG => self::INFO, ]; private bool $errored = false; public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) { $this->output = $output; $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } /** * {@inheritdoc} */ public function log($level, $message, array $context = []): void { if (!isset($this->verbosityLevelMap[$level])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); } $output = $this->output; // Write to the error output if necessary and available if (self::ERROR === $this->formatLevelMap[$level]) { if ($this->output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->errored = true; } // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. // We only do it for efficiency here as the message formatting is relatively expensive. if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { $output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); } } /** * Returns true when any messages have been logged at error levels. */ public function hasErrored(): bool { return $this->errored; } /** * Interpolates context values into the message placeholders. * * @author PHP Framework Interoperability Group */ private function interpolate(string $message, array $context): string { if (!str_contains($message, '{')) { return $message; } $replacements = []; foreach ($context as $key => $val) { if (null === $val || \is_scalar($val) || $val instanceof \Stringable) { $replacements["{{$key}}"] = $val; } elseif ($val instanceof \DateTimeInterface) { $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); } elseif (\is_object($val)) { $replacements["{{$key}}"] = '[object '.\get_class($val).']'; } else { $replacements["{{$key}}"] = '['.\gettype($val).']'; } } return strtr($message, $replacements); } } EventListener/ErrorListener.php 0000644 00000005404 15025056541 0012651 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\EventListener; use Psr\Log\LoggerInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleErrorEvent; use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * @author James Halsall <james.t.halsall@googlemail.com> * @author Robin Chalas <robin.chalas@gmail.com> */ class ErrorListener implements EventSubscriberInterface { private $logger; public function __construct(LoggerInterface $logger = null) { $this->logger = $logger; } public function onConsoleError(ConsoleErrorEvent $event) { if (null === $this->logger) { return; } $error = $event->getError(); if (!$inputString = $this->getInputString($event)) { $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); return; } $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); } public function onConsoleTerminate(ConsoleTerminateEvent $event) { if (null === $this->logger) { return; } $exitCode = $event->getExitCode(); if (0 === $exitCode) { return; } if (!$inputString = $this->getInputString($event)) { $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]); return; } $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]); } public static function getSubscribedEvents(): array { return [ ConsoleEvents::ERROR => ['onConsoleError', -128], ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128], ]; } private static function getInputString(ConsoleEvent $event): ?string { $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; $input = $event->getInput(); if ($input instanceof \Stringable) { if ($commandName) { return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); } return (string) $input; } return $commandName; } } composer.json 0000644 00000003203 15025056541 0007267 0 ustar 00 { "name": "symfony/console", "type": "library", "description": "Eases the creation of beautiful and testable command line interfaces", "keywords": ["console", "cli", "command line", "terminal"], "homepage": "https://symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "require": { "php": ">=8.0.2", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^1.1|^2|^3", "symfony/string": "^5.4|^6.0" }, "require-dev": { "symfony/config": "^5.4|^6.0", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/lock": "^5.4|^6.0", "symfony/process": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0", "psr/log": "^1|^2|^3" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "suggest": { "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "", "psr/log": "For using the console logger" }, "conflict": { "symfony/dependency-injection": "<5.4", "symfony/dotenv": "<5.4", "symfony/event-dispatcher": "<5.4", "symfony/lock": "<5.4", "symfony/process": "<5.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "minimum-stability": "dev" } CHANGELOG.md 0000644 00000021171 15025056541 0006362 0 ustar 00 CHANGELOG ========= 6.0 --- * `Command::setHidden()` has a default value (`true`) for `$hidden` parameter and is final * Remove `Helper::strlen()`, use `Helper::width()` instead * Remove `Helper::strlenWithoutDecoration()`, use `Helper::removeDecoration()` instead * `AddConsoleCommandPass` can not be configured anymore * Remove `HelperSet::setCommand()` and `getCommand()` without replacement 5.4 --- * Add `TesterTrait::assertCommandIsSuccessful()` to test command * Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement 5.3 --- * Add `GithubActionReporter` to render annotations in a Github Action * Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options * Add the `Command::$defaultDescription` static property and the `description` attribute on the `console.command` tag to allow the `list` command to instantiate commands lazily * Add option `--short` to the `list` command * Add support for bright colors * Add `#[AsCommand]` attribute for declaring commands on PHP 8 * Add `Helper::width()` and `Helper::length()` * The `--ansi` and `--no-ansi` options now default to `null`. 5.2.0 ----- * Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester` * added support for multiline responses to questions through `Question::setMultiline()` and `Question::isMultiline()` * Added `SignalRegistry` class to stack signals handlers * Added support for signals: * Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods * Added `SignalableCommandInterface` interface * Added `TableCellStyle` class to customize table cell * Removed `php ` prefix invocation from help messages. 5.1.0 ----- * `Command::setHidden()` is final since Symfony 5.1 * Add `SingleCommandApplication` * Add `Cursor` class 5.0.0 ----- * removed support for finding hidden commands using an abbreviation, use the full name instead * removed `TableStyle::setCrossingChar()` method in favor of `TableStyle::setDefaultCrossingChar()` * removed `TableStyle::setHorizontalBorderChar()` method in favor of `TableStyle::setDefaultCrossingChars()` * removed `TableStyle::getHorizontalBorderChar()` method in favor of `TableStyle::getBorderChars()` * removed `TableStyle::setVerticalBorderChar()` method in favor of `TableStyle::setVerticalBorderChars()` * removed `TableStyle::getVerticalBorderChar()` method in favor of `TableStyle::getBorderChars()` * removed support for returning `null` from `Command::execute()`, return `0` instead * `ProcessHelper::run()` accepts only `array|Symfony\Component\Process\Process` for its `command` argument * `Application::setDispatcher` accepts only `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` for its `dispatcher` argument * renamed `Application::renderException()` and `Application::doRenderException()` to `renderThrowable()` and `doRenderThrowable()` respectively. 4.4.0 ----- * deprecated finding hidden commands using an abbreviation, use the full name instead * added `Question::setTrimmable` default to true to allow the answer to be trimmed * added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar` * `Application` implements `ResetInterface` * marked all dispatched event classes as `@final` * added support for displaying table horizontally * deprecated returning `null` from `Command::execute()`, return `0` instead * Deprecated the `Application::renderException()` and `Application::doRenderException()` methods, use `renderThrowable()` and `doRenderThrowable()` instead. * added support for the `NO_COLOR` env var (https://no-color.org/) 4.3.0 ----- * added support for hyperlinks * added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating * added `Question::setAutocompleterCallback()` to provide a callback function that dynamically generates suggestions as the user types 4.2.0 ----- * allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to `ProcessHelper::run()` to pass environment variables * deprecated passing a command as a string to `ProcessHelper::run()`, pass it the command as an array of its arguments instead * made the `ProcessHelper` class final * added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`) * added `capture_stderr_separately` option to `CommandTester::execute()` 4.1.0 ----- * added option to run suggested command if command is not found and only 1 alternative is available * added option to modify console output and print multiple modifiable sections * added support for iterable messages in output `write` and `writeln` methods 4.0.0 ----- * `OutputFormatter` throws an exception when unknown options are used * removed `QuestionHelper::setInputStream()/getInputStream()` * removed `Application::getTerminalWidth()/getTerminalHeight()` and `Application::setTerminalDimensions()/getTerminalDimensions()` * removed `ConsoleExceptionEvent` * removed `ConsoleEvents::EXCEPTION` 3.4.0 ----- * added `SHELL_VERBOSITY` env var to control verbosity * added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11 `ContainerCommandLoader` for commands lazy-loading * added a case-insensitive command name matching fallback * added static `Command::$defaultName/getDefaultName()`, allowing for commands to be registered at compile time in the application command loader. Setting the `$defaultName` property avoids the need for filling the `command` attribute on the `console.command` tag when using `AddConsoleCommandPass`. 3.3.0 ----- * added `ExceptionListener` * added `AddConsoleCommandPass` (originally in FrameworkBundle) * [BC BREAK] `Input::getOption()` no longer returns the default value for options with value optional explicitly passed empty * added console.error event to catch exceptions thrown by other listeners * deprecated console.exception event in favor of console.error * added ability to handle `CommandNotFoundException` through the `console.error` event * deprecated default validation in `SymfonyQuestionHelper::ask` 3.2.0 ------ * added `setInputs()` method to CommandTester for ease testing of commands expecting inputs * added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface) * added StreamableInputInterface * added LockableTrait 3.1.0 ----- * added truncate method to FormatterHelper * added setColumnWidth(s) method to Table 2.8.3 ----- * remove readline support from the question helper as it caused issues 2.8.0 ----- * use readline for user input in the question helper when available to allow the use of arrow keys 2.6.0 ----- * added a Process helper * added a DebugFormatter helper 2.5.0 ----- * deprecated the dialog helper (use the question helper instead) * deprecated TableHelper in favor of Table * deprecated ProgressHelper in favor of ProgressBar * added ConsoleLogger * added a question helper * added a way to set the process name of a command * added a way to set a default command instead of `ListCommand` 2.4.0 ----- * added a way to force terminal dimensions * added a convenient method to detect verbosity level * [BC BREAK] made descriptors use output instead of returning a string 2.3.0 ----- * added multiselect support to the select dialog helper * added Table Helper for tabular data rendering * added support for events in `Application` * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` * added a way to set the progress bar progress via the `setCurrent` method * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG 2.2.0 ----- * added support for colorization on Windows via ConEmu * add a method to Dialog Helper to ask for a question and hide the response * added support for interactive selections in console (DialogHelper::select()) * added support for autocompletion as you type in Dialog Helper 2.1.0 ----- * added ConsoleOutputInterface * added the possibility to disable a command (Command::isEnabled()) * added suggestions when a command does not exist * added a --raw option to the list command * added support for STDERR in the console output class (errors are now sent to STDERR) * made the defaults (helper set, commands, input definition) in Application more easily customizable * added support for the shell even if readline is not available * added support for process isolation in Symfony shell via `--process-isolation` switch * added support for `--`, which disables options parsing after that point (tokens will be parsed as arguments) Output/ConsoleSectionOutput.php 0000644 00000010114 15025056541 0012725 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Terminal; /** * @author Pierre du Plessis <pdples@gmail.com> * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com> */ class ConsoleSectionOutput extends StreamOutput { private array $content = []; private int $lines = 0; private array $sections; private $terminal; /** * @param resource $stream * @param ConsoleSectionOutput[] $sections */ public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter) { parent::__construct($stream, $verbosity, $decorated, $formatter); array_unshift($sections, $this); $this->sections = &$sections; $this->terminal = new Terminal(); } /** * Clears previous output for this section. * * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared */ public function clear(int $lines = null) { if (empty($this->content) || !$this->isDecorated()) { return; } if ($lines) { array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content } else { $lines = $this->lines; $this->content = []; } $this->lines -= $lines; parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); } /** * Overwrites the previous output with a new message. */ public function overwrite(string|iterable $message) { $this->clear(); $this->writeln($message); } public function getContent(): string { return implode('', $this->content); } /** * @internal */ public function addContent(string $input) { foreach (explode(\PHP_EOL, $input) as $lineContent) { $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; $this->content[] = $lineContent; $this->content[] = \PHP_EOL; } } /** * {@inheritdoc} */ protected function doWrite(string $message, bool $newline) { if (!$this->isDecorated()) { parent::doWrite($message, $newline); return; } $erasedContent = $this->popStreamContentUntilCurrentSection(); $this->addContent($message); parent::doWrite($message, true); parent::doWrite($erasedContent, false); } /** * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits * current section. Then it erases content it crawled through. Optionally, it erases part of current section too. */ private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string { $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; $erasedContent = []; foreach ($this->sections as $section) { if ($section === $this) { break; } $numberOfLinesToClear += $section->lines; $erasedContent[] = $section->getContent(); } if ($numberOfLinesToClear > 0) { // move cursor up n lines parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); // erase to end of screen parent::doWrite("\x1b[0J", false); } return implode('', array_reverse($erasedContent)); } private function getDisplayLength(string $text): int { return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", ' ', $text))); } } Output/StreamOutput.php 0000644 00000006710 15025056541 0011240 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * StreamOutput writes the output to a given stream. * * Usage: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * As `StreamOutput` can use any stream, you can also use a file: * * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); * * @author Fabien Potencier <fabien@symfony.com> */ class StreamOutput extends Output { private $stream; /** * @param resource $stream A stream resource * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @throws InvalidArgumentException When first argument is not a real stream */ public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) { if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); } $this->stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } /** * Gets the stream attached to this StreamOutput instance. * * @return resource */ public function getStream() { return $this->stream; } /** * {@inheritdoc} */ protected function doWrite(string $message, bool $newline) { if ($newline) { $message .= \PHP_EOL; } @fwrite($this->stream, $message); fflush($this->stream); } /** * Returns true if the stream supports colorization. * * Colorization is disabled if not supported by the stream: * * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo * terminals via named pipes, so we can only check the environment. * * Reference: Composer\XdebugHandler\Process::supportsColor * https://github.com/composer/xdebug-handler * * @return bool true if the stream supports colorization, false otherwise */ protected function hasColorSupport(): bool { // Follow https://no-color.org/ if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { return false; } if ('Hyper' === getenv('TERM_PROGRAM')) { return true; } if (\DIRECTORY_SEPARATOR === '\\') { return (\function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support($this->stream)) || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); } return stream_isatty($this->stream); } } Output/OutputInterface.php 0000644 00000004764 15025056541 0011714 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * OutputInterface is the interface implemented by all Output classes. * * @author Fabien Potencier <fabien@symfony.com> */ interface OutputInterface { public const VERBOSITY_QUIET = 16; public const VERBOSITY_NORMAL = 32; public const VERBOSITY_VERBOSE = 64; public const VERBOSITY_VERY_VERBOSE = 128; public const VERBOSITY_DEBUG = 256; public const OUTPUT_NORMAL = 1; public const OUTPUT_RAW = 2; public const OUTPUT_PLAIN = 4; /** * Writes a message to the output. * * @param $newline Whether to add a newline * @param $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL */ public function write(string|iterable $messages, bool $newline = false, int $options = 0); /** * Writes a message to the output and adds a newline at the end. * * @param $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL */ public function writeln(string|iterable $messages, int $options = 0); /** * Sets the verbosity of the output. */ public function setVerbosity(int $level); /** * Gets the current verbosity of the output. */ public function getVerbosity(): int; /** * Returns whether verbosity is quiet (-q). */ public function isQuiet(): bool; /** * Returns whether verbosity is verbose (-v). */ public function isVerbose(): bool; /** * Returns whether verbosity is very verbose (-vv). */ public function isVeryVerbose(): bool; /** * Returns whether verbosity is debug (-vvv). */ public function isDebug(): bool; /** * Sets the decorated flag. */ public function setDecorated(bool $decorated); /** * Gets the decorated flag. */ public function isDecorated(): bool; public function setFormatter(OutputFormatterInterface $formatter); /** * Returns current output formatter instance. */ public function getFormatter(): OutputFormatterInterface; } Output/TrimmedBufferOutput.php 0000644 00000003061 15025056541 0012534 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * A BufferedOutput that keeps only the last N chars. * * @author Jérémy Derussé <jeremy@derusse.com> */ class TrimmedBufferOutput extends Output { private int $maxLength; private string $buffer = ''; public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) { if ($maxLength <= 0) { throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); } parent::__construct($verbosity, $decorated, $formatter); $this->maxLength = $maxLength; } /** * Empties buffer and returns its content. */ public function fetch(): string { $content = $this->buffer; $this->buffer = ''; return $content; } /** * {@inheritdoc} */ protected function doWrite(string $message, bool $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= \PHP_EOL; } $this->buffer = substr($this->buffer, 0 - $this->maxLength); } } Output/BufferedOutput.php 0000644 00000001507 15025056541 0011526 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; /** * @author Jean-François Simon <contact@jfsimon.fr> */ class BufferedOutput extends Output { private string $buffer = ''; /** * Empties buffer and returns its content. */ public function fetch(): string { $content = $this->buffer; $this->buffer = ''; return $content; } /** * {@inheritdoc} */ protected function doWrite(string $message, bool $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= \PHP_EOL; } } } Output/Output.php 0000644 00000010636 15025056541 0010066 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * Base class for output classes. * * There are five levels of verbosity: * * * normal: no option passed (normal output) * * verbose: -v (more output) * * very verbose: -vv (highly extended output) * * debug: -vvv (all debug output) * * quiet: -q (no output) * * @author Fabien Potencier <fabien@symfony.com> */ abstract class Output implements OutputInterface { private int $verbosity; private $formatter; /** * @param int|null $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool $decorated Whether to decorate messages * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) */ public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) { $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL; $this->formatter = $formatter ?? new OutputFormatter(); $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } /** * {@inheritdoc} */ public function getFormatter(): OutputFormatterInterface { return $this->formatter; } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated(): bool { return $this->formatter->isDecorated(); } /** * {@inheritdoc} */ public function setVerbosity(int $level) { $this->verbosity = $level; } /** * {@inheritdoc} */ public function getVerbosity(): int { return $this->verbosity; } /** * {@inheritdoc} */ public function isQuiet(): bool { return self::VERBOSITY_QUIET === $this->verbosity; } /** * {@inheritdoc} */ public function isVerbose(): bool { return self::VERBOSITY_VERBOSE <= $this->verbosity; } /** * {@inheritdoc} */ public function isVeryVerbose(): bool { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } /** * {@inheritdoc} */ public function isDebug(): bool { return self::VERBOSITY_DEBUG <= $this->verbosity; } /** * {@inheritdoc} */ public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) { $this->write($messages, true, $options); } /** * {@inheritdoc} */ public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; } $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; $type = $types & $options ?: self::OUTPUT_NORMAL; $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; if ($verbosity > $this->getVerbosity()) { return; } foreach ($messages as $message) { switch ($type) { case OutputInterface::OUTPUT_NORMAL: $message = $this->formatter->format($message); break; case OutputInterface::OUTPUT_RAW: break; case OutputInterface::OUTPUT_PLAIN: $message = strip_tags($this->formatter->format($message)); break; } $this->doWrite($message ?? '', $newline); } } /** * Writes a message to the output. */ abstract protected function doWrite(string $message, bool $newline); } Output/NullOutput.php 0000644 00000004427 15025056541 0010722 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\NullOutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * NullOutput suppresses all output. * * $output = new NullOutput(); * * @author Fabien Potencier <fabien@symfony.com> * @author Tobias Schultze <http://tobion.de> */ class NullOutput implements OutputInterface { private $formatter; /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { // do nothing } /** * {@inheritdoc} */ public function getFormatter(): OutputFormatterInterface { // to comply with the interface we must return a OutputFormatterInterface return $this->formatter ??= new NullOutputFormatter(); } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { // do nothing } /** * {@inheritdoc} */ public function isDecorated(): bool { return false; } /** * {@inheritdoc} */ public function setVerbosity(int $level) { // do nothing } /** * {@inheritdoc} */ public function getVerbosity(): int { return self::VERBOSITY_QUIET; } /** * {@inheritdoc} */ public function isQuiet(): bool { return true; } /** * {@inheritdoc} */ public function isVerbose(): bool { return false; } /** * {@inheritdoc} */ public function isVeryVerbose(): bool { return false; } /** * {@inheritdoc} */ public function isDebug(): bool { return false; } /** * {@inheritdoc} */ public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) { // do nothing } /** * {@inheritdoc} */ public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) { // do nothing } } Output/ConsoleOutputInterface.php 0000644 00000001413 15025056541 0013223 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; /** * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. * This adds information about stderr and section output stream. * * @author Dariusz Górecki <darek.krk@gmail.com> */ interface ConsoleOutputInterface extends OutputInterface { /** * Gets the OutputInterface for errors. */ public function getErrorOutput(): OutputInterface; public function setErrorOutput(OutputInterface $error); public function section(): ConsoleSectionOutput; } Output/ConsoleOutput.php 0000644 00000011443 15025056541 0011406 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR. * * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR. * * $output = new ConsoleOutput(); * * This is equivalent to: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * $stdErr = new StreamOutput(fopen('php://stderr', 'w')); * * @author Fabien Potencier <fabien@symfony.com> */ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface { private $stderr; private array $consoleSectionOutputs = []; /** * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) */ public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) { parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); if (null === $formatter) { // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); return; } $actualDecorated = $this->isDecorated(); $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); if (null === $decorated) { $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); } } /** * Creates a new output section. */ public function section(): ConsoleSectionOutput { return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } /** * {@inheritdoc} */ public function setVerbosity(int $level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } /** * {@inheritdoc} */ public function getErrorOutput(): OutputInterface { return $this->stderr; } /** * {@inheritdoc} */ public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } /** * Returns true if current environment supports writing console output to * STDOUT. */ protected function hasStdoutSupport(): bool { return false === $this->isRunningOS400(); } /** * Returns true if current environment supports writing console output to * STDERR. */ protected function hasStderrSupport(): bool { return false === $this->isRunningOS400(); } /** * Checks if current executing environment is IBM iSeries (OS400), which * doesn't properly convert character-encodings between ASCII to EBCDIC. */ private function isRunningOS400(): bool { $checks = [ \function_exists('php_uname') ? php_uname('s') : '', getenv('OSTYPE'), \PHP_OS, ]; return false !== stripos(implode(';', $checks), 'OS400'); } /** * @return resource */ private function openOutputStream() { if (!$this->hasStdoutSupport()) { return fopen('php://output', 'w'); } // Use STDOUT when possible to prevent from opening too many file descriptors return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w')); } /** * @return resource */ private function openErrorStream() { if (!$this->hasStderrSupport()) { return fopen('php://output', 'w'); } // Use STDERR when possible to prevent from opening too many file descriptors return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w')); } } Exception/MissingInputException.php 0000644 00000000756 15025056541 0013536 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * Represents failure to read input from stdin. * * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com> */ class MissingInputException extends RuntimeException implements ExceptionInterface { } Exception/LogicException.php 0000644 00000000652 15025056541 0012135 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class LogicException extends \LogicException implements ExceptionInterface { } Exception/NamespaceNotFoundException.php 0000644 00000000732 15025056541 0014450 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * Represents an incorrect namespace typed in the console. * * @author Pierre du Plessis <pdples@gmail.com> */ class NamespaceNotFoundException extends CommandNotFoundException { } Exception/CommandNotFoundException.php 0000644 00000002314 15025056541 0014130 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * Represents an incorrect command name typed in the console. * * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface { private array $alternatives; /** * @param string $message Exception message to throw * @param string[] $alternatives List of similar defined names * @param int $code Exception code * @param \Throwable|null $previous Previous exception used for the exception chaining */ public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->alternatives = $alternatives; } /** * @return string[] */ public function getAlternatives(): array { return $this->alternatives; } } Exception/InvalidArgumentException.php 0000644 00000000676 15025056541 0014177 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } Exception/ExceptionInterface.php 0000644 00000000651 15025056541 0012777 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * ExceptionInterface. * * @author Jérôme Tamarelle <jerome@tamarelle.net> */ interface ExceptionInterface extends \Throwable { } Exception/InvalidOptionException.php 0000644 00000001005 15025056541 0013650 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * Represents an incorrect option name or value typed in the console. * * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface { } Exception/RuntimeException.php 0000644 00000000656 15025056541 0012527 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Exception; /** * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } Descriptor/XmlDescriptor.php 0000644 00000023276 15025056541 0012207 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * XML descriptor. * * @author Jean-François Simon <contact@jfsimon.fr> * * @internal */ class XmlDescriptor extends Descriptor { public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($definitionXML = $dom->createElement('definition')); $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); foreach ($definition->getArguments() as $argument) { $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); } $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($definition->getOptions() as $option) { $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); } return $dom; } public function getCommandDocument(Command $command, bool $short = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $commandXML->setAttribute('id', $command->getName()); $commandXML->setAttribute('name', $command->getName()); $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); $commandXML->appendChild($usagesXML = $dom->createElement('usages')); $commandXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); if ($short) { foreach ($command->getAliases() as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } } else { $command->mergeApplicationDefinition(false); foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } $commandXML->appendChild($helpXML = $dom->createElement('help')); $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); $definitionXML = $this->getInputDefinitionDocument($command->getDefinition()); $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); } return $dom; } public function getApplicationDocument(Application $application, string $namespace = null, bool $short = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($rootXml = $dom->createElement('symfony')); if ('UNKNOWN' !== $application->getName()) { $rootXml->setAttribute('name', $application->getName()); if ('UNKNOWN' !== $application->getVersion()) { $rootXml->setAttribute('version', $application->getVersion()); } } $rootXml->appendChild($commandsXML = $dom->createElement('commands')); $description = new ApplicationDescription($application, $namespace, true); if ($namespace) { $commandsXML->setAttribute('namespace', $namespace); } foreach ($description->getCommands() as $command) { $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short)); } if (!$namespace) { $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); foreach ($description->getNamespaces() as $namespaceDescription) { $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); foreach ($namespaceDescription['commands'] as $name) { $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); $commandXML->appendChild($dom->createTextNode($name)); } } } return $dom; } /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->writeDocument($this->getInputArgumentDocument($argument)); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = []) { $this->writeDocument($this->getInputOptionDocument($option)); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = []) { $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false)); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = []) { $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false)); } /** * Appends document children to parent node. */ private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); } } /** * Writes DOM document. */ private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; $this->write($dom->saveXML()); } private function getInputArgumentDocument(InputArgument $argument): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('argument')); $objectXML->setAttribute('name', $argument->getName()); $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : [])); foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } return $dom; } private function getInputOptionDocument(InputOption $option): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--'.$option->getName()); $pos = strpos($option->getShortcut() ?? '', '|'); if (false !== $pos) { $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); } else { $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); } $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); if ($option->acceptValue()) { $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : [])); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); if (!empty($defaults)) { foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } } } if ($option->isNegatable()) { $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--no-'.$option->getName()); $objectXML->setAttribute('shortcut', ''); $objectXML->setAttribute('accept_value', 0); $objectXML->setAttribute('is_value_required', 0); $objectXML->setAttribute('is_multiple', 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option')); } return $dom; } } Descriptor/TextDescriptor.php 0000644 00000030456 15025056541 0012371 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * Text descriptor. * * @author Jean-François Simon <contact@jfsimon.fr> * * @internal */ class TextDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = []) { if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault())); } else { $default = ''; } $totalWidth = $options['total_width'] ?? Helper::width($argument->getName()); $spacingWidth = $totalWidth - \strlen($argument->getName()); $this->writeText(sprintf(' <info>%s</info> %s%s%s', $argument->getName(), str_repeat(' ', $spacingWidth), // + 4 = 2 spaces before <info>, 2 spaces after </info> preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), $default ), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = []) { if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault())); } else { $default = ''; } $value = ''; if ($option->acceptValue()) { $value = '='.strtoupper($option->getName()); if ($option->isValueOptional()) { $value = '['.$value.']'; } } $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]); $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value) ); $spacingWidth = $totalWidth - Helper::width($synopsis); $this->writeText(sprintf(' <info>%s</info> %s%s%s%s', $synopsis, str_repeat(' ', $spacingWidth), // + 4 = 2 spaces before <info>, 2 spaces after </info> preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), $default, $option->isArray() ? '<comment> (multiple values allowed)</comment>' : '' ), $options); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); foreach ($definition->getArguments() as $argument) { $totalWidth = max($totalWidth, Helper::width($argument->getName())); } if ($definition->getArguments()) { $this->writeText('<comment>Arguments:</comment>', $options); $this->writeText("\n"); foreach ($definition->getArguments() as $argument) { $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); $this->writeText("\n"); } } if ($definition->getArguments() && $definition->getOptions()) { $this->writeText("\n"); } if ($definition->getOptions()) { $laterOptions = []; $this->writeText('<comment>Options:</comment>', $options); foreach ($definition->getOptions() as $option) { if (\strlen($option->getShortcut() ?? '') > 1) { $laterOptions[] = $option; continue; } $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); } foreach ($laterOptions as $option) { $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = []) { $command->mergeApplicationDefinition(false); if ($description = $command->getDescription()) { $this->writeText('<comment>Description:</comment>', $options); $this->writeText("\n"); $this->writeText(' '.$description); $this->writeText("\n\n"); } $this->writeText('<comment>Usage:</comment>', $options); foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { $this->writeText("\n"); $this->writeText(' '.OutputFormatter::escape($usage), $options); } $this->writeText("\n"); $definition = $command->getDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->writeText("\n"); $this->describeInputDefinition($definition, $options); $this->writeText("\n"); } $help = $command->getProcessedHelp(); if ($help && $help !== $description) { $this->writeText("\n"); $this->writeText('<comment>Help:</comment>', $options); $this->writeText("\n"); $this->writeText(' '.str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace); if (isset($options['raw_text']) && $options['raw_text']) { $width = $this->getColumnWidth($description->getCommands()); foreach ($description->getCommands() as $command) { $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); $this->writeText("\n"); } } else { if ('' != $help = $application->getHelp()) { $this->writeText("$help\n\n", $options); } $this->writeText("<comment>Usage:</comment>\n", $options); $this->writeText(" command [options] [arguments]\n\n", $options); $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); $this->writeText("\n"); $this->writeText("\n"); $commands = $description->getCommands(); $namespaces = $description->getNamespaces(); if ($describedNamespace && $namespaces) { // make sure all alias commands are included when describing a specific namespace $describedNamespaceInfo = reset($namespaces); foreach ($describedNamespaceInfo['commands'] as $name) { $commands[$name] = $description->getCommand($name); } } // calculate max. width based on available commands per namespace $width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) { return array_intersect($namespace['commands'], array_keys($commands)); }, array_values($namespaces))))); if ($describedNamespace) { $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options); } else { $this->writeText('<comment>Available commands:</comment>', $options); } foreach ($namespaces as $namespace) { $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { return isset($commands[$name]); }); if (!$namespace['commands']) { continue; } if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->writeText("\n"); $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options); } foreach ($namespace['commands'] as $name) { $this->writeText("\n"); $spacingWidth = $width - Helper::width($name); $command = $commands[$name]; $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; $this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); } } $this->writeText("\n"); } } /** * {@inheritdoc} */ private function writeText(string $content, array $options = []) { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true ); } /** * Formats command aliases to show them in the command description. */ private function getCommandAliasesText(Command $command): string { $text = ''; $aliases = $command->getAliases(); if ($aliases) { $text = '['.implode('|', $aliases).'] '; } return $text; } /** * Formats input option/argument default value. */ private function formatDefaultValue(mixed $default): string { if (\INF === $default) { return 'INF'; } if (\is_string($default)) { $default = OutputFormatter::escape($default); } elseif (\is_array($default)) { foreach ($default as $key => $value) { if (\is_string($value)) { $default[$key] = OutputFormatter::escape($value); } } } return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); } /** * @param array<Command|string> $commands */ private function getColumnWidth(array $commands): int { $widths = []; foreach ($commands as $command) { if ($command instanceof Command) { $widths[] = Helper::width($command->getName()); foreach ($command->getAliases() as $alias) { $widths[] = Helper::width($alias); } } else { $widths[] = Helper::width($command); } } return $widths ? max($widths) + 2 : 0; } /** * @param InputOption[] $options */ private function calculateTotalWidthForOptions(array $options): int { $totalWidth = 0; foreach ($options as $option) { // "-" + shortcut + ", --" + name $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName()); if ($option->isNegatable()) { $nameLength += 6 + Helper::width($option->getName()); // |--no- + name } elseif ($option->acceptValue()) { $valueLength = 1 + Helper::width($option->getName()); // = + value $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] $nameLength += $valueLength; } $totalWidth = max($totalWidth, $nameLength); } return $totalWidth; } } Descriptor/DescriptorInterface.php 0000644 00000001055 15025056541 0013336 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Output\OutputInterface; /** * Descriptor interface. * * @author Jean-François Simon <contact@jfsimon.fr> */ interface DescriptorInterface { public function describe(OutputInterface $output, object $object, array $options = []); } Descriptor/ApplicationDescription.php 0000644 00000007346 15025056541 0014057 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\CommandNotFoundException; /** * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class ApplicationDescription { public const GLOBAL_NAMESPACE = '_global'; private $application; private ?string $namespace; private bool $showHidden; private array $namespaces; /** * @var array<string, Command> */ private array $commands; /** * @var array<string, Command> */ private array $aliases = []; public function __construct(Application $application, string $namespace = null, bool $showHidden = false) { $this->application = $application; $this->namespace = $namespace; $this->showHidden = $showHidden; } public function getNamespaces(): array { if (!isset($this->namespaces)) { $this->inspectApplication(); } return $this->namespaces; } /** * @return Command[] */ public function getCommands(): array { if (!isset($this->commands)) { $this->inspectApplication(); } return $this->commands; } /** * @throws CommandNotFoundException */ public function getCommand(string $name): Command { if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } return $this->commands[$name] ?? $this->aliases[$name]; } private function inspectApplication() { $this->commands = []; $this->namespaces = []; $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); foreach ($this->sortCommands($all) as $namespace => $commands) { $names = []; /** @var Command $command */ foreach ($commands as $name => $command) { if (!$command->getName() || (!$this->showHidden && $command->isHidden())) { continue; } if ($command->getName() === $name) { $this->commands[$name] = $command; } else { $this->aliases[$name] = $command; } $names[] = $name; } $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; } } private function sortCommands(array $commands): array { $namespacedCommands = []; $globalCommands = []; $sortedCommands = []; foreach ($commands as $name => $command) { $key = $this->application->extractNamespace($name, 1); if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) { $globalCommands[$name] = $command; } else { $namespacedCommands[$key][$name] = $command; } } if ($globalCommands) { ksort($globalCommands); $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands; } if ($namespacedCommands) { ksort($namespacedCommands, \SORT_STRING); foreach ($namespacedCommands as $key => $commandsSet) { ksort($commandsSet); $sortedCommands[$key] = $commandsSet; } } return $sortedCommands; } } Descriptor/MarkdownDescriptor.php 0000644 00000015652 15025056541 0013230 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Markdown descriptor. * * @author Jean-François Simon <contact@jfsimon.fr> * * @internal */ class MarkdownDescriptor extends Descriptor { /** * {@inheritdoc} */ public function describe(OutputInterface $output, object $object, array $options = []) { $decorated = $output->isDecorated(); $output->setDecorated(false); parent::describe($output, $object, $options); $output->setDecorated($decorated); } /** * {@inheritdoc} */ protected function write(string $content, bool $decorated = true) { parent::write($content, $decorated); } /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->write( '#### `'.($argument->getName() ?: '<none>')."`\n\n" .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' ); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = []) { $name = '--'.$option->getName(); if ($option->isNegatable()) { $name .= '|--no-'.$option->getName(); } if ($option->getShortcut()) { $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; } $this->write( '#### `'.$name.'`'."\n\n" .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n" .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' ); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { if ($showArguments = \count($definition->getArguments()) > 0) { $this->write('### Arguments'); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); if (null !== $describeInputArgument = $this->describeInputArgument($argument)) { $this->write($describeInputArgument); } } } if (\count($definition->getOptions()) > 0) { if ($showArguments) { $this->write("\n\n"); } $this->write('### Options'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); if (null !== $describeInputOption = $this->describeInputOption($option)) { $this->write($describeInputOption); } } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = []) { if ($options['short'] ?? false) { $this->write( '`'.$command->getName()."`\n" .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" .array_reduce($command->getAliases(), function ($carry, $usage) { return $carry.'* `'.$usage.'`'."\n"; }) ); return; } $command->mergeApplicationDefinition(false); $this->write( '`'.$command->getName()."`\n" .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { return $carry.'* `'.$usage.'`'."\n"; }) ); if ($help = $command->getProcessedHelp()) { $this->write("\n"); $this->write($help); } $definition = $command->getDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->write("\n\n"); $this->describeInputDefinition($definition); } } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace); $title = $this->getApplicationTitle($application); $this->write($title."\n".str_repeat('=', Helper::width($title))); foreach ($description->getNamespaces() as $namespace) { if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->write("\n\n"); $this->write('**'.$namespace['id'].':**'); } $this->write("\n\n"); $this->write(implode("\n", array_map(function ($commandName) use ($description) { return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); }, $namespace['commands']))); } foreach ($description->getCommands() as $command) { $this->write("\n\n"); if (null !== $describeCommand = $this->describeCommand($command, $options)) { $this->write($describeCommand); } } } private function getApplicationTitle(Application $application): string { if ('UNKNOWN' !== $application->getName()) { if ('UNKNOWN' !== $application->getVersion()) { return sprintf('%s %s', $application->getName(), $application->getVersion()); } return $application->getName(); } return 'Console Tool'; } } Descriptor/Descriptor.php 0000644 00000005607 15025056541 0011524 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ abstract class Descriptor implements DescriptorInterface { /** * @var OutputInterface */ protected $output; /** * {@inheritdoc} */ public function describe(OutputInterface $output, object $object, array $options = []) { $this->output = $output; switch (true) { case $object instanceof InputArgument: $this->describeInputArgument($object, $options); break; case $object instanceof InputOption: $this->describeInputOption($object, $options); break; case $object instanceof InputDefinition: $this->describeInputDefinition($object, $options); break; case $object instanceof Command: $this->describeCommand($object, $options); break; case $object instanceof Application: $this->describeApplication($object, $options); break; default: throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))); } } /** * Writes content to output. */ protected function write(string $content, bool $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } /** * Describes an InputArgument instance. */ abstract protected function describeInputArgument(InputArgument $argument, array $options = []); /** * Describes an InputOption instance. */ abstract protected function describeInputOption(InputOption $option, array $options = []); /** * Describes an InputDefinition instance. */ abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []); /** * Describes a Command instance. */ abstract protected function describeCommand(Command $command, array $options = []); /** * Describes an Application instance. */ abstract protected function describeApplication(Application $application, array $options = []); } Descriptor/JsonDescriptor.php 0000644 00000013315 15025056541 0012351 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * JSON descriptor. * * @author Jean-François Simon <contact@jfsimon.fr> * * @internal */ class JsonDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->writeData($this->getInputArgumentData($argument), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = []) { $this->writeData($this->getInputOptionData($option), $options); if ($option->isNegatable()) { $this->writeData($this->getInputOptionData($option, true), $options); } } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeData($this->getInputDefinitionData($definition), $options); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = []) { $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace, true); $commands = []; foreach ($description->getCommands() as $command) { $commands[] = $this->getCommandData($command, $options['short'] ?? false); } $data = []; if ('UNKNOWN' !== $application->getName()) { $data['application']['name'] = $application->getName(); if ('UNKNOWN' !== $application->getVersion()) { $data['application']['version'] = $application->getVersion(); } } $data['commands'] = $commands; if ($describedNamespace) { $data['namespace'] = $describedNamespace; } else { $data['namespaces'] = array_values($description->getNamespaces()); } $this->writeData($data, $options); } /** * Writes data as json. */ private function writeData(array $data, array $options) { $flags = $options['json_encoding'] ?? 0; $this->write(json_encode($data, $flags)); } private function getInputArgumentData(InputArgument $argument): array { return [ 'name' => $argument->getName(), 'is_required' => $argument->isRequired(), 'is_array' => $argument->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), 'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), ]; } private function getInputOptionData(InputOption $option, bool $negated = false): array { return $negated ? [ 'name' => '--no-'.$option->getName(), 'shortcut' => '', 'accept_value' => false, 'is_value_required' => false, 'is_multiple' => false, 'description' => 'Negate the "--'.$option->getName().'" option', 'default' => false, ] : [ 'name' => '--'.$option->getName(), 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), ]; } private function getInputDefinitionData(InputDefinition $definition): array { $inputArguments = []; foreach ($definition->getArguments() as $name => $argument) { $inputArguments[$name] = $this->getInputArgumentData($argument); } $inputOptions = []; foreach ($definition->getOptions() as $name => $option) { $inputOptions[$name] = $this->getInputOptionData($option); if ($option->isNegatable()) { $inputOptions['no-'.$name] = $this->getInputOptionData($option, true); } } return ['arguments' => $inputArguments, 'options' => $inputOptions]; } private function getCommandData(Command $command, bool $short = false): array { $data = [ 'name' => $command->getName(), 'description' => $command->getDescription(), ]; if ($short) { $data += [ 'usage' => $command->getAliases(), ]; } else { $command->mergeApplicationDefinition(false); $data += [ 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), 'help' => $command->getProcessedHelp(), 'definition' => $this->getInputDefinitionData($command->getDefinition()), ]; } $data['hidden'] = $command->isHidden(); return $data; } } Input/Input.php 0000644 00000012466 15025056541 0007467 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; /** * Input is the base class for all concrete Input classes. * * Three concrete classes are provided by default: * * * `ArgvInput`: The input comes from the CLI arguments (argv) * * `StringInput`: The input is provided as a string * * `ArrayInput`: The input is provided as an array * * @author Fabien Potencier <fabien@symfony.com> */ abstract class Input implements InputInterface, StreamableInputInterface { protected $definition; protected $stream; protected $options = []; protected $arguments = []; protected $interactive = true; public function __construct(InputDefinition $definition = null) { if (null === $definition) { $this->definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } /** * {@inheritdoc} */ public function bind(InputDefinition $definition) { $this->arguments = []; $this->options = []; $this->definition = $definition; $this->parse(); } /** * Processes command line arguments. */ abstract protected function parse(); /** * {@inheritdoc} */ public function validate() { $definition = $this->definition; $givenArguments = $this->arguments; $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); }); if (\count($missingArguments) > 0) { throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); } } /** * {@inheritdoc} */ public function isInteractive(): bool { return $this->interactive; } /** * {@inheritdoc} */ public function setInteractive(bool $interactive) { $this->interactive = $interactive; } /** * {@inheritdoc} */ public function getArguments(): array { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } /** * {@inheritdoc} */ public function getArgument(string $name): mixed { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); } /** * {@inheritdoc} */ public function setArgument(string $name, mixed $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } /** * {@inheritdoc} */ public function hasArgument(string $name): bool { return $this->definition->hasArgument($name); } /** * {@inheritdoc} */ public function getOptions(): array { return array_merge($this->definition->getOptionDefaults(), $this->options); } /** * {@inheritdoc} */ public function getOption(string $name): mixed { if ($this->definition->hasNegation($name)) { if (null === $value = $this->getOption($this->definition->negationToName($name))) { return $value; } return !$value; } if (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } /** * {@inheritdoc} */ public function setOption(string $name, mixed $value) { if ($this->definition->hasNegation($name)) { $this->options[$this->definition->negationToName($name)] = !$value; return; } elseif (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } /** * {@inheritdoc} */ public function hasOption(string $name): bool { return $this->definition->hasOption($name) || $this->definition->hasNegation($name); } /** * Escapes a token through escapeshellarg if it contains unsafe chars. */ public function escapeToken(string $token): string { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } /** * {@inheritdoc} */ public function setStream($stream) { $this->stream = $stream; } /** * {@inheritdoc} */ public function getStream() { return $this->stream; } } Input/ArrayInput.php 0000644 00000013166 15025056541 0010464 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\InvalidOptionException; /** * ArrayInput represents an input provided as an array. * * Usage: * * $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']); * * @author Fabien Potencier <fabien@symfony.com> */ class ArrayInput extends Input { private array $parameters; public function __construct(array $parameters, InputDefinition $definition = null) { $this->parameters = $parameters; parent::__construct($definition); } /** * {@inheritdoc} */ public function getFirstArgument(): ?string { foreach ($this->parameters as $param => $value) { if ($param && \is_string($param) && '-' === $param[0]) { continue; } return $value; } return null; } /** * {@inheritdoc} */ public function hasParameterOption(string|array $values, bool $onlyParams = false): bool { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (!\is_int($k)) { $v = $k; } if ($onlyParams && '--' === $v) { return false; } if (\in_array($v, $values)) { return true; } } return false; } /** * {@inheritdoc} */ public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed { $values = (array) $values; foreach ($this->parameters as $k => $v) { if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) { return $default; } if (\is_int($k)) { if (\in_array($v, $values)) { return true; } } elseif (\in_array($k, $values)) { return $v; } } return $default; } /** * Returns a stringified representation of the args passed to the command. */ public function __toString(): string { $params = []; foreach ($this->parameters as $param => $val) { if ($param && \is_string($param) && '-' === $param[0]) { $glue = ('-' === $param[1]) ? '=' : ' '; if (\is_array($val)) { foreach ($val as $v) { $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : ''); } } else { $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : ''); } } else { $params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val); } } return implode(' ', $params); } /** * {@inheritdoc} */ protected function parse() { foreach ($this->parameters as $key => $value) { if ('--' === $key) { return; } if (str_starts_with($key, '--')) { $this->addLongOption(substr($key, 2), $value); } elseif (str_starts_with($key, '-')) { $this->addShortOption(substr($key, 1), $value); } else { $this->addArgument($key, $value); } } } /** * Adds a short option value. * * @throws InvalidOptionException When option given doesn't exist */ private function addShortOption(string $shortcut, mixed $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @throws InvalidOptionException When option given doesn't exist * @throws InvalidOptionException When a required value is missing */ private function addLongOption(string $name, mixed $value) { if (!$this->definition->hasOption($name)) { if (!$this->definition->hasNegation($name)) { throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); } $optionName = $this->definition->negationToName($name); $this->options[$optionName] = false; return; } $option = $this->definition->getOption($name); if (null === $value) { if ($option->isValueRequired()) { throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isValueOptional()) { $value = true; } } $this->options[$name] = $value; } /** * Adds an argument value. * * @throws InvalidArgumentException When argument given doesn't exist */ private function addArgument(string|int $name, mixed $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } } Input/InputAwareInterface.php 0000644 00000001072 15025056541 0012257 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * InputAwareInterface should be implemented by classes that depends on the * Console Input. * * @author Wouter J <waldio.webdesign@gmail.com> */ interface InputAwareInterface { /** * Sets the Console Input. */ public function setInput(InputInterface $input); } Input/InputInterface.php 0000644 00000010355 15025056541 0011303 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; /** * InputInterface is the interface implemented by all input classes. * * @author Fabien Potencier <fabien@symfony.com> */ interface InputInterface { /** * Returns the first argument from the raw parameters (not parsed). */ public function getFirstArgument(): ?string; /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * Does not necessarily return the correct result for short options * when multiple flags are combined in the same option. * * @param string|array $values The values to look for in the raw parameters (can be an array) * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal */ public function hasParameterOption(string|array $values, bool $onlyParams = false): bool; /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * Does not necessarily return the correct result for short options * when multiple flags are combined in the same option. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param string|bool|int|float|array|null $default The default value to return if no result is found * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal * * @return mixed */ public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false); /** * Binds the current Input instance with the given arguments and options. * * @throws RuntimeException */ public function bind(InputDefinition $definition); /** * Validates the input. * * @throws RuntimeException When not enough arguments are given */ public function validate(); /** * Returns all the given arguments merged with the default values. * * @return array<string|bool|int|float|array|null> */ public function getArguments(): array; /** * Returns the argument value for a given argument name. * * @return mixed * * @throws InvalidArgumentException When argument given doesn't exist */ public function getArgument(string $name); /** * Sets an argument value by name. * * @throws InvalidArgumentException When argument given doesn't exist */ public function setArgument(string $name, mixed $value); /** * Returns true if an InputArgument object exists by name or position. */ public function hasArgument(string $name): bool; /** * Returns all the given options merged with the default values. * * @return array<string|bool|int|float|array|null> */ public function getOptions(): array; /** * Returns the option value for a given option name. * * @return mixed * * @throws InvalidArgumentException When option given doesn't exist */ public function getOption(string $name); /** * Sets an option value by name. * * @throws InvalidArgumentException When option given doesn't exist */ public function setOption(string $name, mixed $value); /** * Returns true if an InputOption object exists by name. */ public function hasOption(string $name): bool; /** * Is this input means interactive? */ public function isInteractive(): bool; /** * Sets the input interactivity. */ public function setInteractive(bool $interactive); } Input/StreamableInputInterface.php 0000644 00000001551 15025056541 0013301 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * StreamableInputInterface is the interface implemented by all input classes * that have an input stream. * * @author Robin Chalas <robin.chalas@gmail.com> */ interface StreamableInputInterface extends InputInterface { /** * Sets the input stream to read from when interacting with the user. * * This is mainly useful for testing purpose. * * @param resource $stream The input stream */ public function setStream($stream); /** * Returns the input stream. * * @return resource|null */ public function getStream(); } Input/ArgvInput.php 0000644 00000030321 15025056541 0010275 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\RuntimeException; /** * ArgvInput represents an input coming from the CLI arguments. * * Usage: * * $input = new ArgvInput(); * * By default, the `$_SERVER['argv']` array is used for the input values. * * This can be overridden by explicitly passing the input values in the constructor: * * $input = new ArgvInput($_SERVER['argv']); * * If you pass it yourself, don't forget that the first element of the array * is the name of the running application. * * When passing an argument to the constructor, be sure that it respects * the same rules as the argv one. It's almost always better to use the * `StringInput` when you want to provide your own input. * * @author Fabien Potencier <fabien@symfony.com> * * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 */ class ArgvInput extends Input { private array $tokens; private array $parsed; public function __construct(array $argv = null, InputDefinition $definition = null) { $argv = $argv ?? $_SERVER['argv'] ?? []; // strip the application name array_shift($argv); $this->tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } /** * {@inheritdoc} */ protected function parse() { $parseOptions = true; $this->parsed = $this->tokens; while (null !== $token = array_shift($this->parsed)) { $parseOptions = $this->parseToken($token, $parseOptions); } } protected function parseToken(string $token, bool $parseOptions): bool { if ($parseOptions && '' == $token) { $this->parseArgument($token); } elseif ($parseOptions && '--' == $token) { return false; } elseif ($parseOptions && str_starts_with($token, '--')) { $this->parseLongOption($token); } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { $this->parseShortOption($token); } else { $this->parseArgument($token); } return $parseOptions; } /** * Parses a short option. */ private function parseShortOption(string $token) { $name = substr($token, 1); if (\strlen($name) > 1) { if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { // an option with a value (with no space) $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } /** * Parses a short option set. * * @throws RuntimeException When option given doesn't exist */ private function parseShortOptionSet(string $name) { $len = \strlen($name); for ($i = 0; $i < $len; ++$i) { if (!$this->definition->hasShortcut($name[$i])) { $encoding = mb_detect_encoding($name, null, true); throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding))); } $option = $this->definition->getOptionForShortcut($name[$i]); if ($option->acceptValue()) { $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); break; } else { $this->addLongOption($option->getName(), null); } } } /** * Parses a long option. */ private function parseLongOption(string $token) { $name = substr($token, 2); if (false !== $pos = strpos($name, '=')) { if ('' === $value = substr($name, $pos + 1)) { array_unshift($this->parsed, $value); } $this->addLongOption(substr($name, 0, $pos), $value); } else { $this->addLongOption($name, null); } } /** * Parses an argument. * * @throws RuntimeException When too many arguments are given */ private function parseArgument(string $token) { $c = \count($this->arguments); // if input is expecting another argument, add it if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; // if last argument isArray(), append token to last argument } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; // unexpected argument } else { $all = $this->definition->getArguments(); $symfonyCommandName = null; if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) { $symfonyCommandName = $this->arguments['command'] ?? null; unset($all[$key]); } if (\count($all)) { if ($symfonyCommandName) { $message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all))); } else { $message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))); } } elseif ($symfonyCommandName) { $message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token); } else { $message = sprintf('No arguments expected, got "%s".', $token); } throw new RuntimeException($message); } } /** * Adds a short option value. * * @throws RuntimeException When option given doesn't exist */ private function addShortOption(string $shortcut, mixed $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @throws RuntimeException When option given doesn't exist */ private function addLongOption(string $name, mixed $value) { if (!$this->definition->hasOption($name)) { if (!$this->definition->hasNegation($name)) { throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); } $optionName = $this->definition->negationToName($name); if (null !== $value) { throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); } $this->options[$optionName] = false; return; } $option = $this->definition->getOption($name); if (null !== $value && !$option->acceptValue()) { throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); } if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) { // if option accepts an optional or mandatory argument // let's see if there is one provided $next = array_shift($this->parsed); if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) { $value = $next; } else { array_unshift($this->parsed, $next); } } if (null === $value) { if ($option->isValueRequired()) { throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isArray() && !$option->isValueOptional()) { $value = true; } } if ($option->isArray()) { $this->options[$name][] = $value; } else { $this->options[$name] = $value; } } /** * {@inheritdoc} */ public function getFirstArgument(): ?string { $isOption = false; foreach ($this->tokens as $i => $token) { if ($token && '-' === $token[0]) { if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) { continue; } // If it's a long option, consider that everything after "--" is the option name. // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator) $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1); if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { // noop } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) { $isOption = true; } continue; } if ($isOption) { $isOption = false; continue; } return $token; } return null; } /** * {@inheritdoc} */ public function hasParameterOption(string|array $values, bool $onlyParams = false): bool { $values = (array) $values; foreach ($this->tokens as $token) { if ($onlyParams && '--' === $token) { return false; } foreach ($values as $value) { // Options with values: // For long options, test for '--option=' at beginning // For short options, test for '-o' at beginning $leading = str_starts_with($value, '--') ? $value.'=' : $value; if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) { return true; } } } return false; } /** * {@inheritdoc} */ public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed { $values = (array) $values; $tokens = $this->tokens; while (0 < \count($tokens)) { $token = array_shift($tokens); if ($onlyParams && '--' === $token) { return $default; } foreach ($values as $value) { if ($token === $value) { return array_shift($tokens); } // Options with values: // For long options, test for '--option=' at beginning // For short options, test for '-o' at beginning $leading = str_starts_with($value, '--') ? $value.'=' : $value; if ('' !== $leading && str_starts_with($token, $leading)) { return substr($token, \strlen($leading)); } } } return $default; } /** * Returns a stringified representation of the args passed to the command. */ public function __toString(): string { $tokens = array_map(function ($token) { if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { return $match[1].$this->escapeToken($match[2]); } if ($token && '-' !== $token[0]) { return $this->escapeToken($token); } return $token; }, $this->tokens); return implode(' ', $tokens); } } Input/StringInput.php 0000644 00000005203 15025056541 0010645 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * StringInput represents an input provided as a string. * * Usage: * * $input = new StringInput('foo --bar="foobar"'); * * @author Fabien Potencier <fabien@symfony.com> */ class StringInput extends ArgvInput { public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)'; public const REGEX_UNQUOTED_STRING = '([^\s\\\\]+?)'; public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')'; /** * @param string $input A string representing the parameters from the CLI */ public function __construct(string $input) { parent::__construct([]); $this->setTokens($this->tokenize($input)); } /** * Tokenizes a string. * * @throws InvalidArgumentException When unable to parse input (should never happen) */ private function tokenize(string $input): array { $tokens = []; $length = \strlen($input); $cursor = 0; $token = null; while ($cursor < $length) { if ('\\' === $input[$cursor]) { $token .= $input[++$cursor] ?? ''; ++$cursor; continue; } if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { if (null !== $token) { $tokens[] = $token; $token = null; } } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { $token .= stripcslashes(substr($match[0], 1, -1)); } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) { $token .= $match[1]; } else { // should never happen throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); } $cursor += \strlen($match[0]); } if (null !== $token) { $tokens[] = $token; } return $tokens; } } Input/InputDefinition.php 0000644 00000026705 15025056541 0011501 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * A InputDefinition represents a set of valid command line arguments and options. * * Usage: * * $definition = new InputDefinition([ * new InputArgument('name', InputArgument::REQUIRED), * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), * ]); * * @author Fabien Potencier <fabien@symfony.com> */ class InputDefinition { private array $arguments = []; private int $requiredCount = 0; private $lastArrayArgument = null; private $lastOptionalArgument = null; private array $options = []; private array $negations = []; private array $shortcuts = []; /** * @param array $definition An array of InputArgument and InputOption instance */ public function __construct(array $definition = []) { $this->setDefinition($definition); } /** * Sets the definition of the input. */ public function setDefinition(array $definition) { $arguments = []; $options = []; foreach ($definition as $item) { if ($item instanceof InputOption) { $options[] = $item; } else { $arguments[] = $item; } } $this->setArguments($arguments); $this->setOptions($options); } /** * Sets the InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects */ public function setArguments(array $arguments = []) { $this->arguments = []; $this->requiredCount = 0; $this->lastOptionalArgument = null; $this->lastArrayArgument = null; $this->addArguments($arguments); } /** * Adds an array of InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects */ public function addArguments(?array $arguments = []) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } /** * @throws LogicException When incorrect argument is given */ public function addArgument(InputArgument $argument) { if (isset($this->arguments[$argument->getName()])) { throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); } if (null !== $this->lastArrayArgument) { throw new LogicException(sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName())); } if ($argument->isRequired() && null !== $this->lastOptionalArgument) { throw new LogicException(sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName())); } if ($argument->isArray()) { $this->lastArrayArgument = $argument; } if ($argument->isRequired()) { ++$this->requiredCount; } else { $this->lastOptionalArgument = $argument; } $this->arguments[$argument->getName()] = $argument; } /** * Returns an InputArgument by name or by position. * * @throws InvalidArgumentException When argument given doesn't exist */ public function getArgument(string|int $name): InputArgument { if (!$this->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; return $arguments[$name]; } /** * Returns true if an InputArgument object exists by name or position. */ public function hasArgument(string|int $name): bool { $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } /** * Gets the array of InputArgument objects. * * @return InputArgument[] */ public function getArguments(): array { return $this->arguments; } /** * Returns the number of InputArguments. */ public function getArgumentCount(): int { return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments); } /** * Returns the number of required InputArguments. */ public function getArgumentRequiredCount(): int { return $this->requiredCount; } /** * @return array<string|bool|int|float|array|null> */ public function getArgumentDefaults(): array { $values = []; foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } /** * Sets the InputOption objects. * * @param InputOption[] $options An array of InputOption objects */ public function setOptions(array $options = []) { $this->options = []; $this->shortcuts = []; $this->negations = []; $this->addOptions($options); } /** * Adds an array of InputOption objects. * * @param InputOption[] $options An array of InputOption objects */ public function addOptions(array $options = []) { foreach ($options as $option) { $this->addOption($option); } } /** * @throws LogicException When option given already exist */ public function addOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); } if (isset($this->negations[$option->getName()])) { throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); } if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); } } } $this->options[$option->getName()] = $option; if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { $this->shortcuts[$shortcut] = $option->getName(); } } if ($option->isNegatable()) { $negatedName = 'no-'.$option->getName(); if (isset($this->options[$negatedName])) { throw new LogicException(sprintf('An option named "%s" already exists.', $negatedName)); } $this->negations[$negatedName] = $option->getName(); } } /** * Returns an InputOption by name. * * @throws InvalidArgumentException When option given doesn't exist */ public function getOption(string $name): InputOption { if (!$this->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } /** * Returns true if an InputOption object exists by name. * * This method can't be used to check if the user included the option when * executing the command (use getOption() instead). */ public function hasOption(string $name): bool { return isset($this->options[$name]); } /** * Gets the array of InputOption objects. * * @return InputOption[] */ public function getOptions(): array { return $this->options; } /** * Returns true if an InputOption object exists by shortcut. */ public function hasShortcut(string $name): bool { return isset($this->shortcuts[$name]); } /** * Returns true if an InputOption object exists by negated name. */ public function hasNegation(string $name): bool { return isset($this->negations[$name]); } /** * Gets an InputOption by shortcut. */ public function getOptionForShortcut(string $shortcut): InputOption { return $this->getOption($this->shortcutToName($shortcut)); } /** * @return array<string|bool|int|float|array|null> */ public function getOptionDefaults(): array { $values = []; foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } /** * Returns the InputOption name given a shortcut. * * @throws InvalidArgumentException When option given does not exist * * @internal */ public function shortcutToName(string $shortcut): string { if (!isset($this->shortcuts[$shortcut])) { throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } /** * Returns the InputOption name given a negation. * * @throws InvalidArgumentException When option given does not exist * * @internal */ public function negationToName(string $negation): string { if (!isset($this->negations[$negation])) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $negation)); } return $this->negations[$negation]; } /** * Gets the synopsis. */ public function getSynopsis(bool $short = false): string { $elements = []; if ($short && $this->getOptions()) { $elements[] = '[options]'; } elseif (!$short) { foreach ($this->getOptions() as $option) { $value = ''; if ($option->acceptValue()) { $value = sprintf( ' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '' ); } $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; $negation = $option->isNegatable() ? sprintf('|--no-%s', $option->getName()) : ''; $elements[] = sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation); } } if (\count($elements) && $this->getArguments()) { $elements[] = '[--]'; } $tail = ''; foreach ($this->getArguments() as $argument) { $element = '<'.$argument->getName().'>'; if ($argument->isArray()) { $element .= '...'; } if (!$argument->isRequired()) { $element = '['.$element; $tail .= ']'; } $elements[] = $element; } return implode(' ', $elements).$tail; } } Input/InputArgument.php 0000644 00000006516 15025056541 0011171 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * Represents a command line argument. * * @author Fabien Potencier <fabien@symfony.com> */ class InputArgument { public const REQUIRED = 1; public const OPTIONAL = 2; public const IS_ARRAY = 4; private string $name; private int $mode; private string|int|bool|array|null|float $default; private string $description; /** * @param string $name The argument name * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL * @param string $description A description text * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) * * @throws InvalidArgumentException When argument mode is not valid */ public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null) { if (null === $mode) { $mode = self::OPTIONAL; } elseif ($mode > 7 || $mode < 1) { throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } $this->name = $name; $this->mode = $mode; $this->description = $description; $this->setDefault($default); } /** * Returns the argument name. */ public function getName(): string { return $this->name; } /** * Returns true if the argument is required. * * @return bool true if parameter mode is self::REQUIRED, false otherwise */ public function isRequired(): bool { return self::REQUIRED === (self::REQUIRED & $this->mode); } /** * Returns true if the argument can take multiple values. * * @return bool true if mode is self::IS_ARRAY, false otherwise */ public function isArray(): bool { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } /** * Sets the default value. * * @throws LogicException When incorrect default value is given */ public function setDefault(string|bool|int|float|array $default = null) { if ($this->isRequired() && null !== $default) { throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } if ($this->isArray()) { if (null === $default) { $default = []; } elseif (!\is_array($default)) { throw new LogicException('A default value for an array argument must be an array.'); } } $this->default = $default; } /** * Returns the default value. */ public function getDefault(): string|bool|int|float|array|null { return $this->default; } /** * Returns the description text. */ public function getDescription(): string { return $this->description; } } Input/InputOption.php 0000644 00000015102 15025056541 0010646 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * Represents a command line option. * * @author Fabien Potencier <fabien@symfony.com> */ class InputOption { /** * Do not accept input for the option (e.g. --yell). This is the default behavior of options. */ public const VALUE_NONE = 1; /** * A value must be passed when the option is used (e.g. --iterations=5 or -i5). */ public const VALUE_REQUIRED = 2; /** * The option may or may not have a value (e.g. --yell or --yell=loud). */ public const VALUE_OPTIONAL = 4; /** * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). */ public const VALUE_IS_ARRAY = 8; /** * The option may have either positive or negative value (e.g. --ansi or --no-ansi). */ public const VALUE_NEGATABLE = 16; private string $name; private string|array|null $shortcut; private int $mode; private string|int|bool|array|null|float $default; private string $description; /** * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param int|null $mode The option mode: One of the VALUE_* constants * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) * * @throws InvalidArgumentException If option mode is invalid or incompatible */ public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null) { if (str_starts_with($name, '--')) { $name = substr($name, 2); } if (empty($name)) { throw new InvalidArgumentException('An option name cannot be empty.'); } if (empty($shortcut)) { $shortcut = null; } if (null !== $shortcut) { if (\is_array($shortcut)) { $shortcut = implode('|', $shortcut); } $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); $shortcuts = array_filter($shortcuts); $shortcut = implode('|', $shortcuts); if (empty($shortcut)) { throw new InvalidArgumentException('An option shortcut cannot be empty.'); } } if (null === $mode) { $mode = self::VALUE_NONE; } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; if ($this->isArray() && !$this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } if ($this->isNegatable() && $this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); } $this->setDefault($default); } /** * Returns the option shortcut. */ public function getShortcut(): ?string { return $this->shortcut; } /** * Returns the option name. */ public function getName(): string { return $this->name; } /** * Returns true if the option accepts a value. * * @return bool true if value mode is not self::VALUE_NONE, false otherwise */ public function acceptValue(): bool { return $this->isValueRequired() || $this->isValueOptional(); } /** * Returns true if the option requires a value. * * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise */ public function isValueRequired(): bool { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } /** * Returns true if the option takes an optional value. * * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise */ public function isValueOptional(): bool { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } /** * Returns true if the option can take multiple values. * * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise */ public function isArray(): bool { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } public function isNegatable(): bool { return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); } public function setDefault(string|bool|int|float|array $default = null) { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } if ($this->isArray()) { if (null === $default) { $default = []; } elseif (!\is_array($default)) { throw new LogicException('A default value for an array option must be an array.'); } } $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false; } /** * Returns the default value. */ public function getDefault(): string|bool|int|float|array|null { return $this->default; } /** * Returns the description text. */ public function getDescription(): string { return $this->description; } /** * Checks whether the given option equals this one. */ public function equals(self $option): bool { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() && $option->isNegatable() === $this->isNegatable() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() && $option->isValueOptional() === $this->isValueOptional() ; } } Command/LockableTrait.php 0000644 00000003157 15025056541 0011364 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\Store\FlockStore; use Symfony\Component\Lock\Store\SemaphoreStore; /** * Basic lock feature for commands. * * @author Geoffrey Brier <geoffrey.brier@gmail.com> */ trait LockableTrait { private $lock = null; /** * Locks a command. */ private function lock(string $name = null, bool $blocking = false): bool { if (!class_exists(SemaphoreStore::class)) { throw new LogicException('To enable the locking feature you must install the symfony/lock component.'); } if (null !== $this->lock) { throw new LogicException('A lock is already in place.'); } if (SemaphoreStore::isSupported()) { $store = new SemaphoreStore(); } else { $store = new FlockStore(); } $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName()); if (!$this->lock->acquire($blocking)) { $this->lock = null; return false; } return true; } /** * Releases the command lock if there is one. */ private function release() { if ($this->lock) { $this->lock->release(); $this->lock = null; } } } Command/LazyCommand.php 0000644 00000012053 15025056541 0011055 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Application; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Nicolas Grekas <p@tchwork.com> */ final class LazyCommand extends Command { private $command; private ?bool $isEnabled; public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true) { $this->setName($name) ->setAliases($aliases) ->setHidden($isHidden) ->setDescription($description); $this->command = $commandFactory; $this->isEnabled = $isEnabled; } public function ignoreValidationErrors(): void { $this->getCommand()->ignoreValidationErrors(); } public function setApplication(Application $application = null): void { if ($this->command instanceof parent) { $this->command->setApplication($application); } parent::setApplication($application); } public function setHelperSet(HelperSet $helperSet): void { if ($this->command instanceof parent) { $this->command->setHelperSet($helperSet); } parent::setHelperSet($helperSet); } public function isEnabled(): bool { return $this->isEnabled ?? $this->getCommand()->isEnabled(); } public function run(InputInterface $input, OutputInterface $output): int { return $this->getCommand()->run($input, $output); } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $this->getCommand()->complete($input, $suggestions); } public function setCode(callable $code): static { $this->getCommand()->setCode($code); return $this; } /** * @internal */ public function mergeApplicationDefinition(bool $mergeArgs = true): void { $this->getCommand()->mergeApplicationDefinition($mergeArgs); } public function setDefinition(array|InputDefinition $definition): static { $this->getCommand()->setDefinition($definition); return $this; } public function getDefinition(): InputDefinition { return $this->getCommand()->getDefinition(); } public function getNativeDefinition(): InputDefinition { return $this->getCommand()->getNativeDefinition(); } public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null): static { $this->getCommand()->addArgument($name, $mode, $description, $default); return $this; } public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null): static { $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default); return $this; } public function setProcessTitle(string $title): static { $this->getCommand()->setProcessTitle($title); return $this; } public function setHelp(string $help): static { $this->getCommand()->setHelp($help); return $this; } public function getHelp(): string { return $this->getCommand()->getHelp(); } public function getProcessedHelp(): string { return $this->getCommand()->getProcessedHelp(); } public function getSynopsis(bool $short = false): string { return $this->getCommand()->getSynopsis($short); } public function addUsage(string $usage): static { $this->getCommand()->addUsage($usage); return $this; } public function getUsages(): array { return $this->getCommand()->getUsages(); } public function getHelper(string $name): mixed { return $this->getCommand()->getHelper($name); } public function getCommand(): parent { if (!$this->command instanceof \Closure) { return $this->command; } $command = $this->command = ($this->command)(); $command->setApplication($this->getApplication()); if (null !== $this->getHelperSet()) { $command->setHelperSet($this->getHelperSet()); } $command->setName($this->getName()) ->setAliases($this->getAliases()) ->setHidden($this->isHidden()) ->setDescription($this->getDescription()); // Will throw if the command is not correctly initialized. $command->getDefinition(); return $command; } } Command/HelpCommand.php 0000644 00000005772 15025056541 0011040 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Descriptor\ApplicationDescription; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * HelpCommand displays the help for a given command. * * @author Fabien Potencier <fabien@symfony.com> */ class HelpCommand extends Command { private $command; /** * {@inheritdoc} */ protected function configure() { $this->ignoreValidationErrors(); $this ->setName('help') ->setDefinition([ new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), ]) ->setDescription('Display help for a command') ->setHelp(<<<'EOF' The <info>%command.name%</info> command displays help for a given command: <info>%command.full_name% list</info> You can also output the help in other formats by using the <comment>--format</comment> option: <info>%command.full_name% --format=xml list</info> To display the list of available commands, please use the <info>list</info> command. EOF ) ; } public function setCommand(Command $command) { $this->command = $command; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output): int { $this->command ??= $this->getApplication()->find($input->getArgument('command_name')); $helper = new DescriptorHelper(); $helper->describe($output, $this->command, [ 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), ]); unset($this->command); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('command_name')) { $descriptor = new ApplicationDescription($this->getApplication()); $suggestions->suggestValues(array_keys($descriptor->getCommands())); return; } if ($input->mustSuggestOptionValuesFor('format')) { $helper = new DescriptorHelper(); $suggestions->suggestValues($helper->getFormats()); } } } Command/ListCommand.php 0000644 00000006107 15025056541 0011054 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Descriptor\ApplicationDescription; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * ListCommand displays the list of all available commands for the application. * * @author Fabien Potencier <fabien@symfony.com> */ class ListCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('list') ->setDefinition([ new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), ]) ->setDescription('List commands') ->setHelp(<<<'EOF' The <info>%command.name%</info> command lists all commands: <info>%command.full_name%</info> You can also display the commands for a specific namespace: <info>%command.full_name% test</info> You can also output the information in other formats by using the <comment>--format</comment> option: <info>%command.full_name% --format=xml</info> It's also possible to get raw list of commands (useful for embedding command runner): <info>%command.full_name% --raw</info> EOF ) ; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output): int { $helper = new DescriptorHelper(); $helper->describe($output, $this->getApplication(), [ 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'namespace' => $input->getArgument('namespace'), 'short' => $input->getOption('short'), ]); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('namespace')) { $descriptor = new ApplicationDescription($this->getApplication()); $suggestions->suggestValues(array_keys($descriptor->getNamespaces())); return; } if ($input->mustSuggestOptionValuesFor('format')) { $helper = new DescriptorHelper(); $suggestions->suggestValues($helper->getFormats()); } } } Command/DumpCompletionCommand.php 0000644 00000011324 15025056541 0013075 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; /** * Dumps the completion script for the current shell. * * @author Wouter de Jong <wouter@wouterj.nl> */ final class DumpCompletionCommand extends Command { protected static $defaultName = 'completion'; protected static $defaultDescription = 'Dump the shell completion script'; public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('shell')) { $suggestions->suggestValues($this->getSupportedShells()); } } protected function configure() { $fullCommand = $_SERVER['PHP_SELF']; $commandName = basename($fullCommand); $fullCommand = @realpath($fullCommand) ?: $fullCommand; $this ->setHelp(<<<EOH The <info>%command.name%</> command dumps the shell completion script required to use shell autocompletion (currently only bash completion is supported). <comment>Static installation -------------------</> Dump the script to a global completion file and restart your shell: <info>%command.full_name% bash | sudo tee /etc/bash_completion.d/{$commandName}</> Or dump the script to a local file and source it: <info>%command.full_name% bash > completion.sh</> <comment># source the file whenever you use the project</> <info>source completion.sh</> <comment># or add this line at the end of your "~/.bashrc" file:</> <info>source /path/to/completion.sh</> <comment>Dynamic installation --------------------</> Add this to the end of your shell configuration file (e.g. <info>"~/.bashrc"</>): <info>eval "$({$fullCommand} completion bash)"</> EOH ) ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given') ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log') ; } protected function execute(InputInterface $input, OutputInterface $output): int { $commandName = basename($_SERVER['argv'][0]); if ($input->getOption('debug')) { $this->tailDebugLog($commandName, $output); return self::SUCCESS; } $shell = $input->getArgument('shell') ?? self::guessShell(); $completionFile = __DIR__.'/../Resources/completion.'.$shell; if (!file_exists($completionFile)) { $supportedShells = $this->getSupportedShells(); if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } if ($shell) { $output->writeln(sprintf('<error>Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").</>', $shell, implode('", "', $supportedShells))); } else { $output->writeln(sprintf('<error>Shell not detected, Symfony shell completion only supports "%s").</>', implode('", "', $supportedShells))); } return self::INVALID; } $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile))); return self::SUCCESS; } private static function guessShell(): string { return basename($_SERVER['SHELL'] ?? ''); } private function tailDebugLog(string $commandName, OutputInterface $output): void { $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log'; if (!file_exists($debugFile)) { touch($debugFile); } $process = new Process(['tail', '-f', $debugFile], null, null, null, 0); $process->run(function (string $type, string $line) use ($output): void { $output->write($line); }); } /** * @return string[] */ private function getSupportedShells(): array { return array_map(function ($f) { return pathinfo($f, \PATHINFO_EXTENSION); }, glob(__DIR__.'/../Resources/completion.*')); } } Command/Command.php 0000644 00000047215 15025056541 0010225 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Application; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Base class for all commands. * * @author Fabien Potencier <fabien@symfony.com> */ class Command { // see https://tldp.org/LDP/abs/html/exitcodes.html public const SUCCESS = 0; public const FAILURE = 1; public const INVALID = 2; /** * @var string|null The default command name */ protected static $defaultName; /** * @var string|null The default command description */ protected static $defaultDescription; private $application = null; private ?string $name = null; private ?string $processTitle = null; private array $aliases = []; private $definition; private bool $hidden = false; private string $help = ''; private string $description = ''; private $fullDefinition = null; private bool $ignoreValidationErrors = false; private ?\Closure $code = null; private array $synopsis = []; private array $usages = []; private $helperSet = null; public static function getDefaultName(): ?string { $class = static::class; if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { return $attribute[0]->newInstance()->name; } $r = new \ReflectionProperty($class, 'defaultName'); return $class === $r->class ? static::$defaultName : null; } public static function getDefaultDescription(): ?string { $class = static::class; if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { return $attribute[0]->newInstance()->description; } $r = new \ReflectionProperty($class, 'defaultDescription'); return $class === $r->class ? static::$defaultDescription : null; } /** * @param string|null $name The name of the command; passing null means it must be set in configure() * * @throws LogicException When the command name is empty */ public function __construct(string $name = null) { $this->definition = new InputDefinition(); if (null === $name && null !== $name = static::getDefaultName()) { $aliases = explode('|', $name); if ('' === $name = array_shift($aliases)) { $this->setHidden(true); $name = array_shift($aliases); } $this->setAliases($aliases); } if (null !== $name) { $this->setName($name); } if ('' === $this->description) { $this->setDescription(static::getDefaultDescription() ?? ''); } $this->configure(); } /** * Ignores validation errors. * * This is mainly useful for the help command. */ public function ignoreValidationErrors() { $this->ignoreValidationErrors = true; } public function setApplication(Application $application = null) { $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); } else { $this->helperSet = null; } $this->fullDefinition = null; } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Gets the helper set. */ public function getHelperSet(): ?HelperSet { return $this->helperSet; } /** * Gets the application instance for this command. */ public function getApplication(): ?Application { return $this->application; } /** * Checks whether the command is enabled or not in the current environment. * * Override this to check for x or y and return false if the command cannot * run properly under the current conditions. * * @return bool */ public function isEnabled() { return true; } /** * Configures the current command. */ protected function configure() { } /** * Executes the current command. * * This method is not abstract because you can use this class * as a concrete class. In this case, instead of defining the * execute() method, you set the code to execute by passing * a Closure to the setCode() method. * * @return int 0 if everything went fine, or an exit code * * @throws LogicException When this abstract method is not implemented * * @see setCode() */ protected function execute(InputInterface $input, OutputInterface $output) { throw new LogicException('You must override the execute() method in the concrete command class.'); } /** * Interacts with the user. * * This method is executed before the InputDefinition is validated. * This means that this is the only place where the command can * interactively ask for values of missing required arguments. */ protected function interact(InputInterface $input, OutputInterface $output) { } /** * Initializes the command after the input has been bound and before the input * is validated. * * This is mainly useful when a lot of commands extends one main command * where some things need to be initialized based on the input arguments and options. * * @see InputInterface::bind() * @see InputInterface::validate() */ protected function initialize(InputInterface $input, OutputInterface $output) { } /** * Runs the command. * * The code to execute is either defined directly with the * setCode() method or by overriding the execute() method * in a sub-class. * * @return int The command exit code * * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}. * * @see setCode() * @see execute() */ public function run(InputInterface $input, OutputInterface $output): int { // add the application arguments and options $this->mergeApplicationDefinition(); // bind the input against the command specific arguments/options try { $input->bind($this->getDefinition()); } catch (ExceptionInterface $e) { if (!$this->ignoreValidationErrors) { throw $e; } } $this->initialize($input, $output); if (null !== $this->processTitle) { if (\function_exists('cli_set_process_title')) { if (!@cli_set_process_title($this->processTitle)) { if ('Darwin' === \PHP_OS) { $output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE); } else { cli_set_process_title($this->processTitle); } } } elseif (\function_exists('setproctitle')) { setproctitle($this->processTitle); } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>'); } } if ($input->isInteractive()) { $this->interact($input, $output); } // The command name argument is often omitted when a command is executed directly with its run() method. // It would fail the validation if we didn't make sure the command argument is present, // since it's required by the application. if ($input->hasArgument('command') && null === $input->getArgument('command')) { $input->setArgument('command', $this->getName()); } $input->validate(); if ($this->code) { $statusCode = ($this->code)($input, $output); } else { $statusCode = $this->execute($input, $output); if (!\is_int($statusCode)) { throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode))); } } return is_numeric($statusCode) ? (int) $statusCode : 0; } /** * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { } /** * Sets the code to execute when running this command. * * If this method is used, it overrides the code defined * in the execute() method. * * @param callable $code A callable(InputInterface $input, OutputInterface $output) * * @return $this * * @throws InvalidArgumentException * * @see execute() */ public function setCode(callable $code): static { if ($code instanceof \Closure) { $r = new \ReflectionFunction($code); if (null === $r->getClosureThis()) { set_error_handler(static function () {}); try { if ($c = \Closure::bind($code, $this)) { $code = $c; } } finally { restore_error_handler(); } } } else { $code = \Closure::fromCallable($code); } $this->code = $code; return $this; } /** * Merges the application definition with the command definition. * * This method is not part of public API and should not be used directly. * * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments * * @internal */ public function mergeApplicationDefinition(bool $mergeArgs = true) { if (null === $this->application) { return; } $this->fullDefinition = new InputDefinition(); $this->fullDefinition->setOptions($this->definition->getOptions()); $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions()); if ($mergeArgs) { $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments()); $this->fullDefinition->addArguments($this->definition->getArguments()); } else { $this->fullDefinition->setArguments($this->definition->getArguments()); } } /** * Sets an array of argument and option instances. * * @return $this */ public function setDefinition(array|InputDefinition $definition): static { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->fullDefinition = null; return $this; } /** * Gets the InputDefinition attached to this Command. */ public function getDefinition(): InputDefinition { return $this->fullDefinition ?? $this->getNativeDefinition(); } /** * Gets the InputDefinition to be used to create representations of this Command. * * Can be overridden to provide the original command representation when it would otherwise * be changed by merging with the application InputDefinition. * * This method is not part of public API and should not be used directly. */ public function getNativeDefinition(): InputDefinition { return $this->definition ?? throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); } /** * Adds an argument. * * @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) * * @throws InvalidArgumentException When argument mode is not valid * * @return $this */ public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null): static { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); if (null !== $this->fullDefinition) { $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default)); } return $this; } /** * Adds an option. * * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param $mode The option mode: One of the InputOption::VALUE_* constants * @param $default The default value (must be null for InputOption::VALUE_NONE) * * @throws InvalidArgumentException If option mode is invalid or incompatible * * @return $this */ public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null): static { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); if (null !== $this->fullDefinition) { $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); } return $this; } /** * Sets the name of the command. * * This method can set both the namespace and the name if * you separate them by a colon (:) * * $command->setName('foo:bar'); * * @return $this * * @throws InvalidArgumentException When the name is invalid */ public function setName(string $name): static { $this->validateName($name); $this->name = $name; return $this; } /** * Sets the process title of the command. * * This feature should be used only when creating a long process command, * like a daemon. * * @return $this */ public function setProcessTitle(string $title): static { $this->processTitle = $title; return $this; } /** * Returns the command name. */ public function getName(): ?string { return $this->name; } /** * @param bool $hidden Whether or not the command should be hidden from the list of commands * * @return $this */ public function setHidden(bool $hidden = true): static { $this->hidden = $hidden; return $this; } /** * @return bool whether the command should be publicly shown or not */ public function isHidden(): bool { return $this->hidden; } /** * Sets the description for the command. * * @return $this */ public function setDescription(string $description): static { $this->description = $description; return $this; } /** * Returns the description for the command. */ public function getDescription(): string { return $this->description; } /** * Sets the help for the command. * * @return $this */ public function setHelp(string $help): static { $this->help = $help; return $this; } /** * Returns the help for the command. */ public function getHelp(): string { return $this->help; } /** * Returns the processed help for the command replacing the %command.name% and * %command.full_name% patterns with the real values dynamically. */ public function getProcessedHelp(): string { $name = $this->name; $isSingleCommand = $this->application && $this->application->isSingleCommand(); $placeholders = [ '%command.name%', '%command.full_name%', ]; $replacements = [ $name, $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name, ]; return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); } /** * Sets the aliases for the command. * * @param string[] $aliases An array of aliases for the command * * @return $this * * @throws InvalidArgumentException When an alias is invalid */ public function setAliases(iterable $aliases): static { $list = []; foreach ($aliases as $alias) { $this->validateName($alias); $list[] = $alias; } $this->aliases = \is_array($aliases) ? $aliases : $list; return $this; } /** * Returns the aliases for the command. */ public function getAliases(): array { return $this->aliases; } /** * Returns the synopsis for the command. * * @param bool $short Whether to show the short version of the synopsis (with options folded) or not */ public function getSynopsis(bool $short = false): string { $key = $short ? 'short' : 'long'; if (!isset($this->synopsis[$key])) { $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); } return $this->synopsis[$key]; } /** * Add a command usage example, it'll be prefixed with the command name. * * @return $this */ public function addUsage(string $usage): static { if (!str_starts_with($usage, $this->name)) { $usage = sprintf('%s %s', $this->name, $usage); } $this->usages[] = $usage; return $this; } /** * Returns alternative usages of the command. */ public function getUsages(): array { return $this->usages; } /** * Gets a helper instance by name. * * @throws LogicException if no HelperSet is defined * @throws InvalidArgumentException if the helper is not defined */ public function getHelper(string $name): mixed { if (null === $this->helperSet) { throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); } return $this->helperSet->get($name); } /** * Validates a command name. * * It must be non-empty and parts can optionally be separated by ":". * * @throws InvalidArgumentException When the name is invalid */ private function validateName(string $name) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } } Command/SignalableCommandInterface.php 0000644 00000001252 15025056541 0014017 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; /** * Interface for command reacting to signal. * * @author Grégoire Pineau <lyrixx@lyrix.info> */ interface SignalableCommandInterface { /** * Returns the list of signals to subscribe. */ public function getSubscribedSignals(): array; /** * The method will be called when the application is signaled. */ public function handleSignal(int $signal): void; } Command/CompleteCommand.php 0000644 00000020556 15025056541 0011715 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Completion\Output\BashCompletionOutput; use Symfony\Component\Console\Completion\Output\CompletionOutputInterface; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Responsible for providing the values to the shell completion. * * @author Wouter de Jong <wouter@wouterj.nl> */ final class CompleteCommand extends Command { protected static $defaultName = '|_complete'; protected static $defaultDescription = 'Internal command to provide shell completion suggestions'; private $completionOutputs; private $isDebug = false; /** * @param array<string, class-string<CompletionOutputInterface>> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value */ public function __construct(array $completionOutputs = []) { // must be set before the parent constructor, as the property value is used in configure() $this->completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class]; parent::__construct(); } protected function configure(): void { $this ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")') ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)') ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)') ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'The version of the completion script') ; } protected function initialize(InputInterface $input, OutputInterface $output) { $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOLEAN); } protected function execute(InputInterface $input, OutputInterface $output): int { try { // uncomment when a bugfix or BC break has been introduced in the shell completion scripts // $version = $input->getOption('symfony'); // if ($version && version_compare($version, 'x.y', '>=')) { // $message = sprintf('Completion script version is not supported ("%s" given, ">=x.y" required).', $version); // $this->log($message); // $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.'); // return 126; // } $shell = $input->getOption('shell'); if (!$shell) { throw new \RuntimeException('The "--shell" option must be set.'); } if (!$completionOutput = $this->completionOutputs[$shell] ?? false) { throw new \RuntimeException(sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs)))); } $completionInput = $this->createCompletionInput($input); $suggestions = new CompletionSuggestions(); $this->log([ '', '<comment>'.date('Y-m-d H:i:s').'</>', '<info>Input:</> <comment>("|" indicates the cursor position)</>', ' '.(string) $completionInput, '<info>Command:</>', ' '.(string) implode(' ', $_SERVER['argv']), '<info>Messages:</>', ]); $command = $this->findCommand($completionInput, $output); if (null === $command) { $this->log(' No command found, completing using the Application class.'); $this->getApplication()->complete($completionInput, $suggestions); } elseif ( $completionInput->mustSuggestArgumentValuesFor('command') && $command->getName() !== $completionInput->getCompletionValue() && !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true) ) { $this->log(' No command found, completing using the Application class.'); // expand shortcut names ("cache:cl<TAB>") into their full name ("cache:clear") $suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases()))); } else { $command->mergeApplicationDefinition(); $completionInput->bind($command->getDefinition()); if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { $this->log(' Completing option names for the <comment>'.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'</> command.'); $suggestions->suggestOptions($command->getDefinition()->getOptions()); } else { $this->log([ ' Completing using the <comment>'.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'</> class.', ' Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>', ]); if (null !== $compval = $completionInput->getCompletionValue()) { $this->log(' Current value: <comment>'.$compval.'</>'); } $command->complete($completionInput, $suggestions); } } /** @var CompletionOutputInterface $completionOutput */ $completionOutput = new $completionOutput(); $this->log('<info>Suggestions:</>'); if ($options = $suggestions->getOptionSuggestions()) { $this->log(' --'.implode(' --', array_map(function ($o) { return $o->getName(); }, $options))); } elseif ($values = $suggestions->getValueSuggestions()) { $this->log(' '.implode(' ', $values)); } else { $this->log(' <comment>No suggestions were provided</>'); } $completionOutput->write($suggestions, $output); } catch (\Throwable $e) { $this->log([ '<error>Error!</error>', (string) $e, ]); if ($output->isDebug()) { throw $e; } return self::FAILURE; } return self::SUCCESS; } private function createCompletionInput(InputInterface $input): CompletionInput { $currentIndex = $input->getOption('current'); if (!$currentIndex || !ctype_digit($currentIndex)) { throw new \RuntimeException('The "--current" option must be set and it must be an integer.'); } $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex); try { $completionInput->bind($this->getApplication()->getDefinition()); } catch (ExceptionInterface $e) { } return $completionInput; } private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command { try { $inputName = $completionInput->getFirstArgument(); if (null === $inputName) { return null; } return $this->getApplication()->find($inputName); } catch (CommandNotFoundException $e) { } return null; } private function log($messages): void { if (!$this->isDebug) { return; } $commandName = basename($_SERVER['argv'][0]); file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND); } } CI/GithubActionReporter.php 0000644 00000006077 15025056541 0011670 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\CI; use Symfony\Component\Console\Output\OutputInterface; /** * Utility class for Github actions. * * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> */ class GithubActionReporter { private $output; /** * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85 */ private const ESCAPED_DATA = [ '%' => '%25', "\r" => '%0D', "\n" => '%0A', ]; /** * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94 */ private const ESCAPED_PROPERTIES = [ '%' => '%25', "\r" => '%0D', "\n" => '%0A', ':' => '%3A', ',' => '%2C', ]; public function __construct(OutputInterface $output) { $this->output = $output; } public static function isGithubActionEnvironment(): bool { return false !== getenv('GITHUB_ACTIONS'); } /** * Output an error using the Github annotations format. * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message */ public function error(string $message, string $file = null, int $line = null, int $col = null): void { $this->log('error', $message, $file, $line, $col); } /** * Output a warning using the Github annotations format. * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message */ public function warning(string $message, string $file = null, int $line = null, int $col = null): void { $this->log('warning', $message, $file, $line, $col); } /** * Output a debug log using the Github annotations format. * * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message */ public function debug(string $message, string $file = null, int $line = null, int $col = null): void { $this->log('debug', $message, $file, $line, $col); } private function log(string $type, string $message, string $file = null, int $line = null, int $col = null): void { // Some values must be encoded. $message = strtr($message, self::ESCAPED_DATA); if (!$file) { // No file provided, output the message solely: $this->output->writeln(sprintf('::%s::%s', $type, $message)); return; } $this->output->writeln(sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message)); } } README.md 0000644 00000002320 15025056541 0006023 0 ustar 00 Console Component ================= The Console component eases the creation of beautiful and testable command line interfaces. Sponsor ------- The Console component for Symfony 5.4/6.0 is [backed][1] by [Les-Tilleuls.coop][2]. Les-Tilleuls.coop is a team of 50+ Symfony experts who can help you design, develop and fix your projects. We provide a wide range of professional services including development, consulting, coaching, training and audits. We also are highly skilled in JS, Go and DevOps. We are a worker cooperative! Help Symfony by [sponsoring][3] its development! Resources --------- * [Documentation](https://symfony.com/doc/current/components/console.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) Credits ------- `Resources/bin/hiddeninput.exe` is a third party binary provided within this component. Find sources and license at https://github.com/Seldaek/hidden-input. [1]: https://symfony.com/backers [2]: https://les-tilleuls.coop [3]: https://symfony.com/sponsor LICENSE 0000644 00000002051 15025056541 0005552 0 ustar 00 Copyright (c) 2004-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Formatter/NullOutputFormatter.php 0000644 00000002531 15025056541 0013243 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * @author Tien Xuan Vo <tien.xuan.vo@gmail.com> */ final class NullOutputFormatter implements OutputFormatterInterface { private $style; /** * {@inheritdoc} */ public function format(?string $message): ?string { return null; } /** * {@inheritdoc} */ public function getStyle(string $name): OutputFormatterStyleInterface { // to comply with the interface we must return a OutputFormatterStyleInterface return $this->style ?? $this->style = new NullOutputFormatterStyle(); } /** * {@inheritdoc} */ public function hasStyle(string $name): bool { return false; } /** * {@inheritdoc} */ public function isDecorated(): bool { return false; } /** * {@inheritdoc} */ public function setDecorated(bool $decorated): void { // do nothing } /** * {@inheritdoc} */ public function setStyle(string $name, OutputFormatterStyleInterface $style): void { // do nothing } } Formatter/NullOutputFormatterStyle.php 0000644 00000002243 15025056541 0014264 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * @author Tien Xuan Vo <tien.xuan.vo@gmail.com> */ final class NullOutputFormatterStyle implements OutputFormatterStyleInterface { /** * {@inheritdoc} */ public function apply(string $text): string { return $text; } /** * {@inheritdoc} */ public function setBackground(string $color = null): void { // do nothing } /** * {@inheritdoc} */ public function setForeground(string $color = null): void { // do nothing } /** * {@inheritdoc} */ public function setOption(string $option): void { // do nothing } /** * {@inheritdoc} */ public function setOptions(array $options): void { // do nothing } /** * {@inheritdoc} */ public function unsetOption(string $option): void { // do nothing } } Formatter/OutputFormatterStyle.php 0000644 00000005473 15025056541 0013441 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; use Symfony\Component\Console\Color; /** * Formatter style class for defining styles. * * @author Konstantin Kudryashov <ever.zet@gmail.com> */ class OutputFormatterStyle implements OutputFormatterStyleInterface { private $color; private string $foreground; private string $background; private array $options; private ?string $href = null; private bool $handlesHrefGracefully; /** * Initializes output formatter style. * * @param string|null $foreground The style foreground color name * @param string|null $background The style background color name */ public function __construct(string $foreground = null, string $background = null, array $options = []) { $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); } /** * {@inheritdoc} */ public function setForeground(string $color = null) { $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); } /** * {@inheritdoc} */ public function setBackground(string $color = null) { $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options); } public function setHref(string $url): void { $this->href = $url; } /** * {@inheritdoc} */ public function setOption(string $option) { $this->options[] = $option; $this->color = new Color($this->foreground, $this->background, $this->options); } /** * {@inheritdoc} */ public function unsetOption(string $option) { $pos = array_search($option, $this->options); if (false !== $pos) { unset($this->options[$pos]); } $this->color = new Color($this->foreground, $this->background, $this->options); } /** * {@inheritdoc} */ public function setOptions(array $options) { $this->color = new Color($this->foreground, $this->background, $this->options = $options); } /** * {@inheritdoc} */ public function apply(string $text): string { $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); if (null !== $this->href && $this->handlesHrefGracefully) { $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; } return $this->color->apply($text); } } Formatter/OutputFormatterInterface.php 0000644 00000002337 15025056541 0014235 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter interface for console output. * * @author Konstantin Kudryashov <ever.zet@gmail.com> */ interface OutputFormatterInterface { /** * Sets the decorated flag. */ public function setDecorated(bool $decorated); /** * Whether the output will decorate messages. */ public function isDecorated(): bool; /** * Sets a new style. */ public function setStyle(string $name, OutputFormatterStyleInterface $style); /** * Checks if output formatter has style with specified name. */ public function hasStyle(string $name): bool; /** * Gets style options from style with specified name. * * @throws \InvalidArgumentException When style isn't defined */ public function getStyle(string $name): OutputFormatterStyleInterface; /** * Formats a message according to the given styles. */ public function format(?string $message): ?string; } Formatter/WrappableOutputFormatterInterface.php 0000644 00000001255 15025056541 0016071 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter interface for console output that supports word wrapping. * * @author Roland Franssen <franssen.roland@gmail.com> */ interface WrappableOutputFormatterInterface extends OutputFormatterInterface { /** * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping). */ public function formatAndWrap(?string $message, int $width); } Formatter/OutputFormatterStyleInterface.php 0000644 00000002105 15025056541 0015247 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter style interface for defining styles. * * @author Konstantin Kudryashov <ever.zet@gmail.com> */ interface OutputFormatterStyleInterface { /** * Sets style foreground color. */ public function setForeground(string $color = null); /** * Sets style background color. */ public function setBackground(string $color = null); /** * Sets some specific style option. */ public function setOption(string $option); /** * Unsets some specific style option. */ public function unsetOption(string $option); /** * Sets multiple style options at once. */ public function setOptions(array $options); /** * Applies the style to a given text. */ public function apply(string $text): string; } Formatter/OutputFormatter.php 0000644 00000017554 15025056541 0012423 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * Formatter class for console output. * * @author Konstantin Kudryashov <ever.zet@gmail.com> * @author Roland Franssen <franssen.roland@gmail.com> */ class OutputFormatter implements WrappableOutputFormatterInterface { private bool $decorated; private array $styles = []; private $styleStack; public function __clone() { $this->styleStack = clone $this->styleStack; foreach ($this->styles as $key => $value) { $this->styles[$key] = clone $value; } } /** * Escapes "<" and ">" special chars in given text. */ public static function escape(string $text): string { $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); return self::escapeTrailingBackslash($text); } /** * Escapes trailing "\" in given text. * * @internal */ public static function escapeTrailingBackslash(string $text): string { if (str_ends_with($text, '\\')) { $len = \strlen($text); $text = rtrim($text, '\\'); $text = str_replace("\0", '', $text); $text .= str_repeat("\0", $len - \strlen($text)); } return $text; } /** * Initializes console output formatter. * * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances */ public function __construct(bool $decorated = false, array $styles = []) { $this->decorated = $decorated; $this->setStyle('error', new OutputFormatterStyle('white', 'red')); $this->setStyle('info', new OutputFormatterStyle('green')); $this->setStyle('comment', new OutputFormatterStyle('yellow')); $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); foreach ($styles as $name => $style) { $this->setStyle($name, $style); } $this->styleStack = new OutputFormatterStyleStack(); } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { $this->decorated = $decorated; } /** * {@inheritdoc} */ public function isDecorated(): bool { return $this->decorated; } /** * {@inheritdoc} */ public function setStyle(string $name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } /** * {@inheritdoc} */ public function hasStyle(string $name): bool { return isset($this->styles[strtolower($name)]); } /** * {@inheritdoc} */ public function getStyle(string $name): OutputFormatterStyleInterface { if (!$this->hasStyle($name)) { throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); } return $this->styles[strtolower($name)]; } /** * {@inheritdoc} */ public function format(?string $message): ?string { return $this->formatAndWrap($message, 0); } /** * {@inheritdoc} */ public function formatAndWrap(?string $message, int $width) { if (null === $message) { return ''; } $offset = 0; $output = ''; $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; $closeTagRegex = '[a-z][^<>]*+'; $currentLineLength = 0; preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; $text = $match[0]; if (0 != $pos && '\\' == $message[$pos - 1]) { continue; } // add the text up to the next tag $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); $offset = $pos + \strlen($text); // opening tag? if ($open = '/' != $text[1]) { $tag = $matches[1][$i][0]; } else { $tag = $matches[3][$i][0] ?? ''; } if (!$open && !$tag) { // </> $this->styleStack->pop(); } elseif (null === $style = $this->createStyleFromString($tag)) { $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength); } elseif ($open) { $this->styleStack->push($style); } else { $this->styleStack->pop($style); } } $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength); return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); } public function getStyleStack(): OutputFormatterStyleStack { return $this->styleStack; } /** * Tries to create new style instance from string. */ private function createStyleFromString(string $string): ?OutputFormatterStyleInterface { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) { return null; } $style = new OutputFormatterStyle(); foreach ($matches as $match) { array_shift($match); $match[0] = strtolower($match[0]); if ('fg' == $match[0]) { $style->setForeground(strtolower($match[1])); } elseif ('bg' == $match[0]) { $style->setBackground(strtolower($match[1])); } elseif ('href' === $match[0]) { $url = preg_replace('{\\\\([<>])}', '$1', $match[1]); $style->setHref($url); } elseif ('options' === $match[0]) { preg_match_all('([^,;]+)', strtolower($match[1]), $options); $options = array_shift($options); foreach ($options as $option) { $style->setOption($option); } } else { return null; } } return $style; } /** * Applies current style from stack to text, if must be applied. */ private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string { if ('' === $text) { return ''; } if (!$width) { return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text; } if (!$currentLineLength && '' !== $current) { $text = ltrim($text); } if ($currentLineLength) { $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n"; $text = substr($text, $i); } else { $prefix = ''; } preg_match('~(\\n)$~', $text, $matches); $text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text); $text = rtrim($text, "\n").($matches[1] ?? ''); if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) { $text = "\n".$text; } $lines = explode("\n", $text); foreach ($lines as $line) { $currentLineLength += \strlen($line); if ($width <= $currentLineLength) { $currentLineLength = 0; } } if ($this->isDecorated()) { foreach ($lines as $i => $line) { $lines[$i] = $this->styleStack->getCurrent()->apply($line); } } return implode("\n", $lines); } } Formatter/OutputFormatterStyleStack.php 0000644 00000004721 15025056541 0014422 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Contracts\Service\ResetInterface; /** * @author Jean-François Simon <contact@jfsimon.fr> */ class OutputFormatterStyleStack implements ResetInterface { /** * @var OutputFormatterStyleInterface[] */ private array $styles = []; private $emptyStyle; public function __construct(OutputFormatterStyleInterface $emptyStyle = null) { $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); $this->reset(); } /** * Resets stack (ie. empty internal arrays). */ public function reset() { $this->styles = []; } /** * Pushes a style in the stack. */ public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } /** * Pops a style from the stack. * * @throws InvalidArgumentException When style tags incorrectly nested */ public function pop(OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface { if (empty($this->styles)) { return $this->emptyStyle; } if (null === $style) { return array_pop($this->styles); } foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { if ($style->apply('') === $stackedStyle->apply('')) { $this->styles = \array_slice($this->styles, 0, $index); return $stackedStyle; } } throw new InvalidArgumentException('Incorrectly nested style tag found.'); } /** * Computes current style with stacks top codes. */ public function getCurrent(): OutputFormatterStyleInterface { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[\count($this->styles) - 1]; } /** * @return $this */ public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle): static { $this->emptyStyle = $emptyStyle; return $this; } public function getEmptyStyle(): OutputFormatterStyleInterface { return $this->emptyStyle; } } Cursor.php 0000644 00000010074 15025056541 0006537 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Output\OutputInterface; /** * @author Pierre du Plessis <pdples@gmail.com> */ final class Cursor { private $output; private $input; /** * @param resource|null $input */ public function __construct(OutputInterface $output, $input = null) { $this->output = $output; $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+')); } /** * @return $this */ public function moveUp(int $lines = 1): static { $this->output->write(sprintf("\x1b[%dA", $lines)); return $this; } /** * @return $this */ public function moveDown(int $lines = 1): static { $this->output->write(sprintf("\x1b[%dB", $lines)); return $this; } /** * @return $this */ public function moveRight(int $columns = 1): static { $this->output->write(sprintf("\x1b[%dC", $columns)); return $this; } /** * @return $this */ public function moveLeft(int $columns = 1): static { $this->output->write(sprintf("\x1b[%dD", $columns)); return $this; } /** * @return $this */ public function moveToColumn(int $column): static { $this->output->write(sprintf("\x1b[%dG", $column)); return $this; } /** * @return $this */ public function moveToPosition(int $column, int $row): static { $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column)); return $this; } /** * @return $this */ public function savePosition(): static { $this->output->write("\x1b7"); return $this; } /** * @return $this */ public function restorePosition(): static { $this->output->write("\x1b8"); return $this; } /** * @return $this */ public function hide(): static { $this->output->write("\x1b[?25l"); return $this; } /** * @return $this */ public function show(): static { $this->output->write("\x1b[?25h\x1b[?0c"); return $this; } /** * Clears all the output from the current line. * * @return $this */ public function clearLine(): static { $this->output->write("\x1b[2K"); return $this; } /** * Clears all the output from the current line after the current position. */ public function clearLineAfter(): self { $this->output->write("\x1b[K"); return $this; } /** * Clears all the output from the cursors' current position to the end of the screen. * * @return $this */ public function clearOutput(): static { $this->output->write("\x1b[0J"); return $this; } /** * Clears the entire screen. * * @return $this */ public function clearScreen(): static { $this->output->write("\x1b[2J"); return $this; } /** * Returns the current cursor position as x,y coordinates. */ public function getCurrentPosition(): array { static $isTtySupported; if (null === $isTtySupported && \function_exists('proc_open')) { $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); } if (!$isTtySupported) { return [1, 1]; } $sttyMode = shell_exec('stty -g'); shell_exec('stty -icanon -echo'); @fwrite($this->input, "\033[6n"); $code = trim(fread($this->input, 1024)); shell_exec(sprintf('stty %s', $sttyMode)); sscanf($code, "\033[%d;%dR", $row, $col); return [$col, $row]; } } Completion/Output/BashCompletionOutput.php 0000644 00000001730 15025056541 0015022 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Completion\Output; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Output\OutputInterface; /** * @author Wouter de Jong <wouter@wouterj.nl> */ class BashCompletionOutput implements CompletionOutputInterface { public function write(CompletionSuggestions $suggestions, OutputInterface $output): void { $values = $suggestions->getValueSuggestions(); foreach ($suggestions->getOptionSuggestions() as $option) { $values[] = '--'.$option->getName(); if ($option->isNegatable()) { $values[] = '--no-'.$option->getName(); } } $output->writeln(implode("\n", $values)); } } Completion/Output/CompletionOutputInterface.php 0000644 00000001300 15025056541 0016036 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Completion\Output; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Output\OutputInterface; /** * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion. * * @author Wouter de Jong <wouter@wouterj.nl> */ interface CompletionOutputInterface { public function write(CompletionSuggestions $suggestions, OutputInterface $output): void; } Completion/CompletionInput.php 0000644 00000020012 15025056541 0012515 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Completion; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * An input specialized for shell completion. * * This input allows unfinished option names or values and exposes what kind of * completion is expected. * * @author Wouter de Jong <wouter@wouterj.nl> */ final class CompletionInput extends ArgvInput { public const TYPE_ARGUMENT_VALUE = 'argument_value'; public const TYPE_OPTION_VALUE = 'option_value'; public const TYPE_OPTION_NAME = 'option_name'; public const TYPE_NONE = 'none'; private $tokens; private $currentIndex; private $completionType; private $completionName = null; private $completionValue = ''; /** * Converts a terminal string into tokens. * * This is required for shell completions without COMP_WORDS support. */ public static function fromString(string $inputStr, int $currentIndex): self { preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $inputStr, $tokens); return self::fromTokens($tokens[0], $currentIndex); } /** * Create an input based on an COMP_WORDS token list. * * @param string[] $tokens the set of split tokens (e.g. COMP_WORDS or argv) * @param $currentIndex the index of the cursor (e.g. COMP_CWORD) */ public static function fromTokens(array $tokens, int $currentIndex): self { $input = new self($tokens); $input->tokens = $tokens; $input->currentIndex = $currentIndex; return $input; } /** * {@inheritdoc} */ public function bind(InputDefinition $definition): void { parent::bind($definition); $relevantToken = $this->getRelevantToken(); if ('-' === $relevantToken[0]) { // the current token is an input option: complete either option name or option value [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', '']; $option = $this->getOptionFromToken($optionToken); if (null === $option && !$this->isCursorFree()) { $this->completionType = self::TYPE_OPTION_NAME; $this->completionValue = $relevantToken; return; } if (null !== $option && $option->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $option->getName(); $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : ''); return; } } $previousToken = $this->tokens[$this->currentIndex - 1]; if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) { // check if previous option accepted a value $previousOption = $this->getOptionFromToken($previousToken); if (null !== $previousOption && $previousOption->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $previousOption->getName(); $this->completionValue = $relevantToken; return; } } // complete argument value $this->completionType = self::TYPE_ARGUMENT_VALUE; foreach ($this->definition->getArguments() as $argumentName => $argument) { if (!isset($this->arguments[$argumentName])) { break; } $argumentValue = $this->arguments[$argumentName]; $this->completionName = $argumentName; if (\is_array($argumentValue)) { $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null; } else { $this->completionValue = $argumentValue; } } if ($this->currentIndex >= \count($this->tokens)) { if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) { $this->completionName = $argumentName; $this->completionValue = ''; } else { // we've reached the end $this->completionType = self::TYPE_NONE; $this->completionName = null; $this->completionValue = ''; } } } /** * Returns the type of completion required. * * TYPE_ARGUMENT_VALUE when completing the value of an input argument * TYPE_OPTION_VALUE when completing the value of an input option * TYPE_OPTION_NAME when completing the name of an input option * TYPE_NONE when nothing should be completed * * @return string One of self::TYPE_* constants. TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component */ public function getCompletionType(): string { return $this->completionType; } /** * The name of the input option or argument when completing a value. * * @return string|null returns null when completing an option name */ public function getCompletionName(): ?string { return $this->completionName; } /** * The value already typed by the user (or empty string). */ public function getCompletionValue(): string { return $this->completionValue; } public function mustSuggestOptionValuesFor(string $optionName): bool { return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName(); } public function mustSuggestArgumentValuesFor(string $argumentName): bool { return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName(); } protected function parseToken(string $token, bool $parseOptions): bool { try { return parent::parseToken($token, $parseOptions); } catch (RuntimeException $e) { // suppress errors, completed input is almost never valid } return $parseOptions; } private function getOptionFromToken(string $optionToken): ?InputOption { $optionName = ltrim($optionToken, '-'); if (!$optionName) { return null; } if ('-' === ($optionToken[1] ?? ' ')) { // long option name return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null; } // short option name return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null; } /** * The token of the cursor, or the last token if the cursor is at the end of the input. */ private function getRelevantToken(): string { return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex]; } /** * Whether the cursor is "free" (i.e. at the end of the input preceded by a space). */ private function isCursorFree(): bool { $nrOfTokens = \count($this->tokens); if ($this->currentIndex > $nrOfTokens) { throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.'); } return $this->currentIndex >= $nrOfTokens; } public function __toString() { $str = ''; foreach ($this->tokens as $i => $token) { $str .= $token; if ($this->currentIndex === $i) { $str .= '|'; } $str .= ' '; } if ($this->currentIndex > $i) { $str .= '|'; } return rtrim($str); } } Completion/Suggestion.php 0000644 00000001264 15025056541 0011523 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Completion; /** * Represents a single suggested value. * * @author Wouter de Jong <wouter@wouterj.nl> */ class Suggestion { private string $value; public function __construct(string $value) { $this->value = $value; } public function getValue(): string { return $this->value; } public function __toString(): string { return $this->getValue(); } } Completion/CompletionSuggestions.php 0000644 00000004072 15025056541 0013740 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Completion; use Symfony\Component\Console\Input\InputOption; /** * Stores all completion suggestions for the current input. * * @author Wouter de Jong <wouter@wouterj.nl> */ final class CompletionSuggestions { private $valueSuggestions = []; private $optionSuggestions = []; /** * Add a suggested value for an input option or argument. * * @return $this */ public function suggestValue(string|Suggestion $value): static { $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value; return $this; } /** * Add multiple suggested values at once for an input option or argument. * * @param list<string|Suggestion> $values * * @return $this */ public function suggestValues(array $values): static { foreach ($values as $value) { $this->suggestValue($value); } return $this; } /** * Add a suggestion for an input option name. * * @return $this */ public function suggestOption(InputOption $option): static { $this->optionSuggestions[] = $option; return $this; } /** * Add multiple suggestions for input option names at once. * * @param InputOption[] $options * * @return $this */ public function suggestOptions(array $options): static { foreach ($options as $option) { $this->suggestOption($option); } return $this; } /** * @return InputOption[] */ public function getOptionSuggestions(): array { return $this->optionSuggestions; } /** * @return Suggestion[] */ public function getValueSuggestions(): array { return $this->valueSuggestions; } } SignalRegistry/SignalRegistry.php 0000644 00000003067 15025056541 0013202 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\SignalRegistry; final class SignalRegistry { private array $signalHandlers = []; public function __construct() { if (\function_exists('pcntl_async_signals')) { pcntl_async_signals(true); } } public function register(int $signal, callable $signalHandler): void { if (!isset($this->signalHandlers[$signal])) { $previousCallback = pcntl_signal_get_handler($signal); if (\is_callable($previousCallback)) { $this->signalHandlers[$signal][] = $previousCallback; } } $this->signalHandlers[$signal][] = $signalHandler; pcntl_signal($signal, [$this, 'handle']); } public static function isSupported(): bool { if (!\function_exists('pcntl_signal')) { return false; } if (\in_array('pcntl_signal', explode(',', \ini_get('disable_functions')))) { return false; } return true; } /** * @internal */ public function handle(int $signal): void { $count = \count($this->signalHandlers[$signal]); foreach ($this->signalHandlers[$signal] as $i => $signalHandler) { $hasNext = $i !== $count - 1; $signalHandler($signal, $hasNext); } } } Event/ConsoleTerminateEvent.php 0000644 00000002046 15025056541 0012620 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Allows to manipulate the exit code of a command after its execution. * * @author Francesco Levorato <git@flevour.net> */ final class ConsoleTerminateEvent extends ConsoleEvent { private int $exitCode; public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode) { parent::__construct($command, $input, $output); $this->setExitCode($exitCode); } public function setExitCode(int $exitCode): void { $this->exitCode = $exitCode; } public function getExitCode(): int { return $this->exitCode; } } Event/ConsoleEvent.php 0000644 00000002461 15025056541 0010750 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Contracts\EventDispatcher\Event; /** * Allows to inspect input and output of a command. * * @author Francesco Levorato <git@flevour.net> */ class ConsoleEvent extends Event { protected $command; private $input; private $output; public function __construct(?Command $command, InputInterface $input, OutputInterface $output) { $this->command = $command; $this->input = $input; $this->output = $output; } /** * Gets the command that is executed. */ public function getCommand(): ?Command { return $this->command; } /** * Gets the input instance. */ public function getInput(): InputInterface { return $this->input; } /** * Gets the output instance. */ public function getOutput(): OutputInterface { return $this->output; } } Event/ConsoleErrorEvent.php 0000644 00000002750 15025056541 0011763 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Allows to handle throwables thrown while running a command. * * @author Wouter de Jong <wouter@wouterj.nl> */ final class ConsoleErrorEvent extends ConsoleEvent { private \Throwable $error; private int $exitCode; public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null) { parent::__construct($command, $input, $output); $this->error = $error; } public function getError(): \Throwable { return $this->error; } public function setError(\Throwable $error): void { $this->error = $error; } public function setExitCode(int $exitCode): void { $this->exitCode = $exitCode; $r = new \ReflectionProperty($this->error, 'code'); $r->setAccessible(true); $r->setValue($this->error, $this->exitCode); } public function getExitCode(): int { return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); } } Event/ConsoleCommandEvent.php 0000644 00000002314 15025056541 0012244 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; /** * Allows to do things before the command is executed, like skipping the command or changing the input. * * @author Fabien Potencier <fabien@symfony.com> */ final class ConsoleCommandEvent extends ConsoleEvent { /** * The return code for skipped commands, this will also be passed into the terminate event. */ public const RETURN_CODE_DISABLED = 113; /** * Indicates if the command should be run or skipped. */ private bool $commandShouldRun = true; /** * Disables the command, so it won't be run. */ public function disableCommand(): bool { return $this->commandShouldRun = false; } public function enableCommand(): bool { return $this->commandShouldRun = true; } /** * Returns true if the command is runnable, false otherwise. */ public function commandShouldRun(): bool { return $this->commandShouldRun; } } Event/ConsoleSignalEvent.php 0000644 00000001624 15025056541 0012106 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author marie <marie@users.noreply.github.com> */ final class ConsoleSignalEvent extends ConsoleEvent { private int $handlingSignal; public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal) { parent::__construct($command, $input, $output); $this->handlingSignal = $handlingSignal; } public function getHandlingSignal(): int { return $this->handlingSignal; } } Question/ChoiceQuestion.php 0000644 00000011711 15025056541 0012012 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; use Symfony\Component\Console\Exception\InvalidArgumentException; /** * Represents a choice question. * * @author Fabien Potencier <fabien@symfony.com> */ class ChoiceQuestion extends Question { private array $choices; private bool $multiselect = false; private string $prompt = ' > '; private string $errorMessage = 'Value "%s" is invalid'; /** * @param string $question The question to ask to the user * @param array $choices The list of available choices * @param mixed $default The default answer to return */ public function __construct(string $question, array $choices, mixed $default = null) { if (!$choices) { throw new \LogicException('Choice question must have at least 1 choice available.'); } parent::__construct($question, $default); $this->choices = $choices; $this->setValidator($this->getDefaultValidator()); $this->setAutocompleterValues($choices); } /** * Returns available choices. */ public function getChoices(): array { return $this->choices; } /** * Sets multiselect option. * * When multiselect is set to true, multiple choices can be answered. * * @return $this */ public function setMultiselect(bool $multiselect): static { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); return $this; } /** * Returns whether the choices are multiselect. */ public function isMultiselect(): bool { return $this->multiselect; } /** * Gets the prompt for choices. */ public function getPrompt(): string { return $this->prompt; } /** * Sets the prompt for choices. * * @return $this */ public function setPrompt(string $prompt): static { $this->prompt = $prompt; return $this; } /** * Sets the error message for invalid values. * * The error message has a string placeholder (%s) for the invalid value. * * @return $this */ public function setErrorMessage(string $errorMessage): static { $this->errorMessage = $errorMessage; $this->setValidator($this->getDefaultValidator()); return $this; } private function getDefaultValidator(): callable { $choices = $this->choices; $errorMessage = $this->errorMessage; $multiselect = $this->multiselect; $isAssoc = $this->isAssoc($choices); return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { if ($multiselect) { // Check for a separated comma values if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { throw new InvalidArgumentException(sprintf($errorMessage, $selected)); } $selectedChoices = explode(',', (string) $selected); } else { $selectedChoices = [$selected]; } if ($this->isTrimmable()) { foreach ($selectedChoices as $k => $v) { $selectedChoices[$k] = trim((string) $v); } } $multiselectChoices = []; foreach ($selectedChoices as $value) { $results = []; foreach ($choices as $key => $choice) { if ($choice === $value) { $results[] = $key; } } if (\count($results) > 1) { throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results))); } $result = array_search($value, $choices); if (!$isAssoc) { if (false !== $result) { $result = $choices[$result]; } elseif (isset($choices[$value])) { $result = $choices[$value]; } } elseif (false === $result && isset($choices[$value])) { $result = $value; } if (false === $result) { throw new InvalidArgumentException(sprintf($errorMessage, $value)); } // For associative choices, consistently return the key as string: $multiselectChoices[] = $isAssoc ? (string) $result : $result; } if ($multiselect) { return $multiselectChoices; } return current($multiselectChoices); }; } } Question/Question.php 0000644 00000016137 15025056541 0010706 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; /** * Represents a Question. * * @author Fabien Potencier <fabien@symfony.com> */ class Question { private string $question; private ?int $attempts = null; private bool $hidden = false; private bool $hiddenFallback = true; private ?\Closure $autocompleterCallback = null; private ?\Closure $validator = null; private string|int|bool|null|float $default; private ?\Closure $normalizer = null; private bool $trimmable = true; private bool $multiline = false; /** * @param string $question The question to ask to the user * @param string|bool|int|float|null $default The default answer to return if the user enters nothing */ public function __construct(string $question, string|bool|int|float $default = null) { $this->question = $question; $this->default = $default; } /** * Returns the question. */ public function getQuestion(): string { return $this->question; } /** * Returns the default answer. */ public function getDefault(): string|bool|int|float|null { return $this->default; } /** * Returns whether the user response accepts newline characters. */ public function isMultiline(): bool { return $this->multiline; } /** * Sets whether the user response should accept newline characters. * * @return $this */ public function setMultiline(bool $multiline): static { $this->multiline = $multiline; return $this; } /** * Returns whether the user response must be hidden. */ public function isHidden(): bool { return $this->hidden; } /** * Sets whether the user response must be hidden or not. * * @return $this * * @throws LogicException In case the autocompleter is also used */ public function setHidden(bool $hidden): static { if ($this->autocompleterCallback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->hidden = $hidden; return $this; } /** * In case the response cannot be hidden, whether to fallback on non-hidden question or not. */ public function isHiddenFallback(): bool { return $this->hiddenFallback; } /** * Sets whether to fallback on non-hidden question if the response cannot be hidden. * * @return $this */ public function setHiddenFallback(bool $fallback): static { $this->hiddenFallback = $fallback; return $this; } /** * Gets values for the autocompleter. */ public function getAutocompleterValues(): ?iterable { $callback = $this->getAutocompleterCallback(); return $callback ? $callback('') : null; } /** * Sets values for the autocompleter. * * @return $this * * @throws LogicException */ public function setAutocompleterValues(?iterable $values): static { if (\is_array($values)) { $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); $callback = static function () use ($values) { return $values; }; } elseif ($values instanceof \Traversable) { $valueCache = null; $callback = static function () use ($values, &$valueCache) { return $valueCache ?? $valueCache = iterator_to_array($values, false); }; } else { $callback = null; } return $this->setAutocompleterCallback($callback); } /** * Gets the callback function used for the autocompleter. */ public function getAutocompleterCallback(): ?callable { return $this->autocompleterCallback; } /** * Sets the callback function used for the autocompleter. * * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. * * @return $this */ public function setAutocompleterCallback(callable $callback = null): static { if ($this->hidden && null !== $callback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->autocompleterCallback = null === $callback || $callback instanceof \Closure ? $callback : \Closure::fromCallable($callback); return $this; } /** * Sets a validator for the question. * * @return $this */ public function setValidator(callable $validator = null): static { $this->validator = null === $validator || $validator instanceof \Closure ? $validator : \Closure::fromCallable($validator); return $this; } /** * Gets the validator for the question. */ public function getValidator(): ?callable { return $this->validator; } /** * Sets the maximum number of attempts. * * Null means an unlimited number of attempts. * * @return $this * * @throws InvalidArgumentException in case the number of attempts is invalid */ public function setMaxAttempts(?int $attempts): static { if (null !== $attempts && $attempts < 1) { throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); } $this->attempts = $attempts; return $this; } /** * Gets the maximum number of attempts. * * Null means an unlimited number of attempts. */ public function getMaxAttempts(): ?int { return $this->attempts; } /** * Sets a normalizer for the response. * * The normalizer can be a callable (a string), a closure or a class implementing __invoke. * * @return $this */ public function setNormalizer(callable $normalizer): static { $this->normalizer = $normalizer instanceof \Closure ? $normalizer : \Closure::fromCallable($normalizer); return $this; } /** * Gets the normalizer for the response. * * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. */ public function getNormalizer(): ?callable { return $this->normalizer; } protected function isAssoc(array $array) { return (bool) \count(array_filter(array_keys($array), 'is_string')); } public function isTrimmable(): bool { return $this->trimmable; } /** * @return $this */ public function setTrimmable(bool $trimmable): static { $this->trimmable = $trimmable; return $this; } } Question/ConfirmationQuestion.php 0000644 00000003041 15025056541 0013245 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; /** * Represents a yes/no question. * * @author Fabien Potencier <fabien@symfony.com> */ class ConfirmationQuestion extends Question { private string $trueAnswerRegex; /** * @param string $question The question to ask to the user * @param bool $default The default answer to return, true or false * @param string $trueAnswerRegex A regex to match the "yes" answer */ public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') { parent::__construct($question, $default); $this->trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } /** * Returns the default answer normalizer. */ private function getDefaultNormalizer(): callable { $default = $this->getDefault(); $regex = $this->trueAnswerRegex; return function ($answer) use ($default, $regex) { if (\is_bool($answer)) { return $answer; } $answerIsTrue = (bool) preg_match($regex, $answer); if (false === $default) { return $answer && $answerIsTrue; } return '' === $answer || $answerIsTrue; }; } } Attribute/AsCommand.php 0000644 00000001535 15025056541 0011071 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Attribute; /** * Service tag to autoconfigure commands. */ #[\Attribute(\Attribute::TARGET_CLASS)] class AsCommand { public function __construct( public string $name, public ?string $description = null, array $aliases = [], bool $hidden = false, ) { if (!$hidden && !$aliases) { return; } $name = explode('|', $name); $name = array_merge($name, $aliases); if ($hidden && '' !== $name[0]) { array_unshift($name, ''); } $this->name = implode('|', $name); } } Tester/Constraint/CommandIsSuccessful.php 0000644 00000002264 15025056541 0014570 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester\Constraint; use PHPUnit\Framework\Constraint\Constraint; use Symfony\Component\Console\Command\Command; final class CommandIsSuccessful extends Constraint { /** * {@inheritdoc} */ public function toString(): string { return 'is successful'; } /** * {@inheritdoc} */ protected function matches($other): bool { return Command::SUCCESS === $other; } /** * {@inheritdoc} */ protected function failureDescription($other): string { return 'the command '.$this->toString(); } /** * {@inheritdoc} */ protected function additionalFailureDescription($other): string { $mapping = [ Command::FAILURE => 'Command failed.', Command::INVALID => 'Command was invalid.', ]; return $mapping[$other] ?? sprintf('Command returned exit status %d.', $other); } } Tester/CommandTester.php 0000644 00000004477 15025056541 0011307 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; /** * Eases the testing of console commands. * * @author Fabien Potencier <fabien@symfony.com> * @author Robin Chalas <robin.chalas@gmail.com> */ class CommandTester { use TesterTrait; private $command; public function __construct(Command $command) { $this->command = $command; } /** * Executes the command. * * Available execution options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * capture_stderr_separately: Make output of stdOut and stdErr separately available * * @param array $input An array of command arguments and options * @param array $options An array of execution options * * @return int The command exit code */ public function execute(array $input, array $options = []): int { // set the command name automatically if the application requires // this argument and no command name was passed if (!isset($input['command']) && (null !== $application = $this->command->getApplication()) && $application->getDefinition()->hasArgument('command') ) { $input = array_merge(['command' => $this->command->getName()], $input); } $this->input = new ArrayInput($input); // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN. $this->input->setStream(self::createStream($this->inputs)); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } if (!isset($options['decorated'])) { $options['decorated'] = false; } $this->initOutput($options); return $this->statusCode = $this->command->run($this->input, $this->output); } } Tester/CommandCompletionTester.php 0000644 00000003000 15025056541 0013316 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; /** * Eases the testing of command completion. * * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class CommandCompletionTester { private $command; public function __construct(Command $command) { $this->command = $command; } /** * Create completion suggestions from input tokens. */ public function complete(array $input): array { $currentIndex = \count($input); if ('' === end($input)) { array_pop($input); } array_unshift($input, $this->command->getName()); $completionInput = CompletionInput::fromTokens($input, $currentIndex); $completionInput->bind($this->command->getDefinition()); $suggestions = new CompletionSuggestions(); $this->command->complete($completionInput, $suggestions); $options = []; foreach ($suggestions->getOptionSuggestions() as $option) { $options[] = '--'.$option->getName(); } return array_map('strval', array_merge($options, $suggestions->getValueSuggestions())); } } Tester/TesterTrait.php 0000644 00000013250 15025056541 0011001 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use PHPUnit\Framework\Assert; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful; /** * @author Amrouche Hamza <hamza.simperfit@gmail.com> */ trait TesterTrait { private $output; private array $inputs = []; private bool $captureStreamsIndependently = false; private $input; private int $statusCode; /** * Gets the display returned by the last execution of the command or application. * * @throws \RuntimeException If it's called before the execute method */ public function getDisplay(bool $normalize = false): string { if (!isset($this->output)) { throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?'); } rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(\PHP_EOL, "\n", $display); } return $display; } /** * Gets the output written to STDERR by the application. * * @param bool $normalize Whether to normalize end of lines to \n or not */ public function getErrorOutput(bool $normalize = false): string { if (!$this->captureStreamsIndependently) { throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); } rewind($this->output->getErrorOutput()->getStream()); $display = stream_get_contents($this->output->getErrorOutput()->getStream()); if ($normalize) { $display = str_replace(\PHP_EOL, "\n", $display); } return $display; } /** * Gets the input instance used by the last execution of the command or application. */ public function getInput(): InputInterface { return $this->input; } /** * Gets the output instance used by the last execution of the command or application. */ public function getOutput(): OutputInterface { return $this->output; } /** * Gets the status code returned by the last execution of the command or application. * * @throws \RuntimeException If it's called before the execute method */ public function getStatusCode(): int { return $this->statusCode ?? throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?'); } public function assertCommandIsSuccessful(string $message = ''): void { Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message); } /** * Sets the user inputs. * * @param array $inputs An array of strings representing each input * passed to the command input stream * * @return $this */ public function setInputs(array $inputs): static { $this->inputs = $inputs; return $this; } /** * Initializes the output property. * * Available options: * * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * capture_stderr_separately: Make output of stdOut and stdErr separately available */ private function initOutput(array $options) { $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; if (!$this->captureStreamsIndependently) { $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } } else { $this->output = new ConsoleOutput( $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL, $options['decorated'] ?? null ); $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); $errorOutput->setFormatter($this->output->getFormatter()); $errorOutput->setVerbosity($this->output->getVerbosity()); $errorOutput->setDecorated($this->output->isDecorated()); $reflectedOutput = new \ReflectionObject($this->output); $strErrProperty = $reflectedOutput->getProperty('stderr'); $strErrProperty->setAccessible(true); $strErrProperty->setValue($this->output, $errorOutput); $reflectedParent = $reflectedOutput->getParentClass(); $streamProperty = $reflectedParent->getProperty('stream'); $streamProperty->setAccessible(true); $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); } } /** * @return resource */ private static function createStream(array $inputs) { $stream = fopen('php://memory', 'r+', false); foreach ($inputs as $input) { fwrite($stream, $input.\PHP_EOL); } rewind($stream); return $stream; } } Tester/ApplicationTester.php 0000644 00000005141 15025056541 0012161 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\ArrayInput; /** * Eases the testing of console applications. * * When testing an application, don't forget to disable the auto exit flag: * * $application = new Application(); * $application->setAutoExit(false); * * @author Fabien Potencier <fabien@symfony.com> */ class ApplicationTester { use TesterTrait; private $application; public function __construct(Application $application) { $this->application = $application; } /** * Executes the application. * * Available options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * capture_stderr_separately: Make output of stdOut and stdErr separately available * * @return int The command exit code */ public function run(array $input, array $options = []): int { $prevShellVerbosity = getenv('SHELL_VERBOSITY'); try { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } if ($this->inputs) { $this->input->setStream(self::createStream($this->inputs)); } $this->initOutput($options); return $this->statusCode = $this->application->run($this->input, $this->output); } finally { // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it // to its previous value to avoid one test's verbosity to spread to the following tests if (false === $prevShellVerbosity) { if (\function_exists('putenv')) { @putenv('SHELL_VERBOSITY'); } unset($_ENV['SHELL_VERBOSITY']); unset($_SERVER['SHELL_VERBOSITY']); } else { if (\function_exists('putenv')) { @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); } $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; } } } } Style/SymfonyStyle.php 0000644 00000034224 15025056541 0011052 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\SymfonyQuestionHelper; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\TrimmedBufferOutput; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Terminal; /** * Output decorator helpers for the Symfony Style Guide. * * @author Kevin Bond <kevinbond@gmail.com> */ class SymfonyStyle extends OutputStyle { public const MAX_LINE_LENGTH = 120; private $input; private $output; private $questionHelper; private $progressBar; private int $lineLength; private $bufferedOutput; public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($this->output = $output); } /** * Formats a message as a block of text. */ public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) { $messages = \is_array($messages) ? array_values($messages) : [$messages]; $this->autoPrependBlock(); $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); $this->newLine(); } /** * {@inheritdoc} */ public function title(string $message) { $this->autoPrependBlock(); $this->writeln([ sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)), sprintf('<comment>%s</>', str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), ]); $this->newLine(); } /** * {@inheritdoc} */ public function section(string $message) { $this->autoPrependBlock(); $this->writeln([ sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)), sprintf('<comment>%s</>', str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), ]); $this->newLine(); } /** * {@inheritdoc} */ public function listing(array $elements) { $this->autoPrependText(); $elements = array_map(function ($element) { return sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } /** * {@inheritdoc} */ public function text(string|array $message) { $this->autoPrependText(); $messages = \is_array($message) ? array_values($message) : [$message]; foreach ($messages as $message) { $this->writeln(sprintf(' %s', $message)); } } /** * Formats a command comment. */ public function comment(string|array $message) { $this->block($message, null, null, '<fg=default;bg=default> // </>', false, false); } /** * {@inheritdoc} */ public function success(string|array $message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } /** * {@inheritdoc} */ public function error(string|array $message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } /** * {@inheritdoc} */ public function warning(string|array $message) { $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); } /** * {@inheritdoc} */ public function note(string|array $message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } /** * Formats an info message. */ public function info(string|array $message) { $this->block($message, 'INFO', 'fg=green', ' ', true); } /** * {@inheritdoc} */ public function caution(string|array $message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } /** * {@inheritdoc} */ public function table(array $headers, array $rows) { $this->createTable() ->setHeaders($headers) ->setRows($rows) ->render() ; $this->newLine(); } /** * Formats a horizontal table. */ public function horizontalTable(array $headers, array $rows) { $this->createTable() ->setHorizontal(true) ->setHeaders($headers) ->setRows($rows) ->render() ; $this->newLine(); } /** * Formats a list of key/value horizontally. * * Each row can be one of: * * 'A title' * * ['key' => 'value'] * * new TableSeparator() */ public function definitionList(string|array|TableSeparator ...$list) { $headers = []; $row = []; foreach ($list as $value) { if ($value instanceof TableSeparator) { $headers[] = $value; $row[] = $value; continue; } if (\is_string($value)) { $headers[] = new TableCell($value, ['colspan' => 2]); $row[] = null; continue; } if (!\is_array($value)) { throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.'); } $headers[] = key($value); $row[] = current($value); } $this->horizontalTable($headers, [$row]); } /** * {@inheritdoc} */ public function ask(string $question, string $default = null, callable $validator = null): mixed { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function askHidden(string $question, callable $validator = null): mixed { $question = new Question($question); $question->setHidden(true); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function confirm(string $question, bool $default = true): bool { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } /** * {@inheritdoc} */ public function choice(string $question, array $choices, mixed $default = null): mixed { if (null !== $default) { $values = array_flip($choices); $default = $values[$default] ?? $default; } return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); } /** * {@inheritdoc} */ public function progressStart(int $max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } /** * {@inheritdoc} */ public function progressAdvance(int $step = 1) { $this->getProgressBar()->advance($step); } /** * {@inheritdoc} */ public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); unset($this->progressBar); } /** * {@inheritdoc} */ public function createProgressBar(int $max = 0): ProgressBar { $progressBar = parent::createProgressBar($max); if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); // dark shade character \u2593 } return $progressBar; } /** * @see ProgressBar::iterate() */ public function progressIterate(iterable $iterable, int $max = null): iterable { yield from $this->createProgressBar()->iterate($iterable, $max); $this->newLine(2); } public function askQuestion(Question $question): mixed { if ($this->input->isInteractive()) { $this->autoPrependBlock(); } $this->questionHelper ??= new SymfonyQuestionHelper(); $answer = $this->questionHelper->ask($this->input, $this, $question); if ($this->input->isInteractive()) { $this->newLine(); $this->bufferedOutput->write("\n"); } return $answer; } /** * {@inheritdoc} */ public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; } foreach ($messages as $message) { parent::writeln($message, $type); $this->writeBuffer($message, true, $type); } } /** * {@inheritdoc} */ public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; } foreach ($messages as $message) { parent::write($message, $newline, $type); $this->writeBuffer($message, $newline, $type); } } /** * {@inheritdoc} */ public function newLine(int $count = 1) { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); } /** * Returns a new instance which makes use of stderr if available. */ public function getErrorStyle(): self { return new self($this->input, $this->getErrorOutput()); } public function createTable(): Table { $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output; $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('<info>%s</info>'); return (new Table($output))->setStyle($style); } private function getProgressBar(): ProgressBar { return $this->progressBar ?? throw new RuntimeException('The ProgressBar is not started.'); } private function autoPrependBlock(): void { $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); if (!isset($chars[0])) { $this->newLine(); // empty history, so we should start with a new line. return; } // Prepend new line for each non LF chars (This means no blank line was output before) $this->newLine(2 - substr_count($chars, "\n")); } private function autoPrependText(): void { $fetched = $this->bufferedOutput->fetch(); // Prepend new line if last char isn't EOL: if (!str_ends_with($fetched, "\n")) { $this->newLine(); } } private function writeBuffer(string $message, bool $newLine, int $type): void { // We need to know if the last chars are PHP_EOL $this->bufferedOutput->write($message, $newLine, $type); } private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array { $indentLength = 0; $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix)); $lines = []; if (null !== $type) { $type = sprintf('[%s] ', $type); $indentLength = \strlen($type); $lineIndentation = str_repeat(' ', $indentLength); } // wrap and add newlines for each element foreach ($messages as $key => $message) { if ($escape) { $message = OutputFormatter::escape($message); } $decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message)); $messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength); $messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true)); foreach ($messageLines as $messageLine) { $lines[] = $messageLine; } if (\count($messages) > 1 && $key < \count($messages) - 1) { $lines[] = ''; } } $firstLineIndex = 0; if ($padding && $this->isDecorated()) { $firstLineIndex = 1; array_unshift($lines, ''); $lines[] = ''; } foreach ($lines as $i => &$line) { if (null !== $type) { $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; } $line = $prefix.$line; $line .= str_repeat(' ', max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0)); if ($style) { $line = sprintf('<%s>%s</>', $style, $line); } } return $lines; } } Style/OutputStyle.php 0000644 00000006075 15025056541 0010711 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Decorates output to add console style guide helpers. * * @author Kevin Bond <kevinbond@gmail.com> */ abstract class OutputStyle implements OutputInterface, StyleInterface { private $output; public function __construct(OutputInterface $output) { $this->output = $output; } /** * {@inheritdoc} */ public function newLine(int $count = 1) { $this->output->write(str_repeat(\PHP_EOL, $count)); } public function createProgressBar(int $max = 0): ProgressBar { return new ProgressBar($this->output, $max); } /** * {@inheritdoc} */ public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) { $this->output->write($messages, $newline, $type); } /** * {@inheritdoc} */ public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } /** * {@inheritdoc} */ public function setVerbosity(int $level) { $this->output->setVerbosity($level); } /** * {@inheritdoc} */ public function getVerbosity(): int { return $this->output->getVerbosity(); } /** * {@inheritdoc} */ public function setDecorated(bool $decorated) { $this->output->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated(): bool { return $this->output->isDecorated(); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } /** * {@inheritdoc} */ public function getFormatter(): OutputFormatterInterface { return $this->output->getFormatter(); } /** * {@inheritdoc} */ public function isQuiet(): bool { return $this->output->isQuiet(); } /** * {@inheritdoc} */ public function isVerbose(): bool { return $this->output->isVerbose(); } /** * {@inheritdoc} */ public function isVeryVerbose(): bool { return $this->output->isVeryVerbose(); } /** * {@inheritdoc} */ public function isDebug(): bool { return $this->output->isDebug(); } protected function getErrorOutput() { if (!$this->output instanceof ConsoleOutputInterface) { return $this->output; } return $this->output->getErrorOutput(); } } Style/StyleInterface.php 0000644 00000004435 15025056541 0011307 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; /** * Output style helpers. * * @author Kevin Bond <kevinbond@gmail.com> */ interface StyleInterface { /** * Formats a command title. */ public function title(string $message); /** * Formats a section title. */ public function section(string $message); /** * Formats a list. */ public function listing(array $elements); /** * Formats informational text. */ public function text(string|array $message); /** * Formats a success result bar. */ public function success(string|array $message); /** * Formats an error result bar. */ public function error(string|array $message); /** * Formats an warning result bar. */ public function warning(string|array $message); /** * Formats a note admonition. */ public function note(string|array $message); /** * Formats a caution admonition. */ public function caution(string|array $message); /** * Formats a table. */ public function table(array $headers, array $rows); /** * Asks a question. */ public function ask(string $question, string $default = null, callable $validator = null): mixed; /** * Asks a question with the user input hidden. */ public function askHidden(string $question, callable $validator = null): mixed; /** * Asks for confirmation. */ public function confirm(string $question, bool $default = true): bool; /** * Asks a choice question. */ public function choice(string $question, array $choices, mixed $default = null): mixed; /** * Add newline(s). */ public function newLine(int $count = 1); /** * Starts the progress output. */ public function progressStart(int $max = 0); /** * Advances the progress output X steps. */ public function progressAdvance(int $step = 1); /** * Finishes the progress output. */ public function progressFinish(); } CommandLoader/ContainerCommandLoader.php 0000644 00000003055 15025056541 0014340 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\CommandLoader; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\CommandNotFoundException; /** * Loads commands from a PSR-11 container. * * @author Robin Chalas <robin.chalas@gmail.com> */ class ContainerCommandLoader implements CommandLoaderInterface { private $container; private array $commandMap; /** * @param array $commandMap An array with command names as keys and service ids as values */ public function __construct(ContainerInterface $container, array $commandMap) { $this->container = $container; $this->commandMap = $commandMap; } /** * {@inheritdoc} */ public function get(string $name): Command { if (!$this->has($name)) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } return $this->container->get($this->commandMap[$name]); } /** * {@inheritdoc} */ public function has(string $name): bool { return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); } /** * {@inheritdoc} */ public function getNames(): array { return array_keys($this->commandMap); } } CommandLoader/CommandLoaderInterface.php 0000644 00000001452 15025056541 0014315 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\CommandLoader; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\CommandNotFoundException; /** * @author Robin Chalas <robin.chalas@gmail.com> */ interface CommandLoaderInterface { /** * Loads a command. * * @throws CommandNotFoundException */ public function get(string $name): Command; /** * Checks if a command exists. */ public function has(string $name): bool; /** * @return string[] */ public function getNames(): array; } CommandLoader/FactoryCommandLoader.php 0000644 00000002620 15025056541 0014022 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\CommandLoader; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\CommandNotFoundException; /** * A simple command loader using factories to instantiate commands lazily. * * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> */ class FactoryCommandLoader implements CommandLoaderInterface { private array $factories; /** * @param callable[] $factories Indexed by command names */ public function __construct(array $factories) { $this->factories = $factories; } /** * {@inheritdoc} */ public function has(string $name): bool { return isset($this->factories[$name]); } /** * {@inheritdoc} */ public function get(string $name): Command { if (!isset($this->factories[$name])) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } $factory = $this->factories[$name]; return $factory(); } /** * {@inheritdoc} */ public function getNames(): array { return array_keys($this->factories); } } SingleCommandApplication.php 0000644 00000003402 15025056541 0012163 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Grégoire Pineau <lyrixx@lyrixx.info> */ class SingleCommandApplication extends Command { private string $version = 'UNKNOWN'; private bool $autoExit = true; private bool $running = false; /** * @return $this */ public function setVersion(string $version): static { $this->version = $version; return $this; } /** * @final * * @return $this */ public function setAutoExit(bool $autoExit): static { $this->autoExit = $autoExit; return $this; } public function run(InputInterface $input = null, OutputInterface $output = null): int { if ($this->running) { return parent::run($input, $output); } // We use the command name as the application name $application = new Application($this->getName() ?: 'UNKNOWN', $this->version); $application->setAutoExit($this->autoExit); // Fix the usage of the command displayed with "--help" $this->setName($_SERVER['argv'][0]); $application->add($this); $application->setDefaultCommand($this->getName(), true); $this->running = true; try { $ret = $application->run($input, $output); } finally { $this->running = false; } return $ret ?? 1; } } ConsoleEvents.php 0000644 00000004176 15025056541 0010057 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleErrorEvent; use Symfony\Component\Console\Event\ConsoleSignalEvent; use Symfony\Component\Console\Event\ConsoleTerminateEvent; /** * Contains all events dispatched by an Application. * * @author Francesco Levorato <git@flevour.net> */ final class ConsoleEvents { /** * The COMMAND event allows you to attach listeners before any command is * executed by the console. It also allows you to modify the command, input and output * before they are handed to the command. * * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") */ public const COMMAND = 'console.command'; /** * The SIGNAL event allows you to perform some actions * after the command execution was interrupted. * * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent") */ public const SIGNAL = 'console.signal'; /** * The TERMINATE event allows you to attach listeners after a command is * executed by the console. * * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") */ public const TERMINATE = 'console.terminate'; /** * The ERROR event occurs when an uncaught exception or error appears. * * This event allows you to deal with the exception/error or * to modify the thrown exception. * * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent") */ public const ERROR = 'console.error'; /** * Event aliases. * * These aliases can be consumed by RegisterListenersPass. */ public const ALIASES = [ ConsoleCommandEvent::class => self::COMMAND, ConsoleErrorEvent::class => self::ERROR, ConsoleSignalEvent::class => self::SIGNAL, ConsoleTerminateEvent::class => self::TERMINATE, ]; } Resources/bin/hiddeninput.exe 0000644 00000022000 15025056541 0012301 0 ustar 00 MZ� �� � @ � � � �!�L�!This program cannot be run in DOS mode. $ �,�;�B�;�B�;�B�2�מ:�B�2��-�B�2�ƞ9�B�2�ў?�B�a9�8�B�;�C��B�2�Ȟ:�B�2�֞:�B�2�Ӟ:�B�Rich;�B� PE L �MoO � 8 @ ` ?� @� �"