summaryrefslogtreecommitdiff
path: root/vendor/symfony/console/Helper
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/console/Helper')
-rw-r--r--vendor/symfony/console/Helper/DebugFormatterHelper.php98
-rw-r--r--vendor/symfony/console/Helper/DescriptorHelper.php91
-rw-r--r--vendor/symfony/console/Helper/Dumper.php53
-rw-r--r--vendor/symfony/console/Helper/FormatterHelper.php81
-rw-r--r--vendor/symfony/console/Helper/Helper.php159
-rw-r--r--vendor/symfony/console/Helper/HelperInterface.php35
-rw-r--r--vendor/symfony/console/Helper/HelperSet.php74
-rw-r--r--vendor/symfony/console/Helper/InputAwareHelper.php30
-rw-r--r--vendor/symfony/console/Helper/OutputWrapper.php76
-rw-r--r--vendor/symfony/console/Helper/ProcessHelper.php137
-rw-r--r--vendor/symfony/console/Helper/ProgressBar.php645
-rw-r--r--vendor/symfony/console/Helper/ProgressIndicator.php225
-rw-r--r--vendor/symfony/console/Helper/QuestionHelper.php589
-rw-r--r--vendor/symfony/console/Helper/SymfonyQuestionHelper.php103
-rw-r--r--vendor/symfony/console/Helper/Table.php924
-rw-r--r--vendor/symfony/console/Helper/TableCell.php71
-rw-r--r--vendor/symfony/console/Helper/TableCellStyle.php84
-rw-r--r--vendor/symfony/console/Helper/TableRows.php28
-rw-r--r--vendor/symfony/console/Helper/TableSeparator.php25
-rw-r--r--vendor/symfony/console/Helper/TableStyle.php362
20 files changed, 3890 insertions, 0 deletions
diff --git a/vendor/symfony/console/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php
new file mode 100644
index 0000000..9ea7fb9
--- /dev/null
+++ b/vendor/symfony/console/Helper/DebugFormatterHelper.php
@@ -0,0 +1,98 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14/**
15 * Helps outputting debug information when running an external program from a command.
16 *
17 * An external program can be a Process, an HTTP request, or anything else.
18 *
19 * @author Fabien Potencier <fabien@symfony.com>
20 */
21class DebugFormatterHelper extends Helper
22{
23 private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
24 private array $started = [];
25 private int $count = -1;
26
27 /**
28 * Starts a debug formatting session.
29 */
30 public function start(string $id, string $message, string $prefix = 'RUN'): string
31 {
32 $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)];
33
34 return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
35 }
36
37 /**
38 * Adds progress to a formatting session.
39 */
40 public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string
41 {
42 $message = '';
43
44 if ($error) {
45 if (isset($this->started[$id]['out'])) {
46 $message .= "\n";
47 unset($this->started[$id]['out']);
48 }
49 if (!isset($this->started[$id]['err'])) {
50 $message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
51 $this->started[$id]['err'] = true;
52 }
53
54 $message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
55 } else {
56 if (isset($this->started[$id]['err'])) {
57 $message .= "\n";
58 unset($this->started[$id]['err']);
59 }
60 if (!isset($this->started[$id]['out'])) {
61 $message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
62 $this->started[$id]['out'] = true;
63 }
64
65 $message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
66 }
67
68 return $message;
69 }
70
71 /**
72 * Stops a formatting session.
73 */
74 public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string
75 {
76 $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
77
78 if ($successful) {
79 return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
80 }
81
82 $message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
83
84 unset($this->started[$id]['out'], $this->started[$id]['err']);
85
86 return $message;
87 }
88
89 private function getBorder(string $id): string
90 {
91 return sprintf('<bg=%s> </>', self::COLORS[$this->started[$id]['border']]);
92 }
93
94 public function getName(): string
95 {
96 return 'debug_formatter';
97 }
98}
diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php
new file mode 100644
index 0000000..300c7b1
--- /dev/null
+++ b/vendor/symfony/console/Helper/DescriptorHelper.php
@@ -0,0 +1,91 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Descriptor\DescriptorInterface;
15use Symfony\Component\Console\Descriptor\JsonDescriptor;
16use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
17use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor;
18use Symfony\Component\Console\Descriptor\TextDescriptor;
19use Symfony\Component\Console\Descriptor\XmlDescriptor;
20use Symfony\Component\Console\Exception\InvalidArgumentException;
21use Symfony\Component\Console\Output\OutputInterface;
22
23/**
24 * This class adds helper method to describe objects in various formats.
25 *
26 * @author Jean-François Simon <contact@jfsimon.fr>
27 */
28class DescriptorHelper extends Helper
29{
30 /**
31 * @var DescriptorInterface[]
32 */
33 private array $descriptors = [];
34
35 public function __construct()
36 {
37 $this
38 ->register('txt', new TextDescriptor())
39 ->register('xml', new XmlDescriptor())
40 ->register('json', new JsonDescriptor())
41 ->register('md', new MarkdownDescriptor())
42 ->register('rst', new ReStructuredTextDescriptor())
43 ;
44 }
45
46 /**
47 * Describes an object if supported.
48 *
49 * Available options are:
50 * * format: string, the output format name
51 * * raw_text: boolean, sets output type as raw
52 *
53 * @throws InvalidArgumentException when the given format is not supported
54 */
55 public function describe(OutputInterface $output, ?object $object, array $options = []): void
56 {
57 $options = array_merge([
58 'raw_text' => false,
59 'format' => 'txt',
60 ], $options);
61
62 if (!isset($this->descriptors[$options['format']])) {
63 throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
64 }
65
66 $descriptor = $this->descriptors[$options['format']];
67 $descriptor->describe($output, $object, $options);
68 }
69
70 /**
71 * Registers a descriptor.
72 *
73 * @return $this
74 */
75 public function register(string $format, DescriptorInterface $descriptor): static
76 {
77 $this->descriptors[$format] = $descriptor;
78
79 return $this;
80 }
81
82 public function getName(): string
83 {
84 return 'descriptor';
85 }
86
87 public function getFormats(): array
88 {
89 return array_keys($this->descriptors);
90 }
91}
diff --git a/vendor/symfony/console/Helper/Dumper.php b/vendor/symfony/console/Helper/Dumper.php
new file mode 100644
index 0000000..0cd01e6
--- /dev/null
+++ b/vendor/symfony/console/Helper/Dumper.php
@@ -0,0 +1,53 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Output\OutputInterface;
15use Symfony\Component\VarDumper\Cloner\ClonerInterface;
16use Symfony\Component\VarDumper\Cloner\VarCloner;
17use Symfony\Component\VarDumper\Dumper\CliDumper;
18
19/**
20 * @author Roland Franssen <franssen.roland@gmail.com>
21 */
22final class Dumper
23{
24 private \Closure $handler;
25
26 public function __construct(
27 private OutputInterface $output,
28 private ?CliDumper $dumper = null,
29 private ?ClonerInterface $cloner = null,
30 ) {
31 if (class_exists(CliDumper::class)) {
32 $this->handler = function ($var): string {
33 $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
34 $dumper->setColors($this->output->isDecorated());
35
36 return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true));
37 };
38 } else {
39 $this->handler = fn ($var): string => match (true) {
40 null === $var => 'null',
41 true === $var => 'true',
42 false === $var => 'false',
43 \is_string($var) => '"'.$var.'"',
44 default => rtrim(print_r($var, true)),
45 };
46 }
47 }
48
49 public function __invoke(mixed $var): string
50 {
51 return ($this->handler)($var);
52 }
53}
diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php
new file mode 100644
index 0000000..279e4c7
--- /dev/null
+++ b/vendor/symfony/console/Helper/FormatterHelper.php
@@ -0,0 +1,81 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Formatter\OutputFormatter;
15
16/**
17 * The Formatter class provides helpers to format messages.
18 *
19 * @author Fabien Potencier <fabien@symfony.com>
20 */
21class FormatterHelper extends Helper
22{
23 /**
24 * Formats a message within a section.
25 */
26 public function formatSection(string $section, string $message, string $style = 'info'): string
27 {
28 return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
29 }
30
31 /**
32 * Formats a message as a block of text.
33 */
34 public function formatBlock(string|array $messages, string $style, bool $large = false): string
35 {
36 if (!\is_array($messages)) {
37 $messages = [$messages];
38 }
39
40 $len = 0;
41 $lines = [];
42 foreach ($messages as $message) {
43 $message = OutputFormatter::escape($message);
44 $lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
45 $len = max(self::width($message) + ($large ? 4 : 2), $len);
46 }
47
48 $messages = $large ? [str_repeat(' ', $len)] : [];
49 for ($i = 0; isset($lines[$i]); ++$i) {
50 $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i]));
51 }
52 if ($large) {
53 $messages[] = str_repeat(' ', $len);
54 }
55
56 for ($i = 0; isset($messages[$i]); ++$i) {
57 $messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
58 }
59
60 return implode("\n", $messages);
61 }
62
63 /**
64 * Truncates a message to the given length.
65 */
66 public function truncate(string $message, int $length, string $suffix = '...'): string
67 {
68 $computedLength = $length - self::width($suffix);
69
70 if ($computedLength > self::width($message)) {
71 return $message;
72 }
73
74 return self::substr($message, 0, $length).$suffix;
75 }
76
77 public function getName(): string
78 {
79 return 'formatter';
80 }
81}
diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php
new file mode 100644
index 0000000..de09006
--- /dev/null
+++ b/vendor/symfony/console/Helper/Helper.php
@@ -0,0 +1,159 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Formatter\OutputFormatterInterface;
15use Symfony\Component\String\UnicodeString;
16
17/**
18 * Helper is the base class for all helper classes.
19 *
20 * @author Fabien Potencier <fabien@symfony.com>
21 */
22abstract class Helper implements HelperInterface
23{
24 protected ?HelperSet $helperSet = null;
25
26 public function setHelperSet(?HelperSet $helperSet): void
27 {
28 $this->helperSet = $helperSet;
29 }
30
31 public function getHelperSet(): ?HelperSet
32 {
33 return $this->helperSet;
34 }
35
36 /**
37 * Returns the width of a string, using mb_strwidth if it is available.
38 * The width is how many characters positions the string will use.
39 */
40 public static function width(?string $string): int
41 {
42 $string ??= '';
43
44 if (preg_match('//u', $string)) {
45 return (new UnicodeString($string))->width(false);
46 }
47
48 if (false === $encoding = mb_detect_encoding($string, null, true)) {
49 return \strlen($string);
50 }
51
52 return mb_strwidth($string, $encoding);
53 }
54
55 /**
56 * Returns the length of a string, using mb_strlen if it is available.
57 * The length is related to how many bytes the string will use.
58 */
59 public static function length(?string $string): int
60 {
61 $string ??= '';
62
63 if (preg_match('//u', $string)) {
64 return (new UnicodeString($string))->length();
65 }
66
67 if (false === $encoding = mb_detect_encoding($string, null, true)) {
68 return \strlen($string);
69 }
70
71 return mb_strlen($string, $encoding);
72 }
73
74 /**
75 * Returns the subset of a string, using mb_substr if it is available.
76 */
77 public static function substr(?string $string, int $from, ?int $length = null): string
78 {
79 $string ??= '';
80
81 if (false === $encoding = mb_detect_encoding($string, null, true)) {
82 return substr($string, $from, $length);
83 }
84
85 return mb_substr($string, $from, $length, $encoding);
86 }
87
88 public static function formatTime(int|float $secs, int $precision = 1): string
89 {
90 $secs = (int) floor($secs);
91
92 if (0 === $secs) {
93 return '< 1 sec';
94 }
95
96 static $timeFormats = [
97 [1, '1 sec', 'secs'],
98 [60, '1 min', 'mins'],
99 [3600, '1 hr', 'hrs'],
100 [86400, '1 day', 'days'],
101 ];
102
103 $times = [];
104 foreach ($timeFormats as $index => $format) {
105 $seconds = isset($timeFormats[$index + 1]) ? $secs % $timeFormats[$index + 1][0] : $secs;
106
107 if (isset($times[$index - $precision])) {
108 unset($times[$index - $precision]);
109 }
110
111 if (0 === $seconds) {
112 continue;
113 }
114
115 $unitCount = ($seconds / $format[0]);
116 $times[$index] = 1 === $unitCount ? $format[1] : $unitCount.' '.$format[2];
117
118 if ($secs === $seconds) {
119 break;
120 }
121
122 $secs -= $seconds;
123 }
124
125 return implode(', ', array_reverse($times));
126 }
127
128 public static function formatMemory(int $memory): string
129 {
130 if ($memory >= 1024 * 1024 * 1024) {
131 return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
132 }
133
134 if ($memory >= 1024 * 1024) {
135 return sprintf('%.1f MiB', $memory / 1024 / 1024);
136 }
137
138 if ($memory >= 1024) {
139 return sprintf('%d KiB', $memory / 1024);
140 }
141
142 return sprintf('%d B', $memory);
143 }
144
145 public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string): string
146 {
147 $isDecorated = $formatter->isDecorated();
148 $formatter->setDecorated(false);
149 // remove <...> formatting
150 $string = $formatter->format($string ?? '');
151 // remove already formatted characters
152 $string = preg_replace("/\033\[[^m]*m/", '', $string ?? '');
153 // remove terminal hyperlinks
154 $string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? '');
155 $formatter->setDecorated($isDecorated);
156
157 return $string;
158 }
159}
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 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14/**
15 * HelperInterface is the interface all helpers must implement.
16 *
17 * @author Fabien Potencier <fabien@symfony.com>
18 */
19interface HelperInterface
20{
21 /**
22 * Sets the helper set associated with this helper.
23 */
24 public function setHelperSet(?HelperSet $helperSet): void;
25
26 /**
27 * Gets the helper set associated with this helper.
28 */
29 public function getHelperSet(): ?HelperSet;
30
31 /**
32 * Returns the canonical name of this helper.
33 */
34 public function getName(): string;
35}
diff --git a/vendor/symfony/console/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php
new file mode 100644
index 0000000..30df9f9
--- /dev/null
+++ b/vendor/symfony/console/Helper/HelperSet.php
@@ -0,0 +1,74 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Exception\InvalidArgumentException;
15
16/**
17 * HelperSet represents a set of helpers to be used with a command.
18 *
19 * @author Fabien Potencier <fabien@symfony.com>
20 *
21 * @implements \IteratorAggregate<string, HelperInterface>
22 */
23class HelperSet implements \IteratorAggregate
24{
25 /** @var array<string, HelperInterface> */
26 private array $helpers = [];
27
28 /**
29 * @param HelperInterface[] $helpers
30 */
31 public function __construct(array $helpers = [])
32 {
33 foreach ($helpers as $alias => $helper) {
34 $this->set($helper, \is_int($alias) ? null : $alias);
35 }
36 }
37
38 public function set(HelperInterface $helper, ?string $alias = null): void
39 {
40 $this->helpers[$helper->getName()] = $helper;
41 if (null !== $alias) {
42 $this->helpers[$alias] = $helper;
43 }
44
45 $helper->setHelperSet($this);
46 }
47
48 /**
49 * Returns true if the helper if defined.
50 */
51 public function has(string $name): bool
52 {
53 return isset($this->helpers[$name]);
54 }
55
56 /**
57 * Gets a helper value.
58 *
59 * @throws InvalidArgumentException if the helper is not defined
60 */
61 public function get(string $name): HelperInterface
62 {
63 if (!$this->has($name)) {
64 throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
65 }
66
67 return $this->helpers[$name];
68 }
69
70 public function getIterator(): \Traversable
71 {
72 return new \ArrayIterator($this->helpers);
73 }
74}
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 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Input\InputAwareInterface;
15use Symfony\Component\Console\Input\InputInterface;
16
17/**
18 * An implementation of InputAwareInterface for Helpers.
19 *
20 * @author Wouter J <waldio.webdesign@gmail.com>
21 */
22abstract class InputAwareHelper extends Helper implements InputAwareInterface
23{
24 protected InputInterface $input;
25
26 public function setInput(InputInterface $input): void
27 {
28 $this->input = $input;
29 }
30}
diff --git a/vendor/symfony/console/Helper/OutputWrapper.php b/vendor/symfony/console/Helper/OutputWrapper.php
new file mode 100644
index 0000000..0ea2b70
--- /dev/null
+++ b/vendor/symfony/console/Helper/OutputWrapper.php
@@ -0,0 +1,76 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14/**
15 * Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
16 * answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN).
17 *
18 * (?:
19 * # -- Words/Characters
20 * ( # (1 start)
21 * (?> # Atomic Group - Match words with valid breaks
22 * .{1,16} # 1-N characters
23 * # Followed by one of 4 prioritized, non-linebreak whitespace
24 * (?: # break types:
25 * (?<= [^\S\r\n] ) # 1. - Behind a non-linebreak whitespace
26 * [^\S\r\n]? # ( optionally accept an extra non-linebreak whitespace )
27 * | (?= \r? \n ) # 2. - Ahead a linebreak
28 * | $ # 3. - EOS
29 * | [^\S\r\n] # 4. - Accept an extra non-linebreak whitespace
30 * )
31 * ) # End atomic group
32 * |
33 * .{1,16} # No valid word breaks, just break on the N'th character
34 * ) # (1 end)
35 * (?: \r? \n )? # Optional linebreak after Words/Characters
36 * |
37 * # -- Or, Linebreak
38 * (?: \r? \n | $ ) # Stand alone linebreak or at EOS
39 * )
40 *
41 * @author Krisztián Ferenczi <ferenczi.krisztian@gmail.com>
42 *
43 * @see https://stackoverflow.com/a/20434776/1476819
44 */
45final class OutputWrapper
46{
47 private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
48 private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+';
49 private const URL_PATTERN = 'https?://\S+';
50
51 public function __construct(
52 private bool $allowCutUrls = false,
53 ) {
54 }
55
56 public function wrap(string $text, int $width, string $break = "\n"): string
57 {
58 if (!$width) {
59 return $text;
60 }
61
62 $tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
63 $limitPattern = "{1,$width}";
64 $patternBlocks = [$tagPattern];
65 if (!$this->allowCutUrls) {
66 $patternBlocks[] = self::URL_PATTERN;
67 }
68 $patternBlocks[] = '.';
69 $blocks = implode('|', $patternBlocks);
70 $rowPattern = "(?:$blocks)$limitPattern";
71 $pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
72 $output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);
73
74 return str_replace(' '.$break, $break, $output);
75 }
76}
diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php
new file mode 100644
index 0000000..3ef6f71
--- /dev/null
+++ b/vendor/symfony/console/Helper/ProcessHelper.php
@@ -0,0 +1,137 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Output\ConsoleOutputInterface;
15use Symfony\Component\Console\Output\OutputInterface;
16use Symfony\Component\Process\Exception\ProcessFailedException;
17use Symfony\Component\Process\Process;
18
19/**
20 * The ProcessHelper class provides helpers to run external processes.
21 *
22 * @author Fabien Potencier <fabien@symfony.com>
23 *
24 * @final
25 */
26class ProcessHelper extends Helper
27{
28 /**
29 * Runs an external process.
30 *
31 * @param array|Process $cmd An instance of Process or an array of the command and arguments
32 * @param callable|null $callback A PHP callback to run whenever there is some
33 * output available on STDOUT or STDERR
34 */
35 public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
36 {
37 if (!class_exists(Process::class)) {
38 throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
39 }
40
41 if ($output instanceof ConsoleOutputInterface) {
42 $output = $output->getErrorOutput();
43 }
44
45 $formatter = $this->getHelperSet()->get('debug_formatter');
46
47 if ($cmd instanceof Process) {
48 $cmd = [$cmd];
49 }
50
51 if (\is_string($cmd[0] ?? null)) {
52 $process = new Process($cmd);
53 $cmd = [];
54 } elseif (($cmd[0] ?? null) instanceof Process) {
55 $process = $cmd[0];
56 unset($cmd[0]);
57 } else {
58 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__));
59 }
60
61 if ($verbosity <= $output->getVerbosity()) {
62 $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
63 }
64
65 if ($output->isDebug()) {
66 $callback = $this->wrapCallback($output, $process, $callback);
67 }
68
69 $process->run($callback, $cmd);
70
71 if ($verbosity <= $output->getVerbosity()) {
72 $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
73 $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
74 }
75
76 if (!$process->isSuccessful() && null !== $error) {
77 $output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
78 }
79
80 return $process;
81 }
82
83 /**
84 * Runs the process.
85 *
86 * This is identical to run() except that an exception is thrown if the process
87 * exits with a non-zero exit code.
88 *
89 * @param array|Process $cmd An instance of Process or a command to run
90 * @param callable|null $callback A PHP callback to run whenever there is some
91 * output available on STDOUT or STDERR
92 *
93 * @throws ProcessFailedException
94 *
95 * @see run()
96 */
97 public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process
98 {
99 $process = $this->run($output, $cmd, $error, $callback);
100
101 if (!$process->isSuccessful()) {
102 throw new ProcessFailedException($process);
103 }
104
105 return $process;
106 }
107
108 /**
109 * Wraps a Process callback to add debugging output.
110 */
111 public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable
112 {
113 if ($output instanceof ConsoleOutputInterface) {
114 $output = $output->getErrorOutput();
115 }
116
117 $formatter = $this->getHelperSet()->get('debug_formatter');
118
119 return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
120 $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));
121
122 if (null !== $callback) {
123 $callback($type, $buffer);
124 }
125 };
126 }
127
128 private function escapeString(string $str): string
129 {
130 return str_replace('<', '\\<', $str);
131 }
132
133 public function getName(): string
134 {
135 return 'process';
136 }
137}
diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php
new file mode 100644
index 0000000..7c22b7d
--- /dev/null
+++ b/vendor/symfony/console/Helper/ProgressBar.php
@@ -0,0 +1,645 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Cursor;
15use Symfony\Component\Console\Exception\LogicException;
16use Symfony\Component\Console\Output\ConsoleOutputInterface;
17use Symfony\Component\Console\Output\ConsoleSectionOutput;
18use Symfony\Component\Console\Output\OutputInterface;
19use Symfony\Component\Console\Terminal;
20
21/**
22 * The ProgressBar provides helpers to display progress output.
23 *
24 * @author Fabien Potencier <fabien@symfony.com>
25 * @author Chris Jones <leeked@gmail.com>
26 */
27final class ProgressBar
28{
29 public const FORMAT_VERBOSE = 'verbose';
30 public const FORMAT_VERY_VERBOSE = 'very_verbose';
31 public const FORMAT_DEBUG = 'debug';
32 public const FORMAT_NORMAL = 'normal';
33
34 private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax';
35 private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax';
36 private const FORMAT_DEBUG_NOMAX = 'debug_nomax';
37 private const FORMAT_NORMAL_NOMAX = 'normal_nomax';
38
39 private int $barWidth = 28;
40 private string $barChar;
41 private string $emptyBarChar = '-';
42 private string $progressChar = '>';
43 private ?string $format = null;
44 private ?string $internalFormat = null;
45 private ?int $redrawFreq = 1;
46 private int $writeCount = 0;
47 private float $lastWriteTime = 0;
48 private float $minSecondsBetweenRedraws = 0;
49 private float $maxSecondsBetweenRedraws = 1;
50 private OutputInterface $output;
51 private int $step = 0;
52 private int $startingStep = 0;
53 private ?int $max = null;
54 private int $startTime;
55 private int $stepWidth;
56 private float $percent = 0.0;
57 private array $messages = [];
58 private bool $overwrite = true;
59 private Terminal $terminal;
60 private ?string $previousMessage = null;
61 private Cursor $cursor;
62 private array $placeholders = [];
63
64 private static array $formatters;
65 private static array $formats;
66
67 /**
68 * @param int $max Maximum steps (0 if unknown)
69 */
70 public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25)
71 {
72 if ($output instanceof ConsoleOutputInterface) {
73 $output = $output->getErrorOutput();
74 }
75
76 $this->output = $output;
77 $this->setMaxSteps($max);
78 $this->terminal = new Terminal();
79
80 if (0 < $minSecondsBetweenRedraws) {
81 $this->redrawFreq = null;
82 $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
83 }
84
85 if (!$this->output->isDecorated()) {
86 // disable overwrite when output does not support ANSI codes.
87 $this->overwrite = false;
88
89 // set a reasonable redraw frequency so output isn't flooded
90 $this->redrawFreq = null;
91 }
92
93 $this->startTime = time();
94 $this->cursor = new Cursor($output);
95 }
96
97 /**
98 * Sets a placeholder formatter for a given name, globally for all instances of ProgressBar.
99 *
100 * This method also allow you to override an existing placeholder.
101 *
102 * @param string $name The placeholder name (including the delimiter char like %)
103 * @param callable(ProgressBar):string $callable A PHP callable
104 */
105 public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
106 {
107 self::$formatters ??= self::initPlaceholderFormatters();
108
109 self::$formatters[$name] = $callable;
110 }
111
112 /**
113 * Gets the placeholder formatter for a given name.
114 *
115 * @param string $name The placeholder name (including the delimiter char like %)
116 */
117 public static function getPlaceholderFormatterDefinition(string $name): ?callable
118 {
119 self::$formatters ??= self::initPlaceholderFormatters();
120
121 return self::$formatters[$name] ?? null;
122 }
123
124 /**
125 * Sets a placeholder formatter for a given name, for this instance only.
126 *
127 * @param callable(ProgressBar):string $callable A PHP callable
128 */
129 public function setPlaceholderFormatter(string $name, callable $callable): void
130 {
131 $this->placeholders[$name] = $callable;
132 }
133
134 /**
135 * Gets the placeholder formatter for a given name.
136 *
137 * @param string $name The placeholder name (including the delimiter char like %)
138 */
139 public function getPlaceholderFormatter(string $name): ?callable
140 {
141 return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name);
142 }
143
144 /**
145 * Sets a format for a given name.
146 *
147 * This method also allow you to override an existing format.
148 *
149 * @param string $name The format name
150 * @param string $format A format string
151 */
152 public static function setFormatDefinition(string $name, string $format): void
153 {
154 self::$formats ??= self::initFormats();
155
156 self::$formats[$name] = $format;
157 }
158
159 /**
160 * Gets the format for a given name.
161 *
162 * @param string $name The format name
163 */
164 public static function getFormatDefinition(string $name): ?string
165 {
166 self::$formats ??= self::initFormats();
167
168 return self::$formats[$name] ?? null;
169 }
170
171 /**
172 * Associates a text with a named placeholder.
173 *
174 * The text is displayed when the progress bar is rendered but only
175 * when the corresponding placeholder is part of the custom format line
176 * (by wrapping the name with %).
177 *
178 * @param string $message The text to associate with the placeholder
179 * @param string $name The name of the placeholder
180 */
181 public function setMessage(string $message, string $name = 'message'): void
182 {
183 $this->messages[$name] = $message;
184 }
185
186 public function getMessage(string $name = 'message'): ?string
187 {
188 return $this->messages[$name] ?? null;
189 }
190
191 public function getStartTime(): int
192 {
193 return $this->startTime;
194 }
195
196 public function getMaxSteps(): int
197 {
198 return $this->max ?? 0;
199 }
200
201 public function getProgress(): int
202 {
203 return $this->step;
204 }
205
206 private function getStepWidth(): int
207 {
208 return $this->stepWidth;
209 }
210
211 public function getProgressPercent(): float
212 {
213 return $this->percent;
214 }
215
216 public function getBarOffset(): float
217 {
218 return floor(null !== $this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth);
219 }
220
221 public function getEstimated(): float
222 {
223 if (0 === $this->step || $this->step === $this->startingStep) {
224 return 0;
225 }
226
227 return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max);
228 }
229
230 public function getRemaining(): float
231 {
232 if (!$this->step) {
233 return 0;
234 }
235
236 return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step));
237 }
238
239 public function setBarWidth(int $size): void
240 {
241 $this->barWidth = max(1, $size);
242 }
243
244 public function getBarWidth(): int
245 {
246 return $this->barWidth;
247 }
248
249 public function setBarCharacter(string $char): void
250 {
251 $this->barChar = $char;
252 }
253
254 public function getBarCharacter(): string
255 {
256 return $this->barChar ?? (null !== $this->max ? '=' : $this->emptyBarChar);
257 }
258
259 public function setEmptyBarCharacter(string $char): void
260 {
261 $this->emptyBarChar = $char;
262 }
263
264 public function getEmptyBarCharacter(): string
265 {
266 return $this->emptyBarChar;
267 }
268
269 public function setProgressCharacter(string $char): void
270 {
271 $this->progressChar = $char;
272 }
273
274 public function getProgressCharacter(): string
275 {
276 return $this->progressChar;
277 }
278
279 public function setFormat(string $format): void
280 {
281 $this->format = null;
282 $this->internalFormat = $format;
283 }
284
285 /**
286 * Sets the redraw frequency.
287 *
288 * @param int|null $freq The frequency in steps
289 */
290 public function setRedrawFrequency(?int $freq): void
291 {
292 $this->redrawFreq = null !== $freq ? max(1, $freq) : null;
293 }
294
295 public function minSecondsBetweenRedraws(float $seconds): void
296 {
297 $this->minSecondsBetweenRedraws = $seconds;
298 }
299
300 public function maxSecondsBetweenRedraws(float $seconds): void
301 {
302 $this->maxSecondsBetweenRedraws = $seconds;
303 }
304
305 /**
306 * Returns an iterator that will automatically update the progress bar when iterated.
307 *
308 * @template TKey
309 * @template TValue
310 *
311 * @param iterable<TKey, TValue> $iterable
312 * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
313 *
314 * @return iterable<TKey, TValue>
315 */
316 public function iterate(iterable $iterable, ?int $max = null): iterable
317 {
318 if (0 === $max) {
319 $max = null;
320 }
321
322 $max ??= is_countable($iterable) ? \count($iterable) : null;
323
324 if (0 === $max) {
325 $this->max = 0;
326 $this->stepWidth = 2;
327 $this->finish();
328
329 return;
330 }
331
332 $this->start($max);
333
334 foreach ($iterable as $key => $value) {
335 yield $key => $value;
336
337 $this->advance();
338 }
339
340 $this->finish();
341 }
342
343 /**
344 * Starts the progress output.
345 *
346 * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
347 * @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar)
348 */
349 public function start(?int $max = null, int $startAt = 0): void
350 {
351 $this->startTime = time();
352 $this->step = $startAt;
353 $this->startingStep = $startAt;
354
355 $startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0;
356
357 if (null !== $max) {
358 $this->setMaxSteps($max);
359 }
360
361 $this->display();
362 }
363
364 /**
365 * Advances the progress output X steps.
366 *
367 * @param int $step Number of steps to advance
368 */
369 public function advance(int $step = 1): void
370 {
371 $this->setProgress($this->step + $step);
372 }
373
374 /**
375 * Sets whether to overwrite the progressbar, false for new line.
376 */
377 public function setOverwrite(bool $overwrite): void
378 {
379 $this->overwrite = $overwrite;
380 }
381
382 public function setProgress(int $step): void
383 {
384 if ($this->max && $step > $this->max) {
385 $this->max = $step;
386 } elseif ($step < 0) {
387 $step = 0;
388 }
389
390 $redrawFreq = $this->redrawFreq ?? (($this->max ?? 10) / 10);
391 $prevPeriod = $redrawFreq ? (int) ($this->step / $redrawFreq) : 0;
392 $currPeriod = $redrawFreq ? (int) ($step / $redrawFreq) : 0;
393 $this->step = $step;
394 $this->percent = match ($this->max) {
395 null => 0,
396 0 => 1,
397 default => (float) $this->step / $this->max,
398 };
399 $timeInterval = microtime(true) - $this->lastWriteTime;
400
401 // Draw regardless of other limits
402 if ($this->max === $step) {
403 $this->display();
404
405 return;
406 }
407
408 // Throttling
409 if ($timeInterval < $this->minSecondsBetweenRedraws) {
410 return;
411 }
412
413 // Draw each step period, but not too late
414 if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
415 $this->display();
416 }
417 }
418
419 public function setMaxSteps(?int $max): void
420 {
421 if (0 === $max) {
422 $max = null;
423 }
424
425 $this->format = null;
426 if (null === $max) {
427 $this->max = null;
428 $this->stepWidth = 4;
429 } else {
430 $this->max = max(0, $max);
431 $this->stepWidth = Helper::width((string) $this->max);
432 }
433 }
434
435 /**
436 * Finishes the progress output.
437 */
438 public function finish(): void
439 {
440 if (null === $this->max) {
441 $this->max = $this->step;
442 }
443
444 if (($this->step === $this->max || null === $this->max) && !$this->overwrite) {
445 // prevent double 100% output
446 return;
447 }
448
449 $this->setProgress($this->max ?? $this->step);
450 }
451
452 /**
453 * Outputs the current progress string.
454 */
455 public function display(): void
456 {
457 if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
458 return;
459 }
460
461 if (null === $this->format) {
462 $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
463 }
464
465 $this->overwrite($this->buildLine());
466 }
467
468 /**
469 * Removes the progress bar from the current line.
470 *
471 * This is useful if you wish to write some output
472 * while a progress bar is running.
473 * Call display() to show the progress bar again.
474 */
475 public function clear(): void
476 {
477 if (!$this->overwrite) {
478 return;
479 }
480
481 if (null === $this->format) {
482 $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
483 }
484
485 $this->overwrite('');
486 }
487
488 private function setRealFormat(string $format): void
489 {
490 // try to use the _nomax variant if available
491 if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
492 $this->format = self::getFormatDefinition($format.'_nomax');
493 } elseif (null !== self::getFormatDefinition($format)) {
494 $this->format = self::getFormatDefinition($format);
495 } else {
496 $this->format = $format;
497 }
498 }
499
500 /**
501 * Overwrites a previous message to the output.
502 */
503 private function overwrite(string $message): void
504 {
505 if ($this->previousMessage === $message) {
506 return;
507 }
508
509 $originalMessage = $message;
510
511 if ($this->overwrite) {
512 if (null !== $this->previousMessage) {
513 if ($this->output instanceof ConsoleSectionOutput) {
514 $messageLines = explode("\n", $this->previousMessage);
515 $lineCount = \count($messageLines);
516 foreach ($messageLines as $messageLine) {
517 $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine));
518 if ($messageLineLength > $this->terminal->getWidth()) {
519 $lineCount += floor($messageLineLength / $this->terminal->getWidth());
520 }
521 }
522 $this->output->clear($lineCount);
523 } else {
524 $lineCount = substr_count($this->previousMessage, "\n");
525 for ($i = 0; $i < $lineCount; ++$i) {
526 $this->cursor->moveToColumn(1);
527 $this->cursor->clearLine();
528 $this->cursor->moveUp();
529 }
530
531 $this->cursor->moveToColumn(1);
532 $this->cursor->clearLine();
533 }
534 }
535 } elseif ($this->step > 0) {
536 $message = \PHP_EOL.$message;
537 }
538
539 $this->previousMessage = $originalMessage;
540 $this->lastWriteTime = microtime(true);
541
542 $this->output->write($message);
543 ++$this->writeCount;
544 }
545
546 private function determineBestFormat(): string
547 {
548 return match ($this->output->getVerbosity()) {
549 // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
550 OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX,
551 OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX,
552 OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX,
553 default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX,
554 };
555 }
556
557 private static function initPlaceholderFormatters(): array
558 {
559 return [
560 'bar' => function (self $bar, OutputInterface $output) {
561 $completeBars = $bar->getBarOffset();
562 $display = str_repeat($bar->getBarCharacter(), $completeBars);
563 if ($completeBars < $bar->getBarWidth()) {
564 $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter()));
565 $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
566 }
567
568 return $display;
569 },
570 'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2),
571 'remaining' => function (self $bar) {
572 if (null === $bar->getMaxSteps()) {
573 throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
574 }
575
576 return Helper::formatTime($bar->getRemaining(), 2);
577 },
578 'estimated' => function (self $bar) {
579 if (null === $bar->getMaxSteps()) {
580 throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
581 }
582
583 return Helper::formatTime($bar->getEstimated(), 2);
584 },
585 'memory' => fn (self $bar) => Helper::formatMemory(memory_get_usage(true)),
586 'current' => fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT),
587 'max' => fn (self $bar) => $bar->getMaxSteps(),
588 'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100),
589 ];
590 }
591
592 private static function initFormats(): array
593 {
594 return [
595 self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%',
596 self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]',
597
598 self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
599 self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',
600
601 self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
602 self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',
603
604 self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
605 self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
606 ];
607 }
608
609 private function buildLine(): string
610 {
611 \assert(null !== $this->format);
612
613 $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
614 $callback = function ($matches) {
615 if ($formatter = $this->getPlaceholderFormatter($matches[1])) {
616 $text = $formatter($this, $this->output);
617 } elseif (isset($this->messages[$matches[1]])) {
618 $text = $this->messages[$matches[1]];
619 } else {
620 return $matches[0];
621 }
622
623 if (isset($matches[2])) {
624 $text = sprintf('%'.$matches[2], $text);
625 }
626
627 return $text;
628 };
629 $line = preg_replace_callback($regex, $callback, $this->format);
630
631 // gets string length for each sub line with multiline format
632 $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line));
633
634 $linesWidth = max($linesLength);
635
636 $terminalWidth = $this->terminal->getWidth();
637 if ($linesWidth <= $terminalWidth) {
638 return $line;
639 }
640
641 $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);
642
643 return preg_replace_callback($regex, $callback, $this->format);
644 }
645}
diff --git a/vendor/symfony/console/Helper/ProgressIndicator.php b/vendor/symfony/console/Helper/ProgressIndicator.php
new file mode 100644
index 0000000..969d835
--- /dev/null
+++ b/vendor/symfony/console/Helper/ProgressIndicator.php
@@ -0,0 +1,225 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Exception\InvalidArgumentException;
15use Symfony\Component\Console\Exception\LogicException;
16use Symfony\Component\Console\Output\OutputInterface;
17
18/**
19 * @author Kevin Bond <kevinbond@gmail.com>
20 */
21class ProgressIndicator
22{
23 private const FORMATS = [
24 'normal' => ' %indicator% %message%',
25 'normal_no_ansi' => ' %message%',
26
27 'verbose' => ' %indicator% %message% (%elapsed:6s%)',
28 'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
29
30 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
31 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
32 ];
33
34 private int $startTime;
35 private ?string $format = null;
36 private ?string $message = null;
37 private array $indicatorValues;
38 private int $indicatorCurrent;
39 private float $indicatorUpdateTime;
40 private bool $started = false;
41
42 /**
43 * @var array<string, callable>
44 */
45 private static array $formatters;
46
47 /**
48 * @param int $indicatorChangeInterval Change interval in milliseconds
49 * @param array|null $indicatorValues Animated indicator characters
50 */
51 public function __construct(
52 private OutputInterface $output,
53 ?string $format = null,
54 private int $indicatorChangeInterval = 100,
55 ?array $indicatorValues = null,
56 ) {
57
58 $format ??= $this->determineBestFormat();
59 $indicatorValues ??= ['-', '\\', '|', '/'];
60 $indicatorValues = array_values($indicatorValues);
61
62 if (2 > \count($indicatorValues)) {
63 throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
64 }
65
66 $this->format = self::getFormatDefinition($format);
67 $this->indicatorValues = $indicatorValues;
68 $this->startTime = time();
69 }
70
71 /**
72 * Sets the current indicator message.
73 */
74 public function setMessage(?string $message): void
75 {
76 $this->message = $message;
77
78 $this->display();
79 }
80
81 /**
82 * Starts the indicator output.
83 */
84 public function start(string $message): void
85 {
86 if ($this->started) {
87 throw new LogicException('Progress indicator already started.');
88 }
89
90 $this->message = $message;
91 $this->started = true;
92 $this->startTime = time();
93 $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
94 $this->indicatorCurrent = 0;
95
96 $this->display();
97 }
98
99 /**
100 * Advances the indicator.
101 */
102 public function advance(): void
103 {
104 if (!$this->started) {
105 throw new LogicException('Progress indicator has not yet been started.');
106 }
107
108 if (!$this->output->isDecorated()) {
109 return;
110 }
111
112 $currentTime = $this->getCurrentTimeInMilliseconds();
113
114 if ($currentTime < $this->indicatorUpdateTime) {
115 return;
116 }
117
118 $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
119 ++$this->indicatorCurrent;
120
121 $this->display();
122 }
123
124 /**
125 * Finish the indicator with message.
126 */
127 public function finish(string $message): void
128 {
129 if (!$this->started) {
130 throw new LogicException('Progress indicator has not yet been started.');
131 }
132
133 $this->message = $message;
134 $this->display();
135 $this->output->writeln('');
136 $this->started = false;
137 }
138
139 /**
140 * Gets the format for a given name.
141 */
142 public static function getFormatDefinition(string $name): ?string
143 {
144 return self::FORMATS[$name] ?? null;
145 }
146
147 /**
148 * Sets a placeholder formatter for a given name.
149 *
150 * This method also allow you to override an existing placeholder.
151 */
152 public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
153 {
154 self::$formatters ??= self::initPlaceholderFormatters();
155
156 self::$formatters[$name] = $callable;
157 }
158
159 /**
160 * Gets the placeholder formatter for a given name (including the delimiter char like %).
161 */
162 public static function getPlaceholderFormatterDefinition(string $name): ?callable
163 {
164 self::$formatters ??= self::initPlaceholderFormatters();
165
166 return self::$formatters[$name] ?? null;
167 }
168
169 private function display(): void
170 {
171 if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
172 return;
173 }
174
175 $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
176 if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
177 return $formatter($this);
178 }
179
180 return $matches[0];
181 }, $this->format ?? ''));
182 }
183
184 private function determineBestFormat(): string
185 {
186 return match ($this->output->getVerbosity()) {
187 // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
188 OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi',
189 OutputInterface::VERBOSITY_VERY_VERBOSE,
190 OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi',
191 default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi',
192 };
193 }
194
195 /**
196 * Overwrites a previous message to the output.
197 */
198 private function overwrite(string $message): void
199 {
200 if ($this->output->isDecorated()) {
201 $this->output->write("\x0D\x1B[2K");
202 $this->output->write($message);
203 } else {
204 $this->output->writeln($message);
205 }
206 }
207
208 private function getCurrentTimeInMilliseconds(): float
209 {
210 return round(microtime(true) * 1000);
211 }
212
213 /**
214 * @return array<string, \Closure>
215 */
216 private static function initPlaceholderFormatters(): array
217 {
218 return [
219 'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)],
220 'message' => fn (self $indicator) => $indicator->message,
221 'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2),
222 'memory' => fn () => Helper::formatMemory(memory_get_usage(true)),
223 ];
224 }
225}
diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php
new file mode 100644
index 0000000..54825c6
--- /dev/null
+++ b/vendor/symfony/console/Helper/QuestionHelper.php
@@ -0,0 +1,589 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Cursor;
15use Symfony\Component\Console\Exception\MissingInputException;
16use Symfony\Component\Console\Exception\RuntimeException;
17use Symfony\Component\Console\Formatter\OutputFormatter;
18use Symfony\Component\Console\Formatter\OutputFormatterStyle;
19use Symfony\Component\Console\Input\InputInterface;
20use Symfony\Component\Console\Input\StreamableInputInterface;
21use Symfony\Component\Console\Output\ConsoleOutputInterface;
22use Symfony\Component\Console\Output\ConsoleSectionOutput;
23use Symfony\Component\Console\Output\OutputInterface;
24use Symfony\Component\Console\Question\ChoiceQuestion;
25use Symfony\Component\Console\Question\Question;
26use Symfony\Component\Console\Terminal;
27
28use function Symfony\Component\String\s;
29
30/**
31 * The QuestionHelper class provides helpers to interact with the user.
32 *
33 * @author Fabien Potencier <fabien@symfony.com>
34 */
35class QuestionHelper extends Helper
36{
37 private static bool $stty = true;
38 private static bool $stdinIsInteractive;
39
40 /**
41 * Asks a question to the user.
42 *
43 * @return mixed The user answer
44 *
45 * @throws RuntimeException If there is no data to read in the input stream
46 */
47 public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed
48 {
49 if ($output instanceof ConsoleOutputInterface) {
50 $output = $output->getErrorOutput();
51 }
52
53 if (!$input->isInteractive()) {
54 return $this->getDefaultAnswer($question);
55 }
56
57 $inputStream = $input instanceof StreamableInputInterface ? $input->getStream() : null;
58 $inputStream ??= STDIN;
59
60 try {
61 if (!$question->getValidator()) {
62 return $this->doAsk($inputStream, $output, $question);
63 }
64
65 $interviewer = fn () => $this->doAsk($inputStream, $output, $question);
66
67 return $this->validateAttempts($interviewer, $output, $question);
68 } catch (MissingInputException $exception) {
69 $input->setInteractive(false);
70
71 if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
72 throw $exception;
73 }
74
75 return $fallbackOutput;
76 }
77 }
78
79 public function getName(): string
80 {
81 return 'question';
82 }
83
84 /**
85 * Prevents usage of stty.
86 */
87 public static function disableStty(): void
88 {
89 self::$stty = false;
90 }
91
92 /**
93 * Asks the question to the user.
94 *
95 * @param resource $inputStream
96 *
97 * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
98 */
99 private function doAsk($inputStream, OutputInterface $output, Question $question): mixed
100 {
101 $this->writePrompt($output, $question);
102
103 $autocomplete = $question->getAutocompleterCallback();
104
105 if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
106 $ret = false;
107 if ($question->isHidden()) {
108 try {
109 $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
110 $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
111 } catch (RuntimeException $e) {
112 if (!$question->isHiddenFallback()) {
113 throw $e;
114 }
115 }
116 }
117
118 if (false === $ret) {
119 $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true;
120
121 if (!$isBlocked) {
122 stream_set_blocking($inputStream, true);
123 }
124
125 $ret = $this->readInput($inputStream, $question);
126
127 if (!$isBlocked) {
128 stream_set_blocking($inputStream, false);
129 }
130
131 if (false === $ret) {
132 throw new MissingInputException('Aborted.');
133 }
134 if ($question->isTrimmable()) {
135 $ret = trim($ret);
136 }
137 }
138 } else {
139 $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
140 $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
141 }
142
143 if ($output instanceof ConsoleSectionOutput) {
144 $output->addContent(''); // add EOL to the question
145 $output->addContent($ret);
146 }
147
148 $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
149
150 if ($normalizer = $question->getNormalizer()) {
151 return $normalizer($ret);
152 }
153
154 return $ret;
155 }
156
157 private function getDefaultAnswer(Question $question): mixed
158 {
159 $default = $question->getDefault();
160
161 if (null === $default) {
162 return $default;
163 }
164
165 if ($validator = $question->getValidator()) {
166 return \call_user_func($validator, $default);
167 } elseif ($question instanceof ChoiceQuestion) {
168 $choices = $question->getChoices();
169
170 if (!$question->isMultiselect()) {
171 return $choices[$default] ?? $default;
172 }
173
174 $default = explode(',', $default);
175 foreach ($default as $k => $v) {
176 $v = $question->isTrimmable() ? trim($v) : $v;
177 $default[$k] = $choices[$v] ?? $v;
178 }
179 }
180
181 return $default;
182 }
183
184 /**
185 * Outputs the question prompt.
186 */
187 protected function writePrompt(OutputInterface $output, Question $question): void
188 {
189 $message = $question->getQuestion();
190
191 if ($question instanceof ChoiceQuestion) {
192 $output->writeln(array_merge([
193 $question->getQuestion(),
194 ], $this->formatChoiceQuestionChoices($question, 'info')));
195
196 $message = $question->getPrompt();
197 }
198
199 $output->write($message);
200 }
201
202 /**
203 * @return string[]
204 */
205 protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array
206 {
207 $messages = [];
208
209 $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices())));
210
211 foreach ($choices as $key => $value) {
212 $padding = str_repeat(' ', $maxWidth - self::width($key));
213
214 $messages[] = sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value);
215 }
216
217 return $messages;
218 }
219
220 /**
221 * Outputs an error message.
222 */
223 protected function writeError(OutputInterface $output, \Exception $error): void
224 {
225 if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
226 $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
227 } else {
228 $message = '<error>'.$error->getMessage().'</error>';
229 }
230
231 $output->writeln($message);
232 }
233
234 /**
235 * Autocompletes a question.
236 *
237 * @param resource $inputStream
238 */
239 private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
240 {
241 $cursor = new Cursor($output, $inputStream);
242
243 $fullChoice = '';
244 $ret = '';
245
246 $i = 0;
247 $ofs = -1;
248 $matches = $autocomplete($ret);
249 $numMatches = \count($matches);
250
251 $sttyMode = shell_exec('stty -g');
252 $isStdin = 'php://stdin' === (stream_get_meta_data($inputStream)['uri'] ?? null);
253 $r = [$inputStream];
254 $w = [];
255
256 // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
257 shell_exec('stty -icanon -echo');
258
259 // Add highlighted text style
260 $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
261
262 // Read a keypress
263 while (!feof($inputStream)) {
264 while ($isStdin && 0 === @stream_select($r, $w, $w, 0, 100)) {
265 // Give signal handlers a chance to run
266 $r = [$inputStream];
267 }
268 $c = fread($inputStream, 1);
269
270 // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
271 if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
272 shell_exec('stty '.$sttyMode);
273 throw new MissingInputException('Aborted.');
274 } elseif ("\177" === $c) { // Backspace Character
275 if (0 === $numMatches && 0 !== $i) {
276 --$i;
277 $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));
278
279 $fullChoice = self::substr($fullChoice, 0, $i);
280 }
281
282 if (0 === $i) {
283 $ofs = -1;
284 $matches = $autocomplete($ret);
285 $numMatches = \count($matches);
286 } else {
287 $numMatches = 0;
288 }
289
290 // Pop the last character off the end of our string
291 $ret = self::substr($ret, 0, $i);
292 } elseif ("\033" === $c) {
293 // Did we read an escape sequence?
294 $c .= fread($inputStream, 2);
295
296 // A = Up Arrow. B = Down Arrow
297 if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
298 if ('A' === $c[2] && -1 === $ofs) {
299 $ofs = 0;
300 }
301
302 if (0 === $numMatches) {
303 continue;
304 }
305
306 $ofs += ('A' === $c[2]) ? -1 : 1;
307 $ofs = ($numMatches + $ofs) % $numMatches;
308 }
309 } elseif (\ord($c) < 32) {
310 if ("\t" === $c || "\n" === $c) {
311 if ($numMatches > 0 && -1 !== $ofs) {
312 $ret = (string) $matches[$ofs];
313 // Echo out remaining chars for current match
314 $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
315 $output->write($remainingCharacters);
316 $fullChoice .= $remainingCharacters;
317 $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);
318
319 $matches = array_filter(
320 $autocomplete($ret),
321 fn ($match) => '' === $ret || str_starts_with($match, $ret)
322 );
323 $numMatches = \count($matches);
324 $ofs = -1;
325 }
326
327 if ("\n" === $c) {
328 $output->write($c);
329 break;
330 }
331
332 $numMatches = 0;
333 }
334
335 continue;
336 } else {
337 if ("\x80" <= $c) {
338 $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
339 }
340
341 $output->write($c);
342 $ret .= $c;
343 $fullChoice .= $c;
344 ++$i;
345
346 $tempRet = $ret;
347
348 if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
349 $tempRet = $this->mostRecentlyEnteredValue($fullChoice);
350 }
351
352 $numMatches = 0;
353 $ofs = 0;
354
355 foreach ($autocomplete($ret) as $value) {
356 // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
357 if (str_starts_with($value, $tempRet)) {
358 $matches[$numMatches++] = $value;
359 }
360 }
361 }
362
363 $cursor->clearLineAfter();
364
365 if ($numMatches > 0 && -1 !== $ofs) {
366 $cursor->savePosition();
367 // Write highlighted text, complete the partially entered response
368 $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
369 $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
370 $cursor->restorePosition();
371 }
372 }
373
374 // Reset stty so it behaves normally again
375 shell_exec('stty '.$sttyMode);
376
377 return $fullChoice;
378 }
379
380 private function mostRecentlyEnteredValue(string $entered): string
381 {
382 // Determine the most recent value that the user entered
383 if (!str_contains($entered, ',')) {
384 return $entered;
385 }
386
387 $choices = explode(',', $entered);
388 if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) {
389 return $lastChoice;
390 }
391
392 return $entered;
393 }
394
395 /**
396 * Gets a hidden response from user.
397 *
398 * @param resource $inputStream The handler resource
399 * @param bool $trimmable Is the answer trimmable
400 *
401 * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
402 */
403 private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
404 {
405 if ('\\' === \DIRECTORY_SEPARATOR) {
406 $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
407
408 // handle code running from a phar
409 if (str_starts_with(__FILE__, 'phar:')) {
410 $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
411 copy($exe, $tmpExe);
412 $exe = $tmpExe;
413 }
414
415 $sExec = shell_exec('"'.$exe.'"');
416 $value = $trimmable ? rtrim($sExec) : $sExec;
417 $output->writeln('');
418
419 if (isset($tmpExe)) {
420 unlink($tmpExe);
421 }
422
423 return $value;
424 }
425
426 if (self::$stty && Terminal::hasSttyAvailable()) {
427 $sttyMode = shell_exec('stty -g');
428 shell_exec('stty -echo');
429 } elseif ($this->isInteractiveInput($inputStream)) {
430 throw new RuntimeException('Unable to hide the response.');
431 }
432
433 $value = fgets($inputStream, 4096);
434
435 if (4095 === \strlen($value)) {
436 $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
437 $errOutput->warning('The value was possibly truncated by your shell or terminal emulator');
438 }
439
440 if (self::$stty && Terminal::hasSttyAvailable()) {
441 shell_exec('stty '.$sttyMode);
442 }
443
444 if (false === $value) {
445 throw new MissingInputException('Aborted.');
446 }
447 if ($trimmable) {
448 $value = trim($value);
449 }
450 $output->writeln('');
451
452 return $value;
453 }
454
455 /**
456 * Validates an attempt.
457 *
458 * @param callable $interviewer A callable that will ask for a question and return the result
459 *
460 * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
461 */
462 private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question): mixed
463 {
464 $error = null;
465 $attempts = $question->getMaxAttempts();
466
467 while (null === $attempts || $attempts--) {
468 if (null !== $error) {
469 $this->writeError($output, $error);
470 }
471
472 try {
473 return $question->getValidator()($interviewer());
474 } catch (RuntimeException $e) {
475 throw $e;
476 } catch (\Exception $error) {
477 }
478 }
479
480 throw $error;
481 }
482
483 private function isInteractiveInput($inputStream): bool
484 {
485 if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
486 return false;
487 }
488
489 if (isset(self::$stdinIsInteractive)) {
490 return self::$stdinIsInteractive;
491 }
492
493 return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
494 }
495
496 /**
497 * Reads one or more lines of input and returns what is read.
498 *
499 * @param resource $inputStream The handler resource
500 * @param Question $question The question being asked
501 */
502 private function readInput($inputStream, Question $question): string|false
503 {
504 if (!$question->isMultiline()) {
505 $cp = $this->setIOCodepage();
506 $ret = fgets($inputStream, 4096);
507
508 return $this->resetIOCodepage($cp, $ret);
509 }
510
511 $multiLineStreamReader = $this->cloneInputStream($inputStream);
512 if (null === $multiLineStreamReader) {
513 return false;
514 }
515
516 $ret = '';
517 $cp = $this->setIOCodepage();
518 while (false !== ($char = fgetc($multiLineStreamReader))) {
519 if (\PHP_EOL === "{$ret}{$char}") {
520 break;
521 }
522 $ret .= $char;
523 }
524
525 return $this->resetIOCodepage($cp, $ret);
526 }
527
528 private function setIOCodepage(): int
529 {
530 if (\function_exists('sapi_windows_cp_set')) {
531 $cp = sapi_windows_cp_get();
532 sapi_windows_cp_set(sapi_windows_cp_get('oem'));
533
534 return $cp;
535 }
536
537 return 0;
538 }
539
540 /**
541 * Sets console I/O to the specified code page and converts the user input.
542 */
543 private function resetIOCodepage(int $cp, string|false $input): string|false
544 {
545 if (0 !== $cp) {
546 sapi_windows_cp_set($cp);
547
548 if (false !== $input && '' !== $input) {
549 $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
550 }
551 }
552
553 return $input;
554 }
555
556 /**
557 * Clones an input stream in order to act on one instance of the same
558 * stream without affecting the other instance.
559 *
560 * @param resource $inputStream The handler resource
561 *
562 * @return resource|null The cloned resource, null in case it could not be cloned
563 */
564 private function cloneInputStream($inputStream)
565 {
566 $streamMetaData = stream_get_meta_data($inputStream);
567 $seekable = $streamMetaData['seekable'] ?? false;
568 $mode = $streamMetaData['mode'] ?? 'rb';
569 $uri = $streamMetaData['uri'] ?? null;
570
571 if (null === $uri) {
572 return null;
573 }
574
575 $cloneStream = fopen($uri, $mode);
576
577 // For seekable and writable streams, add all the same data to the
578 // cloned stream and then seek to the same offset.
579 if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
580 $offset = ftell($inputStream);
581 rewind($inputStream);
582 stream_copy_to_stream($inputStream, $cloneStream);
583 fseek($inputStream, $offset);
584 fseek($cloneStream, $offset);
585 }
586
587 return $cloneStream;
588 }
589}
diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php
new file mode 100644
index 0000000..48d947b
--- /dev/null
+++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php
@@ -0,0 +1,103 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Formatter\OutputFormatter;
15use Symfony\Component\Console\Output\OutputInterface;
16use Symfony\Component\Console\Question\ChoiceQuestion;
17use Symfony\Component\Console\Question\ConfirmationQuestion;
18use Symfony\Component\Console\Question\Question;
19use Symfony\Component\Console\Style\SymfonyStyle;
20
21/**
22 * Symfony Style Guide compliant question helper.
23 *
24 * @author Kevin Bond <kevinbond@gmail.com>
25 */
26class SymfonyQuestionHelper extends QuestionHelper
27{
28 protected function writePrompt(OutputInterface $output, Question $question): void
29 {
30 $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
31 $default = $question->getDefault();
32
33 if ($question->isMultiline()) {
34 $text .= sprintf(' (press %s to continue)', $this->getEofShortcut());
35 }
36
37 switch (true) {
38 case null === $default:
39 $text = sprintf(' <info>%s</info>:', $text);
40
41 break;
42
43 case $question instanceof ConfirmationQuestion:
44 $text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
45
46 break;
47
48 case $question instanceof ChoiceQuestion && $question->isMultiselect():
49 $choices = $question->getChoices();
50 $default = explode(',', $default);
51
52 foreach ($default as $key => $value) {
53 $default[$key] = $choices[trim($value)];
54 }
55
56 $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));
57
58 break;
59
60 case $question instanceof ChoiceQuestion:
61 $choices = $question->getChoices();
62 $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));
63
64 break;
65
66 default:
67 $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
68 }
69
70 $output->writeln($text);
71
72 $prompt = ' > ';
73
74 if ($question instanceof ChoiceQuestion) {
75 $output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
76
77 $prompt = $question->getPrompt();
78 }
79
80 $output->write($prompt);
81 }
82
83 protected function writeError(OutputInterface $output, \Exception $error): void
84 {
85 if ($output instanceof SymfonyStyle) {
86 $output->newLine();
87 $output->error($error->getMessage());
88
89 return;
90 }
91
92 parent::writeError($output, $error);
93 }
94
95 private function getEofShortcut(): string
96 {
97 if ('Windows' === \PHP_OS_FAMILY) {
98 return '<comment>Ctrl+Z</comment> then <comment>Enter</comment>';
99 }
100
101 return '<comment>Ctrl+D</comment>';
102 }
103}
diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php
new file mode 100644
index 0000000..09709a2
--- /dev/null
+++ b/vendor/symfony/console/Helper/Table.php
@@ -0,0 +1,924 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Exception\InvalidArgumentException;
15use Symfony\Component\Console\Exception\RuntimeException;
16use Symfony\Component\Console\Formatter\OutputFormatter;
17use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
18use Symfony\Component\Console\Output\ConsoleSectionOutput;
19use Symfony\Component\Console\Output\OutputInterface;
20
21/**
22 * Provides helpers to display a table.
23 *
24 * @author Fabien Potencier <fabien@symfony.com>
25 * @author Саша Стаменковић <umpirsky@gmail.com>
26 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
27 * @author Max Grigorian <maxakawizard@gmail.com>
28 * @author Dany Maillard <danymaillard93b@gmail.com>
29 */
30class Table
31{
32 private const SEPARATOR_TOP = 0;
33 private const SEPARATOR_TOP_BOTTOM = 1;
34 private const SEPARATOR_MID = 2;
35 private const SEPARATOR_BOTTOM = 3;
36 private const BORDER_OUTSIDE = 0;
37 private const BORDER_INSIDE = 1;
38 private const DISPLAY_ORIENTATION_DEFAULT = 'default';
39 private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal';
40 private const DISPLAY_ORIENTATION_VERTICAL = 'vertical';
41
42 private ?string $headerTitle = null;
43 private ?string $footerTitle = null;
44 private array $headers = [];
45 private array $rows = [];
46 private array $effectiveColumnWidths = [];
47 private int $numberOfColumns;
48 private TableStyle $style;
49 private array $columnStyles = [];
50 private array $columnWidths = [];
51 private array $columnMaxWidths = [];
52 private bool $rendered = false;
53 private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT;
54
55 private static array $styles;
56
57 public function __construct(
58 private OutputInterface $output,
59 ) {
60 self::$styles ??= self::initStyles();
61
62 $this->setStyle('default');
63 }
64
65 /**
66 * Sets a style definition.
67 */
68 public static function setStyleDefinition(string $name, TableStyle $style): void
69 {
70 self::$styles ??= self::initStyles();
71
72 self::$styles[$name] = $style;
73 }
74
75 /**
76 * Gets a style definition by name.
77 */
78 public static function getStyleDefinition(string $name): TableStyle
79 {
80 self::$styles ??= self::initStyles();
81
82 return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
83 }
84
85 /**
86 * Sets table style.
87 *
88 * @return $this
89 */
90 public function setStyle(TableStyle|string $name): static
91 {
92 $this->style = $this->resolveStyle($name);
93
94 return $this;
95 }
96
97 /**
98 * Gets the current table style.
99 */
100 public function getStyle(): TableStyle
101 {
102 return $this->style;
103 }
104
105 /**
106 * Sets table column style.
107 *
108 * @param TableStyle|string $name The style name or a TableStyle instance
109 *
110 * @return $this
111 */
112 public function setColumnStyle(int $columnIndex, TableStyle|string $name): static
113 {
114 $this->columnStyles[$columnIndex] = $this->resolveStyle($name);
115
116 return $this;
117 }
118
119 /**
120 * Gets the current style for a column.
121 *
122 * If style was not set, it returns the global table style.
123 */
124 public function getColumnStyle(int $columnIndex): TableStyle
125 {
126 return $this->columnStyles[$columnIndex] ?? $this->getStyle();
127 }
128
129 /**
130 * Sets the minimum width of a column.
131 *
132 * @return $this
133 */
134 public function setColumnWidth(int $columnIndex, int $width): static
135 {
136 $this->columnWidths[$columnIndex] = $width;
137
138 return $this;
139 }
140
141 /**
142 * Sets the minimum width of all columns.
143 *
144 * @return $this
145 */
146 public function setColumnWidths(array $widths): static
147 {
148 $this->columnWidths = [];
149 foreach ($widths as $index => $width) {
150 $this->setColumnWidth($index, $width);
151 }
152
153 return $this;
154 }
155
156 /**
157 * Sets the maximum width of a column.
158 *
159 * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
160 * formatted strings are preserved.
161 *
162 * @return $this
163 */
164 public function setColumnMaxWidth(int $columnIndex, int $width): static
165 {
166 if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
167 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())));
168 }
169
170 $this->columnMaxWidths[$columnIndex] = $width;
171
172 return $this;
173 }
174
175 /**
176 * @return $this
177 */
178 public function setHeaders(array $headers): static
179 {
180 $headers = array_values($headers);
181 if ($headers && !\is_array($headers[0])) {
182 $headers = [$headers];
183 }
184
185 $this->headers = $headers;
186
187 return $this;
188 }
189
190 /**
191 * @return $this
192 */
193 public function setRows(array $rows): static
194 {
195 $this->rows = [];
196
197 return $this->addRows($rows);
198 }
199
200 /**
201 * @return $this
202 */
203 public function addRows(array $rows): static
204 {
205 foreach ($rows as $row) {
206 $this->addRow($row);
207 }
208
209 return $this;
210 }
211
212 /**
213 * @return $this
214 */
215 public function addRow(TableSeparator|array $row): static
216 {
217 if ($row instanceof TableSeparator) {
218 $this->rows[] = $row;
219
220 return $this;
221 }
222
223 $this->rows[] = array_values($row);
224
225 return $this;
226 }
227
228 /**
229 * Adds a row to the table, and re-renders the table.
230 *
231 * @return $this
232 */
233 public function appendRow(TableSeparator|array $row): static
234 {
235 if (!$this->output instanceof ConsoleSectionOutput) {
236 throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
237 }
238
239 if ($this->rendered) {
240 $this->output->clear($this->calculateRowCount());
241 }
242
243 $this->addRow($row);
244 $this->render();
245
246 return $this;
247 }
248
249 /**
250 * @return $this
251 */
252 public function setRow(int|string $column, array $row): static
253 {
254 $this->rows[$column] = $row;
255
256 return $this;
257 }
258
259 /**
260 * @return $this
261 */
262 public function setHeaderTitle(?string $title): static
263 {
264 $this->headerTitle = $title;
265
266 return $this;
267 }
268
269 /**
270 * @return $this
271 */
272 public function setFooterTitle(?string $title): static
273 {
274 $this->footerTitle = $title;
275
276 return $this;
277 }
278
279 /**
280 * @return $this
281 */
282 public function setHorizontal(bool $horizontal = true): static
283 {
284 $this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT;
285
286 return $this;
287 }
288
289 /**
290 * @return $this
291 */
292 public function setVertical(bool $vertical = true): static
293 {
294 $this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT;
295
296 return $this;
297 }
298
299 /**
300 * Renders table to output.
301 *
302 * Example:
303 *
304 * +---------------+-----------------------+------------------+
305 * | ISBN | Title | Author |
306 * +---------------+-----------------------+------------------+
307 * | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
308 * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
309 * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
310 * +---------------+-----------------------+------------------+
311 */
312 public function render(): void
313 {
314 $divider = new TableSeparator();
315 $isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2;
316
317 $horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation;
318 $vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation;
319
320 $rows = [];
321 if ($horizontal) {
322 foreach ($this->headers[0] ?? [] as $i => $header) {
323 $rows[$i] = [$header];
324 foreach ($this->rows as $row) {
325 if ($row instanceof TableSeparator) {
326 continue;
327 }
328 if (isset($row[$i])) {
329 $rows[$i][] = $row[$i];
330 } elseif ($isCellWithColspan($rows[$i][0])) {
331 // Noop, there is a "title"
332 } else {
333 $rows[$i][] = null;
334 }
335 }
336 }
337 } elseif ($vertical) {
338 $formatter = $this->output->getFormatter();
339 $maxHeaderLength = array_reduce($this->headers[0] ?? [], static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0);
340
341 foreach ($this->rows as $row) {
342 if ($row instanceof TableSeparator) {
343 continue;
344 }
345
346 if ($rows) {
347 $rows[] = [$divider];
348 }
349
350 $containsColspan = false;
351 foreach ($row as $cell) {
352 if ($containsColspan = $isCellWithColspan($cell)) {
353 break;
354 }
355 }
356
357 $headers = $this->headers[0] ?? [];
358 $maxRows = max(\count($headers), \count($row));
359 for ($i = 0; $i < $maxRows; ++$i) {
360 $cell = (string) ($row[$i] ?? '');
361
362 $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
363 $parts = explode($eol, $cell);
364 foreach ($parts as $idx => $part) {
365 if ($headers && !$containsColspan) {
366 if (0 === $idx) {
367 $rows[] = [sprintf(
368 '<comment>%s%s</>: %s',
369 str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
370 $headers[$i] ?? '',
371 $part
372 )];
373 } else {
374 $rows[] = [sprintf(
375 '%s %s',
376 str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT),
377 $part
378 )];
379 }
380 } elseif ('' !== $cell) {
381 $rows[] = [$part];
382 }
383 }
384 }
385 }
386 } else {
387 $rows = array_merge($this->headers, [$divider], $this->rows);
388 }
389
390 $this->calculateNumberOfColumns($rows);
391
392 $rowGroups = $this->buildTableRows($rows);
393 $this->calculateColumnsWidth($rowGroups);
394
395 $isHeader = !$horizontal;
396 $isFirstRow = $horizontal;
397 $hasTitle = (bool) $this->headerTitle;
398
399 foreach ($rowGroups as $rowGroup) {
400 $isHeaderSeparatorRendered = false;
401
402 foreach ($rowGroup as $row) {
403 if ($divider === $row) {
404 $isHeader = false;
405 $isFirstRow = true;
406
407 continue;
408 }
409
410 if ($row instanceof TableSeparator) {
411 $this->renderRowSeparator();
412
413 continue;
414 }
415
416 if (!$row) {
417 continue;
418 }
419
420 if ($isHeader && !$isHeaderSeparatorRendered) {
421 $this->renderRowSeparator(
422 self::SEPARATOR_TOP,
423 $hasTitle ? $this->headerTitle : null,
424 $hasTitle ? $this->style->getHeaderTitleFormat() : null
425 );
426 $hasTitle = false;
427 $isHeaderSeparatorRendered = true;
428 }
429
430 if ($isFirstRow) {
431 $this->renderRowSeparator(
432 $horizontal ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
433 $hasTitle ? $this->headerTitle : null,
434 $hasTitle ? $this->style->getHeaderTitleFormat() : null
435 );
436 $isFirstRow = false;
437 $hasTitle = false;
438 }
439
440 if ($vertical) {
441 $isHeader = false;
442 $isFirstRow = false;
443 }
444
445 if ($horizontal) {
446 $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
447 } else {
448 $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
449 }
450 }
451 }
452 $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
453
454 $this->cleanup();
455 $this->rendered = true;
456 }
457
458 /**
459 * Renders horizontal header separator.
460 *
461 * Example:
462 *
463 * +-----+-----------+-------+
464 */
465 private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void
466 {
467 if (!$count = $this->numberOfColumns) {
468 return;
469 }
470
471 $borders = $this->style->getBorderChars();
472 if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
473 return;
474 }
475
476 $crossings = $this->style->getCrossingChars();
477 if (self::SEPARATOR_MID === $type) {
478 [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
479 } elseif (self::SEPARATOR_TOP === $type) {
480 [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
481 } elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
482 [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
483 } else {
484 [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
485 }
486
487 $markup = $leftChar;
488 for ($column = 0; $column < $count; ++$column) {
489 $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
490 $markup .= $column === $count - 1 ? $rightChar : $midChar;
491 }
492
493 if (null !== $title) {
494 $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)));
495 $markupLength = Helper::width($markup);
496 if ($titleLength > $limit = $markupLength - 4) {
497 $titleLength = $limit;
498 $formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, '')));
499 $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
500 }
501
502 $titleStart = intdiv($markupLength - $titleLength, 2);
503 if (false === mb_detect_encoding($markup, null, true)) {
504 $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
505 } else {
506 $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
507 }
508 }
509
510 $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
511 }
512
513 /**
514 * Renders vertical column separator.
515 */
516 private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
517 {
518 $borders = $this->style->getBorderChars();
519
520 return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
521 }
522
523 /**
524 * Renders table row.
525 *
526 * Example:
527 *
528 * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
529 */
530 private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void
531 {
532 $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
533 $columns = $this->getRowColumns($row);
534 $last = \count($columns) - 1;
535 foreach ($columns as $i => $column) {
536 if ($firstCellFormat && 0 === $i) {
537 $rowContent .= $this->renderCell($row, $column, $firstCellFormat);
538 } else {
539 $rowContent .= $this->renderCell($row, $column, $cellFormat);
540 }
541 $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
542 }
543 $this->output->writeln($rowContent);
544 }
545
546 /**
547 * Renders table cell with padding.
548 */
549 private function renderCell(array $row, int $column, string $cellFormat): string
550 {
551 $cell = $row[$column] ?? '';
552 $width = $this->effectiveColumnWidths[$column];
553 if ($cell instanceof TableCell && $cell->getColspan() > 1) {
554 // add the width of the following columns(numbers of colspan).
555 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
556 $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
557 }
558 }
559
560 // str_pad won't work properly with multi-byte strings, we need to fix the padding
561 if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
562 $width += \strlen($cell) - mb_strwidth($cell, $encoding);
563 }
564
565 $style = $this->getColumnStyle($column);
566
567 if ($cell instanceof TableSeparator) {
568 return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
569 }
570
571 $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
572 $content = sprintf($style->getCellRowContentFormat(), $cell);
573
574 $padType = $style->getPadType();
575 if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
576 $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
577 if ($isNotStyledByTag) {
578 $cellFormat = $cell->getStyle()->getCellFormat();
579 if (!\is_string($cellFormat)) {
580 $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';');
581 $cellFormat = '<'.$tag.'>%s</>';
582 }
583
584 if (str_contains($content, '</>')) {
585 $content = str_replace('</>', '', $content);
586 $width -= 3;
587 }
588 if (str_contains($content, '<fg=default;bg=default>')) {
589 $content = str_replace('<fg=default;bg=default>', '', $content);
590 $width -= \strlen('<fg=default;bg=default>');
591 }
592 }
593
594 $padType = $cell->getStyle()->getPadByAlign();
595 }
596
597 return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
598 }
599
600 /**
601 * Calculate number of columns for this table.
602 */
603 private function calculateNumberOfColumns(array $rows): void
604 {
605 $columns = [0];
606 foreach ($rows as $row) {
607 if ($row instanceof TableSeparator) {
608 continue;
609 }
610
611 $columns[] = $this->getNumberOfColumns($row);
612 }
613
614 $this->numberOfColumns = max($columns);
615 }
616
617 private function buildTableRows(array $rows): TableRows
618 {
619 /** @var WrappableOutputFormatterInterface $formatter */
620 $formatter = $this->output->getFormatter();
621 $unmergedRows = [];
622 for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
623 $rows = $this->fillNextRows($rows, $rowKey);
624
625 // Remove any new line breaks and replace it with a new line
626 foreach ($rows[$rowKey] as $column => $cell) {
627 $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
628
629 if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) {
630 $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
631 }
632 if (!str_contains($cell ?? '', "\n")) {
633 continue;
634 }
635 $eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n";
636 $escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell)));
637 $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
638 $lines = explode($eol, str_replace($eol, '<fg=default;bg=default></>'.$eol, $cell));
639 foreach ($lines as $lineKey => $line) {
640 if ($colspan > 1) {
641 $line = new TableCell($line, ['colspan' => $colspan]);
642 }
643 if (0 === $lineKey) {
644 $rows[$rowKey][$column] = $line;
645 } else {
646 if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
647 $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
648 }
649 $unmergedRows[$rowKey][$lineKey][$column] = $line;
650 }
651 }
652 }
653 }
654
655 return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
656 foreach ($rows as $rowKey => $row) {
657 $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)];
658
659 if (isset($unmergedRows[$rowKey])) {
660 foreach ($unmergedRows[$rowKey] as $row) {
661 $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row);
662 }
663 }
664 yield $rowGroup;
665 }
666 });
667 }
668
669 private function calculateRowCount(): int
670 {
671 $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
672
673 if ($this->headers) {
674 ++$numberOfRows; // Add row for header separator
675 }
676
677 if ($this->rows) {
678 ++$numberOfRows; // Add row for footer separator
679 }
680
681 return $numberOfRows;
682 }
683
684 /**
685 * fill rows that contains rowspan > 1.
686 *
687 * @throws InvalidArgumentException
688 */
689 private function fillNextRows(array $rows, int $line): array
690 {
691 $unmergedRows = [];
692 foreach ($rows[$line] as $column => $cell) {
693 if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) {
694 throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
695 }
696 if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
697 $nbLines = $cell->getRowspan() - 1;
698 $lines = [$cell];
699 if (str_contains($cell, "\n")) {
700 $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
701 $lines = explode($eol, str_replace($eol, '<fg=default;bg=default>'.$eol.'</>', $cell));
702 $nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines;
703
704 $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
705 unset($lines[0]);
706 }
707
708 // create a two dimensional array (rowspan x colspan)
709 $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
710 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
711 $value = $lines[$unmergedRowKey - $line] ?? '';
712 $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
713 if ($nbLines === $unmergedRowKey - $line) {
714 break;
715 }
716 }
717 }
718 }
719
720 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
721 // we need to know if $unmergedRow will be merged or inserted into $rows
722 if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRow) <= $this->numberOfColumns)) {
723 foreach ($unmergedRow as $cellKey => $cell) {
724 // insert cell into row at cellKey position
725 array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
726 }
727 } else {
728 $row = $this->copyRow($rows, $unmergedRowKey - 1);
729 foreach ($unmergedRow as $column => $cell) {
730 if ($cell) {
731 $row[$column] = $cell;
732 }
733 }
734 array_splice($rows, $unmergedRowKey, 0, [$row]);
735 }
736 }
737
738 return $rows;
739 }
740
741 /**
742 * fill cells for a row that contains colspan > 1.
743 */
744 private function fillCells(iterable $row): iterable
745 {
746 $newRow = [];
747
748 foreach ($row as $column => $cell) {
749 $newRow[] = $cell;
750 if ($cell instanceof TableCell && $cell->getColspan() > 1) {
751 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
752 // insert empty value at column position
753 $newRow[] = '';
754 }
755 }
756 }
757
758 return $newRow ?: $row;
759 }
760
761 private function copyRow(array $rows, int $line): array
762 {
763 $row = $rows[$line];
764 foreach ($row as $cellKey => $cellValue) {
765 $row[$cellKey] = '';
766 if ($cellValue instanceof TableCell) {
767 $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
768 }
769 }
770
771 return $row;
772 }
773
774 /**
775 * Gets number of columns by row.
776 */
777 private function getNumberOfColumns(array $row): int
778 {
779 $columns = \count($row);
780 foreach ($row as $column) {
781 $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
782 }
783
784 return $columns;
785 }
786
787 /**
788 * Gets list of columns for the given row.
789 */
790 private function getRowColumns(array $row): array
791 {
792 $columns = range(0, $this->numberOfColumns - 1);
793 foreach ($row as $cellKey => $cell) {
794 if ($cell instanceof TableCell && $cell->getColspan() > 1) {
795 // exclude grouped columns.
796 $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
797 }
798 }
799
800 return $columns;
801 }
802
803 /**
804 * Calculates columns widths.
805 */
806 private function calculateColumnsWidth(iterable $groups): void
807 {
808 for ($column = 0; $column < $this->numberOfColumns; ++$column) {
809 $lengths = [];
810 foreach ($groups as $group) {
811 foreach ($group as $row) {
812 if ($row instanceof TableSeparator) {
813 continue;
814 }
815
816 foreach ($row as $i => $cell) {
817 if ($cell instanceof TableCell) {
818 $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
819 $textLength = Helper::width($textContent);
820 if ($textLength > 0) {
821 $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan()));
822 foreach ($contentColumns as $position => $content) {
823 $row[$i + $position] = $content;
824 }
825 }
826 }
827 }
828
829 $lengths[] = $this->getCellWidth($row, $column);
830 }
831 }
832
833 $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2;
834 }
835 }
836
837 private function getColumnSeparatorWidth(): int
838 {
839 return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
840 }
841
842 private function getCellWidth(array $row, int $column): int
843 {
844 $cellWidth = 0;
845
846 if (isset($row[$column])) {
847 $cell = $row[$column];
848 $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
849 }
850
851 $columnWidth = $this->columnWidths[$column] ?? 0;
852 $cellWidth = max($cellWidth, $columnWidth);
853
854 return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
855 }
856
857 /**
858 * Called after rendering to cleanup cache data.
859 */
860 private function cleanup(): void
861 {
862 $this->effectiveColumnWidths = [];
863 unset($this->numberOfColumns);
864 }
865
866 /**
867 * @return array<string, TableStyle>
868 */
869 private static function initStyles(): array
870 {
871 $borderless = new TableStyle();
872 $borderless
873 ->setHorizontalBorderChars('=')
874 ->setVerticalBorderChars(' ')
875 ->setDefaultCrossingChar(' ')
876 ;
877
878 $compact = new TableStyle();
879 $compact
880 ->setHorizontalBorderChars('')
881 ->setVerticalBorderChars('')
882 ->setDefaultCrossingChar('')
883 ->setCellRowContentFormat('%s ')
884 ;
885
886 $styleGuide = new TableStyle();
887 $styleGuide
888 ->setHorizontalBorderChars('-')
889 ->setVerticalBorderChars(' ')
890 ->setDefaultCrossingChar(' ')
891 ->setCellHeaderFormat('%s')
892 ;
893
894 $box = (new TableStyle())
895 ->setHorizontalBorderChars('─')
896 ->setVerticalBorderChars('│')
897 ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
898 ;
899
900 $boxDouble = (new TableStyle())
901 ->setHorizontalBorderChars('═', '─')
902 ->setVerticalBorderChars('║', '│')
903 ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
904 ;
905
906 return [
907 'default' => new TableStyle(),
908 'borderless' => $borderless,
909 'compact' => $compact,
910 'symfony-style-guide' => $styleGuide,
911 'box' => $box,
912 'box-double' => $boxDouble,
913 ];
914 }
915
916 private function resolveStyle(TableStyle|string $name): TableStyle
917 {
918 if ($name instanceof TableStyle) {
919 return $name;
920 }
921
922 return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
923 }
924}
diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php
new file mode 100644
index 0000000..1c4eeea
--- /dev/null
+++ b/vendor/symfony/console/Helper/TableCell.php
@@ -0,0 +1,71 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Exception\InvalidArgumentException;
15
16/**
17 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
18 */
19class TableCell
20{
21 private array $options = [
22 'rowspan' => 1,
23 'colspan' => 1,
24 'style' => null,
25 ];
26
27 public function __construct(
28 private string $value = '',
29 array $options = [],
30 ) {
31 // check option names
32 if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
33 throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
34 }
35
36 if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
37 throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".');
38 }
39
40 $this->options = array_merge($this->options, $options);
41 }
42
43 /**
44 * Returns the cell value.
45 */
46 public function __toString(): string
47 {
48 return $this->value;
49 }
50
51 /**
52 * Gets number of colspan.
53 */
54 public function getColspan(): int
55 {
56 return (int) $this->options['colspan'];
57 }
58
59 /**
60 * Gets number of rowspan.
61 */
62 public function getRowspan(): int
63 {
64 return (int) $this->options['rowspan'];
65 }
66
67 public function getStyle(): ?TableCellStyle
68 {
69 return $this->options['style'];
70 }
71}
diff --git a/vendor/symfony/console/Helper/TableCellStyle.php b/vendor/symfony/console/Helper/TableCellStyle.php
new file mode 100644
index 0000000..49b97f8
--- /dev/null
+++ b/vendor/symfony/console/Helper/TableCellStyle.php
@@ -0,0 +1,84 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Exception\InvalidArgumentException;
15
16/**
17 * @author Yewhen Khoptynskyi <khoptynskyi@gmail.com>
18 */
19class TableCellStyle
20{
21 public const DEFAULT_ALIGN = 'left';
22
23 private const TAG_OPTIONS = [
24 'fg',
25 'bg',
26 'options',
27 ];
28
29 private const ALIGN_MAP = [
30 'left' => \STR_PAD_RIGHT,
31 'center' => \STR_PAD_BOTH,
32 'right' => \STR_PAD_LEFT,
33 ];
34
35 private array $options = [
36 'fg' => 'default',
37 'bg' => 'default',
38 'options' => null,
39 'align' => self::DEFAULT_ALIGN,
40 'cellFormat' => null,
41 ];
42
43 public function __construct(array $options = [])
44 {
45 if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
46 throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
47 }
48
49 if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) {
50 throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP))));
51 }
52
53 $this->options = array_merge($this->options, $options);
54 }
55
56 public function getOptions(): array
57 {
58 return $this->options;
59 }
60
61 /**
62 * Gets options we need for tag for example fg, bg.
63 *
64 * @return string[]
65 */
66 public function getTagOptions(): array
67 {
68 return array_filter(
69 $this->getOptions(),
70 fn ($key) => \in_array($key, self::TAG_OPTIONS, true) && isset($this->options[$key]),
71 \ARRAY_FILTER_USE_KEY
72 );
73 }
74
75 public function getPadByAlign(): int
76 {
77 return self::ALIGN_MAP[$this->getOptions()['align']];
78 }
79
80 public function getCellFormat(): ?string
81 {
82 return $this->getOptions()['cellFormat'];
83 }
84}
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 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14/**
15 * @internal
16 */
17class TableRows implements \IteratorAggregate
18{
19 public function __construct(
20 private \Closure $generator,
21 ) {
22 }
23
24 public function getIterator(): \Traversable
25 {
26 return ($this->generator)();
27 }
28}
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 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14/**
15 * Marks a row as being a separator.
16 *
17 * @author Fabien Potencier <fabien@symfony.com>
18 */
19class TableSeparator extends TableCell
20{
21 public function __construct(array $options = [])
22 {
23 parent::__construct('', $options);
24 }
25}
diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php
new file mode 100644
index 0000000..be956c1
--- /dev/null
+++ b/vendor/symfony/console/Helper/TableStyle.php
@@ -0,0 +1,362 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Exception\InvalidArgumentException;
15use Symfony\Component\Console\Exception\LogicException;
16
17/**
18 * Defines the styles for a Table.
19 *
20 * @author Fabien Potencier <fabien@symfony.com>
21 * @author Саша Стаменковић <umpirsky@gmail.com>
22 * @author Dany Maillard <danymaillard93b@gmail.com>
23 */
24class TableStyle
25{
26 private string $paddingChar = ' ';
27 private string $horizontalOutsideBorderChar = '-';
28 private string $horizontalInsideBorderChar = '-';
29 private string $verticalOutsideBorderChar = '|';
30 private string $verticalInsideBorderChar = '|';
31 private string $crossingChar = '+';
32 private string $crossingTopRightChar = '+';
33 private string $crossingTopMidChar = '+';
34 private string $crossingTopLeftChar = '+';
35 private string $crossingMidRightChar = '+';
36 private string $crossingBottomRightChar = '+';
37 private string $crossingBottomMidChar = '+';
38 private string $crossingBottomLeftChar = '+';
39 private string $crossingMidLeftChar = '+';
40 private string $crossingTopLeftBottomChar = '+';
41 private string $crossingTopMidBottomChar = '+';
42 private string $crossingTopRightBottomChar = '+';
43 private string $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
44 private string $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
45 private string $cellHeaderFormat = '<info>%s</info>';
46 private string $cellRowFormat = '%s';
47 private string $cellRowContentFormat = ' %s ';
48 private string $borderFormat = '%s';
49 private int $padType = \STR_PAD_RIGHT;
50
51 /**
52 * Sets padding character, used for cell padding.
53 *
54 * @return $this
55 */
56 public function setPaddingChar(string $paddingChar): static
57 {
58 if (!$paddingChar) {
59 throw new LogicException('The padding char must not be empty.');
60 }
61
62 $this->paddingChar = $paddingChar;
63
64 return $this;
65 }
66
67 /**
68 * Gets padding character, used for cell padding.
69 */
70 public function getPaddingChar(): string
71 {
72 return $this->paddingChar;
73 }
74
75 /**
76 * Sets horizontal border characters.
77 *
78 * <code>
79 * ╔═══════════════╤══════════════════════════╤══════════════════╗
80 * 1 ISBN 2 Title │ Author ║
81 * ╠═══════════════╪══════════════════════════╪══════════════════╣
82 * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
83 * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
84 * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
85 * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
86 * ╚═══════════════╧══════════════════════════╧══════════════════╝
87 * </code>
88 *
89 * @return $this
90 */
91 public function setHorizontalBorderChars(string $outside, ?string $inside = null): static
92 {
93 $this->horizontalOutsideBorderChar = $outside;
94 $this->horizontalInsideBorderChar = $inside ?? $outside;
95
96 return $this;
97 }
98
99 /**
100 * Sets vertical border characters.
101 *
102 * <code>
103 * ╔═══════════════╤══════════════════════════╤══════════════════╗
104 * ║ ISBN │ Title │ Author ║
105 * ╠═══════1═══════╪══════════════════════════╪══════════════════╣
106 * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
107 * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
108 * ╟───────2───────┼──────────────────────────┼──────────────────╢
109 * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
110 * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
111 * ╚═══════════════╧══════════════════════════╧══════════════════╝
112 * </code>
113 *
114 * @return $this
115 */
116 public function setVerticalBorderChars(string $outside, ?string $inside = null): static
117 {
118 $this->verticalOutsideBorderChar = $outside;
119 $this->verticalInsideBorderChar = $inside ?? $outside;
120
121 return $this;
122 }
123
124 /**
125 * Gets border characters.
126 *
127 * @internal
128 */
129 public function getBorderChars(): array
130 {
131 return [
132 $this->horizontalOutsideBorderChar,
133 $this->verticalOutsideBorderChar,
134 $this->horizontalInsideBorderChar,
135 $this->verticalInsideBorderChar,
136 ];
137 }
138
139 /**
140 * Sets crossing characters.
141 *
142 * Example:
143 * <code>
144 * 1═══════════════2══════════════════════════2══════════════════3
145 * ║ ISBN │ Title │ Author ║
146 * 8'══════════════0'═════════════════════════0'═════════════════4'
147 * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
148 * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
149 * 8───────────────0──────────────────────────0──────────────────4
150 * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
151 * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
152 * 7═══════════════6══════════════════════════6══════════════════5
153 * </code>
154 *
155 * @param string $cross Crossing char (see #0 of example)
156 * @param string $topLeft Top left char (see #1 of example)
157 * @param string $topMid Top mid char (see #2 of example)
158 * @param string $topRight Top right char (see #3 of example)
159 * @param string $midRight Mid right char (see #4 of example)
160 * @param string $bottomRight Bottom right char (see #5 of example)
161 * @param string $bottomMid Bottom mid char (see #6 of example)
162 * @param string $bottomLeft Bottom left char (see #7 of example)
163 * @param string $midLeft Mid left char (see #8 of example)
164 * @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null
165 * @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null
166 * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
167 *
168 * @return $this
169 */
170 public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static
171 {
172 $this->crossingChar = $cross;
173 $this->crossingTopLeftChar = $topLeft;
174 $this->crossingTopMidChar = $topMid;
175 $this->crossingTopRightChar = $topRight;
176 $this->crossingMidRightChar = $midRight;
177 $this->crossingBottomRightChar = $bottomRight;
178 $this->crossingBottomMidChar = $bottomMid;
179 $this->crossingBottomLeftChar = $bottomLeft;
180 $this->crossingMidLeftChar = $midLeft;
181 $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
182 $this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
183 $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;
184
185 return $this;
186 }
187
188 /**
189 * Sets default crossing character used for each cross.
190 *
191 * @see {@link setCrossingChars()} for setting each crossing individually.
192 */
193 public function setDefaultCrossingChar(string $char): self
194 {
195 return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
196 }
197
198 /**
199 * Gets crossing character.
200 */
201 public function getCrossingChar(): string
202 {
203 return $this->crossingChar;
204 }
205
206 /**
207 * Gets crossing characters.
208 *
209 * @internal
210 */
211 public function getCrossingChars(): array
212 {
213 return [
214 $this->crossingChar,
215 $this->crossingTopLeftChar,
216 $this->crossingTopMidChar,
217 $this->crossingTopRightChar,
218 $this->crossingMidRightChar,
219 $this->crossingBottomRightChar,
220 $this->crossingBottomMidChar,
221 $this->crossingBottomLeftChar,
222 $this->crossingMidLeftChar,
223 $this->crossingTopLeftBottomChar,
224 $this->crossingTopMidBottomChar,
225 $this->crossingTopRightBottomChar,
226 ];
227 }
228
229 /**
230 * Sets header cell format.
231 *
232 * @return $this
233 */
234 public function setCellHeaderFormat(string $cellHeaderFormat): static
235 {
236 $this->cellHeaderFormat = $cellHeaderFormat;
237
238 return $this;
239 }
240
241 /**
242 * Gets header cell format.
243 */
244 public function getCellHeaderFormat(): string
245 {
246 return $this->cellHeaderFormat;
247 }
248
249 /**
250 * Sets row cell format.
251 *
252 * @return $this
253 */
254 public function setCellRowFormat(string $cellRowFormat): static
255 {
256 $this->cellRowFormat = $cellRowFormat;
257
258 return $this;
259 }
260
261 /**
262 * Gets row cell format.
263 */
264 public function getCellRowFormat(): string
265 {
266 return $this->cellRowFormat;
267 }
268
269 /**
270 * Sets row cell content format.
271 *
272 * @return $this
273 */
274 public function setCellRowContentFormat(string $cellRowContentFormat): static
275 {
276 $this->cellRowContentFormat = $cellRowContentFormat;
277
278 return $this;
279 }
280
281 /**
282 * Gets row cell content format.
283 */
284 public function getCellRowContentFormat(): string
285 {
286 return $this->cellRowContentFormat;
287 }
288
289 /**
290 * Sets table border format.
291 *
292 * @return $this
293 */
294 public function setBorderFormat(string $borderFormat): static
295 {
296 $this->borderFormat = $borderFormat;
297
298 return $this;
299 }
300
301 /**
302 * Gets table border format.
303 */
304 public function getBorderFormat(): string
305 {
306 return $this->borderFormat;
307 }
308
309 /**
310 * Sets cell padding type.
311 *
312 * @return $this
313 */
314 public function setPadType(int $padType): static
315 {
316 if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
317 throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
318 }
319
320 $this->padType = $padType;
321
322 return $this;
323 }
324
325 /**
326 * Gets cell padding type.
327 */
328 public function getPadType(): int
329 {
330 return $this->padType;
331 }
332
333 public function getHeaderTitleFormat(): string
334 {
335 return $this->headerTitleFormat;
336 }
337
338 /**
339 * @return $this
340 */
341 public function setHeaderTitleFormat(string $format): static
342 {
343 $this->headerTitleFormat = $format;
344
345 return $this;
346 }
347
348 public function getFooterTitleFormat(): string
349 {
350 return $this->footerTitleFormat;
351 }
352
353 /**
354 * @return $this
355 */
356 public function setFooterTitleFormat(string $format): static
357 {
358 $this->footerTitleFormat = $format;
359
360 return $this;
361 }
362}