|null
+ */
+ private ?array $interactions = null;
+ private bool $triggerDeprecations = false;
+ private $code;
+
+ public function __construct(
+ private readonly Command $command,
+ callable $code,
+ ) {
+ $this->code = $code;
+ $this->signalableCommand = $code instanceof SignalableCommandInterface ? $code : null;
+ $this->invokable = new \ReflectionFunction($this->getClosure($code));
+ }
+
+ /**
+ * Invokes a callable with parameters generated from the input interface.
+ */
+ public function __invoke(InputInterface $input, OutputInterface $output): int
+ {
+ self::$running[] = [$this->invokable->getClosureThis(), $this->command];
+
+ try {
+ $statusCode = $this->invokable->invoke(...$this->getParameters($this->invokable, $input, $output));
+ } finally {
+ array_pop(self::$running);
+ }
+
+ if (!\is_int($statusCode)) {
+ if ($this->triggerDeprecations) {
+ trigger_deprecation('symfony/console', '7.3', \sprintf('Returning a non-integer value from the command "%s" is deprecated and will throw an exception in Symfony 8.0.', $this->command->getName()));
+
+ return 0;
+ }
+
+ throw new \TypeError(\sprintf('The command "%s" must return an integer value in the "%s" method, but "%s" was returned.', $this->command->getName(), $this->invokable->getName(), get_debug_type($statusCode)));
+ }
+
+ return $statusCode;
+ }
+
+ /**
+ * Configures the input definition from an invokable-defined function.
+ *
+ * Processes the parameters of the reflection function to extract and
+ * add arguments or options to the provided input definition.
+ */
+ public function configure(InputDefinition $definition): void
+ {
+ foreach ($this->invokable->getParameters() as $parameter) {
+ if ($argument = Argument::tryFrom($parameter)) {
+ $definition->addArgument($argument->toInputArgument());
+ continue;
+ }
+
+ if ($option = Option::tryFrom($parameter)) {
+ $definition->addOption($option->toInputOption());
+ continue;
+ }
+
+ if ($input = MapInput::tryFrom($parameter)) {
+ $inputArguments = array_map(static fn (Argument $a) => $a->toInputArgument(), iterator_to_array($input->getArguments(), false));
+
+ // make sure optional arguments are defined after required ones
+ usort($inputArguments, static fn (InputArgument $a, InputArgument $b) => (int) $b->isRequired() - (int) $a->isRequired());
+
+ foreach ($inputArguments as $inputArgument) {
+ $definition->addArgument($inputArgument);
+ }
+
+ foreach ($input->getOptions() as $option) {
+ $definition->addOption($option->toInputOption());
+ }
+ }
+ }
+ }
+
+ public function getCode(): callable
+ {
+ return $this->code;
+ }
+
+ /**
+ * Returns the command that is currently running the given object, if any.
+ */
+ public static function getRunningCommand(object $code): ?Command
+ {
+ for ($i = \count(self::$running) - 1; 0 <= $i; --$i) {
+ if ($code === self::$running[$i][0]) {
+ return self::$running[$i][1];
+ }
+ }
+
+ return null;
+ }
+
+ private function getClosure(callable $code): \Closure
+ {
+ if (!$code instanceof \Closure) {
+ return $code(...);
+ }
+
+ $this->triggerDeprecations = true;
+
+ if (null !== (new \ReflectionFunction($code))->getClosureThis()) {
+ return $code;
+ }
+
+ set_error_handler(static function () {});
+ try {
+ if ($c = \Closure::bind($code, $this->command)) {
+ $code = $c;
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ return $code;
+ }
+
+ private function getParameters(\ReflectionFunction $function, InputInterface $input, OutputInterface $output): array
+ {
+ $parameters = [];
+ foreach ($function->getParameters() as $parameter) {
+ if ($argument = Argument::tryFrom($parameter)) {
+ $parameters[] = $argument->resolveValue($input);
+
+ continue;
+ }
+
+ if ($option = Option::tryFrom($parameter)) {
+ $parameters[] = $option->resolveValue($input);
+
+ continue;
+ }
+
+ if ($in = MapInput::tryFrom($parameter)) {
+ $parameters[] = $in->resolveValue($input);
+
+ continue;
+ }
+
+ $type = $parameter->getType();
+
+ if (!$type instanceof \ReflectionNamedType) {
+ if ($this->triggerDeprecations) {
+ trigger_deprecation('symfony/console', '7.3', \sprintf('Omitting the type declaration for the parameter "$%s" is deprecated and will throw an exception in Symfony 8.0.', $parameter->getName()));
+
+ continue;
+ }
+
+ throw new LogicException(\sprintf('The parameter "$%s" must have a named type. Untyped, Union or Intersection types are not supported.', $parameter->getName()));
+ }
+
+ $parameters[] = match ($type->getName()) {
+ InputInterface::class => $input,
+ OutputInterface::class => $output,
+ Cursor::class => new Cursor($output),
+ SymfonyStyle::class => new SymfonyStyle($input, $output),
+ Application::class => $this->command->getApplication(),
+ default => throw new RuntimeException(\sprintf('Unsupported type "%s" for parameter "$%s".', $type->getName(), $parameter->getName())),
+ };
+ }
+
+ return $parameters ?: [$input, $output];
+ }
+
+ public function getSubscribedSignals(): array
+ {
+ return $this->signalableCommand?->getSubscribedSignals() ?? [];
+ }
+
+ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
+ {
+ return $this->signalableCommand?->handleSignal($signal, $previousExitCode) ?? false;
+ }
+
+ public function isInteractive(): bool
+ {
+ if (null === $this->interactions) {
+ $this->collectInteractions();
+ }
+
+ return [] !== $this->interactions;
+ }
+
+ public function interact(InputInterface $input, OutputInterface $output): void
+ {
+ if (null === $this->interactions) {
+ $this->collectInteractions();
+ }
+
+ foreach ($this->interactions as $interaction) {
+ $interaction->interact($input, $output, $this->getParameters(...));
+ }
+ }
+
+ private function collectInteractions(): void
+ {
+ $invokableThis = $this->invokable->getClosureThis();
+
+ $this->interactions = [];
+ foreach ($this->invokable->getParameters() as $parameter) {
+ if ($spec = Argument::tryFrom($parameter)) {
+ if ($attribute = $spec->getInteractiveAttribute()) {
+ $this->interactions[] = new Interaction($invokableThis, $attribute);
+ }
+
+ continue;
+ }
+
+ if ($spec = MapInput::tryFrom($parameter)) {
+ $this->interactions = [...$this->interactions, ...$spec->getPropertyInteractions(), ...$spec->getMethodInteractions()];
+ }
+ }
+
+ if (!$class = $this->invokable->getClosureCalledClass()) {
+ return;
+ }
+
+ foreach ($class->getMethods() as $method) {
+ if ($attribute = Interact::tryFrom($method)) {
+ $this->interactions[] = new Interaction($invokableThis, $attribute);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/console/Command/LazyCommand.php b/vendor/symfony/console/Command/LazyCommand.php
new file mode 100644
index 0000000..fd2c300
--- /dev/null
+++ b/vendor/symfony/console/Command/LazyCommand.php
@@ -0,0 +1,206 @@
+
+ *
+ * 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\Completion\Suggestion;
+use Symfony\Component\Console\Helper\HelperInterface;
+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
+ */
+final class LazyCommand extends Command
+{
+ private \Closure|Command $command;
+
+ public function __construct(
+ string $name,
+ array $aliases,
+ string $description,
+ bool $isHidden,
+ \Closure $commandFactory,
+ private ?bool $isEnabled = true,
+ ) {
+ $this->setName($name)
+ ->setAliases($aliases)
+ ->setHidden($isHidden)
+ ->setDescription($description);
+
+ $this->command = $commandFactory;
+ }
+
+ public function ignoreValidationErrors(): void
+ {
+ $this->getCommand()->ignoreValidationErrors();
+ }
+
+ public function setApplication(?Application $application): 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();
+ }
+
+ /**
+ * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion
+ */
+ public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
+ {
+ $this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues);
+
+ return $this;
+ }
+
+ /**
+ * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion
+ */
+ public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
+ {
+ $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
+
+ 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): HelperInterface
+ {
+ 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;
+ }
+}
diff --git a/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php
new file mode 100644
index 0000000..c120d13
--- /dev/null
+++ b/vendor/symfony/console/Command/ListCommand.php
@@ -0,0 +1,72 @@
+
+ *
+ * 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\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
+ */
+class ListCommand extends Command
+{
+ protected function configure(): void
+ {
+ $this
+ ->setName('list')
+ ->setDefinition([
+ new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())),
+ 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', static fn () => (new DescriptorHelper())->getFormats()),
+ new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
+ ])
+ ->setDescription('List commands')
+ ->setHelp(<<<'EOF'
+ The %command.name% command lists all commands:
+
+ %command.full_name%
+
+ You can also display the commands for a specific namespace:
+
+ %command.full_name% test
+
+ You can also output the information in other formats by using the --format option:
+
+ %command.full_name% --format=xml
+
+ It's also possible to get raw list of commands (useful for embedding command runner):
+
+ %command.full_name% --raw
+ EOF
+ )
+ ;
+ }
+
+ 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;
+ }
+}
diff --git a/vendor/symfony/console/Command/LockableTrait.php b/vendor/symfony/console/Command/LockableTrait.php
new file mode 100644
index 0000000..25261a7
--- /dev/null
+++ b/vendor/symfony/console/Command/LockableTrait.php
@@ -0,0 +1,87 @@
+
+ *
+ * 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\Attribute\AsCommand;
+use Symfony\Component\Console\Exception\LogicException;
+use Symfony\Component\Lock\LockFactory;
+use Symfony\Component\Lock\LockInterface;
+use Symfony\Component\Lock\Store\FlockStore;
+use Symfony\Component\Lock\Store\SemaphoreStore;
+
+/**
+ * Basic lock feature for commands.
+ *
+ * @author Geoffrey Brier
+ */
+trait LockableTrait
+{
+ private ?LockInterface $lock = null;
+
+ private ?LockFactory $lockFactory = 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. Try running "composer require symfony/lock".');
+ }
+
+ if (null !== $this->lock) {
+ throw new LogicException('A lock is already in place.');
+ }
+
+ if (null === $this->lockFactory) {
+ if (SemaphoreStore::isSupported()) {
+ $store = new SemaphoreStore();
+ } else {
+ $store = new FlockStore();
+ }
+
+ $this->lockFactory = new LockFactory($store);
+ }
+
+ if (!$name) {
+ if ($this instanceof Command) {
+ $name = $this->getName();
+ } elseif ($command = InvokableCommand::getRunningCommand($this)) {
+ $name = $command->getName();
+ } elseif ($attribute = (new \ReflectionClass($this::class))->getAttributes(AsCommand::class)) {
+ $name = explode('|', ltrim($attribute[0]->newInstance()->name, '|'), 2)[0];
+ } else {
+ throw new LogicException(\sprintf('Lock name missing: provide it via "%s()", #[AsCommand] attribute, or by extending Command class.', __METHOD__));
+ }
+ }
+
+ $lock = $this->lockFactory->createLock($name);
+ if (!$lock->acquire($blocking)) {
+ return false;
+ }
+
+ $this->lock = $lock;
+
+ return true;
+ }
+
+ /**
+ * Releases the command lock if there is one.
+ */
+ private function release(): void
+ {
+ if ($lock = $this->lock) {
+ $this->lock = null;
+ $lock->release();
+ }
+ }
+}
diff --git a/vendor/symfony/console/Command/SignalableCommandInterface.php b/vendor/symfony/console/Command/SignalableCommandInterface.php
new file mode 100644
index 0000000..1cf851d
--- /dev/null
+++ b/vendor/symfony/console/Command/SignalableCommandInterface.php
@@ -0,0 +1,36 @@
+
+ *
+ * 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
+ */
+interface SignalableCommandInterface
+{
+ /**
+ * Returns the list of signals to subscribe.
+ *
+ * @return list<\SIG*>
+ *
+ * @see https://php.net/pcntl.constants for signals
+ */
+ public function getSubscribedSignals(): array;
+
+ /**
+ * The method will be called when the application is signaled.
+ *
+ * @return int|false The exit code to return or false to continue the normal execution
+ */
+ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false;
+}
diff --git a/vendor/symfony/console/Command/TraceableCommand.php b/vendor/symfony/console/Command/TraceableCommand.php
new file mode 100644
index 0000000..2cf190e
--- /dev/null
+++ b/vendor/symfony/console/Command/TraceableCommand.php
@@ -0,0 +1,368 @@
+
+ *
+ * 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\HelperInterface;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+/**
+ * @internal
+ *
+ * @author Jules Pietri
+ */
+final class TraceableCommand extends Command
+{
+ public readonly Command $command;
+ public int $exitCode;
+ public ?int $interruptedBySignal = null;
+ public bool $ignoreValidation;
+ public bool $isInteractive = false;
+ public string $duration = 'n/a';
+ public string $maxMemoryUsage = 'n/a';
+ public InputInterface $input;
+ public OutputInterface $output;
+ /** @var array */
+ public array $arguments;
+ /** @var array */
+ public array $options;
+ /** @var array */
+ public array $interactiveInputs = [];
+ public array $handledSignals = [];
+ public ?array $invokableCommandInfo = null;
+
+ public function __construct(
+ Command $command,
+ private readonly Stopwatch $stopwatch,
+ ) {
+ if ($command instanceof LazyCommand) {
+ $command = $command->getCommand();
+ }
+
+ $this->command = $command;
+
+ // prevent call to self::getDefaultDescription()
+ $this->setDescription($command->getDescription());
+
+ parent::__construct($command->getName());
+
+ // init below enables calling {@see parent::run()}
+ [$code, $processTitle, $ignoreValidationErrors] = \Closure::bind(fn () => [$this->code, $this->processTitle, $this->ignoreValidationErrors], $command, Command::class)();
+
+ if (\is_callable($code)) {
+ $this->setCode($code);
+ }
+
+ if ($processTitle) {
+ parent::setProcessTitle($processTitle);
+ }
+
+ if ($ignoreValidationErrors) {
+ parent::ignoreValidationErrors();
+ }
+
+ $this->ignoreValidation = $ignoreValidationErrors;
+ }
+
+ public function __call(string $name, array $arguments): mixed
+ {
+ return $this->command->{$name}(...$arguments);
+ }
+
+ public function getSubscribedSignals(): array
+ {
+ return $this->command->getSubscribedSignals();
+ }
+
+ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
+ {
+ $event = $this->stopwatch->start($this->getName().'.handle_signal');
+
+ $exit = $this->command->handleSignal($signal, $previousExitCode);
+
+ $event->stop();
+
+ if (!isset($this->handledSignals[$signal])) {
+ $this->handledSignals[$signal] = [
+ 'handled' => 0,
+ 'duration' => 0,
+ 'memory' => 0,
+ ];
+ }
+
+ ++$this->handledSignals[$signal]['handled'];
+ $this->handledSignals[$signal]['duration'] += $event->getDuration();
+ $this->handledSignals[$signal]['memory'] = max(
+ $this->handledSignals[$signal]['memory'],
+ $event->getMemory() >> 20
+ );
+
+ return $exit;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Calling parent method is required to be used in {@see parent::run()}.
+ */
+ public function ignoreValidationErrors(): void
+ {
+ $this->ignoreValidation = true;
+ $this->command->ignoreValidationErrors();
+
+ parent::ignoreValidationErrors();
+ }
+
+ public function setApplication(?Application $application = null): void
+ {
+ $this->command->setApplication($application);
+ }
+
+ public function getApplication(): ?Application
+ {
+ return $this->command->getApplication();
+ }
+
+ public function setHelperSet(HelperSet $helperSet): void
+ {
+ $this->command->setHelperSet($helperSet);
+ }
+
+ public function getHelperSet(): ?HelperSet
+ {
+ return $this->command->getHelperSet();
+ }
+
+ public function isEnabled(): bool
+ {
+ return $this->command->isEnabled();
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ $this->command->complete($input, $suggestions);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Calling parent method is required to be used in {@see parent::run()}.
+ */
+ public function setCode(callable $code): static
+ {
+ if ($code instanceof InvokableCommand) {
+ $r = \Closure::bind(fn () => $this->invokable, $code, InvokableCommand::class)();
+
+ $this->invokableCommandInfo = [
+ 'class' => $r->getClosureScopeClass()->name,
+ 'file' => $r->getFileName(),
+ 'line' => $r->getStartLine(),
+ ];
+
+ // Pass the original callable to avoid double-wrapping in Command::setCode()
+ $this->command->setCode($code->getCode());
+ } else {
+ $this->command->setCode($code);
+ }
+
+ return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int {
+ $event = $this->stopwatch->start($this->getName().'.code');
+
+ $this->exitCode = $code($input, $output);
+
+ $event->stop();
+
+ return $this->exitCode;
+ });
+ }
+
+ /**
+ * @internal
+ */
+ public function mergeApplicationDefinition(bool $mergeArgs = true): void
+ {
+ $this->command->mergeApplicationDefinition($mergeArgs);
+ }
+
+ public function setDefinition(array|InputDefinition $definition): static
+ {
+ $this->command->setDefinition($definition);
+
+ return $this;
+ }
+
+ public function getDefinition(): InputDefinition
+ {
+ return $this->command->getDefinition();
+ }
+
+ public function getNativeDefinition(): InputDefinition
+ {
+ return $this->command->getNativeDefinition();
+ }
+
+ public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
+ {
+ $this->command->addArgument($name, $mode, $description, $default, $suggestedValues);
+
+ return $this;
+ }
+
+ public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
+ {
+ $this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Calling parent method is required to be used in {@see parent::run()}.
+ */
+ public function setProcessTitle(string $title): static
+ {
+ $this->command->setProcessTitle($title);
+
+ return parent::setProcessTitle($title);
+ }
+
+ public function setHelp(string $help): static
+ {
+ $this->command->setHelp($help);
+
+ return $this;
+ }
+
+ public function getHelp(): string
+ {
+ return $this->command->getHelp();
+ }
+
+ public function getProcessedHelp(): string
+ {
+ return $this->command->getProcessedHelp();
+ }
+
+ public function getSynopsis(bool $short = false): string
+ {
+ return $this->command->getSynopsis($short);
+ }
+
+ public function addUsage(string $usage): static
+ {
+ $this->command->addUsage($usage);
+
+ return $this;
+ }
+
+ public function getUsages(): array
+ {
+ return $this->command->getUsages();
+ }
+
+ public function getHelper(string $name): HelperInterface
+ {
+ return $this->command->getHelper($name);
+ }
+
+ public function run(InputInterface $input, OutputInterface $output): int
+ {
+ $this->input = $input;
+ $this->output = $output;
+ $initialArguments = $input->getArguments();
+ $initialOptions = $input->getOptions();
+ $event = $this->stopwatch->start($this->getName(), 'command');
+
+ try {
+ $this->exitCode = $this->command->run($input, $output);
+ } finally {
+ $event->stop();
+
+ if ($output instanceof ConsoleOutputInterface && $output->isDebug()) {
+ $output->getErrorOutput()->writeln((string) $event);
+ }
+
+ $this->duration = $event->getDuration().' ms';
+ $this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB';
+
+ $this->arguments = $input->getArguments();
+ $this->options = $input->getOptions();
+
+ $this->extractInteractiveInputs($initialArguments, $initialOptions);
+ $this->isInteractive = $this->isInteractive || $this->interactiveInputs;
+ }
+
+ return $this->exitCode;
+ }
+
+ protected function initialize(InputInterface $input, OutputInterface $output): void
+ {
+ $event = $this->stopwatch->start($this->getName().'.init', 'command');
+
+ $this->command->initialize($input, $output);
+
+ $event->stop();
+ }
+
+ protected function interact(InputInterface $input, OutputInterface $output): void
+ {
+ if (!$this->isInteractive = Command::class !== (new \ReflectionMethod($this->command, 'interact'))->getDeclaringClass()->getName()) {
+ return;
+ }
+
+ $event = $this->stopwatch->start($this->getName().'.interact', 'command');
+
+ $this->command->interact($input, $output);
+
+ $event->stop();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $event = $this->stopwatch->start($this->getName().'.execute', 'command');
+
+ $exitCode = $this->command->execute($input, $output);
+
+ $event->stop();
+
+ return $exitCode;
+ }
+
+ private function extractInteractiveInputs(array $initialArguments, array $initialOptions): void
+ {
+ $nativeDefinition = $this->command->getNativeDefinition();
+
+ foreach ($nativeDefinition->getArguments() as $argName => $argument) {
+ if (\array_key_exists($argName, $initialArguments) && $initialArguments[$argName] === $this->arguments[$argName]) {
+ continue;
+ }
+
+ $this->interactiveInputs[$argName] = $this->arguments[$argName];
+ }
+
+ foreach ($nativeDefinition->getOptions() as $optName => $option) {
+ if (\array_key_exists($optName, $initialOptions) && $initialOptions[$optName] === $this->options[$optName]) {
+ continue;
+ }
+
+ $this->interactiveInputs['--'.$optName] = $this->options[$optName];
+ }
+ }
+}
diff --git a/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php
new file mode 100644
index 0000000..b6b637c
--- /dev/null
+++ b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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
+ */
+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;
+}
diff --git a/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php
new file mode 100644
index 0000000..eb49451
--- /dev/null
+++ b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php
@@ -0,0 +1,52 @@
+
+ *
+ * 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
+ */
+class ContainerCommandLoader implements CommandLoaderInterface
+{
+ /**
+ * @param array $commandMap An array with command names as keys and service ids as values
+ */
+ public function __construct(
+ private ContainerInterface $container,
+ private array $commandMap,
+ ) {
+ }
+
+ 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]);
+ }
+
+ public function has(string $name): bool
+ {
+ return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
+ }
+
+ public function getNames(): array
+ {
+ return array_keys($this->commandMap);
+ }
+}
diff --git a/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php
new file mode 100644
index 0000000..2d13139
--- /dev/null
+++ b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php
@@ -0,0 +1,52 @@
+
+ *
+ * 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
+ */
+class FactoryCommandLoader implements CommandLoaderInterface
+{
+ /**
+ * @param callable[] $factories Indexed by command names
+ */
+ public function __construct(
+ private array $factories,
+ ) {
+ }
+
+ public function has(string $name): bool
+ {
+ return isset($this->factories[$name]);
+ }
+
+ 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();
+ }
+
+ public function getNames(): array
+ {
+ return array_keys($this->factories);
+ }
+}
diff --git a/vendor/symfony/console/Completion/CompletionInput.php b/vendor/symfony/console/Completion/CompletionInput.php
new file mode 100644
index 0000000..9f9619e
--- /dev/null
+++ b/vendor/symfony/console/Completion/CompletionInput.php
@@ -0,0 +1,248 @@
+
+ *
+ * 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
+ */
+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 array $tokens;
+ private int $currentIndex;
+ private string $completionType;
+ private ?string $completionName = null;
+ private string $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)([\'"]?)(.+?)(?tokens = $tokens;
+ $input->currentIndex = $currentIndex;
+
+ return $input;
+ }
+
+ 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 ($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 ($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;
+ } 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
+ *
+ * TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component.
+ *
+ * @return self::TYPE_*
+ */
+ 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) {
+ // 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(): string
+ {
+ $str = '';
+ foreach ($this->tokens as $i => $token) {
+ $str .= $token;
+
+ if ($this->currentIndex === $i) {
+ $str .= '|';
+ }
+
+ $str .= ' ';
+ }
+
+ if ($this->currentIndex > $i) {
+ $str .= '|';
+ }
+
+ return rtrim($str);
+ }
+}
diff --git a/vendor/symfony/console/Completion/CompletionSuggestions.php b/vendor/symfony/console/Completion/CompletionSuggestions.php
new file mode 100644
index 0000000..549bbaf
--- /dev/null
+++ b/vendor/symfony/console/Completion/CompletionSuggestions.php
@@ -0,0 +1,97 @@
+
+ *
+ * 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
+ */
+final class CompletionSuggestions
+{
+ private array $valueSuggestions = [];
+ private array $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 $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;
+ }
+}
diff --git a/vendor/symfony/console/Completion/Output/BashCompletionOutput.php b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php
new file mode 100644
index 0000000..c6f76eb
--- /dev/null
+++ b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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
+ */
+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));
+ }
+}
diff --git a/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php
new file mode 100644
index 0000000..659e596
--- /dev/null
+++ b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * 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
+ */
+interface CompletionOutputInterface
+{
+ public function write(CompletionSuggestions $suggestions, OutputInterface $output): void;
+}
diff --git a/vendor/symfony/console/Completion/Output/FishCompletionOutput.php b/vendor/symfony/console/Completion/Output/FishCompletionOutput.php
new file mode 100644
index 0000000..356a974
--- /dev/null
+++ b/vendor/symfony/console/Completion/Output/FishCompletionOutput.php
@@ -0,0 +1,36 @@
+
+ *
+ * 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 Guillaume Aveline
+ */
+class FishCompletionOutput implements CompletionOutputInterface
+{
+ public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
+ {
+ $values = [];
+ foreach ($suggestions->getValueSuggestions() as $value) {
+ $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : '');
+ }
+ foreach ($suggestions->getOptionSuggestions() as $option) {
+ $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
+ if ($option->isNegatable()) {
+ $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
+ }
+ }
+ $output->write(implode("\n", $values));
+ }
+}
diff --git a/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php b/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php
new file mode 100644
index 0000000..bb4ce70
--- /dev/null
+++ b/vendor/symfony/console/Completion/Output/ZshCompletionOutput.php
@@ -0,0 +1,36 @@
+
+ *
+ * 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 Jitendra A
+ */
+class ZshCompletionOutput implements CompletionOutputInterface
+{
+ public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
+ {
+ $values = [];
+ foreach ($suggestions->getValueSuggestions() as $value) {
+ $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : '');
+ }
+ foreach ($suggestions->getOptionSuggestions() as $option) {
+ $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
+ if ($option->isNegatable()) {
+ $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
+ }
+ }
+ $output->write(implode("\n", $values)."\n");
+ }
+}
diff --git a/vendor/symfony/console/Completion/Suggestion.php b/vendor/symfony/console/Completion/Suggestion.php
new file mode 100644
index 0000000..3251b07
--- /dev/null
+++ b/vendor/symfony/console/Completion/Suggestion.php
@@ -0,0 +1,41 @@
+
+ *
+ * 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
+ */
+class Suggestion implements \Stringable
+{
+ public function __construct(
+ private readonly string $value,
+ private readonly string $description = '',
+ ) {
+ }
+
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+
+ public function __toString(): string
+ {
+ return $this->getValue();
+ }
+}
diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php
new file mode 100644
index 0000000..6ae8f32
--- /dev/null
+++ b/vendor/symfony/console/ConsoleEvents.php
@@ -0,0 +1,72 @@
+
+ *
+ * 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
+ */
+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,
+ ];
+}
diff --git a/vendor/symfony/console/Cursor.php b/vendor/symfony/console/Cursor.php
new file mode 100644
index 0000000..e2618cf
--- /dev/null
+++ b/vendor/symfony/console/Cursor.php
@@ -0,0 +1,204 @@
+
+ *
+ * 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
+ */
+final class Cursor
+{
+ /** @var resource */
+ private $input;
+
+ /**
+ * @param resource|null $input
+ */
+ public function __construct(
+ private OutputInterface $output,
+ $input = null,
+ ) {
+ $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 (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) {
+ 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];
+ }
+}
diff --git a/vendor/symfony/console/DataCollector/CommandDataCollector.php b/vendor/symfony/console/DataCollector/CommandDataCollector.php
new file mode 100644
index 0000000..724af54
--- /dev/null
+++ b/vendor/symfony/console/DataCollector/CommandDataCollector.php
@@ -0,0 +1,239 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\DataCollector;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Debug\CliRequest;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\SignalRegistry\SignalMap;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\VarDumper\Cloner\Data;
+
+/**
+ * @internal
+ *
+ * @author Jules Pietri
+ */
+final class CommandDataCollector extends DataCollector
+{
+ public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
+ {
+ if (!$request instanceof CliRequest) {
+ return;
+ }
+
+ $command = $request->command;
+ $application = $command->getApplication();
+
+ $this->data = [
+ 'command' => $command->invokableCommandInfo ?? $this->cloneVar($command->command),
+ 'exit_code' => $command->exitCode,
+ 'interrupted_by_signal' => $command->interruptedBySignal,
+ 'duration' => $command->duration,
+ 'max_memory_usage' => $command->maxMemoryUsage,
+ 'verbosity_level' => match ($command->output->getVerbosity()) {
+ OutputInterface::VERBOSITY_SILENT => 'silent',
+ OutputInterface::VERBOSITY_QUIET => 'quiet',
+ OutputInterface::VERBOSITY_NORMAL => 'normal',
+ OutputInterface::VERBOSITY_VERBOSE => 'verbose',
+ OutputInterface::VERBOSITY_VERY_VERBOSE => 'very verbose',
+ OutputInterface::VERBOSITY_DEBUG => 'debug',
+ },
+ 'interactive' => $command->isInteractive,
+ 'validate_input' => !$command->ignoreValidation,
+ 'enabled' => $command->isEnabled(),
+ 'visible' => !$command->isHidden(),
+ 'input' => $this->cloneVar($command->input),
+ 'output' => $this->cloneVar($command->output),
+ 'interactive_inputs' => array_map($this->cloneVar(...), $command->interactiveInputs),
+ 'signalable' => $command->getSubscribedSignals(),
+ 'handled_signals' => $command->handledSignals,
+ 'helper_set' => array_map($this->cloneVar(...), iterator_to_array($command->getHelperSet())),
+ ];
+
+ $baseDefinition = $application->getDefinition();
+
+ foreach ($command->arguments as $argName => $argValue) {
+ if ($baseDefinition->hasArgument($argName)) {
+ $this->data['application_inputs'][$argName] = $this->cloneVar($argValue);
+ } else {
+ $this->data['arguments'][$argName] = $this->cloneVar($argValue);
+ }
+ }
+
+ foreach ($command->options as $optName => $optValue) {
+ if ($baseDefinition->hasOption($optName)) {
+ $this->data['application_inputs']['--'.$optName] = $this->cloneVar($optValue);
+ } else {
+ $this->data['options'][$optName] = $this->cloneVar($optValue);
+ }
+ }
+ }
+
+ public function getName(): string
+ {
+ return 'command';
+ }
+
+ /**
+ * @return array{
+ * class?: class-string,
+ * executor?: string,
+ * file: string,
+ * line: int,
+ * }
+ */
+ public function getCommand(): array
+ {
+ if (\is_array($this->data['command'])) {
+ return $this->data['command'];
+ }
+
+ $class = $this->data['command']->getType();
+ $r = new \ReflectionMethod($class, 'execute');
+
+ if (Command::class !== $r->getDeclaringClass()) {
+ return [
+ 'executor' => $class.'::'.$r->name,
+ 'file' => $r->getFileName(),
+ 'line' => $r->getStartLine(),
+ ];
+ }
+
+ $r = new \ReflectionClass($class);
+
+ return [
+ 'class' => $class,
+ 'file' => $r->getFileName(),
+ 'line' => $r->getStartLine(),
+ ];
+ }
+
+ public function getInterruptedBySignal(): ?string
+ {
+ if (isset($this->data['interrupted_by_signal'])) {
+ return \sprintf('%s (%d)', SignalMap::getSignalName($this->data['interrupted_by_signal']), $this->data['interrupted_by_signal']);
+ }
+
+ return null;
+ }
+
+ public function getDuration(): string
+ {
+ return $this->data['duration'];
+ }
+
+ public function getMaxMemoryUsage(): string
+ {
+ return $this->data['max_memory_usage'];
+ }
+
+ public function getVerbosityLevel(): string
+ {
+ return $this->data['verbosity_level'];
+ }
+
+ public function getInteractive(): bool
+ {
+ return $this->data['interactive'];
+ }
+
+ public function getValidateInput(): bool
+ {
+ return $this->data['validate_input'];
+ }
+
+ public function getEnabled(): bool
+ {
+ return $this->data['enabled'];
+ }
+
+ public function getVisible(): bool
+ {
+ return $this->data['visible'];
+ }
+
+ public function getInput(): Data
+ {
+ return $this->data['input'];
+ }
+
+ public function getOutput(): Data
+ {
+ return $this->data['output'];
+ }
+
+ /**
+ * @return Data[]
+ */
+ public function getArguments(): array
+ {
+ return $this->data['arguments'] ?? [];
+ }
+
+ /**
+ * @return Data[]
+ */
+ public function getOptions(): array
+ {
+ return $this->data['options'] ?? [];
+ }
+
+ /**
+ * @return Data[]
+ */
+ public function getApplicationInputs(): array
+ {
+ return $this->data['application_inputs'] ?? [];
+ }
+
+ /**
+ * @return Data[]
+ */
+ public function getInteractiveInputs(): array
+ {
+ return $this->data['interactive_inputs'] ?? [];
+ }
+
+ public function getSignalable(): array
+ {
+ return array_map(
+ static fn (int $signal): string => \sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
+ $this->data['signalable']
+ );
+ }
+
+ public function getHandledSignals(): array
+ {
+ $keys = array_map(
+ static fn (int $signal): string => \sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
+ array_keys($this->data['handled_signals'])
+ );
+
+ return array_combine($keys, array_values($this->data['handled_signals']));
+ }
+
+ /**
+ * @return Data[]
+ */
+ public function getHelperSet(): array
+ {
+ return $this->data['helper_set'] ?? [];
+ }
+
+ public function reset(): void
+ {
+ $this->data = [];
+ }
+}
diff --git a/vendor/symfony/console/Debug/CliRequest.php b/vendor/symfony/console/Debug/CliRequest.php
new file mode 100644
index 0000000..412e907
--- /dev/null
+++ b/vendor/symfony/console/Debug/CliRequest.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Debug;
+
+use Symfony\Component\Console\Command\TraceableCommand;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * @internal
+ */
+final class CliRequest extends Request
+{
+ public function __construct(
+ public readonly TraceableCommand $command,
+ ) {
+ parent::__construct(
+ attributes: ['_controller' => $command->command::class, '_virtual_type' => 'command'],
+ server: $_SERVER,
+ );
+ }
+
+ // Methods below allow to populate a profile, thus enable search and filtering
+ public function getUri(): string
+ {
+ if ($this->server->has('SYMFONY_CLI_BINARY_NAME')) {
+ $binary = $this->server->get('SYMFONY_CLI_BINARY_NAME').' console';
+ } else {
+ $binary = $this->server->get('argv')[0];
+ }
+
+ return $binary.' '.$this->command->input;
+ }
+
+ public function getMethod(): string
+ {
+ return $this->command->isInteractive ? 'INTERACTIVE' : 'BATCH';
+ }
+
+ public function getResponse(): Response
+ {
+ return new class($this->command->exitCode) extends Response {
+ public function __construct(int $exitCode)
+ {
+ parent::__construct();
+
+ // getStatusCode() is final and setStatusCode() rejects an exit code
+ $this->statusCode = $exitCode;
+ }
+ };
+ }
+
+ public function getClientIp(): string
+ {
+ $application = $this->command->getApplication();
+
+ return $application->getName().' '.$application->getVersion();
+ }
+}
diff --git a/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php
new file mode 100644
index 0000000..9fef057
--- /dev/null
+++ b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php
@@ -0,0 +1,169 @@
+
+ *
+ * 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\Attribute\AsCommand;
+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
+ */
+class AddConsoleCommandPass implements CompilerPassInterface
+{
+ public function process(ContainerBuilder $container): void
+ {
+ $commandServices = $container->findTaggedServiceIds('console.command', true);
+ $lazyCommandMap = [];
+ $lazyCommandRefs = [];
+ $serviceIds = [];
+
+ foreach ($commandServices as $id => $tags) {
+ $definition = $container->getDefinition($id);
+ $class = $container->getParameterBag()->resolveValue($definition->getClass());
+
+ 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)) {
+ if (!$r->hasMethod('__invoke')) {
+ throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must either be a subclass of "%s" or have an "__invoke()" method.', $id, 'console.command', Command::class));
+ }
+
+ $invokableRef = new Reference($id);
+ $definition = $container->register($id .= '.command', $class = Command::class)
+ ->addMethodCall('setCode', [$invokableRef]);
+ } else {
+ $invokableRef = null;
+ }
+
+ $definition->addTag('container.no_preload');
+
+ /** @var AsCommand|null $attribute */
+ $attribute = ($r->getAttributes(AsCommand::class)[0] ?? null)?->newInstance();
+
+ if (Command::class !== (new \ReflectionMethod($class, 'getDefaultName'))->class) {
+ trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultName()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', $class);
+
+ $defaultName = $class::getDefaultName();
+ } else {
+ $defaultName = $attribute?->name;
+ }
+
+ $aliases = str_replace('%', '%%', $tags[0]['command'] ?? $defaultName ?? '');
+ $aliases = explode('|', $aliases);
+ $commandName = array_shift($aliases);
+
+ if ($isHidden = '' === $commandName) {
+ $commandName = array_shift($aliases);
+ }
+
+ if (null === $commandName) {
+ if ($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;
+ $help = $tags[0]['help'] ?? null;
+ $usages = $tags[0]['usages'] ?? 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 ??= $tag['description'] ?? null;
+ $help ??= $tag['help'] ?? null;
+ $usages ??= $tag['usages'] ?? null;
+ }
+
+ if ($invokableRef) {
+ $help ??= $attribute?->help;
+ $usages ??= $attribute?->usages;
+ }
+
+ $definition->addMethodCall('setName', [$commandName]);
+
+ if ($aliases) {
+ $definition->addMethodCall('setAliases', [$aliases]);
+ }
+
+ if ($isHidden) {
+ $definition->addMethodCall('setHidden', [true]);
+ }
+
+ if ($help && $invokableRef) {
+ $definition->addMethodCall('setHelp', [str_replace('%', '%%', $help)]);
+ }
+
+ if ($usages) {
+ foreach ($usages as $usage) {
+ $definition->addMethodCall('addUsage', [$usage]);
+ }
+ }
+
+ if (!$description) {
+ if (Command::class !== (new \ReflectionMethod($class, 'getDefaultDescription'))->class) {
+ trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultDescription()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', $class);
+
+ $description = $class::getDefaultDescription();
+ } else {
+ $description = $attribute?->description;
+ }
+ }
+
+ if ($description) {
+ $escapedDescription = str_replace('%', '%%', $description);
+ $definition->addMethodCall('setDescription', [$escapedDescription]);
+
+ $container->register('.'.$id.'.lazy', LazyCommand::class)
+ ->setArguments([$commandName, $aliases, $escapedDescription, $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);
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php
new file mode 100644
index 0000000..ce778c1
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/ApplicationDescription.php
@@ -0,0 +1,138 @@
+
+ *
+ * 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
+ *
+ * @internal
+ */
+class ApplicationDescription
+{
+ public const GLOBAL_NAMESPACE = '_global';
+
+ private array $namespaces;
+
+ /**
+ * @var array
+ */
+ private array $commands;
+
+ /**
+ * @var array
+ */
+ private array $aliases = [];
+
+ public function __construct(
+ private Application $application,
+ private ?string $namespace = null,
+ private bool $showHidden = false,
+ ) {
+ }
+
+ 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(): void
+ {
+ $this->commands = [];
+ $this->namespaces = [];
+
+ $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
+ foreach ($this->sortCommands($all) as $namespace => $commands) {
+ $names = [];
+
+ 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];
+ }
+ }
+
+ /**
+ * @return array>
+ */
+ 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;
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php
new file mode 100644
index 0000000..2143a17
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/Descriptor.php
@@ -0,0 +1,74 @@
+
+ *
+ * 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
+ *
+ * @internal
+ */
+abstract class Descriptor implements DescriptorInterface
+{
+ protected OutputInterface $output;
+
+ public function describe(OutputInterface $output, object $object, array $options = []): void
+ {
+ $this->output = $output;
+
+ match (true) {
+ $object instanceof InputArgument => $this->describeInputArgument($object, $options),
+ $object instanceof InputOption => $this->describeInputOption($object, $options),
+ $object instanceof InputDefinition => $this->describeInputDefinition($object, $options),
+ $object instanceof Command => $this->describeCommand($object, $options),
+ $object instanceof Application => $this->describeApplication($object, $options),
+ default => throw new InvalidArgumentException(\sprintf('Object of type "%s" is not describable.', get_debug_type($object))),
+ };
+ }
+
+ protected function write(string $content, bool $decorated = false): void
+ {
+ $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
+ }
+
+ /**
+ * Describes an InputArgument instance.
+ */
+ abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void;
+
+ /**
+ * Describes an InputOption instance.
+ */
+ abstract protected function describeInputOption(InputOption $option, array $options = []): void;
+
+ /**
+ * Describes an InputDefinition instance.
+ */
+ abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void;
+
+ /**
+ * Describes a Command instance.
+ */
+ abstract protected function describeCommand(Command $command, array $options = []): void;
+
+ /**
+ * Describes an Application instance.
+ */
+ abstract protected function describeApplication(Application $application, array $options = []): void;
+}
diff --git a/vendor/symfony/console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php
new file mode 100644
index 0000000..04e5a7c
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/DescriptorInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * 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
+ */
+interface DescriptorInterface
+{
+ public function describe(OutputInterface $output, object $object, array $options = []): void;
+}
diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php
new file mode 100644
index 0000000..9a8e696
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php
@@ -0,0 +1,166 @@
+
+ *
+ * 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
+ *
+ * @internal
+ */
+class JsonDescriptor extends Descriptor
+{
+ protected function describeInputArgument(InputArgument $argument, array $options = []): void
+ {
+ $this->writeData($this->getInputArgumentData($argument), $options);
+ }
+
+ protected function describeInputOption(InputOption $option, array $options = []): void
+ {
+ $this->writeData($this->getInputOptionData($option), $options);
+ if ($option->isNegatable()) {
+ $this->writeData($this->getInputOptionData($option, true), $options);
+ }
+ }
+
+ protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
+ {
+ $this->writeData($this->getInputDefinitionData($definition), $options);
+ }
+
+ protected function describeCommand(Command $command, array $options = []): void
+ {
+ $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
+ }
+
+ protected function describeApplication(Application $application, array $options = []): void
+ {
+ $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): void
+ {
+ $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' => null === $option->getDefault() ? null : !$option->getDefault(),
+ ] : [
+ '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;
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php
new file mode 100644
index 0000000..afa3a69
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php
@@ -0,0 +1,173 @@
+
+ *
+ * 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
+ *
+ * @internal
+ */
+class MarkdownDescriptor extends Descriptor
+{
+ public function describe(OutputInterface $output, object $object, array $options = []): void
+ {
+ $decorated = $output->isDecorated();
+ $output->setDecorated(false);
+
+ parent::describe($output, $object, $options);
+
+ $output->setDecorated($decorated);
+ }
+
+ protected function write(string $content, bool $decorated = true): void
+ {
+ parent::write($content, $decorated);
+ }
+
+ protected function describeInputArgument(InputArgument $argument, array $options = []): void
+ {
+ $this->write(
+ '#### `'.($argument->getName() ?: '')."`\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)).'`'
+ );
+ }
+
+ protected function describeInputOption(InputOption $option, array $options = []): void
+ {
+ $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)).'`'
+ );
+ }
+
+ protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
+ {
+ if ($showArguments = $definition->getArguments()) {
+ $this->write('### Arguments');
+ foreach ($definition->getArguments() as $argument) {
+ $this->write("\n\n");
+ $this->describeInputArgument($argument);
+ }
+ }
+
+ if (\count($definition->getOptions()) > 0) {
+ if ($showArguments) {
+ $this->write("\n\n");
+ }
+
+ $this->write('### Options');
+ foreach ($definition->getOptions() as $option) {
+ $this->write("\n\n");
+ $this->describeInputOption($option);
+ }
+ }
+ }
+
+ protected function describeCommand(Command $command, array $options = []): void
+ {
+ 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(), static fn ($carry, $usage) => $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()), static fn ($carry, $usage) => $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);
+ }
+ }
+
+ protected function describeApplication(Application $application, array $options = []): void
+ {
+ $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(static fn ($commandName) => \sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands'])));
+ }
+
+ foreach ($description->getCommands() as $command) {
+ $this->write("\n\n");
+ $this->describeCommand($command, $options);
+ }
+ }
+
+ 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';
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php b/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php
new file mode 100644
index 0000000..d2dde6f
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php
@@ -0,0 +1,273 @@
+
+ *
+ * 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;
+use Symfony\Component\String\UnicodeString;
+
+class ReStructuredTextDescriptor extends Descriptor
+{
+ //
+ private string $partChar = '=';
+ //
+ private string $chapterChar = '-';
+ //
+ private string $sectionChar = '~';
+ //
+ private string $subsectionChar = '.';
+ //
+ private string $subsubsectionChar = '^';
+ //
+ private string $paragraphsChar = '"';
+
+ private array $visibleNamespaces = [];
+
+ public function describe(OutputInterface $output, object $object, array $options = []): void
+ {
+ $decorated = $output->isDecorated();
+ $output->setDecorated(false);
+
+ parent::describe($output, $object, $options);
+
+ $output->setDecorated($decorated);
+ }
+
+ /**
+ * Override parent method to set $decorated = true.
+ */
+ protected function write(string $content, bool $decorated = true): void
+ {
+ parent::write($content, $decorated);
+ }
+
+ protected function describeInputArgument(InputArgument $argument, array $options = []): void
+ {
+ $this->write(
+ $argument->getName() ?: ''."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\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)).'``'
+ );
+ }
+
+ protected function describeInputOption(InputOption $option, array $options = []): void
+ {
+ $name = '\-\-'.$option->getName();
+ if ($option->isNegatable()) {
+ $name .= '|\-\-no-'.$option->getName();
+ }
+ if ($option->getShortcut()) {
+ $name .= '|-'.str_replace('|', '|-', $option->getShortcut());
+ }
+
+ $optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : '';
+ $optionDescription = (new UnicodeString($optionDescription))->ascii();
+ $this->write(
+ $name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n"
+ .$optionDescription
+ .'- **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)).'``'."\n"
+ );
+ }
+
+ protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
+ {
+ if ($showArguments = ((bool) $definition->getArguments())) {
+ $this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9));
+ foreach ($definition->getArguments() as $argument) {
+ $this->write("\n\n");
+ $this->describeInputArgument($argument);
+ }
+ }
+
+ if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) {
+ if ($showArguments) {
+ $this->write("\n\n");
+ }
+
+ $this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n");
+ foreach ($nonDefaultOptions as $option) {
+ $this->describeInputOption($option);
+ $this->write("\n");
+ }
+ }
+ }
+
+ protected function describeCommand(Command $command, array $options = []): void
+ {
+ if ($options['short'] ?? false) {
+ $this->write(
+ '``'.$command->getName()."``\n"
+ .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
+ .($command->getDescription() ? $command->getDescription()."\n\n" : '')
+ ."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n"
+ .array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
+ );
+
+ return;
+ }
+
+ $command->mergeApplicationDefinition(false);
+
+ foreach ($command->getAliases() as $alias) {
+ $this->write('.. _'.$alias.":\n\n");
+ }
+ $this->write(
+ $command->getName()."\n"
+ .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
+ .($command->getDescription() ? $command->getDescription()."\n\n" : '')
+ ."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n"
+ .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $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);
+ }
+ }
+
+ protected function describeApplication(Application $application, array $options = []): void
+ {
+ $description = new ApplicationDescription($application, $options['namespace'] ?? null);
+ $title = $this->getApplicationTitle($application);
+
+ $this->write($title."\n".str_repeat($this->partChar, Helper::width($title)));
+ $this->createTableOfContents($description, $application);
+ $this->describeCommands($application, $options);
+ }
+
+ private function getApplicationTitle(Application $application): string
+ {
+ if ('UNKNOWN' === $application->getName()) {
+ return 'Console Tool';
+ }
+ if ('UNKNOWN' !== $application->getVersion()) {
+ return \sprintf('%s %s', $application->getName(), $application->getVersion());
+ }
+
+ return $application->getName();
+ }
+
+ private function describeCommands($application, array $options): void
+ {
+ $title = 'Commands';
+ $this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n");
+ foreach ($this->visibleNamespaces as $namespace) {
+ if ('_global' === $namespace) {
+ $commands = $application->all('');
+ $this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n");
+ } else {
+ $commands = $application->all($namespace);
+ $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
+ }
+
+ foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) {
+ $this->describeCommand($command, $options);
+ $this->write("\n\n");
+ }
+ }
+ }
+
+ private function createTableOfContents(ApplicationDescription $description, Application $application): void
+ {
+ $this->setVisibleNamespaces($description);
+ $chapterTitle = 'Table of Contents';
+ $this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n");
+ foreach ($this->visibleNamespaces as $namespace) {
+ if ('_global' === $namespace) {
+ $commands = $application->all('');
+ } else {
+ $commands = $application->all($namespace);
+ $this->write("\n\n");
+ $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
+ }
+ $commands = $this->removeAliasesAndHiddenCommands($commands);
+
+ $this->write("\n\n");
+ $this->write(implode("\n", array_map(static fn ($commandName) => \sprintf('- `%s`_', $commandName), array_keys($commands))));
+ }
+ }
+
+ private function getNonDefaultOptions(InputDefinition $definition): array
+ {
+ $globalOptions = [
+ 'help',
+ 'silent',
+ 'quiet',
+ 'verbose',
+ 'version',
+ 'ansi',
+ 'no-interaction',
+ ];
+ $nonDefaultOptions = [];
+ foreach ($definition->getOptions() as $option) {
+ // Skip global options.
+ if (!\in_array($option->getName(), $globalOptions, true)) {
+ $nonDefaultOptions[] = $option;
+ }
+ }
+
+ return $nonDefaultOptions;
+ }
+
+ private function setVisibleNamespaces(ApplicationDescription $description): void
+ {
+ $commands = $description->getCommands();
+ foreach ($description->getNamespaces() as $namespace) {
+ try {
+ $namespaceCommands = $namespace['commands'];
+ foreach ($namespaceCommands as $key => $commandName) {
+ if (!\array_key_exists($commandName, $commands)) {
+ // If the array key does not exist, then this is an alias.
+ unset($namespaceCommands[$key]);
+ } elseif ($commands[$commandName]->isHidden()) {
+ unset($namespaceCommands[$key]);
+ }
+ }
+ if (!$namespaceCommands) {
+ // If the namespace contained only aliases or hidden commands, skip the namespace.
+ continue;
+ }
+ } catch (\Exception) {
+ }
+ $this->visibleNamespaces[] = $namespace['id'];
+ }
+ }
+
+ private function removeAliasesAndHiddenCommands(array $commands): array
+ {
+ foreach ($commands as $key => $command) {
+ if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) {
+ unset($commands[$key]);
+ }
+ }
+ unset($commands['completion']);
+
+ return $commands;
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php
new file mode 100644
index 0000000..b2e9df5
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/TextDescriptor.php
@@ -0,0 +1,317 @@
+
+ *
+ * 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
+ *
+ * @internal
+ */
+class TextDescriptor extends Descriptor
+{
+ protected function describeInputArgument(InputArgument $argument, array $options = []): void
+ {
+ if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
+ $default = \sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault()));
+ } else {
+ $default = '';
+ }
+
+ $totalWidth = $options['total_width'] ?? Helper::width($argument->getName());
+ $spacingWidth = $totalWidth - Helper::width($argument->getName());
+
+ $this->writeText(\sprintf(' %s %s%s%s',
+ $argument->getName(),
+ str_repeat(' ', $spacingWidth),
+ // + 4 = 2 spaces before , 2 spaces after
+ preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
+ $default
+ ), $options);
+ }
+
+ protected function describeInputOption(InputOption $option, array $options = []): void
+ {
+ if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
+ $default = \sprintf(' [default: %s]', $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(' %s %s%s%s%s',
+ $synopsis,
+ str_repeat(' ', $spacingWidth),
+ // + 4 = 2 spaces before , 2 spaces after
+ preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
+ $default,
+ $option->isArray() ? ' (multiple values allowed)' : ''
+ ), $options);
+ }
+
+ protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
+ {
+ $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
+ foreach ($definition->getArguments() as $argument) {
+ $totalWidth = max($totalWidth, Helper::width($argument->getName()));
+ }
+
+ if ($definition->getArguments()) {
+ $this->writeText('Arguments:', $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('Options:', $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]));
+ }
+ }
+ }
+
+ protected function describeCommand(Command $command, array $options = []): void
+ {
+ $command->mergeApplicationDefinition(false);
+
+ if ($description = $command->getDescription()) {
+ $this->writeText('Description:', $options);
+ $this->writeText("\n");
+ $this->writeText(' '.$description);
+ $this->writeText("\n\n");
+ }
+
+ $this->writeText('Usage:', $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('Help:', $options);
+ $this->writeText("\n");
+ $this->writeText(' '.str_replace("\n", "\n ", $help), $options);
+ $this->writeText("\n");
+ }
+ }
+
+ protected function describeApplication(Application $application, array $options = []): void
+ {
+ $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("Usage:\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(static fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces)))));
+
+ if ($describedNamespace) {
+ $this->writeText(\sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options);
+ } else {
+ $this->writeText('Available commands:', $options);
+ }
+
+ foreach ($namespaces as $namespace) {
+ $namespace['commands'] = array_filter($namespace['commands'], static fn ($name) => isset($commands[$name]));
+
+ if (!$namespace['commands']) {
+ continue;
+ }
+
+ if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
+ $this->writeText("\n");
+ $this->writeText(' '.$namespace['id'].'', $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(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
+ }
+ }
+
+ $this->writeText("\n");
+ }
+ }
+
+ private function writeText(string $content, array $options = []): void
+ {
+ $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 $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;
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php
new file mode 100644
index 0000000..0005555
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/XmlDescriptor.php
@@ -0,0 +1,230 @@
+
+ *
+ * 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
+ *
+ * @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;
+ }
+
+ protected function describeInputArgument(InputArgument $argument, array $options = []): void
+ {
+ $this->writeDocument($this->getInputArgumentDocument($argument));
+ }
+
+ protected function describeInputOption(InputOption $option, array $options = []): void
+ {
+ $this->writeDocument($this->getInputOptionDocument($option));
+ }
+
+ protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
+ {
+ $this->writeDocument($this->getInputDefinitionDocument($definition));
+ }
+
+ protected function describeCommand(Command $command, array $options = []): void
+ {
+ $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
+ }
+
+ protected function describeApplication(Application $application, array $options = []): void
+ {
+ $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
+ }
+
+ /**
+ * Appends document children to parent node.
+ */
+ private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void
+ {
+ foreach ($importedParent->childNodes as $childNode) {
+ $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
+ }
+ }
+
+ /**
+ * Writes DOM document.
+ */
+ private function writeDocument(\DOMDocument $dom): void
+ {
+ $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'));
+
+ 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;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleAlarmEvent.php b/vendor/symfony/console/Event/ConsoleAlarmEvent.php
new file mode 100644
index 0000000..876ab59
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleAlarmEvent.php
@@ -0,0 +1,47 @@
+
+ *
+ * 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;
+
+final class ConsoleAlarmEvent extends ConsoleEvent
+{
+ public function __construct(
+ Command $command,
+ InputInterface $input,
+ OutputInterface $output,
+ private int|false $exitCode = 0,
+ ) {
+ parent::__construct($command, $input, $output);
+ }
+
+ public function setExitCode(int $exitCode): void
+ {
+ if ($exitCode < 0 || $exitCode > 255) {
+ throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
+ }
+
+ $this->exitCode = $exitCode;
+ }
+
+ public function abortExit(): void
+ {
+ $this->exitCode = false;
+ }
+
+ public function getExitCode(): int|false
+ {
+ return $this->exitCode;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php
new file mode 100644
index 0000000..0757a23
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleCommandEvent.php
@@ -0,0 +1,54 @@
+
+ *
+ * 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 executing code before the command is
+ * going to be executed.
+ *
+ * Changing the input arguments will have no effect.
+ *
+ * @author Fabien Potencier
+ */
+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;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleErrorEvent.php b/vendor/symfony/console/Event/ConsoleErrorEvent.php
new file mode 100644
index 0000000..1c0d626
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleErrorEvent.php
@@ -0,0 +1,58 @@
+
+ *
+ * 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
+ */
+final class ConsoleErrorEvent extends ConsoleEvent
+{
+ private int $exitCode;
+
+ public function __construct(
+ InputInterface $input,
+ OutputInterface $output,
+ private \Throwable $error,
+ ?Command $command = null,
+ ) {
+ parent::__construct($command, $input, $output);
+ }
+
+ 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->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);
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php
new file mode 100644
index 0000000..2f9f077
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleEvent.php
@@ -0,0 +1,56 @@
+
+ *
+ * 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
+ */
+class ConsoleEvent extends Event
+{
+ public function __construct(
+ protected ?Command $command,
+ private InputInterface $input,
+ private OutputInterface $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;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleSignalEvent.php b/vendor/symfony/console/Event/ConsoleSignalEvent.php
new file mode 100644
index 0000000..b27f08a
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleSignalEvent.php
@@ -0,0 +1,56 @@
+
+ *
+ * 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
+ */
+final class ConsoleSignalEvent extends ConsoleEvent
+{
+ public function __construct(
+ Command $command,
+ InputInterface $input,
+ OutputInterface $output,
+ private int $handlingSignal,
+ private int|false $exitCode = 0,
+ ) {
+ parent::__construct($command, $input, $output);
+ }
+
+ public function getHandlingSignal(): int
+ {
+ return $this->handlingSignal;
+ }
+
+ public function setExitCode(int $exitCode): void
+ {
+ if ($exitCode < 0 || $exitCode > 255) {
+ throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
+ }
+
+ $this->exitCode = $exitCode;
+ }
+
+ public function abortExit(): void
+ {
+ $this->exitCode = false;
+ }
+
+ public function getExitCode(): int|false
+ {
+ return $this->exitCode;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php
new file mode 100644
index 0000000..38f7253
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleTerminateEvent.php
@@ -0,0 +1,50 @@
+
+ *
+ * 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
+ * @author Jules Pietri
+ */
+final class ConsoleTerminateEvent extends ConsoleEvent
+{
+ public function __construct(
+ Command $command,
+ InputInterface $input,
+ OutputInterface $output,
+ private int $exitCode,
+ private readonly ?int $interruptingSignal = null,
+ ) {
+ parent::__construct($command, $input, $output);
+ }
+
+ public function setExitCode(int $exitCode): void
+ {
+ $this->exitCode = $exitCode;
+ }
+
+ public function getExitCode(): int
+ {
+ return $this->exitCode;
+ }
+
+ public function getInterruptingSignal(): ?int
+ {
+ return $this->interruptingSignal;
+ }
+}
diff --git a/vendor/symfony/console/EventListener/ErrorListener.php b/vendor/symfony/console/EventListener/ErrorListener.php
new file mode 100644
index 0000000..9acb0e4
--- /dev/null
+++ b/vendor/symfony/console/EventListener/ErrorListener.php
@@ -0,0 +1,89 @@
+
+ *
+ * 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
+ * @author Robin Chalas
+ */
+class ErrorListener implements EventSubscriberInterface
+{
+ public function __construct(
+ private ?LoggerInterface $logger = null,
+ ) {
+ }
+
+ public function onConsoleError(ConsoleErrorEvent $event): void
+ {
+ if (null === $this->logger) {
+ return;
+ }
+
+ $error = $event->getError();
+
+ if (!$inputString = self::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): void
+ {
+ if (null === $this->logger) {
+ return;
+ }
+
+ $exitCode = $event->getExitCode();
+
+ if (0 === $exitCode) {
+ return;
+ }
+
+ if (!$inputString = self::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()?->getName();
+ $inputString = (string) $event->getInput();
+
+ if ($commandName) {
+ return str_replace(["'$commandName'", "\"$commandName\""], $commandName, $inputString);
+ }
+
+ return $inputString;
+ }
+}
diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php
new file mode 100644
index 0000000..246f04f
--- /dev/null
+++ b/vendor/symfony/console/Exception/CommandNotFoundException.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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
+ */
+class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
+{
+ /**
+ * @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,
+ private array $alternatives = [],
+ int $code = 0,
+ ?\Throwable $previous = null,
+ ) {
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getAlternatives(): array
+ {
+ return $this->alternatives;
+ }
+}
diff --git a/vendor/symfony/console/Exception/ExceptionInterface.php b/vendor/symfony/console/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..1624e13
--- /dev/null
+++ b/vendor/symfony/console/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * 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
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/symfony/console/Exception/InvalidArgumentException.php b/vendor/symfony/console/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..c81eb3a
--- /dev/null
+++ b/vendor/symfony/console/Exception/InvalidArgumentException.php
@@ -0,0 +1,40 @@
+
+ *
+ * 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
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+ /**
+ * @internal
+ */
+ public static function fromEnumValue(string $name, string $value, array|\Closure $suggestedValues): self
+ {
+ $error = \sprintf('The value "%s" is not valid for the "%s" argument.', $value, $name);
+
+ if (\is_array($suggestedValues)) {
+ $error .= \sprintf(' Supported values are "%s".', implode('", "', $suggestedValues));
+ }
+
+ return new self($error);
+ }
+
+ /**
+ * @internal
+ */
+ public static function fromInvalidType(string $name, string $value, string $type): self
+ {
+ return new self(\sprintf('The value "%s" is not valid for the "%s" argument. Expected a value of type "%s".', $value, $name, $type));
+ }
+}
diff --git a/vendor/symfony/console/Exception/InvalidOptionException.php b/vendor/symfony/console/Exception/InvalidOptionException.php
new file mode 100644
index 0000000..0d8bac3
--- /dev/null
+++ b/vendor/symfony/console/Exception/InvalidOptionException.php
@@ -0,0 +1,42 @@
+
+ *
+ * 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
+ */
+class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
+{
+ /**
+ * @internal
+ */
+ public static function fromEnumValue(string $name, string $value, array|\Closure $suggestedValues): self
+ {
+ $error = \sprintf('The value "%s" is not valid for the "%s" option.', $value, $name);
+
+ if (\is_array($suggestedValues)) {
+ $error .= \sprintf(' Supported values are "%s".', implode('", "', $suggestedValues));
+ }
+
+ return new self($error);
+ }
+
+ /**
+ * @internal
+ */
+ public static function fromInvalidType(string $name, string $value, string $type): self
+ {
+ return new self(\sprintf('The value "%s" is not valid for the "%s" option. Expected a value of type "%s".', $value, $name, $type));
+ }
+}
diff --git a/vendor/symfony/console/Exception/LogicException.php b/vendor/symfony/console/Exception/LogicException.php
new file mode 100644
index 0000000..fc37b8d
--- /dev/null
+++ b/vendor/symfony/console/Exception/LogicException.php
@@ -0,0 +1,19 @@
+
+ *
+ * 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
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/console/Exception/MissingInputException.php b/vendor/symfony/console/Exception/MissingInputException.php
new file mode 100644
index 0000000..04f02ad
--- /dev/null
+++ b/vendor/symfony/console/Exception/MissingInputException.php
@@ -0,0 +1,21 @@
+
+ *
+ * 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ý
+ */
+class MissingInputException extends RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/console/Exception/NamespaceNotFoundException.php b/vendor/symfony/console/Exception/NamespaceNotFoundException.php
new file mode 100644
index 0000000..dd16e45
--- /dev/null
+++ b/vendor/symfony/console/Exception/NamespaceNotFoundException.php
@@ -0,0 +1,21 @@
+
+ *
+ * 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
+ */
+class NamespaceNotFoundException extends CommandNotFoundException
+{
+}
diff --git a/vendor/symfony/console/Exception/RunCommandFailedException.php b/vendor/symfony/console/Exception/RunCommandFailedException.php
new file mode 100644
index 0000000..e25ad2b
--- /dev/null
+++ b/vendor/symfony/console/Exception/RunCommandFailedException.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+use Symfony\Component\Console\Messenger\RunCommandContext;
+
+/**
+ * @author Kevin Bond
+ */
+final class RunCommandFailedException extends RuntimeException
+{
+ public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context)
+ {
+ parent::__construct(
+ $exception instanceof \Throwable ? $exception->getMessage() : $exception,
+ $exception instanceof \Throwable && \is_int($exception->getCode()) ? $exception->getCode() : 0,
+ $exception instanceof \Throwable ? $exception : null,
+ );
+ }
+}
diff --git a/vendor/symfony/console/Exception/RuntimeException.php b/vendor/symfony/console/Exception/RuntimeException.php
new file mode 100644
index 0000000..51d7d80
--- /dev/null
+++ b/vendor/symfony/console/Exception/RuntimeException.php
@@ -0,0 +1,19 @@
+
+ *
+ * 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
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/console/Formatter/NullOutputFormatter.php b/vendor/symfony/console/Formatter/NullOutputFormatter.php
new file mode 100644
index 0000000..5c11c76
--- /dev/null
+++ b/vendor/symfony/console/Formatter/NullOutputFormatter.php
@@ -0,0 +1,51 @@
+
+ *
+ * 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
+ */
+final class NullOutputFormatter implements OutputFormatterInterface
+{
+ private NullOutputFormatterStyle $style;
+
+ public function format(?string $message): ?string
+ {
+ return null;
+ }
+
+ public function getStyle(string $name): OutputFormatterStyleInterface
+ {
+ // to comply with the interface we must return a OutputFormatterStyleInterface
+ return $this->style ??= new NullOutputFormatterStyle();
+ }
+
+ public function hasStyle(string $name): bool
+ {
+ return false;
+ }
+
+ public function isDecorated(): bool
+ {
+ return false;
+ }
+
+ public function setDecorated(bool $decorated): void
+ {
+ // do nothing
+ }
+
+ public function setStyle(string $name, OutputFormatterStyleInterface $style): void
+ {
+ // do nothing
+ }
+}
diff --git a/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php b/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php
new file mode 100644
index 0000000..06fa6e4
--- /dev/null
+++ b/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php
@@ -0,0 +1,48 @@
+
+ *
+ * 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
+ */
+final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
+{
+ public function apply(string $text): string
+ {
+ return $text;
+ }
+
+ public function setBackground(?string $color): void
+ {
+ // do nothing
+ }
+
+ public function setForeground(?string $color): void
+ {
+ // do nothing
+ }
+
+ public function setOption(string $option): void
+ {
+ // do nothing
+ }
+
+ public function setOptions(array $options): void
+ {
+ // do nothing
+ }
+
+ public function unsetOption(string $option): void
+ {
+ // do nothing
+ }
+}
diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php
new file mode 100644
index 0000000..21800eb
--- /dev/null
+++ b/vendor/symfony/console/Formatter/OutputFormatter.php
@@ -0,0 +1,492 @@
+
+ *
+ * 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\Component\Console\Helper\Helper;
+
+use function Symfony\Component\String\b;
+
+/**
+ * Formatter class for console output.
+ *
+ * @author Konstantin Kudryashov
+ * @author Roland Franssen
+ */
+class OutputFormatter implements WrappableOutputFormatterInterface
+{
+ private const ANSI_PATTERN = '\\x1B(?:\\[[0-9;:]*m|\\]8;[^;]*;[^\\x1B]*\\x1B\\\\)';
+ private const ANSI_REGEX = '/'.self::ANSI_PATTERN.'/';
+
+ private array $styles = [];
+ private OutputFormatterStyleStack $styleStack;
+ private array $charWidths = [];
+
+ 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 = str_replace(['<', '>'], ['\\<', '\\>'], $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(
+ private bool $decorated = false,
+ array $styles = [],
+ ) {
+ $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();
+ }
+
+ public function setDecorated(bool $decorated): void
+ {
+ $this->decorated = $decorated;
+ }
+
+ public function isDecorated(): bool
+ {
+ return $this->decorated;
+ }
+
+ public function setStyle(string $name, OutputFormatterStyleInterface $style): void
+ {
+ $this->styles[strtolower($name)] = $style;
+ }
+
+ public function hasStyle(string $name): bool
+ {
+ return isset($this->styles[strtolower($name)]);
+ }
+
+ public function getStyle(string $name): OutputFormatterStyleInterface
+ {
+ if (!$this->hasStyle($name)) {
+ throw new InvalidArgumentException(\sprintf('Undefined style: "%s".', $name));
+ }
+
+ return $this->styles[strtolower($name)];
+ }
+
+ public function format(?string $message): ?string
+ {
+ return $this->formatAndWrap($message, 0);
+ }
+
+ public function formatAndWrap(?string $message, int $width): string
+ {
+ if (null === $message) {
+ return '';
+ }
+
+ $offset = 0;
+ $output = '';
+ $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
+ $closeTagRegex = '[a-z][^<>]*+';
+ $currentLineLength = 0;
+ $lineBreak = false;
+ 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;
+ }
+
+ // tags are delimited by "<" and ">", which are ASCII, so a tag offset can never
+ // fall inside a multibyte sequence: slicing by bytes is safe here
+ $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $width, $currentLineLength, $lineBreak);
+ $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, $width, $currentLineLength, $lineBreak);
+ } elseif ($open) {
+ $this->styleStack->push($style);
+ } else {
+ $this->styleStack->pop($style);
+ }
+ }
+
+ $output .= $this->applyCurrentStyle(substr($message, $offset), $width, $currentLineLength, $lineBreak);
+
+ if ($lineBreak) {
+ $output .= "\n";
+ }
+
+ if ($width && str_contains($output, "\r")) {
+ // keep "\r\n" line breaks together when ANSI codes end the line
+ $output = preg_replace('/\r((?:'.self::ANSI_PATTERN.')++)(?=\n|$)/', "\$1\r", $output);
+ }
+
+ return str_replace("\0", '\\', $output);
+ }
+
+ 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[$lowerString = strtolower($string)])) {
+ return $this->styles[$lowerString];
+ }
+
+ 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, int $width, int &$currentLineLength, bool &$lineBreak): string
+ {
+ if ('' === $text) {
+ return '';
+ }
+
+ if (!$width || str_contains($text, "\e") && '' === preg_replace(self::ANSI_REGEX, '', $text)) {
+ $text = str_replace(['\\<', '\\>'], ['<', '>'], $text);
+
+ return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
+ }
+
+ $text = str_replace(['\\<', '\\>'], ['<', '>'], $text);
+ $prefix = '';
+ $visibleText = str_contains($text, "\e") ? preg_replace(self::ANSI_REGEX, '', $text) : $text;
+
+ if ($currentLineLength >= $width) {
+ // the current line is full: drop the whitespace that follows and break the line before the next character
+ if (str_contains($text, "\e")) {
+ $text = preg_replace_callback('/^(?:[\\x00\\x09\\x0B\\x0D\\x20]|'.self::ANSI_PATTERN.')++/', static function ($match) {
+ preg_match_all(self::ANSI_REGEX, $match[0], $ansi);
+
+ return implode('', $ansi[0]);
+ }, $text);
+ $visibleText = ltrim($visibleText, " \t\r\0\x0B");
+ } else {
+ $text = $visibleText = ltrim($text, " \t\r\0\x0B");
+ }
+
+ // whitespace at the end of the message still breaks the line
+ if ($lineBreak = '' === $visibleText) {
+ return '' === $text || !$this->isDecorated() ? $text : $this->styleStack->getCurrent()->apply($text);
+ }
+
+ if ("\n" !== $visibleText[0]) {
+ $prefix = "\n";
+ }
+
+ $currentLineLength = 0;
+ }
+
+ $trailingNewlines = substr($visibleText, \strlen(rtrim($visibleText, "\r\n")));
+
+ if ($currentLineLength) {
+ $lines = explode("\n", $text, 2);
+ $eol = "\n";
+ if (isset($lines[1]) && str_ends_with($lines[0], "\r")) {
+ $lines[0] = substr($lines[0], 0, -1);
+ $eol = "\r\n";
+ }
+ $i = $width - $currentLineLength;
+ if ($widths = $this->getWidths($lines[0])) {
+ $room = $i;
+ $i = \count($widths);
+ foreach ($widths as $j => $charWidth) {
+ if (0 > $room -= $charWidth) {
+ $i = $j;
+ break;
+ }
+ }
+ }
+ $prefix = ($i ? $this->substr($lines[0], 0, $i) : '')."\n";
+ $text = $i ? $this->substr($lines[0], $i) : $lines[0];
+
+ if (isset($lines[1])) {
+ // $prefix may contain the full first line in which the \n is already a part of $prefix.
+ if ('' !== $text) {
+ $text .= $eol;
+ } else {
+ $prefix = substr($prefix, 0, -1).$eol;
+ }
+
+ $text .= $lines[1];
+ }
+ }
+
+ $text = $prefix.$this->addLineBreaks($text, $width);
+ if (str_contains($text, "\e")) {
+ $parts = preg_split('/('.self::ANSI_PATTERN.')/', $text, -1, \PREG_SPLIT_DELIM_CAPTURE);
+ $sequences = '';
+ for ($i = \count($parts) - 1; $i >= 0; --$i) {
+ if ($i % 2) {
+ $sequences = $parts[$i].$sequences;
+ } elseif ('' !== $parts[$i] = rtrim($parts[$i], "\r\n")) {
+ break;
+ }
+ }
+ $text = implode('', \array_slice($parts, 0, $i + 1)).$sequences;
+ } else {
+ $text = rtrim($text, "\r\n");
+ }
+ $text .= $trailingNewlines;
+
+ $lines = explode("\n", $text);
+
+ foreach ($lines as $i => $line) {
+ $lineLength = array_sum($this->getWidths($line)) ?: Helper::length(str_contains($line, "\e") ? preg_replace(self::ANSI_REGEX, '', $line) : $line);
+ $currentLineLength = 0 === $i ? $currentLineLength + $lineLength : $lineLength;
+ }
+
+ foreach ($lines as $i => $line) {
+ $lines[$i] = $this->isDecorated() ? $this->styleStack->getCurrent()->apply($line) : $line;
+ }
+
+ return implode("\n", $lines);
+ }
+
+ private function addLineBreaks(string $text, int $width): string
+ {
+ $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8';
+ $widths = $this->getWidths($text);
+
+ if (!$widths && !str_contains($text, "\e") && !str_contains($text, "\r\n")) {
+ return b($text)->toUnicodeString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding);
+ }
+
+ $chars = [];
+ $mask = $pending = '';
+ foreach ($this->getTokens($text) as $token) {
+ if (preg_match(self::ANSI_REGEX, $token)) {
+ $pending .= $token;
+ continue;
+ }
+ $chars[] = [$pending, $token];
+ $pending = '';
+ $mask .= "\n" === $token || "\r\n" === $token ? '#' : (' ' === $token ? ' ' : '?');
+ }
+
+ if (!$chars) {
+ return $pending;
+ }
+
+ $wrapped = rtrim($mask, '#');
+ $mask = ($widths ? $this->wordwrap($wrapped, $width, $widths) : wordwrap($wrapped, $width, '#', true)).substr($mask, \strlen($wrapped));
+ $result = '';
+ $j = 0;
+ foreach (str_split($mask) as $char) {
+ if ('#' === $char) {
+ $eol = "\n";
+ if (isset($chars[$j]) && ("\n" === $chars[$j][1] || "\r\n" === $chars[$j][1] || ' ' === $chars[$j][1])) {
+ $eol = ' ' === $chars[$j][1] ? "\n" : $chars[$j][1];
+ $result .= $chars[$j++][0];
+ }
+ $result .= $eol;
+ } else {
+ $result .= implode('', $chars[$j++]);
+ }
+ }
+
+ return $result.$pending;
+ }
+
+ private function substr(string $text, int $from, ?int $length = null): string
+ {
+ if (!str_contains($text, "\e")) {
+ return Helper::substr($text, $from, $length);
+ }
+
+ $result = '';
+ $position = 0;
+ foreach ($this->getTokens($text) as $token) {
+ $isAnsi = preg_match(self::ANSI_REGEX, $token);
+ if ($position >= $from && (null === $length || $position < $from + $length || $isAnsi && $position === $from + $length)
+ && (!$isAnsi || !$from || $position > $from)
+ ) {
+ $result .= $token;
+ }
+ if (!$isAnsi) {
+ ++$position;
+ }
+ }
+
+ return $result;
+ }
+
+ private function getTokens(string $text): iterable
+ {
+ $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8';
+ foreach (preg_split('/('.self::ANSI_PATTERN.')/', $text, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $part) {
+ if (preg_match(self::ANSI_REGEX, $part)) {
+ yield $part;
+ continue;
+ }
+ foreach (b($part)->toUnicodeString($encoding)->chunk() as $char) {
+ yield (string) $char->toByteString($encoding);
+ }
+ }
+ }
+
+ /**
+ * Returns the number of columns of each visible character, or an empty array when none uses more than one column.
+ *
+ * @return list
+ */
+ private function getWidths(string $text): array
+ {
+ if (!preg_match('/[^\x00-\x{10FF}]/u', $text)) {
+ return [];
+ }
+
+ $widths = [];
+ $wide = false;
+ foreach ($this->getTokens($text) as $token) {
+ if (!preg_match(self::ANSI_REGEX, $token)) {
+ $widths[] = $charWidth = 2 < \strlen($token) ? ($this->charWidths[$token] ??= Helper::width($token)) : 1;
+ $wide = $wide || 1 < $charWidth;
+ }
+ }
+
+ return $wide ? $widths : [];
+ }
+
+ /**
+ * Wraps a mask like wordwrap() does when cutting long words, but using the number of columns of each character.
+ *
+ * @param list $widths
+ */
+ private function wordwrap(string $mask, int $width, array $widths): string
+ {
+ $result = '';
+ $start = $lastSpace = $lineWidth = 0;
+
+ for ($i = 0, $length = \strlen($mask); $i < $length; ++$i) {
+ if ('#' === $mask[$i]) {
+ $result .= substr($mask, $start, $i + 1 - $start);
+ $start = $lastSpace = $i + 1;
+ $lineWidth = 0;
+
+ continue;
+ }
+
+ if (' ' === $mask[$i]) {
+ if ($lineWidth >= $width) {
+ $result .= substr($mask, $start, $i - $start).'#';
+ $start = $i + 1;
+ $lineWidth = 0;
+ } else {
+ ++$lineWidth;
+ }
+ $lastSpace = $i;
+
+ continue;
+ }
+
+ if ($lineWidth + $widths[$i] > $width && $start < $lastSpace) {
+ $result .= substr($mask, $start, $lastSpace - $start).'#';
+ $start = $lastSpace = $lastSpace + 1;
+ $lineWidth = array_sum(\array_slice($widths, $start, $i - $start));
+ }
+
+ // cut long words, and also before a wide character that still doesn't fit after breaking at the last space
+ if ($lineWidth && $lineWidth + $widths[$i] > $width) {
+ $result .= substr($mask, $start, $i - $start).'#';
+ $start = $lastSpace = $i;
+ $lineWidth = 0;
+ }
+
+ $lineWidth += $widths[$i];
+ }
+
+ return $result.substr($mask, $start);
+ }
+}
diff --git a/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Formatter/OutputFormatterInterface.php
new file mode 100644
index 0000000..947347f
--- /dev/null
+++ b/vendor/symfony/console/Formatter/OutputFormatterInterface.php
@@ -0,0 +1,52 @@
+
+ *
+ * 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
+ */
+interface OutputFormatterInterface
+{
+ /**
+ * Sets the decorated flag.
+ */
+ public function setDecorated(bool $decorated): void;
+
+ /**
+ * Whether the output will decorate messages.
+ */
+ public function isDecorated(): bool;
+
+ /**
+ * Sets a new style.
+ */
+ public function setStyle(string $name, OutputFormatterStyleInterface $style): void;
+
+ /**
+ * 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;
+}
diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php
new file mode 100644
index 0000000..de800f9
--- /dev/null
+++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php
@@ -0,0 +1,88 @@
+
+ *
+ * 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
+ */
+class OutputFormatterStyle implements OutputFormatterStyleInterface
+{
+ private Color $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);
+ }
+
+ public function setForeground(?string $color): void
+ {
+ $this->color = new Color($foreground = $color ?: '', $this->background, $this->options);
+ $this->foreground = $foreground;
+ }
+
+ public function setBackground(?string $color): void
+ {
+ $this->color = new Color($this->foreground, $background = $color ?: '', $this->options);
+ $this->background = $background;
+ }
+
+ public function setHref(string $url): void
+ {
+ $this->href = $url;
+ }
+
+ public function setOption(string $option): void
+ {
+ $this->color = new Color($this->foreground, $this->background, [...$this->options, $option]);
+ $this->options[] = $option;
+ }
+
+ public function unsetOption(string $option): void
+ {
+ $this->options = array_diff($this->options, [$option]);
+ $this->color = new Color($this->foreground, $this->background, $this->options);
+ }
+
+ public function setOptions(array $options): void
+ {
+ $this->color = new Color($this->foreground, $this->background, $options);
+ $this->options = $options;
+ }
+
+ public function apply(string $text): string
+ {
+ $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
+ && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100)
+ && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']);
+
+ if (null !== $this->href && $this->handlesHrefGracefully) {
+ $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
+ }
+
+ return $this->color->apply($text);
+ }
+}
diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php
new file mode 100644
index 0000000..0374192
--- /dev/null
+++ b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php
@@ -0,0 +1,50 @@
+
+ *
+ * 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
+ */
+interface OutputFormatterStyleInterface
+{
+ /**
+ * Sets style foreground color.
+ */
+ public function setForeground(?string $color): void;
+
+ /**
+ * Sets style background color.
+ */
+ public function setBackground(?string $color): void;
+
+ /**
+ * Sets some specific style option.
+ */
+ public function setOption(string $option): void;
+
+ /**
+ * Unsets some specific style option.
+ */
+ public function unsetOption(string $option): void;
+
+ /**
+ * Sets multiple style options at once.
+ */
+ public function setOptions(array $options): void;
+
+ /**
+ * Applies the style to a given text.
+ */
+ public function apply(string $text): string;
+}
diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php
new file mode 100644
index 0000000..4985213
--- /dev/null
+++ b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php
@@ -0,0 +1,103 @@
+
+ *
+ * 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
+ */
+class OutputFormatterStyleStack implements ResetInterface
+{
+ /**
+ * @var OutputFormatterStyleInterface[]
+ */
+ private array $styles = [];
+
+ private OutputFormatterStyleInterface $emptyStyle;
+
+ public function __construct(?OutputFormatterStyleInterface $emptyStyle = null)
+ {
+ $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle();
+ $this->reset();
+ }
+
+ /**
+ * Resets stack (ie. empty internal arrays).
+ */
+ public function reset(): void
+ {
+ $this->styles = [];
+ }
+
+ /**
+ * Pushes a style in the stack.
+ */
+ public function push(OutputFormatterStyleInterface $style): void
+ {
+ $this->styles[] = $style;
+ }
+
+ /**
+ * Pops a style from the stack.
+ *
+ * @throws InvalidArgumentException When style tags incorrectly nested
+ */
+ public function pop(?OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface
+ {
+ if (!$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 (!$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;
+ }
+}
diff --git a/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php
new file mode 100644
index 0000000..412d997
--- /dev/null
+++ b/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * 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
+ */
+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): string;
+}
diff --git a/vendor/symfony/console/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php
new file mode 100644
index 0000000..dfdb8a8
--- /dev/null
+++ b/vendor/symfony/console/Helper/DebugFormatterHelper.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+/**
+ * Helps outputting debug information when running an external program from a command.
+ *
+ * An external program can be a Process, an HTTP request, or anything else.
+ *
+ * @author Fabien Potencier
+ */
+class DebugFormatterHelper extends Helper
+{
+ private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
+ private array $started = [];
+ private int $count = -1;
+
+ /**
+ * Starts a debug formatting session.
+ */
+ public function start(string $id, string $message, string $prefix = 'RUN'): string
+ {
+ $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)];
+
+ return \sprintf("%s %s > %s>\n", $this->getBorder($id), $prefix, $message);
+ }
+
+ /**
+ * Adds progress to a formatting session.
+ */
+ public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string
+ {
+ $message = '';
+
+ if ($error) {
+ if (isset($this->started[$id]['out'])) {
+ $message .= "\n";
+ unset($this->started[$id]['out']);
+ }
+ if (!isset($this->started[$id]['err'])) {
+ $message .= \sprintf('%s %s > ', $this->getBorder($id), $errorPrefix);
+ $this->started[$id]['err'] = true;
+ }
+
+ $message .= str_replace("\n", \sprintf("\n%s %s > ", $this->getBorder($id), $errorPrefix), $buffer);
+ } else {
+ if (isset($this->started[$id]['err'])) {
+ $message .= "\n";
+ unset($this->started[$id]['err']);
+ }
+ if (!isset($this->started[$id]['out'])) {
+ $message .= \sprintf('%s %s > ', $this->getBorder($id), $prefix);
+ $this->started[$id]['out'] = true;
+ }
+
+ $message .= str_replace("\n", \sprintf("\n%s %s > ", $this->getBorder($id), $prefix), $buffer);
+ }
+
+ return $message;
+ }
+
+ /**
+ * Stops a formatting session.
+ */
+ public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string
+ {
+ $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
+
+ if ($successful) {
+ return \sprintf("%s%s %s > %s>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
+ }
+
+ $message = \sprintf("%s%s %s > %s>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
+
+ unset($this->started[$id]['out'], $this->started[$id]['err']);
+
+ return $message;
+ }
+
+ private function getBorder(string $id): string
+ {
+ return \sprintf(' >', self::COLORS[$this->started[$id]['border']]);
+ }
+
+ public function getName(): string
+ {
+ return 'debug_formatter';
+ }
+}
diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php
new file mode 100644
index 0000000..9422271
--- /dev/null
+++ b/vendor/symfony/console/Helper/DescriptorHelper.php
@@ -0,0 +1,91 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Descriptor\DescriptorInterface;
+use Symfony\Component\Console\Descriptor\JsonDescriptor;
+use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
+use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor;
+use Symfony\Component\Console\Descriptor\TextDescriptor;
+use Symfony\Component\Console\Descriptor\XmlDescriptor;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * This class adds helper method to describe objects in various formats.
+ *
+ * @author Jean-François Simon
+ */
+class DescriptorHelper extends Helper
+{
+ /**
+ * @var DescriptorInterface[]
+ */
+ private array $descriptors = [];
+
+ public function __construct()
+ {
+ $this
+ ->register('txt', new TextDescriptor())
+ ->register('xml', new XmlDescriptor())
+ ->register('json', new JsonDescriptor())
+ ->register('md', new MarkdownDescriptor())
+ ->register('rst', new ReStructuredTextDescriptor())
+ ;
+ }
+
+ /**
+ * Describes an object if supported.
+ *
+ * Available options are:
+ * * format: string, the output format name
+ * * raw_text: boolean, sets output type as raw
+ *
+ * @throws InvalidArgumentException when the given format is not supported
+ */
+ public function describe(OutputInterface $output, ?object $object, array $options = []): void
+ {
+ $options = array_merge([
+ 'raw_text' => false,
+ 'format' => 'txt',
+ ], $options);
+
+ if (!isset($this->descriptors[$options['format']])) {
+ throw new InvalidArgumentException(\sprintf('Unsupported format "%s".', $options['format']));
+ }
+
+ $descriptor = $this->descriptors[$options['format']];
+ $descriptor->describe($output, $object, $options);
+ }
+
+ /**
+ * Registers a descriptor.
+ *
+ * @return $this
+ */
+ public function register(string $format, DescriptorInterface $descriptor): static
+ {
+ $this->descriptors[$format] = $descriptor;
+
+ return $this;
+ }
+
+ public function getName(): string
+ {
+ return 'descriptor';
+ }
+
+ public function getFormats(): array
+ {
+ return array_keys($this->descriptors);
+ }
+}
diff --git a/vendor/symfony/console/Helper/Dumper.php b/vendor/symfony/console/Helper/Dumper.php
new file mode 100644
index 0000000..2c70519
--- /dev/null
+++ b/vendor/symfony/console/Helper/Dumper.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\VarDumper\Cloner\ClonerInterface;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+/**
+ * @author Roland Franssen
+ */
+final class Dumper
+{
+ private \Closure $handler;
+
+ public function __construct(
+ private OutputInterface $output,
+ private ?CliDumper $dumper = null,
+ private ?ClonerInterface $cloner = null,
+ ) {
+ if (class_exists(CliDumper::class)) {
+ $this->handler = function ($var): string {
+ $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
+ $dumper->setColors($this->output->isDecorated());
+
+ return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true));
+ };
+ } else {
+ $this->handler = static fn ($var): string => match (true) {
+ null === $var => 'null',
+ true === $var => 'true',
+ false === $var => 'false',
+ \is_string($var) => '"'.$var.'"',
+ default => rtrim(print_r($var, true)),
+ };
+ }
+ }
+
+ public function __invoke(mixed $var): string
+ {
+ return ($this->handler)($var);
+ }
+}
diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php
new file mode 100644
index 0000000..3646b3d
--- /dev/null
+++ b/vendor/symfony/console/Helper/FormatterHelper.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Formatter\OutputFormatter;
+
+/**
+ * The Formatter class provides helpers to format messages.
+ *
+ * @author Fabien Potencier
+ */
+class FormatterHelper extends Helper
+{
+ /**
+ * Formats a message within a section.
+ */
+ public function formatSection(string $section, string $message, string $style = 'info'): string
+ {
+ return \sprintf('<%s>[%s]%s> %s', $style, $section, $style, $message);
+ }
+
+ /**
+ * Formats a message as a block of text.
+ */
+ public function formatBlock(string|array $messages, string $style, bool $large = false): string
+ {
+ if (!\is_array($messages)) {
+ $messages = [$messages];
+ }
+
+ $len = 0;
+ $lines = [];
+ foreach ($messages as $message) {
+ $message = OutputFormatter::escape($message);
+ $lines[] = \sprintf($large ? ' %s ' : ' %s ', $message);
+ $len = max(self::width($message) + ($large ? 4 : 2), $len);
+ }
+
+ $messages = $large ? [str_repeat(' ', $len)] : [];
+ for ($i = 0; isset($lines[$i]); ++$i) {
+ $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i]));
+ }
+ if ($large) {
+ $messages[] = str_repeat(' ', $len);
+ }
+
+ for ($i = 0; isset($messages[$i]); ++$i) {
+ $messages[$i] = \sprintf('<%s>%s%s>', $style, $messages[$i], $style);
+ }
+
+ return implode("\n", $messages);
+ }
+
+ /**
+ * Truncates a message to the given length.
+ */
+ public function truncate(string $message, int $length, string $suffix = '...'): string
+ {
+ $computedLength = $length - self::width($suffix);
+
+ if ($computedLength > self::width($message)) {
+ return $message;
+ }
+
+ return self::substr($message, 0, $length).$suffix;
+ }
+
+ public function getName(): string
+ {
+ return 'formatter';
+ }
+}
diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php
new file mode 100644
index 0000000..46e7e2f
--- /dev/null
+++ b/vendor/symfony/console/Helper/Helper.php
@@ -0,0 +1,167 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Formatter\OutputFormatterInterface;
+use Symfony\Component\String\UnicodeString;
+
+/**
+ * Helper is the base class for all helper classes.
+ *
+ * @author Fabien Potencier
+ */
+abstract class Helper implements HelperInterface
+{
+ protected ?HelperSet $helperSet = null;
+
+ public function setHelperSet(?HelperSet $helperSet): void
+ {
+ $this->helperSet = $helperSet;
+ }
+
+ public function getHelperSet(): ?HelperSet
+ {
+ return $this->helperSet;
+ }
+
+ /**
+ * Returns the width of a string, using mb_strwidth if it is available.
+ * The width is how many characters positions the string will use.
+ */
+ public static function width(?string $string): int
+ {
+ $string ??= '';
+
+ if (preg_match('//u', $string)) {
+ $string = preg_replace('/[\p{Cc}\x7F]++/u', '', $string, -1, $count);
+
+ return (new UnicodeString($string))->width(false) + $count;
+ }
+
+ if (false === $encoding = mb_detect_encoding($string, null, true)) {
+ return \strlen($string);
+ }
+
+ return mb_strwidth($string, $encoding);
+ }
+
+ /**
+ * Returns the length of a string, using mb_strlen if it is available.
+ * The length is related to how many bytes the string will use.
+ */
+ public static function length(?string $string): int
+ {
+ $string ??= '';
+
+ if (preg_match('//u', $string)) {
+ return (new UnicodeString($string))->length();
+ }
+
+ if (false === $encoding = mb_detect_encoding($string, null, true)) {
+ return \strlen($string);
+ }
+
+ return mb_strlen($string, $encoding);
+ }
+
+ /**
+ * Returns the subset of a string, using mb_substr if it is available.
+ */
+ public static function substr(?string $string, int $from, ?int $length = null): string
+ {
+ $string ??= '';
+
+ if (preg_match('//u', $string)) {
+ return (new UnicodeString($string))->slice($from, $length);
+ }
+
+ if (false === $encoding = mb_detect_encoding($string, null, true)) {
+ return substr($string, $from, $length);
+ }
+
+ return mb_substr($string, $from, $length, $encoding);
+ }
+
+ public static function formatTime(int|float $secs, int $precision = 1): string
+ {
+ $ms = (int) ($secs * 1000);
+ $secs = (int) floor($secs);
+
+ if (0 === $ms) {
+ return '< 1 ms';
+ }
+
+ static $timeFormats = [
+ [1, 'ms'],
+ [1000, 's'],
+ [60000, 'min'],
+ [3600000, 'h'],
+ [86_400_000, 'd'],
+ ];
+
+ $times = [];
+ foreach ($timeFormats as $index => $format) {
+ $milliSeconds = isset($timeFormats[$index + 1]) ? $ms % $timeFormats[$index + 1][0] : $ms;
+
+ if (isset($times[$index - $precision])) {
+ unset($times[$index - $precision]);
+ }
+
+ if (0 === $milliSeconds) {
+ continue;
+ }
+
+ $unitCount = ($milliSeconds / $format[0]);
+ $times[$index] = $unitCount.' '.$format[1];
+
+ if ($ms === $milliSeconds) {
+ break;
+ }
+
+ $ms -= $milliSeconds;
+ }
+
+ return implode(', ', array_reverse($times));
+ }
+
+ public static function formatMemory(int $memory): string
+ {
+ if ($memory >= 1024 * 1024 * 1024) {
+ return \sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
+ }
+
+ if ($memory >= 1024 * 1024) {
+ return \sprintf('%.1f MiB', $memory / 1024 / 1024);
+ }
+
+ if ($memory >= 1024) {
+ return \sprintf('%d KiB', $memory / 1024);
+ }
+
+ return \sprintf('%d B', $memory);
+ }
+
+ public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string): string
+ {
+ $isDecorated = $formatter->isDecorated();
+ $formatter->setDecorated(false);
+ // remove <...> formatting
+ $string = $formatter->format($string ?? '');
+ // remove already formatted characters
+ $string = preg_replace("/\033\[[^m]*m/", '', $string ?? '');
+ // remove terminal hyperlinks
+ $string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? '');
+ $formatter->setDecorated($isDecorated);
+
+ return $string;
+ }
+}
diff --git a/vendor/symfony/console/Helper/HelperInterface.php b/vendor/symfony/console/Helper/HelperInterface.php
new file mode 100644
index 0000000..8c4da3c
--- /dev/null
+++ b/vendor/symfony/console/Helper/HelperInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+/**
+ * HelperInterface is the interface all helpers must implement.
+ *
+ * @author Fabien Potencier
+ */
+interface HelperInterface
+{
+ /**
+ * Sets the helper set associated with this helper.
+ */
+ public function setHelperSet(?HelperSet $helperSet): void;
+
+ /**
+ * Gets the helper set associated with this helper.
+ */
+ public function getHelperSet(): ?HelperSet;
+
+ /**
+ * Returns the canonical name of this helper.
+ */
+ public function getName(): string;
+}
diff --git a/vendor/symfony/console/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php
new file mode 100644
index 0000000..ffe756c
--- /dev/null
+++ b/vendor/symfony/console/Helper/HelperSet.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+
+/**
+ * HelperSet represents a set of helpers to be used with a command.
+ *
+ * @author Fabien Potencier
+ *
+ * @implements \IteratorAggregate
+ */
+class HelperSet implements \IteratorAggregate
+{
+ /** @var array */
+ private array $helpers = [];
+
+ /**
+ * @param HelperInterface[] $helpers
+ */
+ public function __construct(array $helpers = [])
+ {
+ foreach ($helpers as $alias => $helper) {
+ $this->set($helper, \is_int($alias) ? null : $alias);
+ }
+ }
+
+ public function set(HelperInterface $helper, ?string $alias = null): void
+ {
+ $this->helpers[$helper->getName()] = $helper;
+ if (null !== $alias) {
+ $this->helpers[$alias] = $helper;
+ }
+
+ $helper->setHelperSet($this);
+ }
+
+ /**
+ * Returns true if the helper if defined.
+ */
+ public function has(string $name): bool
+ {
+ return isset($this->helpers[$name]);
+ }
+
+ /**
+ * Gets a helper value.
+ *
+ * @throws InvalidArgumentException if the helper is not defined
+ */
+ public function get(string $name): HelperInterface
+ {
+ if (!$this->has($name)) {
+ throw new InvalidArgumentException(\sprintf('The helper "%s" is not defined.', $name));
+ }
+
+ return $this->helpers[$name];
+ }
+
+ public function getIterator(): \Traversable
+ {
+ return new \ArrayIterator($this->helpers);
+ }
+}
diff --git a/vendor/symfony/console/Helper/InputAwareHelper.php b/vendor/symfony/console/Helper/InputAwareHelper.php
new file mode 100644
index 0000000..47126bd
--- /dev/null
+++ b/vendor/symfony/console/Helper/InputAwareHelper.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Input\InputAwareInterface;
+use Symfony\Component\Console\Input\InputInterface;
+
+/**
+ * An implementation of InputAwareInterface for Helpers.
+ *
+ * @author Wouter J
+ */
+abstract class InputAwareHelper extends Helper implements InputAwareInterface
+{
+ protected InputInterface $input;
+
+ public function setInput(InputInterface $input): void
+ {
+ $this->input = $input;
+ }
+}
diff --git a/vendor/symfony/console/Helper/OutputWrapper.php b/vendor/symfony/console/Helper/OutputWrapper.php
new file mode 100644
index 0000000..1e3ac90
--- /dev/null
+++ b/vendor/symfony/console/Helper/OutputWrapper.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+/**
+ * Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
+ * answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN).
+ *
+ * (?:
+ * # -- Words/Characters
+ * ( # (1 start)
+ * (?> # Atomic Group - Match words with valid breaks
+ * .{1,16} # 1-N characters
+ * # Followed by one of 4 prioritized, non-linebreak whitespace
+ * (?: # break types:
+ * (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace
+ * [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace )
+ * | (?= \r? \n ) # 2. - Ahead a linebreak
+ * | $ # 3. - EOS
+ * | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace
+ * )
+ * ) # End atomic group
+ * |
+ * .{1,16} # No valid word breaks, just break on the N'th character
+ * ) # (1 end)
+ * (?: \r? \n )? # Optional linebreak after Words/Characters
+ * |
+ * # -- Or, Linebreak
+ * (?: \r? \n | $ ) # Stand alone linebreak or at EOS
+ * )
+ *
+ * @author Krisztián Ferenczi
+ *
+ * @see https://stackoverflow.com/a/20434776/1476819
+ */
+final class OutputWrapper
+{
+ private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
+ private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+';
+ private const URL_PATTERN = 'https?://\S+';
+
+ public function __construct(
+ private bool $allowCutUrls = false,
+ ) {
+ }
+
+ public function wrap(string $text, int $width, string $break = "\n"): string
+ {
+ if (!$width) {
+ return $text;
+ }
+
+ $tagPattern = \sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
+ $limitPattern = "{1,$width}";
+ $patternBlocks = [$tagPattern];
+ if (!$this->allowCutUrls) {
+ $patternBlocks[] = self::URL_PATTERN;
+ }
+ $patternBlocks[] = '.';
+ $blocks = implode('|', $patternBlocks);
+ $rowPattern = "(?:$blocks)$limitPattern";
+ // the u modifier counts characters instead of bytes, but preg_replace() returns null
+ // on malformed UTF-8, which would empty the text
+ $modifiers = preg_match('//u', $text) ? 'imux' : 'imx';
+ $pattern = \sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#%2$s', $rowPattern, $modifiers);
+ $output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);
+
+ return str_replace(' '.$break, $break, $output);
+ }
+}
diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php
new file mode 100644
index 0000000..2b12ec5
--- /dev/null
+++ b/vendor/symfony/console/Helper/ProcessHelper.php
@@ -0,0 +1,137 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Process;
+
+/**
+ * The ProcessHelper class provides helpers to run external processes.
+ *
+ * @author Fabien Potencier
+ *
+ * @final
+ */
+class ProcessHelper extends Helper
+{
+ /**
+ * Runs an external process.
+ *
+ * @param array|Process $cmd An instance of Process or an array of the command and arguments
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ */
+ public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
+ {
+ if (!class_exists(Process::class)) {
+ throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
+ }
+
+ if ($output instanceof ConsoleOutputInterface) {
+ $output = $output->getErrorOutput();
+ }
+
+ $formatter = $this->getHelperSet()->get('debug_formatter');
+
+ if ($cmd instanceof Process) {
+ $cmd = [$cmd];
+ }
+
+ if (\is_string($cmd[0] ?? null)) {
+ $process = new Process($cmd);
+ $cmd = [];
+ } elseif (($cmd[0] ?? null) instanceof Process) {
+ $process = $cmd[0];
+ unset($cmd[0]);
+ } else {
+ throw new \InvalidArgumentException(\sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
+ }
+
+ if ($verbosity <= $output->getVerbosity()) {
+ $output->write($formatter->start(spl_object_id($process), $this->escapeString($process->getCommandLine())));
+ }
+
+ if ($output->isDebug()) {
+ $callback = $this->wrapCallback($output, $process, $callback);
+ }
+
+ $process->run($callback, $cmd);
+
+ if ($verbosity <= $output->getVerbosity()) {
+ $message = $process->isSuccessful() ? 'Command ran successfully' : \sprintf('%s Command did not run successfully', $process->getExitCode());
+ $output->write($formatter->stop(spl_object_id($process), $message, $process->isSuccessful()));
+ }
+
+ if (!$process->isSuccessful() && null !== $error) {
+ $output->writeln(\sprintf('%s', $this->escapeString($error)));
+ }
+
+ return $process;
+ }
+
+ /**
+ * Runs the process.
+ *
+ * This is identical to run() except that an exception is thrown if the process
+ * exits with a non-zero exit code.
+ *
+ * @param array|Process $cmd An instance of Process or a command to run
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @throws ProcessFailedException
+ *
+ * @see run()
+ */
+ public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
+ {
+ $process = $this->run($output, $cmd, $error, $callback, $verbosity);
+
+ if (!$process->isSuccessful()) {
+ throw new ProcessFailedException($process);
+ }
+
+ return $process;
+ }
+
+ /**
+ * Wraps a Process callback to add debugging output.
+ */
+ public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable
+ {
+ if ($output instanceof ConsoleOutputInterface) {
+ $output = $output->getErrorOutput();
+ }
+
+ $formatter = $this->getHelperSet()->get('debug_formatter');
+
+ return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
+ $output->write($formatter->progress(spl_object_id($process), $this->escapeString($buffer), Process::ERR === $type));
+
+ if (null !== $callback) {
+ $callback($type, $buffer);
+ }
+ };
+ }
+
+ private function escapeString(string $str): string
+ {
+ return str_replace('<', '\\<', $str);
+ }
+
+ public function getName(): string
+ {
+ return 'process';
+ }
+}
diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php
new file mode 100644
index 0000000..3982a3a
--- /dev/null
+++ b/vendor/symfony/console/Helper/ProgressBar.php
@@ -0,0 +1,647 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Cursor;
+use Symfony\Component\Console\Exception\LogicException;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\ConsoleSectionOutput;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Terminal;
+
+/**
+ * The ProgressBar provides helpers to display progress output.
+ *
+ * @author Fabien Potencier
+ * @author Chris Jones
+ */
+final class ProgressBar
+{
+ public const FORMAT_VERBOSE = 'verbose';
+ public const FORMAT_VERY_VERBOSE = 'very_verbose';
+ public const FORMAT_DEBUG = 'debug';
+ public const FORMAT_NORMAL = 'normal';
+
+ private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax';
+ private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax';
+ private const FORMAT_DEBUG_NOMAX = 'debug_nomax';
+ private const FORMAT_NORMAL_NOMAX = 'normal_nomax';
+
+ private int $barWidth = 28;
+ private string $barChar;
+ private string $emptyBarChar = '-';
+ private string $progressChar = '>';
+ private ?string $format = null;
+ private ?string $internalFormat = null;
+ private ?int $redrawFreq = 1;
+ private int $writeCount = 0;
+ private float $lastWriteTime = 0;
+ private float $minSecondsBetweenRedraws = 0;
+ private float $maxSecondsBetweenRedraws = 1;
+ private OutputInterface $output;
+ private int $step = 0;
+ private int $startingStep = 0;
+ private ?int $max = null;
+ private int $startTime;
+ private int $stepWidth;
+ private float $percent = 0.0;
+ private array $messages = [];
+ private bool $overwrite = true;
+ private Terminal $terminal;
+ private ?string $previousMessage = null;
+ private Cursor $cursor;
+ private array $placeholders = [];
+
+ private static array $formatters;
+ private static array $formats;
+
+ /**
+ * @param int $max Maximum steps (0 if unknown)
+ */
+ public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25)
+ {
+ if ($output instanceof ConsoleOutputInterface) {
+ $output = $output->getErrorOutput();
+ }
+
+ $this->output = $output;
+ $this->setMaxSteps($max);
+ $this->terminal = new Terminal();
+
+ if (0 < $minSecondsBetweenRedraws) {
+ $this->redrawFreq = null;
+ $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
+ }
+
+ if (!$this->output->isDecorated()) {
+ // disable overwrite when output does not support ANSI codes.
+ $this->overwrite = false;
+
+ // set a reasonable redraw frequency so output isn't flooded
+ $this->redrawFreq = null;
+ }
+
+ $this->startTime = time();
+ $this->cursor = new Cursor($output);
+ }
+
+ /**
+ * Sets a placeholder formatter for a given name, globally for all instances of ProgressBar.
+ *
+ * This method also allow you to override an existing placeholder.
+ *
+ * @param string $name The placeholder name (including the delimiter char like %)
+ * @param callable(ProgressBar):string $callable A PHP callable
+ */
+ public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
+ {
+ self::$formatters ??= self::initPlaceholderFormatters();
+
+ self::$formatters[$name] = $callable;
+ }
+
+ /**
+ * Gets the placeholder formatter for a given name.
+ *
+ * @param string $name The placeholder name (including the delimiter char like %)
+ */
+ public static function getPlaceholderFormatterDefinition(string $name): ?callable
+ {
+ self::$formatters ??= self::initPlaceholderFormatters();
+
+ return self::$formatters[$name] ?? null;
+ }
+
+ /**
+ * Sets a placeholder formatter for a given name, for this instance only.
+ *
+ * @param callable(ProgressBar):string $callable A PHP callable
+ */
+ public function setPlaceholderFormatter(string $name, callable $callable): void
+ {
+ $this->placeholders[$name] = $callable;
+ }
+
+ /**
+ * Gets the placeholder formatter for a given name.
+ *
+ * @param string $name The placeholder name (including the delimiter char like %)
+ */
+ public function getPlaceholderFormatter(string $name): ?callable
+ {
+ return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name);
+ }
+
+ /**
+ * Sets a format for a given name.
+ *
+ * This method also allow you to override an existing format.
+ *
+ * @param string $name The format name
+ * @param string $format A format string
+ */
+ public static function setFormatDefinition(string $name, string $format): void
+ {
+ self::$formats ??= self::initFormats();
+
+ self::$formats[$name] = $format;
+ }
+
+ /**
+ * Gets the format for a given name.
+ *
+ * @param string $name The format name
+ */
+ public static function getFormatDefinition(string $name): ?string
+ {
+ self::$formats ??= self::initFormats();
+
+ return self::$formats[$name] ?? null;
+ }
+
+ /**
+ * Associates a text with a named placeholder.
+ *
+ * The text is displayed when the progress bar is rendered but only
+ * when the corresponding placeholder is part of the custom format line
+ * (by wrapping the name with %).
+ *
+ * @param string $message The text to associate with the placeholder
+ * @param string $name The name of the placeholder
+ */
+ public function setMessage(string $message, string $name = 'message'): void
+ {
+ $this->messages[$name] = $message;
+ }
+
+ public function getMessage(string $name = 'message'): ?string
+ {
+ return $this->messages[$name] ?? null;
+ }
+
+ public function getStartTime(): int
+ {
+ return $this->startTime;
+ }
+
+ public function getMaxSteps(): int
+ {
+ return $this->max ?? 0;
+ }
+
+ public function getProgress(): int
+ {
+ return $this->step;
+ }
+
+ private function getStepWidth(): int
+ {
+ return $this->stepWidth;
+ }
+
+ public function getProgressPercent(): float
+ {
+ return $this->percent;
+ }
+
+ public function getBarOffset(): float
+ {
+ return floor(null !== $this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth);
+ }
+
+ public function getEstimated(): float
+ {
+ if (0 === $this->step || $this->step === $this->startingStep) {
+ return 0;
+ }
+
+ return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max);
+ }
+
+ public function getRemaining(): float
+ {
+ if (0 === $this->step || $this->step === $this->startingStep) {
+ return 0;
+ }
+
+ return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step));
+ }
+
+ public function setBarWidth(int $size): void
+ {
+ $this->barWidth = max(1, $size);
+ }
+
+ public function getBarWidth(): int
+ {
+ return $this->barWidth;
+ }
+
+ public function setBarCharacter(string $char): void
+ {
+ $this->barChar = $char;
+ }
+
+ public function getBarCharacter(): string
+ {
+ return $this->barChar ?? (null !== $this->max ? '=' : $this->emptyBarChar);
+ }
+
+ public function setEmptyBarCharacter(string $char): void
+ {
+ $this->emptyBarChar = $char;
+ }
+
+ public function getEmptyBarCharacter(): string
+ {
+ return $this->emptyBarChar;
+ }
+
+ public function setProgressCharacter(string $char): void
+ {
+ $this->progressChar = $char;
+ }
+
+ public function getProgressCharacter(): string
+ {
+ return $this->progressChar;
+ }
+
+ public function setFormat(string $format): void
+ {
+ $this->format = null;
+ $this->internalFormat = $format;
+ }
+
+ /**
+ * Sets the redraw frequency.
+ *
+ * @param int|null $freq The frequency in steps
+ */
+ public function setRedrawFrequency(?int $freq): void
+ {
+ $this->redrawFreq = null !== $freq ? max(1, $freq) : null;
+ }
+
+ public function minSecondsBetweenRedraws(float $seconds): void
+ {
+ $this->minSecondsBetweenRedraws = $seconds;
+ }
+
+ public function maxSecondsBetweenRedraws(float $seconds): void
+ {
+ $this->maxSecondsBetweenRedraws = $seconds;
+ }
+
+ /**
+ * Returns an iterator that will automatically update the progress bar when iterated.
+ *
+ * @template TKey
+ * @template TValue
+ *
+ * @param iterable $iterable
+ * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
+ *
+ * @return iterable
+ */
+ public function iterate(iterable $iterable, ?int $max = null): iterable
+ {
+ if (0 === $max) {
+ $max = null;
+ }
+
+ $max ??= is_countable($iterable) ? \count($iterable) : null;
+
+ if (0 === $max) {
+ $this->max = 0;
+ $this->stepWidth = 2;
+ $this->finish();
+
+ return;
+ }
+
+ $this->start($max);
+
+ foreach ($iterable as $key => $value) {
+ yield $key => $value;
+
+ $this->advance();
+ }
+
+ $this->finish();
+ }
+
+ /**
+ * Starts the progress output.
+ *
+ * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
+ * @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar)
+ */
+ public function start(?int $max = null, int $startAt = 0): void
+ {
+ $this->startTime = time();
+ $this->step = $startAt;
+ $this->startingStep = $startAt;
+
+ $startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0;
+
+ if (null !== $max) {
+ $this->setMaxSteps($max);
+ }
+
+ $this->display();
+ }
+
+ /**
+ * Advances the progress output X steps.
+ *
+ * @param int $step Number of steps to advance
+ */
+ public function advance(int $step = 1): void
+ {
+ $this->setProgress($this->step + $step);
+ }
+
+ /**
+ * Sets whether to overwrite the progressbar, false for new line.
+ */
+ public function setOverwrite(bool $overwrite): void
+ {
+ $this->overwrite = $overwrite;
+ }
+
+ public function setProgress(int $step): void
+ {
+ if ($this->max && $step > $this->max) {
+ $this->max = $step;
+ } elseif ($step < 0) {
+ $step = 0;
+ }
+
+ $redrawFreq = $this->redrawFreq ?? (($this->max ?? 10) / 10);
+ $prevPeriod = $redrawFreq ? (int) ($this->step / $redrawFreq) : 0;
+ $currPeriod = $redrawFreq ? (int) ($step / $redrawFreq) : 0;
+ $this->step = $step;
+ $this->percent = match ($this->max) {
+ null => 0,
+ 0 => 1,
+ default => (float) $this->step / $this->max,
+ };
+ $timeInterval = microtime(true) - $this->lastWriteTime;
+
+ // Draw regardless of other limits
+ if ($this->max === $step) {
+ $this->display();
+
+ return;
+ }
+
+ // Throttling
+ if ($timeInterval < $this->minSecondsBetweenRedraws) {
+ return;
+ }
+
+ // Draw each step period, but not too late
+ if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
+ $this->display();
+ }
+ }
+
+ public function setMaxSteps(?int $max): void
+ {
+ if (0 === $max) {
+ $max = null;
+ }
+
+ $this->format = null;
+ if (null === $max) {
+ $this->max = null;
+ $this->stepWidth = 4;
+ } else {
+ $this->max = max(0, $max);
+ $this->stepWidth = Helper::width((string) $this->max);
+ }
+ }
+
+ /**
+ * Finishes the progress output.
+ */
+ public function finish(): void
+ {
+ if (null === $this->max) {
+ $this->max = $this->step;
+ }
+
+ if (($this->step === $this->max || null === $this->max) && !$this->overwrite) {
+ // prevent double 100% output
+ return;
+ }
+
+ $this->setProgress($this->max ?? $this->step);
+ }
+
+ /**
+ * Outputs the current progress string.
+ */
+ public function display(): void
+ {
+ if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
+ return;
+ }
+
+ if (null === $this->format) {
+ $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
+ }
+
+ $this->overwrite($this->buildLine());
+ }
+
+ /**
+ * Removes the progress bar from the current line.
+ *
+ * This is useful if you wish to write some output
+ * while a progress bar is running.
+ * Call display() to show the progress bar again.
+ */
+ public function clear(): void
+ {
+ if (!$this->overwrite) {
+ return;
+ }
+
+ if (null === $this->format) {
+ $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
+ }
+
+ $this->overwrite('');
+ }
+
+ private function setRealFormat(string $format): void
+ {
+ // try to use the _nomax variant if available
+ if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
+ $this->format = self::getFormatDefinition($format.'_nomax');
+ } elseif (null !== self::getFormatDefinition($format)) {
+ $this->format = self::getFormatDefinition($format);
+ } else {
+ $this->format = $format;
+ }
+ }
+
+ /**
+ * Overwrites a previous message to the output.
+ */
+ private function overwrite(string $message): void
+ {
+ if ($this->previousMessage === $message) {
+ return;
+ }
+
+ $originalMessage = $message;
+
+ if ($this->overwrite) {
+ if (null !== $this->previousMessage) {
+ if ($this->output instanceof ConsoleSectionOutput) {
+ $messageLines = explode("\n", $this->previousMessage);
+ $lineCount = \count($messageLines);
+
+ $lastLineWithoutDecoration = Helper::removeDecoration($this->output->getFormatter(), end($messageLines) ?? '');
+
+ // When the last previous line is empty (without formatting) it is already cleared by the section output, so we don't need to clear it again
+ if ('' === $lastLineWithoutDecoration) {
+ --$lineCount;
+ }
+
+ $this->output->clear($lineCount);
+ } else {
+ $lineCount = substr_count($this->previousMessage, "\n");
+ for ($i = 0; $i < $lineCount; ++$i) {
+ $this->cursor->moveToColumn(1);
+ $this->cursor->clearLine();
+ $this->cursor->moveUp();
+ }
+
+ $this->cursor->moveToColumn(1);
+ $this->cursor->clearLine();
+ }
+ }
+ } elseif ($this->step > 0) {
+ $message = \PHP_EOL.$message;
+ }
+
+ $this->previousMessage = $originalMessage;
+ $this->lastWriteTime = microtime(true);
+
+ $this->output->write($message);
+ ++$this->writeCount;
+ }
+
+ private function determineBestFormat(): string
+ {
+ return match ($this->output->getVerbosity()) {
+ // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
+ OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX,
+ OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX,
+ OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX,
+ default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX,
+ };
+ }
+
+ private static function initPlaceholderFormatters(): array
+ {
+ return [
+ 'bar' => static function (self $bar, OutputInterface $output) {
+ $completeBars = $bar->getBarOffset();
+ $display = str_repeat($bar->getBarCharacter(), $completeBars);
+ if ($completeBars < $bar->getBarWidth()) {
+ $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter()));
+ $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), max(0, $emptyBars));
+ }
+
+ return $display;
+ },
+ 'elapsed' => static fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2),
+ 'remaining' => static function (self $bar) {
+ if (null === $bar->max) {
+ throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
+ }
+
+ return Helper::formatTime($bar->getRemaining(), 2);
+ },
+ 'estimated' => static function (self $bar) {
+ if (null === $bar->max) {
+ throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
+ }
+
+ return Helper::formatTime($bar->getEstimated(), 2);
+ },
+ 'memory' => static fn (self $bar) => Helper::formatMemory(memory_get_usage(true)),
+ 'current' => static fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT),
+ 'max' => static fn (self $bar) => $bar->getMaxSteps(),
+ 'percent' => static fn (self $bar) => floor($bar->getProgressPercent() * 100),
+ ];
+ }
+
+ private static function initFormats(): array
+ {
+ return [
+ self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%',
+ self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]',
+
+ self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
+ self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',
+
+ self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
+ self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',
+
+ self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
+ self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
+ ];
+ }
+
+ private function buildLine(): string
+ {
+ \assert(null !== $this->format);
+
+ $regex = '{%([a-z\-_]+)(?:\:([^%]+))?%}i';
+ $callback = function ($matches) {
+ if ($formatter = $this->getPlaceholderFormatter($matches[1])) {
+ $text = $formatter($this, $this->output);
+ } elseif (isset($this->messages[$matches[1]])) {
+ $text = $this->messages[$matches[1]];
+ } else {
+ return $matches[0];
+ }
+
+ if (isset($matches[2])) {
+ $text = \sprintf('%'.$matches[2], $text);
+ }
+
+ return $text;
+ };
+ $line = preg_replace_callback($regex, $callback, $this->format);
+
+ // gets string length for each sub line with multiline format
+ $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line));
+
+ $linesWidth = max($linesLength);
+
+ $terminalWidth = $this->terminal->getWidth();
+ if ($linesWidth <= $terminalWidth) {
+ return $line;
+ }
+
+ $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);
+
+ return preg_replace_callback($regex, $callback, $this->format);
+ }
+}
diff --git a/vendor/symfony/console/Helper/ProgressIndicator.php b/vendor/symfony/console/Helper/ProgressIndicator.php
new file mode 100644
index 0000000..3bd6fed
--- /dev/null
+++ b/vendor/symfony/console/Helper/ProgressIndicator.php
@@ -0,0 +1,247 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Exception\LogicException;
+use Symfony\Component\Console\Output\ConsoleSectionOutput;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @author Kevin Bond
+ */
+class ProgressIndicator
+{
+ private const FORMATS = [
+ 'normal' => ' %indicator% %message%',
+ 'normal_no_ansi' => ' %message%',
+
+ 'verbose' => ' %indicator% %message% (%elapsed:6s%)',
+ 'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
+
+ 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
+ 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
+ ];
+
+ private int $startTime;
+ private ?string $format = null;
+ private ?string $message = null;
+ private array $indicatorValues;
+ private int $indicatorCurrent;
+ private string $finishedIndicatorValue;
+ private float $indicatorUpdateTime;
+ private bool $started = false;
+ private bool $finished = false;
+
+ /**
+ * @var array
+ */
+ private static array $formatters;
+
+ /**
+ * @param int $indicatorChangeInterval Change interval in milliseconds
+ * @param array|null $indicatorValues Animated indicator characters
+ */
+ public function __construct(
+ private OutputInterface $output,
+ ?string $format = null,
+ private int $indicatorChangeInterval = 100,
+ ?array $indicatorValues = null,
+ ?string $finishedIndicatorValue = null,
+ ) {
+ $format ??= $this->determineBestFormat();
+ $indicatorValues ??= ['-', '\\', '|', '/'];
+ $indicatorValues = array_values($indicatorValues);
+ $finishedIndicatorValue ??= '✔';
+
+ if (2 > \count($indicatorValues)) {
+ throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
+ }
+
+ $this->format = self::getFormatDefinition($format);
+ $this->indicatorValues = $indicatorValues;
+ $this->finishedIndicatorValue = $finishedIndicatorValue;
+ $this->startTime = time();
+ }
+
+ /**
+ * Sets the current indicator message.
+ */
+ public function setMessage(?string $message): void
+ {
+ $this->message = $message;
+
+ $this->display();
+ }
+
+ /**
+ * Starts the indicator output.
+ */
+ public function start(string $message): void
+ {
+ if ($this->started) {
+ throw new LogicException('Progress indicator already started.');
+ }
+
+ $this->message = $message;
+ $this->started = true;
+ $this->finished = false;
+ $this->startTime = time();
+ $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
+ $this->indicatorCurrent = 0;
+
+ $this->display();
+ }
+
+ /**
+ * Advances the indicator.
+ */
+ public function advance(): void
+ {
+ if (!$this->started) {
+ throw new LogicException('Progress indicator has not yet been started.');
+ }
+
+ if (!$this->output->isDecorated()) {
+ return;
+ }
+
+ $currentTime = $this->getCurrentTimeInMilliseconds();
+
+ if ($currentTime < $this->indicatorUpdateTime) {
+ return;
+ }
+
+ $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
+ ++$this->indicatorCurrent;
+
+ $this->display();
+ }
+
+ /**
+ * Finish the indicator with message.
+ *
+ * @param ?string $finishedIndicator
+ */
+ public function finish(string $message/* , ?string $finishedIndicator = null */): void
+ {
+ $finishedIndicator = 1 < \func_num_args() ? func_get_arg(1) : null;
+ if (null !== $finishedIndicator && !\is_string($finishedIndicator)) {
+ throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be of the type string or null, "%s" given.', __METHOD__, get_debug_type($finishedIndicator)));
+ }
+
+ if (!$this->started) {
+ throw new LogicException('Progress indicator has not yet been started.');
+ }
+
+ if (null !== $finishedIndicator) {
+ $this->finishedIndicatorValue = $finishedIndicator;
+ }
+
+ $this->finished = true;
+ $this->message = $message;
+ $this->display();
+ if (!$this->output instanceof ConsoleSectionOutput) {
+ $this->output->writeln('');
+ }
+ $this->started = false;
+ }
+
+ /**
+ * Gets the format for a given name.
+ */
+ public static function getFormatDefinition(string $name): ?string
+ {
+ return self::FORMATS[$name] ?? null;
+ }
+
+ /**
+ * Sets a placeholder formatter for a given name.
+ *
+ * This method also allow you to override an existing placeholder.
+ */
+ public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
+ {
+ self::$formatters ??= self::initPlaceholderFormatters();
+
+ self::$formatters[$name] = $callable;
+ }
+
+ /**
+ * Gets the placeholder formatter for a given name (including the delimiter char like %).
+ */
+ public static function getPlaceholderFormatterDefinition(string $name): ?callable
+ {
+ self::$formatters ??= self::initPlaceholderFormatters();
+
+ return self::$formatters[$name] ?? null;
+ }
+
+ private function display(): void
+ {
+ if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
+ return;
+ }
+
+ $this->overwrite(preg_replace_callback('{%([a-z\-_]+)(?:\:([^%]+))?%}i', function ($matches) {
+ if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
+ return $formatter($this);
+ }
+
+ return $matches[0];
+ }, $this->format ?? ''));
+ }
+
+ private function determineBestFormat(): string
+ {
+ return match ($this->output->getVerbosity()) {
+ // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
+ OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi',
+ OutputInterface::VERBOSITY_VERY_VERBOSE,
+ OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi',
+ default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi',
+ };
+ }
+
+ /**
+ * Overwrites a previous message to the output.
+ */
+ private function overwrite(string $message): void
+ {
+ if ($this->output instanceof ConsoleSectionOutput) {
+ $this->output->overwrite($message);
+ } elseif ($this->output->isDecorated()) {
+ $this->output->write("\x0D\x1B[2K");
+ $this->output->write($message);
+ } else {
+ $this->output->writeln($message);
+ }
+ }
+
+ private function getCurrentTimeInMilliseconds(): float
+ {
+ return round(microtime(true) * 1000);
+ }
+
+ /**
+ * @return array
+ */
+ private static function initPlaceholderFormatters(): array
+ {
+ return [
+ 'indicator' => static fn (self $indicator) => $indicator->finished ? $indicator->finishedIndicatorValue : $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)],
+ 'message' => static fn (self $indicator) => $indicator->message,
+ 'elapsed' => static fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2),
+ 'memory' => static fn () => Helper::formatMemory(memory_get_usage(true)),
+ ];
+ }
+}
diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php
new file mode 100644
index 0000000..ea82acc
--- /dev/null
+++ b/vendor/symfony/console/Helper/QuestionHelper.php
@@ -0,0 +1,638 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Cursor;
+use Symfony\Component\Console\Exception\MissingInputException;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Formatter\OutputFormatter;
+use Symfony\Component\Console\Formatter\OutputFormatterStyle;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\StreamableInputInterface;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\ConsoleSectionOutput;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ChoiceQuestion;
+use Symfony\Component\Console\Question\Question;
+use Symfony\Component\Console\Terminal;
+
+use function Symfony\Component\String\s;
+
+/**
+ * The QuestionHelper class provides helpers to interact with the user.
+ *
+ * @author Fabien Potencier
+ */
+class QuestionHelper extends Helper
+{
+ private static bool $stty = true;
+ private static bool $stdinIsInteractive;
+
+ /**
+ * Asks a question to the user.
+ *
+ * @return mixed The user answer
+ *
+ * @throws RuntimeException If there is no data to read in the input stream
+ */
+ public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed
+ {
+ if ($output instanceof ConsoleOutputInterface) {
+ $output = $output->getErrorOutput();
+ }
+
+ if (!$input->isInteractive()) {
+ return $this->getDefaultAnswer($question);
+ }
+
+ $inputStream = $input instanceof StreamableInputInterface ? $input->getStream() : null;
+ $inputStream ??= \STDIN;
+
+ try {
+ if (!$question->getValidator()) {
+ return $this->doAsk($inputStream, $output, $question);
+ }
+
+ $interviewer = fn () => $this->doAsk($inputStream, $output, $question);
+
+ return $this->validateAttempts($interviewer, $output, $question);
+ } catch (MissingInputException $exception) {
+ $input->setInteractive(false);
+
+ if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
+ throw $exception;
+ }
+
+ return $fallbackOutput;
+ }
+ }
+
+ public function getName(): string
+ {
+ return 'question';
+ }
+
+ /**
+ * Prevents usage of stty.
+ */
+ public static function disableStty(): void
+ {
+ self::$stty = false;
+ }
+
+ /**
+ * Asks the question to the user.
+ *
+ * @param resource $inputStream
+ *
+ * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
+ */
+ private function doAsk($inputStream, OutputInterface $output, Question $question): mixed
+ {
+ $this->writePrompt($output, $question);
+
+ $autocomplete = $question->getAutocompleterCallback();
+
+ if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
+ $ret = false;
+ if ($question->isHidden()) {
+ try {
+ $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
+ $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
+ } catch (RuntimeException $e) {
+ if (!$question->isHiddenFallback()) {
+ throw $e;
+ }
+ }
+ }
+
+ if (false === $ret) {
+ $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true;
+
+ if (!$isBlocked) {
+ stream_set_blocking($inputStream, true);
+ }
+
+ $ret = $this->readInput($inputStream, $question);
+
+ if (!$isBlocked) {
+ stream_set_blocking($inputStream, false);
+ }
+
+ if (false === $ret) {
+ throw new MissingInputException('Aborted.');
+ }
+ if ($question->isTrimmable()) {
+ $ret = trim($ret);
+ }
+ }
+ } else {
+ $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
+ $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
+ }
+
+ if ($output instanceof ConsoleSectionOutput) {
+ $output->addContent(''); // add EOL to the question
+ $output->addContent($ret);
+ }
+
+ $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
+
+ if ($normalizer = $question->getNormalizer()) {
+ return $normalizer($ret);
+ }
+
+ return $ret;
+ }
+
+ private function getDefaultAnswer(Question $question): mixed
+ {
+ $default = $question->getDefault();
+
+ if (null === $default) {
+ return $default;
+ }
+
+ if ($validator = $question->getValidator()) {
+ return \call_user_func($validator, $default);
+ } elseif ($question instanceof ChoiceQuestion) {
+ $choices = $question->getChoices();
+
+ if (!$question->isMultiselect()) {
+ return $choices[$default] ?? $default;
+ }
+
+ $default = explode(',', $default);
+ foreach ($default as $k => $v) {
+ $v = $question->isTrimmable() ? trim($v) : $v;
+ $default[$k] = $choices[$v] ?? $v;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Outputs the question prompt.
+ */
+ protected function writePrompt(OutputInterface $output, Question $question): void
+ {
+ $message = $question->getQuestion();
+
+ if ($question instanceof ChoiceQuestion) {
+ $output->writeln(array_merge([
+ $question->getQuestion(),
+ ], $this->formatChoiceQuestionChoices($question, 'info')));
+
+ $message = $question->getPrompt();
+ }
+
+ $output->write($message);
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array
+ {
+ $messages = [];
+
+ $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices())));
+
+ foreach ($choices as $key => $value) {
+ $padding = str_repeat(' ', $maxWidth - self::width($key));
+
+ $messages[] = \sprintf(" [<$tag>%s$padding$tag>] %s", $key, $value);
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Outputs an error message.
+ */
+ protected function writeError(OutputInterface $output, \Exception $error): void
+ {
+ if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
+ $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
+ } else {
+ $message = ''.$error->getMessage().'';
+ }
+
+ $output->writeln($message);
+ }
+
+ /**
+ * Autocompletes a question.
+ *
+ * @param resource $inputStream
+ * @param callable(string):list $autocomplete
+ */
+ private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
+ {
+ $cursor = new Cursor($output, $inputStream);
+
+ $fullChoice = '';
+ $ret = '';
+
+ $i = 0;
+ $ofs = -1;
+ $matches = $autocomplete($ret);
+ $numMatches = \count($matches);
+ $inputHelper = new TerminalInputHelper($inputStream);
+
+ // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
+ shell_exec('stty -icanon -echo');
+
+ // Add highlighted text style
+ $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
+
+ // Read a keypress
+ while (!feof($inputStream)) {
+ $inputHelper->waitForInput();
+ $c = fread($inputStream, 1);
+
+ // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
+ if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
+ // Restore the terminal so it behaves normally again
+ $inputHelper->finish();
+ throw new MissingInputException('Aborted while asking: '.$question->getQuestion());
+ } elseif ("\177" === $c) { // Backspace Character
+ if (0 === $numMatches && 0 !== $i) {
+ --$i;
+ $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));
+
+ $fullChoice = self::substr($fullChoice, 0, $i);
+ }
+
+ if (0 === $i) {
+ $ofs = -1;
+ $matches = $autocomplete($ret);
+ $numMatches = \count($matches);
+ } else {
+ $numMatches = 0;
+ }
+
+ // Pop the last character off the end of our string
+ $ret = self::substr($ret, 0, $i);
+ } elseif ("\033" === $c) {
+ // Did we read an escape sequence?
+ $c .= fread($inputStream, 2);
+
+ // A CSI sequence ends with a byte in the 0x40-0x7E range, preceded by any number of
+ // parameter and intermediate bytes; consume them all so that none leaks into the answer
+ if ('[' === ($c[1] ?? '')) {
+ while (($o = \ord($c[\strlen($c) - 1])) >= 0x20 && $o <= 0x3F) {
+ if (!$next = fread($inputStream, 1)) {
+ break;
+ }
+
+ $c .= $next;
+ }
+ }
+
+ // A = Up Arrow. B = Down Arrow
+ if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
+ if ('A' === $c[2] && -1 === $ofs) {
+ $ofs = 0;
+ }
+
+ if (0 === $numMatches) {
+ continue;
+ }
+
+ $ofs += ('A' === $c[2]) ? -1 : 1;
+ $ofs = ($numMatches + $ofs) % $numMatches;
+ }
+ } elseif ('' === $c || \ord($c) < 32) {
+ if ("\t" === $c || "\n" === $c) {
+ if ($numMatches > 0 && -1 !== $ofs) {
+ $ret = (string) $matches[$ofs];
+ // Echo out remaining chars for current match
+ $remainingCharacters = substr($ret, \strlen($this->mostRecentlyEnteredValue($fullChoice)));
+ $output->write($remainingCharacters);
+ $fullChoice .= $remainingCharacters;
+ $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);
+
+ $matches = array_filter(
+ $autocomplete($ret),
+ static fn ($match) => '' === $ret || str_starts_with($match, $ret)
+ );
+ $numMatches = \count($matches);
+ $ofs = -1;
+ }
+
+ if ("\n" === $c) {
+ $output->write($c);
+ break;
+ }
+
+ $numMatches = 0;
+ }
+
+ continue;
+ } else {
+ if ("\x80" <= $c) {
+ $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
+ }
+
+ $output->write($c);
+ $ret .= $c;
+ $fullChoice .= $c;
+ ++$i;
+
+ $tempRet = $ret;
+
+ if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
+ $tempRet = $this->mostRecentlyEnteredValue($fullChoice);
+ }
+
+ $numMatches = 0;
+ $ofs = 0;
+
+ foreach ($autocomplete($ret) as $value) {
+ // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
+ if (str_starts_with($value, $tempRet)) {
+ $matches[$numMatches++] = $value;
+ }
+ }
+ }
+
+ $cursor->clearLineAfter();
+
+ if ($numMatches > 0 && -1 !== $ofs) {
+ $cursor->savePosition();
+ // Write highlighted text, complete the partially entered response
+ $charactersEntered = \strlen($this->mostRecentlyEnteredValue($fullChoice));
+ $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'');
+ $cursor->restorePosition();
+ }
+ }
+
+ // Restore the terminal so it behaves normally again
+ $inputHelper->finish();
+
+ return $fullChoice;
+ }
+
+ private function mostRecentlyEnteredValue(string $entered): string
+ {
+ // Determine the most recent value that the user entered
+ if (!str_contains($entered, ',')) {
+ return $entered;
+ }
+
+ if (false === $lastCommaPos = strrpos($entered, ',')) {
+ return $entered;
+ }
+
+ $lastChoice = trim(substr($entered, $lastCommaPos + 1));
+
+ return '' !== $lastChoice ? $lastChoice : $entered;
+ }
+
+ /**
+ * Gets a hidden response from user.
+ *
+ * @param resource $inputStream The handler resource
+ * @param bool $trimmable Is the answer trimmable
+ *
+ * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
+ */
+ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
+
+ // handle code running from a phar
+ if (str_starts_with(__FILE__, 'phar:')) {
+ $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
+ copy($exe, $tmpExe);
+ $exe = $tmpExe;
+ }
+
+ $sExec = shell_exec('"'.$exe.'"');
+ $value = $trimmable ? rtrim($sExec) : $sExec;
+ $output->writeln('');
+
+ if (isset($tmpExe)) {
+ unlink($tmpExe);
+ }
+
+ return $value;
+ }
+
+ $inputHelper = null;
+
+ if (self::$stty && Terminal::hasSttyAvailable()) {
+ $inputHelper = new TerminalInputHelper($inputStream);
+ shell_exec('stty -echo');
+ } elseif ($this->isInteractiveInput($inputStream)) {
+ throw new RuntimeException('Unable to hide the response.');
+ }
+
+ $value = $this->doReadInput($inputStream, helper: $inputHelper);
+
+ if (4095 === \strlen($value)) {
+ $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
+ $errOutput->warning('The value was possibly truncated by your shell or terminal emulator');
+ }
+
+ // Restore the terminal so it behaves normally again
+ $inputHelper?->finish();
+
+ if ($trimmable) {
+ $value = trim($value);
+ }
+ $output->writeln('');
+
+ return $value;
+ }
+
+ /**
+ * Validates an attempt.
+ *
+ * @param callable $interviewer A callable that will ask for a question and return the result
+ *
+ * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
+ */
+ private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question): mixed
+ {
+ $error = null;
+ $attempts = $question->getMaxAttempts();
+
+ while (null === $attempts || $attempts--) {
+ if (null !== $error) {
+ $this->writeError($output, $error);
+ }
+
+ try {
+ return $question->getValidator()($interviewer());
+ } catch (MissingInputException $e) {
+ throw $error ?? $e;
+ } catch (RuntimeException $e) {
+ throw $e;
+ } catch (\Exception $error) {
+ }
+ }
+
+ throw $error;
+ }
+
+ private function isInteractiveInput($inputStream): bool
+ {
+ if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
+ return false;
+ }
+
+ if (isset(self::$stdinIsInteractive)) {
+ return self::$stdinIsInteractive;
+ }
+
+ return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
+ }
+
+ /**
+ * Reads one or more lines of input and returns what is read.
+ *
+ * @param resource $inputStream The handler resource
+ * @param Question $question The question being asked
+ */
+ private function readInput($inputStream, Question $question): string|false
+ {
+ if (null !== $question->getTimeout() && $this->isInteractiveInput($inputStream)) {
+ $read = [$inputStream];
+ $write = null;
+ $except = null;
+ $timeoutSeconds = $question->getTimeout();
+ $changedStreams = stream_select($read, $write, $except, $timeoutSeconds);
+
+ if (0 === $changedStreams) {
+ throw new MissingInputException(\sprintf('Timed out after waiting for input for %d second%s.', $timeoutSeconds, 1 === $timeoutSeconds ? '' : 's'));
+ }
+ }
+
+ if (!$question->isMultiline()) {
+ $cp = $this->setIOCodepage();
+ $ret = $this->doReadInput($inputStream);
+
+ return $this->resetIOCodepage($cp, $ret);
+ }
+
+ $multiLineStreamReader = $this->cloneInputStream($inputStream);
+ if (null === $multiLineStreamReader) {
+ return false;
+ }
+
+ $cp = $this->setIOCodepage();
+ $ret = $this->doReadInput($multiLineStreamReader, "\x4");
+
+ if (stream_get_meta_data($inputStream)['seekable']) {
+ fseek($inputStream, ftell($multiLineStreamReader));
+ }
+
+ return $this->resetIOCodepage($cp, $ret);
+ }
+
+ private function setIOCodepage(): int
+ {
+ if (\function_exists('sapi_windows_cp_set')) {
+ $cp = sapi_windows_cp_get();
+ sapi_windows_cp_set(sapi_windows_cp_get('oem'));
+
+ return $cp;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Sets console I/O to the specified code page and converts the user input.
+ */
+ private function resetIOCodepage(int $cp, string|false $input): string|false
+ {
+ if (0 !== $cp) {
+ sapi_windows_cp_set($cp);
+
+ if (false !== $input && '' !== $input) {
+ $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
+ }
+ }
+
+ return $input;
+ }
+
+ /**
+ * Clones an input stream in order to act on one instance of the same
+ * stream without affecting the other instance.
+ *
+ * @param resource $inputStream The handler resource
+ *
+ * @return resource|null The cloned resource, null in case it could not be cloned
+ */
+ private function cloneInputStream($inputStream)
+ {
+ $streamMetaData = stream_get_meta_data($inputStream);
+ $seekable = $streamMetaData['seekable'] ?? false;
+ $mode = $streamMetaData['mode'] ?? 'rb';
+ $uri = $streamMetaData['uri'] ?? null;
+
+ if (null === $uri) {
+ return null;
+ }
+
+ $cloneStream = fopen($uri, $mode);
+
+ // For seekable and writable streams, add all the same data to the
+ // cloned stream and then seek to the same offset.
+ if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'], true)) {
+ $offset = ftell($inputStream);
+ rewind($inputStream);
+ stream_copy_to_stream($inputStream, $cloneStream);
+ fseek($inputStream, $offset);
+ fseek($cloneStream, $offset);
+ }
+
+ return $cloneStream;
+ }
+
+ /**
+ * @param resource $inputStream
+ */
+ private function doReadInput($inputStream, ?string $exitChar = null, ?TerminalInputHelper $helper = null): string
+ {
+ $ret = '';
+ $helper ??= new TerminalInputHelper($inputStream, false);
+
+ while (!feof($inputStream)) {
+ $helper->waitForInput();
+ $char = fread($inputStream, 1);
+
+ // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
+ if (false === $char || ('' === $ret && '' === $char)) {
+ throw new MissingInputException('Aborted.');
+ }
+
+ if (\PHP_EOL === "{$ret}{$char}" || $exitChar === $char) {
+ break;
+ }
+
+ $ret .= $char;
+
+ if (null === $exitChar && "\n" === $char) {
+ break;
+ }
+ }
+
+ return $ret;
+ }
+}
diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php
new file mode 100644
index 0000000..cf6a6dd
--- /dev/null
+++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Formatter\OutputFormatter;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ChoiceQuestion;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+use Symfony\Component\Console\Question\Question;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Symfony Style Guide compliant question helper.
+ *
+ * @author Kevin Bond
+ */
+class SymfonyQuestionHelper extends QuestionHelper
+{
+ protected function writePrompt(OutputInterface $output, Question $question): void
+ {
+ $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
+ $default = $question->getDefault();
+
+ if ($question->isMultiline()) {
+ $text .= \sprintf(' (press %s to continue)', $this->getEofShortcut($output));
+ }
+
+ switch (true) {
+ case null === $default:
+ $text = \sprintf(' %s:', $text);
+
+ break;
+
+ case $question instanceof ConfirmationQuestion:
+ $text = \sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no');
+
+ break;
+
+ case $question instanceof ChoiceQuestion && $question->isMultiselect():
+ $choices = $question->getChoices();
+ $default = explode(',', $default);
+
+ foreach ($default as $key => $value) {
+ $default[$key] = $choices[trim($value)];
+ }
+
+ $text = \sprintf(' %s [%s]:', $text, implode(', ', $default));
+
+ break;
+
+ case $question instanceof ChoiceQuestion:
+ $choices = $question->getChoices();
+ $text = \sprintf(' %s [%s]:', $text, $choices[$default] ?? $default);
+
+ break;
+
+ default:
+ $text = \sprintf(' %s [%s]:', $text, OutputFormatter::escape($default));
+ }
+
+ $output->writeln($text);
+
+ $prompt = ' > ';
+
+ if ($question instanceof ChoiceQuestion) {
+ $output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
+
+ $prompt = $question->getPrompt();
+ }
+
+ $output->write($prompt);
+ }
+
+ protected function writeError(OutputInterface $output, \Exception $error): void
+ {
+ if ($output instanceof SymfonyStyle) {
+ $output->newLine();
+ $output->error($error->getMessage());
+
+ return;
+ }
+
+ parent::writeError($output, $error);
+ }
+
+ private function getEofShortcut(OutputInterface $output): string
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && !$output->isDecorated()) {
+ return 'Ctrl+Z then Enter';
+ }
+
+ return 'Ctrl+D';
+ }
+}
diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php
new file mode 100644
index 0000000..aef14b9
--- /dev/null
+++ b/vendor/symfony/console/Helper/Table.php
@@ -0,0 +1,1155 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Formatter\OutputFormatter;
+use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
+use Symfony\Component\Console\Output\BufferedOutput;
+use Symfony\Component\Console\Output\ConsoleSectionOutput;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Provides helpers to display a table.
+ *
+ * @author Fabien Potencier
+ * @author Саша Стаменковић
+ * @author Abdellatif Ait boudad
+ * @author Max Grigorian
+ * @author Dany Maillard
+ */
+class Table
+{
+ private const SEPARATOR_TOP = 0;
+ private const SEPARATOR_TOP_BOTTOM = 1;
+ private const SEPARATOR_MID = 2;
+ private const SEPARATOR_BOTTOM = 3;
+ private const BORDER_OUTSIDE = 0;
+ private const BORDER_INSIDE = 1;
+ private const DISPLAY_ORIENTATION_DEFAULT = 'default';
+ private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal';
+ private const DISPLAY_ORIENTATION_VERTICAL = 'vertical';
+
+ private ?string $headerTitle = null;
+ private ?string $footerTitle = null;
+ private array $headers = [];
+ private array $rows = [];
+ private array $effectiveColumnWidths = [];
+ private int $numberOfColumns;
+ private TableStyle $style;
+ private array $columnStyles = [];
+ private array $columnWidths = [];
+ private array $configuredColumnWidths = [];
+ private array $columnMaxWidths = [];
+ private array $renderedLines = [];
+ private array $tagStyles = [];
+ private ?\WeakMap $rowspanLines = null;
+ private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT;
+
+ private static array $styles;
+
+ public function __construct(
+ private OutputInterface $output,
+ ) {
+ self::$styles ??= self::initStyles();
+
+ $this->setStyle('default');
+ }
+
+ /**
+ * Sets a style definition.
+ */
+ public static function setStyleDefinition(string $name, TableStyle $style): void
+ {
+ self::$styles ??= self::initStyles();
+
+ self::$styles[$name] = $style;
+ }
+
+ /**
+ * Gets a style definition by name.
+ */
+ public static function getStyleDefinition(string $name): TableStyle
+ {
+ self::$styles ??= self::initStyles();
+
+ return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
+ }
+
+ /**
+ * Sets table style.
+ *
+ * @return $this
+ */
+ public function setStyle(TableStyle|string $name): static
+ {
+ $this->style = $this->resolveStyle($name);
+
+ return $this;
+ }
+
+ /**
+ * Gets the current table style.
+ */
+ public function getStyle(): TableStyle
+ {
+ return $this->style;
+ }
+
+ /**
+ * Sets table column style.
+ *
+ * @param TableStyle|string $name The style name or a TableStyle instance
+ *
+ * @return $this
+ */
+ public function setColumnStyle(int $columnIndex, TableStyle|string $name): static
+ {
+ $this->columnStyles[$columnIndex] = $this->resolveStyle($name);
+
+ return $this;
+ }
+
+ /**
+ * Gets the current style for a column.
+ *
+ * If style was not set, it returns the global table style.
+ */
+ public function getColumnStyle(int $columnIndex): TableStyle
+ {
+ return $this->columnStyles[$columnIndex] ?? $this->getStyle();
+ }
+
+ /**
+ * Sets the minimum width of a column.
+ *
+ * @return $this
+ */
+ public function setColumnWidth(int $columnIndex, int $width): static
+ {
+ $this->columnWidths[$columnIndex] = $width;
+ $this->configuredColumnWidths[$columnIndex] = $width;
+
+ return $this;
+ }
+
+ /**
+ * Sets the minimum width of all columns.
+ *
+ * @return $this
+ */
+ public function setColumnWidths(array $widths): static
+ {
+ $this->columnWidths = [];
+ $this->configuredColumnWidths = [];
+ foreach ($widths as $index => $width) {
+ $this->setColumnWidth($index, $width);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the maximum width of a column.
+ *
+ * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
+ * formatted strings are preserved.
+ *
+ * @return $this
+ */
+ public function setColumnMaxWidth(int $columnIndex, int $width): static
+ {
+ if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
+ throw new \LogicException(\sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
+ }
+
+ $this->columnMaxWidths[$columnIndex] = $width;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setHeaders(array $headers): static
+ {
+ $headers = array_values($headers);
+ if ($headers && !\is_array($headers[0])) {
+ $headers = [$headers];
+ }
+
+ $this->headers = $headers;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setRows(array $rows): static
+ {
+ $this->rows = [];
+
+ return $this->addRows($rows);
+ }
+
+ /**
+ * @return $this
+ */
+ public function addRows(array $rows): static
+ {
+ foreach ($rows as $row) {
+ $this->addRow($row);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function addRow(TableSeparator|array $row): static
+ {
+ if ($row instanceof TableSeparator) {
+ $this->rows[] = $row;
+
+ return $this;
+ }
+
+ $this->rows[] = array_values($row);
+
+ return $this;
+ }
+
+ /**
+ * Adds a row to the table, and re-renders the table.
+ *
+ * @return $this
+ */
+ public function appendRow(TableSeparator|array $row): static
+ {
+ $output = $this->output;
+ if (!$output instanceof ConsoleSectionOutput) {
+ throw new RuntimeException(\sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
+ }
+
+ if (!$output->isDecorated()) {
+ $this->addRow($row);
+ $this->render();
+
+ return $this;
+ }
+
+ $previous = $this->renderedLines;
+ if ($previous && !str_ends_with($output->getContent(), implode('', $previous))) {
+ // the section was cleared or written to since the table was rendered: keep its content and render the whole table again
+ $previous = [];
+ }
+
+ $this->addRow($row);
+ $lines = $this->renderLines();
+
+ // Only rewrite the lines after the part both renderings have in common.
+ // While no column gets wider, that is just the bottom border, so lines that scrolled out of the terminal, which can't be cleared, are not touched.
+ $common = 0;
+ $max = min(\count($previous), \count($lines));
+ while ($common < $max && $previous[$common] === $lines[$common]) {
+ ++$common;
+ }
+
+ if (\count($previous) > $common) {
+ $output->clear(\count($previous) - $common);
+ }
+ $output->write(implode('', \array_slice($lines, $common)), false, OutputInterface::OUTPUT_RAW);
+ $this->renderedLines = $lines;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setRow(int|string $column, array $row): static
+ {
+ $this->rows[$column] = $row;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setHeaderTitle(?string $title): static
+ {
+ $this->headerTitle = $title;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setFooterTitle(?string $title): static
+ {
+ $this->footerTitle = $title;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setHorizontal(bool $horizontal = true): static
+ {
+ $this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setVertical(bool $vertical = true): static
+ {
+ $this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT;
+
+ return $this;
+ }
+
+ /**
+ * Renders table to output.
+ *
+ * Example:
+ *
+ * +---------------+-----------------------+------------------+
+ * | ISBN | Title | Author |
+ * +---------------+-----------------------+------------------+
+ * | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+ * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+ * +---------------+-----------------------+------------------+
+ */
+ public function render(): void
+ {
+ if ($this->output instanceof ConsoleSectionOutput && $this->output->isDecorated()) {
+ // remember the lines on screen, so that appendRow() can rewrite only the ones that change
+ $lines = $this->renderLines();
+ $this->output->write(implode('', $lines), false, OutputInterface::OUTPUT_RAW);
+ $this->renderedLines = $lines;
+
+ return;
+ }
+
+ $divider = new TableSeparator();
+ $isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2;
+
+ $horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation;
+ $vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation;
+
+ $rows = [];
+ if ($horizontal) {
+ foreach ($this->headers[0] ?? [] as $i => $header) {
+ $rows[$i] = [$this->closeStyles($header)];
+ foreach ($this->rows as $row) {
+ if ($row instanceof TableSeparator) {
+ continue;
+ }
+ if (isset($row[$i])) {
+ $rows[$i][] = $this->closeStyles($row[$i]);
+ } elseif ($isCellWithColspan($rows[$i][0])) {
+ // Noop, there is a "title"
+ } else {
+ $rows[$i][] = null;
+ }
+ }
+ }
+ } elseif ($vertical) {
+ $formatter = $this->output->getFormatter();
+ $headers = array_map($this->closeStyles(...), $this->headers[0] ?? []);
+ $maxHeaderLength = array_reduce($headers, static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0);
+
+ foreach ($this->rows as $row) {
+ if ($row instanceof TableSeparator) {
+ continue;
+ }
+
+ if ($rows) {
+ $rows[] = [$divider];
+ }
+
+ $containsColspan = false;
+ foreach ($row as $cell) {
+ if ($containsColspan = $isCellWithColspan($cell)) {
+ break;
+ }
+ }
+
+ $maxRows = max(\count($headers), \count($row));
+ for ($i = 0; $i < $maxRows; ++$i) {
+ $cell = (string) $this->closeStyles($row[$i] ?? '');
+
+ $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
+ $parts = explode($eol, $this->closeStyles($cell, $eol));
+ foreach ($parts as $idx => $part) {
+ if ($headers && !$containsColspan) {
+ if (0 === $idx) {
+ $rows[] = [\sprintf(
+ '%s%s>: %s',
+ str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
+ $headers[$i] ?? '',
+ $part
+ )];
+ } else {
+ $rows[] = [\sprintf(
+ '%s %s',
+ str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT),
+ $part
+ )];
+ }
+ } elseif ('' !== $cell) {
+ $rows[] = [$part];
+ }
+ }
+ }
+ }
+ } else {
+ $closeStyles = $this->closeStyles(...);
+ $rows = array_map(static fn ($row) => $row instanceof TableSeparator ? $row : array_map($closeStyles, $row), array_merge($this->headers, [$divider], $this->rows));
+ }
+
+ $this->calculateNumberOfColumns($rows);
+
+ $rowGroups = $this->buildTableRows($rows);
+ $this->calculateColumnsWidth($rowGroups);
+
+ $isMarkdown = self::$styles['markdown'] === $this->style;
+ // a markdown table must start with a header row, so one is added when the table has none
+ $needsMarkdownHeader = $isMarkdown && ($horizontal || $vertical || !array_filter($this->headers));
+ $isHeader = !$horizontal;
+ $isFirstRow = $horizontal;
+ // a title would break the delimiter row of a markdown table
+ $hasTitle = (bool) $this->headerTitle && !$isMarkdown;
+ $previousRow = null;
+ $pendingSeparators = 0;
+
+ foreach ($rowGroups as $rowGroup) {
+ $isHeaderSeparatorRendered = false;
+
+ foreach ($rowGroup as $row) {
+ if ($divider === $row) {
+ $isHeader = false;
+ // the delimiter row of a markdown table is written after its first header row
+ $isFirstRow = !$isMarkdown;
+
+ continue;
+ }
+
+ if ($row instanceof TableSeparator) {
+ // defer rendering until the row below is known, to align crossings with it
+ ++$pendingSeparators;
+
+ continue;
+ }
+
+ if (!$row) {
+ continue;
+ }
+
+ if ($needsMarkdownHeader) {
+ $this->renderRow(array_fill(0, $this->numberOfColumns, ''), $this->style->getCellHeaderFormat());
+ $this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
+ $needsMarkdownHeader = false;
+ $isFirstRow = false;
+ }
+
+ for (; $pendingSeparators > 0; --$pendingSeparators) {
+ $this->renderRowSeparator(self::SEPARATOR_MID, null, null, $previousRow, $row);
+ }
+
+ if ($isHeader && !$isHeaderSeparatorRendered && $this->style->displayOutsideBorder()) {
+ $this->renderRowSeparator(
+ self::SEPARATOR_TOP,
+ $hasTitle ? $this->headerTitle : null,
+ $hasTitle ? $this->style->getHeaderTitleFormat() : null,
+ null,
+ $row
+ );
+ $hasTitle = false;
+ $isHeaderSeparatorRendered = true;
+ }
+
+ if ($isFirstRow) {
+ $this->renderRowSeparator(
+ $horizontal || !array_filter($this->headers) ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
+ $hasTitle ? $this->headerTitle : null,
+ $hasTitle ? $this->style->getHeaderTitleFormat() : null,
+ $previousRow,
+ $row
+ );
+ $isFirstRow = false;
+ $hasTitle = false;
+ }
+
+ if ($vertical) {
+ $isHeader = false;
+ $isFirstRow = false;
+ }
+
+ if ($horizontal) {
+ $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
+ } else {
+ $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
+ }
+
+ if ($isMarkdown && $isHeader) {
+ // a markdown table has a single header row, so the next header rows are rendered as body rows
+ $this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
+ $isHeader = false;
+ }
+
+ $previousRow = $row;
+ }
+ }
+
+ for (; $pendingSeparators > 0; --$pendingSeparators) {
+ $this->renderRowSeparator(self::SEPARATOR_MID, null, null, $previousRow);
+ }
+
+ if ($this->getStyle()->displayOutsideBorder()) {
+ $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat(), $previousRow);
+ }
+
+ $this->cleanup();
+ }
+
+ /**
+ * Renders horizontal header separator.
+ *
+ * Example:
+ *
+ * +-----+-----------+-------+
+ */
+ private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null, ?array $rowAbove = null, ?array $rowBelow = null): void
+ {
+ if (!$count = $this->numberOfColumns) {
+ return;
+ }
+
+ $borders = $this->style->getBorderChars();
+ if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
+ return;
+ }
+
+ $crossings = $this->style->getCrossingChars();
+ if (self::SEPARATOR_MID === $type) {
+ [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
+ } elseif (self::SEPARATOR_TOP === $type) {
+ [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
+ } elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
+ [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
+ } else {
+ [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
+ }
+
+ // only draw a crossing where a vertical border meets the separator; a cell
+ // spanning several columns has no border on the columns it covers
+ $hasLineAbove = self::SEPARATOR_TOP !== $type;
+ $hasLineBelow = self::SEPARATOR_BOTTOM !== $type;
+ $columnsAbove = null !== $rowAbove ? $this->getRowColumns($rowAbove) : null;
+ $columnsBelow = null !== $rowBelow ? $this->getRowColumns($rowBelow) : null;
+
+ $markup = $leftChar;
+ for ($column = 0; $column < $count; ++$column) {
+ $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
+ if ($column === $count - 1) {
+ $markup .= $rightChar;
+
+ continue;
+ }
+
+ $borderAbove = $hasLineAbove && (null === $columnsAbove || \in_array($column + 1, $columnsAbove, true));
+ $borderBelow = $hasLineBelow && (null === $columnsBelow || \in_array($column + 1, $columnsBelow, true));
+ // the one-sided junctions reuse the top/bottom crossing chars; for the "box-double"
+ // style these are double-horizontal (╤/╧) while an inner separator is single (─), as
+ // that style has no single-line ┬/┴ to fall back to
+ $markup .= match (true) {
+ $borderAbove && $borderBelow => $midChar,
+ $borderBelow => $crossings[2],
+ $borderAbove => $crossings[6],
+ default => $horizontal,
+ };
+ }
+
+ if (null !== $title) {
+ $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = \sprintf($titleFormat, $title)));
+ $markupLength = Helper::width($markup);
+ if ($titleLength > $limit = $markupLength - 4) {
+ $titleLength = $limit;
+ $formatLength = Helper::width(Helper::removeDecoration($formatter, \sprintf($titleFormat, '')));
+ $formattedTitle = \sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
+ }
+
+ $titleStart = intdiv($markupLength - $titleLength, 2);
+ if (false === mb_detect_encoding($markup, null, true)) {
+ $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
+ } else {
+ $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
+ }
+ }
+
+ $this->output->writeln(\sprintf($this->style->getBorderFormat(), $markup));
+ }
+
+ /**
+ * Renders vertical column separator.
+ */
+ private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
+ {
+ $borders = $this->style->getBorderChars();
+
+ return \sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
+ }
+
+ /**
+ * Renders table row.
+ *
+ * Example:
+ *
+ * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ */
+ private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void
+ {
+ $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
+ $columns = $this->getRowColumns($row);
+ $last = array_key_last($columns);
+ foreach ($columns as $i => $column) {
+ if ($firstCellFormat && 0 === $i) {
+ $rowContent .= $this->renderCell($row, $column, $firstCellFormat);
+ } else {
+ $rowContent .= $this->renderCell($row, $column, $cellFormat);
+ }
+ $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
+ }
+ $this->output->writeln($rowContent);
+ }
+
+ /**
+ * Renders table cell with padding.
+ */
+ private function renderCell(array $row, int $column, string $cellFormat): string
+ {
+ $cell = $row[$column] ?? '';
+ $width = $this->effectiveColumnWidths[$column];
+ if ($cell instanceof TableCell && $cell->getColspan() > 1) {
+ // add the width of the following columns(numbers of colspan).
+ foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
+ $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
+ }
+ }
+
+ // str_pad won't work properly with multi-byte strings, we need to fix the padding
+ $width += \strlen($cell) - Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
+ $style = $this->getColumnStyle($column);
+
+ if ($cell instanceof TableSeparator) {
+ return \sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
+ }
+
+ $content = \sprintf($style->getCellRowContentFormat(), $cell);
+
+ $padType = $style->getPadType();
+ if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
+ // the lines of a cell that spans rows get the style of the cell, even when a tag wraps them
+ $isNotStyledByTag = isset($this->rowspanLines[$cell]) || !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
+ if ($isNotStyledByTag) {
+ $cellFormat = $cell->getStyle()->getCellFormat();
+ if (!\is_string($cellFormat)) {
+ $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';');
+ $cellFormat = '<'.$tag.'>%s>';
+ }
+ }
+
+ $padType = $cell->getStyle()->getPadByAlign();
+ }
+
+ return \sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
+ }
+
+ /**
+ * Calculate number of columns for this table.
+ */
+ private function calculateNumberOfColumns(array $rows): void
+ {
+ $columns = [0];
+ foreach ($rows as $row) {
+ if ($row instanceof TableSeparator) {
+ continue;
+ }
+
+ $columns[] = $this->getNumberOfColumns($row);
+ }
+
+ $this->numberOfColumns = max($columns);
+ }
+
+ private function buildTableRows(array $rows): TableRows
+ {
+ /** @var WrappableOutputFormatterInterface $formatter */
+ $formatter = $this->output->getFormatter();
+ $isMarkdown = self::$styles['markdown'] === $this->style;
+ $unmergedRows = [];
+ for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
+ $rows = $this->fillNextRows($rows, $rowKey);
+
+ // Remove any new line breaks and replace it with a new line
+ foreach ($rows[$rowKey] as $column => $cell) {
+ $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
+
+ $minWrappedWidth = 0;
+ $widthApplied = [];
+ $lengthColumnBorder = $this->getColumnSeparatorWidth() + Helper::width($this->style->getCellRowContentFormat()) - 2;
+ for ($i = $column; $i < ($column + $colspan); ++$i) {
+ if (isset($this->columnMaxWidths[$i])) {
+ $minWrappedWidth += $this->columnMaxWidths[$i];
+ $widthApplied[] = ['type' => 'max', 'column' => $i];
+ } elseif (($this->columnWidths[$i] ?? 0) > 0 && $colspan > 1) {
+ $minWrappedWidth += $this->columnWidths[$i];
+ $widthApplied[] = ['type' => 'min', 'column' => $i];
+ }
+ }
+ if (1 === \count($widthApplied)) {
+ if ($colspan > 1) {
+ $minWrappedWidth *= $colspan; // previous logic
+ }
+ } elseif (\count($widthApplied) > 1) {
+ $minWrappedWidth += (\count($widthApplied) - 1) * $lengthColumnBorder;
+ }
+
+ $cellWidth = Helper::width(Helper::removeDecoration($formatter, $cell));
+ if ($wrapped = $minWrappedWidth && $cellWidth > $minWrappedWidth) {
+ // the wrapped cell is already formatted and gets formatted again when rendered
+ $cell = str_replace(['<', '>'], ['\\<', '\\>'], $formatter->formatAndWrap($cell, $minWrappedWidth));
+ }
+ // update minimal columnWidths for spanned columns
+ if ($colspan > 1 && $minWrappedWidth > 0) {
+ $columnsMinWidthProcessed = [];
+ $cellWidth = min($cellWidth, $minWrappedWidth);
+ foreach ($widthApplied as $item) {
+ if ('max' === $item['type'] && $cellWidth >= $this->columnMaxWidths[$item['column']]) {
+ $minWidthColumn = $this->columnMaxWidths[$item['column']];
+ $this->columnWidths[$item['column']] = $minWidthColumn;
+ $columnsMinWidthProcessed[$item['column']] = true;
+ $cellWidth -= $minWidthColumn + $lengthColumnBorder;
+ } elseif ('min' === $item['type'] && ($this->configuredColumnWidths[$item['column']] ?? 0) > 0) {
+ // this column already covers part of the cell, so share only the rest
+ $columnsMinWidthProcessed[$item['column']] = true;
+ $cellWidth -= $this->configuredColumnWidths[$item['column']] + $lengthColumnBorder;
+ }
+ }
+ for ($i = $column; $i < ($column + $colspan); ++$i) {
+ if (isset($columnsMinWidthProcessed[$i])) {
+ continue;
+ }
+ // a width set through setColumnWidth() is a minimum, unlike one computed for an earlier row
+ $this->columnWidths[$i] = max($this->configuredColumnWidths[$i] ?? 0, $cellWidth + $lengthColumnBorder);
+ }
+ }
+ if ($isMarkdown && !$cell instanceof TableSeparator) {
+ // double the backslashes before a
so that markdown does not escape it, and add one that the output formatter removes
+ $escaped = preg_replace(['/\\\\++(?=[\r\n])/', '/\r\n?|\n/'], ['$0$0\\\\', '
'], str_replace('|', '\|', $cell ?? ''));
+ $rows[$rowKey][$column] = $cell instanceof TableCell || $colspan > 1 ? new TableCell($escaped, ['colspan' => $colspan, 'style' => $cell instanceof TableCell ? $cell->getStyle() : null]) : $escaped;
+
+ continue;
+ }
+ if (!$wrapped && !str_contains($cell ?? '', "\n")) {
+ continue;
+ }
+ $escaped = implode("\n", array_map(OutputFormatter::escapeTrailingBackslash(...), preg_split('/\r?\n/', $wrapped ? $cell : $this->closeStyles(str_replace("\r\n", "\n", $cell), "\n"))));
+ $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
+ $lines = explode("\n", str_replace("\n", '>'."\n", $cell));
+ foreach ($lines as $lineKey => $line) {
+ if ($colspan > 1) {
+ $line = new TableCell($line, ['colspan' => $colspan]);
+ }
+ if (0 === $lineKey) {
+ $rows[$rowKey][$column] = $line;
+ } else {
+ if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
+ $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
+ }
+ $unmergedRows[$rowKey][$lineKey][$column] = $line;
+ }
+ }
+ }
+ }
+
+ return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
+ foreach ($rows as $rowKey => $row) {
+ $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)];
+
+ if (isset($unmergedRows[$rowKey])) {
+ foreach ($unmergedRows[$rowKey] as $row) {
+ $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row);
+ }
+ }
+ yield $rowGroup;
+ }
+ });
+ }
+
+ /**
+ * fill rows that contains rowspan > 1.
+ *
+ * @throws InvalidArgumentException
+ */
+ private function fillNextRows(array $rows, int $line): array
+ {
+ $this->rowspanLines ??= new \WeakMap();
+ $unmergedRows = [];
+ foreach ($rows[$line] as $column => $cell) {
+ if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) {
+ throw new InvalidArgumentException(\sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
+ }
+ if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
+ $nbLines = $cell->getRowspan() - 1;
+ $lines = [$cell];
+ if (str_contains($cell, "\n")) {
+ $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
+ $lines = explode($eol, $this->closeStyles($cell, $eol));
+ $nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines;
+
+ $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
+ $this->rowspanLines[$rows[$line][$column]] = true;
+ unset($lines[0]);
+ }
+
+ // create a two dimensional array (rowspan x colspan)
+ $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
+ foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
+ $value = $lines[$unmergedRowKey - $line] ?? '';
+ $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
+ $this->rowspanLines[$unmergedRows[$unmergedRowKey][$column]] = true;
+ if ($nbLines === $unmergedRowKey - $line) {
+ break;
+ }
+ }
+ }
+ }
+
+ foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
+ // we need to know if $unmergedRow will be merged or inserted into $rows
+ if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRow) <= $this->numberOfColumns)) {
+ foreach ($unmergedRow as $cellKey => $cell) {
+ // insert cell into row at cellKey position
+ array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
+ }
+ } else {
+ $row = $this->copyRow($rows, $unmergedRowKey - 1);
+ foreach ($unmergedRow as $column => $cell) {
+ if ($cell) {
+ $row[$column] = $cell;
+ }
+ }
+ array_splice($rows, $unmergedRowKey, 0, [$row]);
+ }
+ }
+
+ return $rows;
+ }
+
+ /**
+ * fill cells for a row that contains colspan > 1.
+ */
+ private function fillCells(iterable $row): iterable
+ {
+ $newRow = [];
+
+ foreach ($row as $column => $cell) {
+ $newRow[] = $cell;
+ if ($cell instanceof TableCell && $cell->getColspan() > 1) {
+ if (self::$styles['markdown'] === $this->style) {
+ // markdown has no colspan and needs one cell per column, so the cell only fills the first column it spans
+ $newRow[array_key_last($newRow)] = new TableCell((string) $cell, ['style' => $cell->getStyle()]);
+ }
+ foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
+ // insert empty value at column position
+ $newRow[] = '';
+ }
+ }
+ }
+
+ return $newRow ?: $row;
+ }
+
+ private function copyRow(array $rows, int $line): array
+ {
+ $row = $rows[$line];
+ foreach ($row as $cellKey => $cellValue) {
+ $row[$cellKey] = '';
+ if ($cellValue instanceof TableCell) {
+ $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
+ }
+ }
+
+ return $row;
+ }
+
+ /**
+ * Closes the styles that a cell leaves open at its end.
+ *
+ * With $eol, closes the styles that span a line break at the end of each line, and reopens them on the next line.
+ */
+ private function closeStyles(mixed $cell, ?string $eol = null): mixed
+ {
+ if (!\is_string($cell) && !$cell instanceof \Stringable || !str_contains($value = (string) $cell, '<') || null !== $eol && !str_contains($value, $eol) || !($formatter = $this->output->getFormatter()) instanceof OutputFormatter) {
+ return $cell;
+ }
+
+ $lines = null === $eol ? [$value] : explode($eol, $value);
+ // the tags that are open at the start of each line, then at the end of the last one, with the codes of their style and their closing tag
+ $openTags = [[]];
+ foreach ($lines as $i => $line) {
+ $tags = $openTags[$i];
+ preg_match_all('#<(?:[a-z](?:[^\\\\<>]*+|\\\\.)*|/(?:[a-z][^<>]*+)?)>#i', $line, $matches, \PREG_OFFSET_CAPTURE);
+
+ foreach ($matches[0] as [$tag, $offset]) {
+ if ($offset && '\\' === $line[$offset - 1]) {
+ continue;
+ }
+
+ if ('>' !== $tag) {
+ // the formatter pushes the style of a style tag, and prints any other tag as is
+ $name = ltrim(substr($tag, 1), '/');
+ $this->tagStyles[$name] ??= '' === $formatter->format('<'.$name) ? $formatter->getStyleStack()->pop()->apply('') : false;
+ if (false === $style = $this->tagStyles[$name]) {
+ continue;
+ }
+
+ if ('/' !== $tag[1]) {
+ // a closing tag cannot contain an escaped "<" or ">"
+ $tags[] = [$tag, $style, strpbrk(substr($tag, 1, -1), '<>') ? '>' : ''.substr($tag, 1)];
+ continue;
+ }
+ }
+
+ if (!$tags) {
+ continue;
+ } elseif ('>' === $tag) {
+ array_pop($tags);
+ } else {
+ // like the formatter, close the last tag that has the same style, and the ones opened after it
+ for ($j = \count($tags) - 1; $j >= 0; --$j) {
+ if ($style === $tags[$j][1]) {
+ array_splice($tags, $j);
+ break;
+ }
+ }
+ }
+ }
+
+ $openTags[] = $tags;
+ }
+
+ if (!array_filter(\array_slice($openTags, 1, null === $eol ? null : -1))) {
+ return $cell;
+ }
+
+ foreach ($lines as $i => $line) {
+ // closing tags go before the trailing backslashes of the line, which would escape them otherwise
+ $text = rtrim($line, '\\');
+ $line = $text.implode('', array_reverse(array_column($openTags[$i + 1], 2))).substr($line, \strlen($text));
+ $lines[$i] = implode('', array_column($openTags[$i], 0)).$line;
+ }
+ $value = implode($eol ?? '', $lines);
+
+ return $cell instanceof TableCell ? new TableCell($value, ['rowspan' => $cell->getRowspan(), 'colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]) : $value;
+ }
+
+ /**
+ * Gets number of columns by row.
+ */
+ private function getNumberOfColumns(array $row): int
+ {
+ $columns = \count($row);
+ foreach ($row as $column) {
+ $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Gets list of columns for the given row.
+ */
+ private function getRowColumns(array $row): array
+ {
+ $columns = range(0, $this->numberOfColumns - 1);
+ foreach ($row as $cellKey => $cell) {
+ if ($cell instanceof TableCell && $cell->getColspan() > 1) {
+ // exclude grouped columns.
+ $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
+ }
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Calculates columns widths.
+ */
+ private function calculateColumnsWidth(iterable $groups): void
+ {
+ for ($column = 0; $column < $this->numberOfColumns; ++$column) {
+ $lengths = [];
+ foreach ($groups as $group) {
+ foreach ($group as $row) {
+ if ($row instanceof TableSeparator) {
+ continue;
+ }
+
+ foreach ($row as $i => $cell) {
+ if ($cell instanceof TableCell) {
+ $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
+ $textLength = Helper::width($textContent);
+ if ($textLength > 0) {
+ $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan()));
+ foreach ($contentColumns as $position => $content) {
+ $row[$i + $position] = OutputFormatter::escape($content);
+ }
+ }
+ }
+ }
+
+ $lengths[] = $this->getCellWidth($row, $column);
+ }
+ }
+
+ $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2;
+ }
+ }
+
+ private function getColumnSeparatorWidth(): int
+ {
+ return Helper::width(\sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
+ }
+
+ private function getCellWidth(array $row, int $column): int
+ {
+ $cellWidth = 0;
+
+ if (isset($row[$column])) {
+ $cell = $row[$column];
+ $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
+ }
+
+ $columnWidth = $this->columnWidths[$column] ?? 0;
+ $cellWidth = max($cellWidth, $columnWidth);
+
+ return isset($this->columnMaxWidths[$column]) && self::$styles['markdown'] !== $this->style ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
+ }
+
+ /**
+ * Called after rendering to cleanup cache data.
+ */
+ private function cleanup(): void
+ {
+ $this->effectiveColumnWidths = [];
+ $this->tagStyles = [];
+ $this->rowspanLines = null;
+ unset($this->numberOfColumns);
+ }
+
+ /**
+ * @return array
+ */
+ private static function initStyles(): array
+ {
+ $markdown = new TableStyle();
+ $markdown
+ ->setDefaultCrossingChar('|')
+ ->setDisplayOutsideBorder(false)
+ ;
+
+ $borderless = new TableStyle();
+ $borderless
+ ->setHorizontalBorderChars('=')
+ ->setVerticalBorderChars(' ')
+ ->setDefaultCrossingChar(' ')
+ ;
+
+ $compact = new TableStyle();
+ $compact
+ ->setHorizontalBorderChars('')
+ ->setVerticalBorderChars('')
+ ->setDefaultCrossingChar('')
+ ->setCellRowContentFormat('%s ')
+ ;
+
+ $styleGuide = new TableStyle();
+ $styleGuide
+ ->setHorizontalBorderChars('-')
+ ->setVerticalBorderChars(' ')
+ ->setDefaultCrossingChar(' ')
+ ->setCellHeaderFormat('%s')
+ ;
+
+ $box = (new TableStyle())
+ ->setHorizontalBorderChars('─')
+ ->setVerticalBorderChars('│')
+ ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
+ ;
+
+ $boxDouble = (new TableStyle())
+ ->setHorizontalBorderChars('═', '─')
+ ->setVerticalBorderChars('║', '│')
+ ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
+ ;
+
+ return [
+ 'default' => new TableStyle(),
+ 'markdown' => $markdown,
+ 'borderless' => $borderless,
+ 'compact' => $compact,
+ 'symfony-style-guide' => $styleGuide,
+ 'box' => $box,
+ 'box-double' => $boxDouble,
+ ];
+ }
+
+ private function resolveStyle(TableStyle|string $name): TableStyle
+ {
+ if ($name instanceof TableStyle) {
+ return $name;
+ }
+
+ return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
+ }
+
+ /**
+ * @return string[]
+ */
+ private function renderLines(): array
+ {
+ $output = $this->output;
+ $buffer = new BufferedOutput($output->getVerbosity(), true, $output->getFormatter());
+ $this->output = $buffer;
+
+ try {
+ $this->render();
+ } finally {
+ $this->output = $output;
+ }
+
+ return preg_split('/(?<=\n)/', $buffer->fetch(), -1, \PREG_SPLIT_NO_EMPTY) ?: [];
+ }
+}
diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php
new file mode 100644
index 0000000..ab83392
--- /dev/null
+++ b/vendor/symfony/console/Helper/TableCell.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+
+/**
+ * @author Abdellatif Ait boudad
+ */
+class TableCell
+{
+ private array $options = [
+ 'rowspan' => 1,
+ 'colspan' => 1,
+ 'style' => null,
+ ];
+
+ public function __construct(
+ private string $value = '',
+ array $options = [],
+ ) {
+ // check option names
+ if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
+ throw new InvalidArgumentException(\sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
+ }
+
+ if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
+ throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".');
+ }
+
+ $this->options = array_merge($this->options, $options);
+ }
+
+ /**
+ * Returns the cell value.
+ */
+ public function __toString(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Gets number of colspan.
+ */
+ public function getColspan(): int
+ {
+ return (int) $this->options['colspan'];
+ }
+
+ /**
+ * Gets number of rowspan.
+ */
+ public function getRowspan(): int
+ {
+ return (int) $this->options['rowspan'];
+ }
+
+ public function getStyle(): ?TableCellStyle
+ {
+ return $this->options['style'];
+ }
+}
diff --git a/vendor/symfony/console/Helper/TableCellStyle.php b/vendor/symfony/console/Helper/TableCellStyle.php
new file mode 100644
index 0000000..af1a17e
--- /dev/null
+++ b/vendor/symfony/console/Helper/TableCellStyle.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+
+/**
+ * @author Yewhen Khoptynskyi
+ */
+class TableCellStyle
+{
+ public const DEFAULT_ALIGN = 'left';
+
+ private const TAG_OPTIONS = [
+ 'fg',
+ 'bg',
+ 'options',
+ ];
+
+ private const ALIGN_MAP = [
+ 'left' => \STR_PAD_RIGHT,
+ 'center' => \STR_PAD_BOTH,
+ 'right' => \STR_PAD_LEFT,
+ ];
+
+ private array $options = [
+ 'fg' => 'default',
+ 'bg' => 'default',
+ 'options' => null,
+ 'align' => self::DEFAULT_ALIGN,
+ 'cellFormat' => null,
+ ];
+
+ public function __construct(array $options = [])
+ {
+ if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
+ throw new InvalidArgumentException(\sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
+ }
+
+ if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) {
+ throw new InvalidArgumentException(\sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP))));
+ }
+
+ $this->options = array_merge($this->options, $options);
+ }
+
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ /**
+ * Gets options we need for tag for example fg, bg.
+ *
+ * @return string[]
+ */
+ public function getTagOptions(): array
+ {
+ return array_filter(
+ $this->getOptions(),
+ fn ($key) => \in_array($key, self::TAG_OPTIONS, true) && isset($this->options[$key]),
+ \ARRAY_FILTER_USE_KEY
+ );
+ }
+
+ public function getPadByAlign(): int
+ {
+ return self::ALIGN_MAP[$this->getOptions()['align']];
+ }
+
+ public function getCellFormat(): ?string
+ {
+ return $this->getOptions()['cellFormat'];
+ }
+}
diff --git a/vendor/symfony/console/Helper/TableRows.php b/vendor/symfony/console/Helper/TableRows.php
new file mode 100644
index 0000000..fb2dc27
--- /dev/null
+++ b/vendor/symfony/console/Helper/TableRows.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+/**
+ * @internal
+ */
+class TableRows implements \IteratorAggregate
+{
+ public function __construct(
+ private \Closure $generator,
+ ) {
+ }
+
+ public function getIterator(): \Traversable
+ {
+ return ($this->generator)();
+ }
+}
diff --git a/vendor/symfony/console/Helper/TableSeparator.php b/vendor/symfony/console/Helper/TableSeparator.php
new file mode 100644
index 0000000..e541c53
--- /dev/null
+++ b/vendor/symfony/console/Helper/TableSeparator.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+/**
+ * Marks a row as being a separator.
+ *
+ * @author Fabien Potencier
+ */
+class TableSeparator extends TableCell
+{
+ public function __construct(array $options = [])
+ {
+ parent::__construct('', $options);
+ }
+}
diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php
new file mode 100644
index 0000000..bc941ef
--- /dev/null
+++ b/vendor/symfony/console/Helper/TableStyle.php
@@ -0,0 +1,375 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Exception\LogicException;
+
+/**
+ * Defines the styles for a Table.
+ *
+ * @author Fabien Potencier
+ * @author Саша Стаменковић
+ * @author Dany Maillard
+ */
+class TableStyle
+{
+ private string $paddingChar = ' ';
+ private string $horizontalOutsideBorderChar = '-';
+ private string $horizontalInsideBorderChar = '-';
+ private string $verticalOutsideBorderChar = '|';
+ private string $verticalInsideBorderChar = '|';
+ private string $crossingChar = '+';
+ private string $crossingTopRightChar = '+';
+ private string $crossingTopMidChar = '+';
+ private string $crossingTopLeftChar = '+';
+ private string $crossingMidRightChar = '+';
+ private string $crossingBottomRightChar = '+';
+ private string $crossingBottomMidChar = '+';
+ private string $crossingBottomLeftChar = '+';
+ private string $crossingMidLeftChar = '+';
+ private string $crossingTopLeftBottomChar = '+';
+ private string $crossingTopMidBottomChar = '+';
+ private string $crossingTopRightBottomChar = '+';
+ private string $headerTitleFormat = ' %s >';
+ private string $footerTitleFormat = '