Completed
Pull Request — develop (#1656)
by
unknown
17:57
created
vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php 1 patch
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -2,16 +2,16 @@
 block discarded – undo
2 2
 
3 3
 class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer
4 4
 {
5
-    /**
6
-     * @deprecated since ICU 56 and removed in PHP 8
7
-     */
8
-    public const NONE = 2;
9
-    public const FORM_D = 4;
10
-    public const FORM_KD = 8;
11
-    public const FORM_C = 16;
12
-    public const FORM_KC = 32;
13
-    public const NFD = 4;
14
-    public const NFKD = 8;
15
-    public const NFC = 16;
16
-    public const NFKC = 32;
5
+	/**
6
+	 * @deprecated since ICU 56 and removed in PHP 8
7
+	 */
8
+	public const NONE = 2;
9
+	public const FORM_D = 4;
10
+	public const FORM_KD = 8;
11
+	public const FORM_C = 16;
12
+	public const FORM_KC = 32;
13
+	public const NFD = 4;
14
+	public const NFKD = 8;
15
+	public const NFC = 16;
16
+	public const NFKC = 32;
17 17
 }
Please login to merge, or discard this patch.
vendor/symfony/console/Helper/ProcessHelper.php 1 patch
Indentation   +120 added lines, -120 removed lines patch added patch discarded remove patch
@@ -25,124 +25,124 @@
 block discarded – undo
25 25
  */
26 26
 class ProcessHelper extends Helper
27 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
-     * @return Process The process that ran
36
-     */
37
-    public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
38
-    {
39
-        if (!class_exists(Process::class)) {
40
-            throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
41
-        }
42
-
43
-        if ($output instanceof ConsoleOutputInterface) {
44
-            $output = $output->getErrorOutput();
45
-        }
46
-
47
-        $formatter = $this->getHelperSet()->get('debug_formatter');
48
-
49
-        if ($cmd instanceof Process) {
50
-            $cmd = [$cmd];
51
-        }
52
-
53
-        if (!\is_array($cmd)) {
54
-            throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd)));
55
-        }
56
-
57
-        if (\is_string($cmd[0] ?? null)) {
58
-            $process = new Process($cmd);
59
-            $cmd = [];
60
-        } elseif (($cmd[0] ?? null) instanceof Process) {
61
-            $process = $cmd[0];
62
-            unset($cmd[0]);
63
-        } else {
64
-            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__));
65
-        }
66
-
67
-        if ($verbosity <= $output->getVerbosity()) {
68
-            $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
69
-        }
70
-
71
-        if ($output->isDebug()) {
72
-            $callback = $this->wrapCallback($output, $process, $callback);
73
-        }
74
-
75
-        $process->run($callback, $cmd);
76
-
77
-        if ($verbosity <= $output->getVerbosity()) {
78
-            $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
79
-            $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
80
-        }
81
-
82
-        if (!$process->isSuccessful() && null !== $error) {
83
-            $output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
84
-        }
85
-
86
-        return $process;
87
-    }
88
-
89
-    /**
90
-     * Runs the process.
91
-     *
92
-     * This is identical to run() except that an exception is thrown if the process
93
-     * exits with a non-zero exit code.
94
-     *
95
-     * @param string|Process $cmd      An instance of Process or a command to run
96
-     * @param callable|null  $callback A PHP callback to run whenever there is some
97
-     *                                 output available on STDOUT or STDERR
98
-     *
99
-     * @return Process The process that ran
100
-     *
101
-     * @throws ProcessFailedException
102
-     *
103
-     * @see run()
104
-     */
105
-    public function mustRun(OutputInterface $output, $cmd, string $error = null, callable $callback = null): Process
106
-    {
107
-        $process = $this->run($output, $cmd, $error, $callback);
108
-
109
-        if (!$process->isSuccessful()) {
110
-            throw new ProcessFailedException($process);
111
-        }
112
-
113
-        return $process;
114
-    }
115
-
116
-    /**
117
-     * Wraps a Process callback to add debugging output.
118
-     */
119
-    public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable
120
-    {
121
-        if ($output instanceof ConsoleOutputInterface) {
122
-            $output = $output->getErrorOutput();
123
-        }
124
-
125
-        $formatter = $this->getHelperSet()->get('debug_formatter');
126
-
127
-        return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
128
-            $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));
129
-
130
-            if (null !== $callback) {
131
-                $callback($type, $buffer);
132
-            }
133
-        };
134
-    }
135
-
136
-    private function escapeString(string $str): string
137
-    {
138
-        return str_replace('<', '\\<', $str);
139
-    }
140
-
141
-    /**
142
-     * {@inheritdoc}
143
-     */
144
-    public function getName(): string
145
-    {
146
-        return 'process';
147
-    }
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
+	 * @return Process The process that ran
36
+	 */
37
+	public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
38
+	{
39
+		if (!class_exists(Process::class)) {
40
+			throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
41
+		}
42
+
43
+		if ($output instanceof ConsoleOutputInterface) {
44
+			$output = $output->getErrorOutput();
45
+		}
46
+
47
+		$formatter = $this->getHelperSet()->get('debug_formatter');
48
+
49
+		if ($cmd instanceof Process) {
50
+			$cmd = [$cmd];
51
+		}
52
+
53
+		if (!\is_array($cmd)) {
54
+			throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd)));
55
+		}
56
+
57
+		if (\is_string($cmd[0] ?? null)) {
58
+			$process = new Process($cmd);
59
+			$cmd = [];
60
+		} elseif (($cmd[0] ?? null) instanceof Process) {
61
+			$process = $cmd[0];
62
+			unset($cmd[0]);
63
+		} else {
64
+			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__));
65
+		}
66
+
67
+		if ($verbosity <= $output->getVerbosity()) {
68
+			$output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
69
+		}
70
+
71
+		if ($output->isDebug()) {
72
+			$callback = $this->wrapCallback($output, $process, $callback);
73
+		}
74
+
75
+		$process->run($callback, $cmd);
76
+
77
+		if ($verbosity <= $output->getVerbosity()) {
78
+			$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
79
+			$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
80
+		}
81
+
82
+		if (!$process->isSuccessful() && null !== $error) {
83
+			$output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
84
+		}
85
+
86
+		return $process;
87
+	}
88
+
89
+	/**
90
+	 * Runs the process.
91
+	 *
92
+	 * This is identical to run() except that an exception is thrown if the process
93
+	 * exits with a non-zero exit code.
94
+	 *
95
+	 * @param string|Process $cmd      An instance of Process or a command to run
96
+	 * @param callable|null  $callback A PHP callback to run whenever there is some
97
+	 *                                 output available on STDOUT or STDERR
98
+	 *
99
+	 * @return Process The process that ran
100
+	 *
101
+	 * @throws ProcessFailedException
102
+	 *
103
+	 * @see run()
104
+	 */
105
+	public function mustRun(OutputInterface $output, $cmd, string $error = null, callable $callback = null): Process
106
+	{
107
+		$process = $this->run($output, $cmd, $error, $callback);
108
+
109
+		if (!$process->isSuccessful()) {
110
+			throw new ProcessFailedException($process);
111
+		}
112
+
113
+		return $process;
114
+	}
115
+
116
+	/**
117
+	 * Wraps a Process callback to add debugging output.
118
+	 */
119
+	public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable
120
+	{
121
+		if ($output instanceof ConsoleOutputInterface) {
122
+			$output = $output->getErrorOutput();
123
+		}
124
+
125
+		$formatter = $this->getHelperSet()->get('debug_formatter');
126
+
127
+		return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
128
+			$output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));
129
+
130
+			if (null !== $callback) {
131
+				$callback($type, $buffer);
132
+			}
133
+		};
134
+	}
135
+
136
+	private function escapeString(string $str): string
137
+	{
138
+		return str_replace('<', '\\<', $str);
139
+	}
140
+
141
+	/**
142
+	 * {@inheritdoc}
143
+	 */
144
+	public function getName(): string
145
+	{
146
+		return 'process';
147
+	}
148 148
 }
Please login to merge, or discard this patch.
vendor/symfony/console/Helper/ProgressIndicator.php 1 patch
Indentation   +231 added lines, -231 removed lines patch added patch discarded remove patch
@@ -20,235 +20,235 @@
 block discarded – undo
20 20
  */
21 21
 class ProgressIndicator
22 22
 {
23
-    private $output;
24
-    private $startTime;
25
-    private $format;
26
-    private $message;
27
-    private $indicatorValues;
28
-    private $indicatorCurrent;
29
-    private $indicatorChangeInterval;
30
-    private $indicatorUpdateTime;
31
-    private $started = false;
32
-
33
-    private static $formatters;
34
-    private static $formats;
35
-
36
-    /**
37
-     * @param int        $indicatorChangeInterval Change interval in milliseconds
38
-     * @param array|null $indicatorValues         Animated indicator characters
39
-     */
40
-    public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
41
-    {
42
-        $this->output = $output;
43
-
44
-        if (null === $format) {
45
-            $format = $this->determineBestFormat();
46
-        }
47
-
48
-        if (null === $indicatorValues) {
49
-            $indicatorValues = ['-', '\\', '|', '/'];
50
-        }
51
-
52
-        $indicatorValues = array_values($indicatorValues);
53
-
54
-        if (2 > \count($indicatorValues)) {
55
-            throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
56
-        }
57
-
58
-        $this->format = self::getFormatDefinition($format);
59
-        $this->indicatorChangeInterval = $indicatorChangeInterval;
60
-        $this->indicatorValues = $indicatorValues;
61
-        $this->startTime = time();
62
-    }
63
-
64
-    /**
65
-     * Sets the current indicator message.
66
-     */
67
-    public function setMessage(?string $message)
68
-    {
69
-        $this->message = $message;
70
-
71
-        $this->display();
72
-    }
73
-
74
-    /**
75
-     * Starts the indicator output.
76
-     */
77
-    public function start(string $message)
78
-    {
79
-        if ($this->started) {
80
-            throw new LogicException('Progress indicator already started.');
81
-        }
82
-
83
-        $this->message = $message;
84
-        $this->started = true;
85
-        $this->startTime = time();
86
-        $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
87
-        $this->indicatorCurrent = 0;
88
-
89
-        $this->display();
90
-    }
91
-
92
-    /**
93
-     * Advances the indicator.
94
-     */
95
-    public function advance()
96
-    {
97
-        if (!$this->started) {
98
-            throw new LogicException('Progress indicator has not yet been started.');
99
-        }
100
-
101
-        if (!$this->output->isDecorated()) {
102
-            return;
103
-        }
104
-
105
-        $currentTime = $this->getCurrentTimeInMilliseconds();
106
-
107
-        if ($currentTime < $this->indicatorUpdateTime) {
108
-            return;
109
-        }
110
-
111
-        $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
112
-        ++$this->indicatorCurrent;
113
-
114
-        $this->display();
115
-    }
116
-
117
-    /**
118
-     * Finish the indicator with message.
119
-     *
120
-     * @param $message
121
-     */
122
-    public function finish(string $message)
123
-    {
124
-        if (!$this->started) {
125
-            throw new LogicException('Progress indicator has not yet been started.');
126
-        }
127
-
128
-        $this->message = $message;
129
-        $this->display();
130
-        $this->output->writeln('');
131
-        $this->started = false;
132
-    }
133
-
134
-    /**
135
-     * Gets the format for a given name.
136
-     *
137
-     * @return string|null A format string
138
-     */
139
-    public static function getFormatDefinition(string $name)
140
-    {
141
-        if (!self::$formats) {
142
-            self::$formats = self::initFormats();
143
-        }
144
-
145
-        return self::$formats[$name] ?? null;
146
-    }
147
-
148
-    /**
149
-     * Sets a placeholder formatter for a given name.
150
-     *
151
-     * This method also allow you to override an existing placeholder.
152
-     */
153
-    public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
154
-    {
155
-        if (!self::$formatters) {
156
-            self::$formatters = self::initPlaceholderFormatters();
157
-        }
158
-
159
-        self::$formatters[$name] = $callable;
160
-    }
161
-
162
-    /**
163
-     * Gets the placeholder formatter for a given name (including the delimiter char like %).
164
-     *
165
-     * @return callable|null A PHP callable
166
-     */
167
-    public static function getPlaceholderFormatterDefinition(string $name)
168
-    {
169
-        if (!self::$formatters) {
170
-            self::$formatters = self::initPlaceholderFormatters();
171
-        }
172
-
173
-        return self::$formatters[$name] ?? null;
174
-    }
175
-
176
-    private function display()
177
-    {
178
-        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
179
-            return;
180
-        }
181
-
182
-        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
183
-            if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
184
-                return $formatter($this);
185
-            }
186
-
187
-            return $matches[0];
188
-        }, $this->format ?? ''));
189
-    }
190
-
191
-    private function determineBestFormat(): string
192
-    {
193
-        switch ($this->output->getVerbosity()) {
194
-            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
195
-            case OutputInterface::VERBOSITY_VERBOSE:
196
-                return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
197
-            case OutputInterface::VERBOSITY_VERY_VERBOSE:
198
-            case OutputInterface::VERBOSITY_DEBUG:
199
-                return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
200
-            default:
201
-                return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
202
-        }
203
-    }
204
-
205
-    /**
206
-     * Overwrites a previous message to the output.
207
-     */
208
-    private function overwrite(string $message)
209
-    {
210
-        if ($this->output->isDecorated()) {
211
-            $this->output->write("\x0D\x1B[2K");
212
-            $this->output->write($message);
213
-        } else {
214
-            $this->output->writeln($message);
215
-        }
216
-    }
217
-
218
-    private function getCurrentTimeInMilliseconds(): float
219
-    {
220
-        return round(microtime(true) * 1000);
221
-    }
222
-
223
-    private static function initPlaceholderFormatters(): array
224
-    {
225
-        return [
226
-            'indicator' => function (self $indicator) {
227
-                return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
228
-            },
229
-            'message' => function (self $indicator) {
230
-                return $indicator->message;
231
-            },
232
-            'elapsed' => function (self $indicator) {
233
-                return Helper::formatTime(time() - $indicator->startTime);
234
-            },
235
-            'memory' => function () {
236
-                return Helper::formatMemory(memory_get_usage(true));
237
-            },
238
-        ];
239
-    }
240
-
241
-    private static function initFormats(): array
242
-    {
243
-        return [
244
-            'normal' => ' %indicator% %message%',
245
-            'normal_no_ansi' => ' %message%',
246
-
247
-            'verbose' => ' %indicator% %message% (%elapsed:6s%)',
248
-            'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
249
-
250
-            'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
251
-            'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
252
-        ];
253
-    }
23
+	private $output;
24
+	private $startTime;
25
+	private $format;
26
+	private $message;
27
+	private $indicatorValues;
28
+	private $indicatorCurrent;
29
+	private $indicatorChangeInterval;
30
+	private $indicatorUpdateTime;
31
+	private $started = false;
32
+
33
+	private static $formatters;
34
+	private static $formats;
35
+
36
+	/**
37
+	 * @param int        $indicatorChangeInterval Change interval in milliseconds
38
+	 * @param array|null $indicatorValues         Animated indicator characters
39
+	 */
40
+	public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
41
+	{
42
+		$this->output = $output;
43
+
44
+		if (null === $format) {
45
+			$format = $this->determineBestFormat();
46
+		}
47
+
48
+		if (null === $indicatorValues) {
49
+			$indicatorValues = ['-', '\\', '|', '/'];
50
+		}
51
+
52
+		$indicatorValues = array_values($indicatorValues);
53
+
54
+		if (2 > \count($indicatorValues)) {
55
+			throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
56
+		}
57
+
58
+		$this->format = self::getFormatDefinition($format);
59
+		$this->indicatorChangeInterval = $indicatorChangeInterval;
60
+		$this->indicatorValues = $indicatorValues;
61
+		$this->startTime = time();
62
+	}
63
+
64
+	/**
65
+	 * Sets the current indicator message.
66
+	 */
67
+	public function setMessage(?string $message)
68
+	{
69
+		$this->message = $message;
70
+
71
+		$this->display();
72
+	}
73
+
74
+	/**
75
+	 * Starts the indicator output.
76
+	 */
77
+	public function start(string $message)
78
+	{
79
+		if ($this->started) {
80
+			throw new LogicException('Progress indicator already started.');
81
+		}
82
+
83
+		$this->message = $message;
84
+		$this->started = true;
85
+		$this->startTime = time();
86
+		$this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
87
+		$this->indicatorCurrent = 0;
88
+
89
+		$this->display();
90
+	}
91
+
92
+	/**
93
+	 * Advances the indicator.
94
+	 */
95
+	public function advance()
96
+	{
97
+		if (!$this->started) {
98
+			throw new LogicException('Progress indicator has not yet been started.');
99
+		}
100
+
101
+		if (!$this->output->isDecorated()) {
102
+			return;
103
+		}
104
+
105
+		$currentTime = $this->getCurrentTimeInMilliseconds();
106
+
107
+		if ($currentTime < $this->indicatorUpdateTime) {
108
+			return;
109
+		}
110
+
111
+		$this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
112
+		++$this->indicatorCurrent;
113
+
114
+		$this->display();
115
+	}
116
+
117
+	/**
118
+	 * Finish the indicator with message.
119
+	 *
120
+	 * @param $message
121
+	 */
122
+	public function finish(string $message)
123
+	{
124
+		if (!$this->started) {
125
+			throw new LogicException('Progress indicator has not yet been started.');
126
+		}
127
+
128
+		$this->message = $message;
129
+		$this->display();
130
+		$this->output->writeln('');
131
+		$this->started = false;
132
+	}
133
+
134
+	/**
135
+	 * Gets the format for a given name.
136
+	 *
137
+	 * @return string|null A format string
138
+	 */
139
+	public static function getFormatDefinition(string $name)
140
+	{
141
+		if (!self::$formats) {
142
+			self::$formats = self::initFormats();
143
+		}
144
+
145
+		return self::$formats[$name] ?? null;
146
+	}
147
+
148
+	/**
149
+	 * Sets a placeholder formatter for a given name.
150
+	 *
151
+	 * This method also allow you to override an existing placeholder.
152
+	 */
153
+	public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
154
+	{
155
+		if (!self::$formatters) {
156
+			self::$formatters = self::initPlaceholderFormatters();
157
+		}
158
+
159
+		self::$formatters[$name] = $callable;
160
+	}
161
+
162
+	/**
163
+	 * Gets the placeholder formatter for a given name (including the delimiter char like %).
164
+	 *
165
+	 * @return callable|null A PHP callable
166
+	 */
167
+	public static function getPlaceholderFormatterDefinition(string $name)
168
+	{
169
+		if (!self::$formatters) {
170
+			self::$formatters = self::initPlaceholderFormatters();
171
+		}
172
+
173
+		return self::$formatters[$name] ?? null;
174
+	}
175
+
176
+	private function display()
177
+	{
178
+		if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
179
+			return;
180
+		}
181
+
182
+		$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
183
+			if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
184
+				return $formatter($this);
185
+			}
186
+
187
+			return $matches[0];
188
+		}, $this->format ?? ''));
189
+	}
190
+
191
+	private function determineBestFormat(): string
192
+	{
193
+		switch ($this->output->getVerbosity()) {
194
+			// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
195
+			case OutputInterface::VERBOSITY_VERBOSE:
196
+				return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
197
+			case OutputInterface::VERBOSITY_VERY_VERBOSE:
198
+			case OutputInterface::VERBOSITY_DEBUG:
199
+				return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
200
+			default:
201
+				return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
202
+		}
203
+	}
204
+
205
+	/**
206
+	 * Overwrites a previous message to the output.
207
+	 */
208
+	private function overwrite(string $message)
209
+	{
210
+		if ($this->output->isDecorated()) {
211
+			$this->output->write("\x0D\x1B[2K");
212
+			$this->output->write($message);
213
+		} else {
214
+			$this->output->writeln($message);
215
+		}
216
+	}
217
+
218
+	private function getCurrentTimeInMilliseconds(): float
219
+	{
220
+		return round(microtime(true) * 1000);
221
+	}
222
+
223
+	private static function initPlaceholderFormatters(): array
224
+	{
225
+		return [
226
+			'indicator' => function (self $indicator) {
227
+				return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
228
+			},
229
+			'message' => function (self $indicator) {
230
+				return $indicator->message;
231
+			},
232
+			'elapsed' => function (self $indicator) {
233
+				return Helper::formatTime(time() - $indicator->startTime);
234
+			},
235
+			'memory' => function () {
236
+				return Helper::formatMemory(memory_get_usage(true));
237
+			},
238
+		];
239
+	}
240
+
241
+	private static function initFormats(): array
242
+	{
243
+		return [
244
+			'normal' => ' %indicator% %message%',
245
+			'normal_no_ansi' => ' %message%',
246
+
247
+			'verbose' => ' %indicator% %message% (%elapsed:6s%)',
248
+			'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
249
+
250
+			'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
251
+			'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
252
+		];
253
+	}
254 254
 }
Please login to merge, or discard this patch.
vendor/symfony/console/Helper/DescriptorHelper.php 1 patch
Indentation   +51 added lines, -51 removed lines patch added patch discarded remove patch
@@ -26,62 +26,62 @@
 block discarded – undo
26 26
  */
27 27
 class DescriptorHelper extends Helper
28 28
 {
29
-    /**
30
-     * @var DescriptorInterface[]
31
-     */
32
-    private $descriptors = [];
29
+	/**
30
+	 * @var DescriptorInterface[]
31
+	 */
32
+	private $descriptors = [];
33 33
 
34
-    public function __construct()
35
-    {
36
-        $this
37
-            ->register('txt', new TextDescriptor())
38
-            ->register('xml', new XmlDescriptor())
39
-            ->register('json', new JsonDescriptor())
40
-            ->register('md', new MarkdownDescriptor())
41
-        ;
42
-    }
34
+	public function __construct()
35
+	{
36
+		$this
37
+			->register('txt', new TextDescriptor())
38
+			->register('xml', new XmlDescriptor())
39
+			->register('json', new JsonDescriptor())
40
+			->register('md', new MarkdownDescriptor())
41
+		;
42
+	}
43 43
 
44
-    /**
45
-     * Describes an object if supported.
46
-     *
47
-     * Available options are:
48
-     * * format: string, the output format name
49
-     * * raw_text: boolean, sets output type as raw
50
-     *
51
-     * @throws InvalidArgumentException when the given format is not supported
52
-     */
53
-    public function describe(OutputInterface $output, ?object $object, array $options = [])
54
-    {
55
-        $options = array_merge([
56
-            'raw_text' => false,
57
-            'format' => 'txt',
58
-        ], $options);
44
+	/**
45
+	 * Describes an object if supported.
46
+	 *
47
+	 * Available options are:
48
+	 * * format: string, the output format name
49
+	 * * raw_text: boolean, sets output type as raw
50
+	 *
51
+	 * @throws InvalidArgumentException when the given format is not supported
52
+	 */
53
+	public function describe(OutputInterface $output, ?object $object, array $options = [])
54
+	{
55
+		$options = array_merge([
56
+			'raw_text' => false,
57
+			'format' => 'txt',
58
+		], $options);
59 59
 
60
-        if (!isset($this->descriptors[$options['format']])) {
61
-            throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
62
-        }
60
+		if (!isset($this->descriptors[$options['format']])) {
61
+			throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
62
+		}
63 63
 
64
-        $descriptor = $this->descriptors[$options['format']];
65
-        $descriptor->describe($output, $object, $options);
66
-    }
64
+		$descriptor = $this->descriptors[$options['format']];
65
+		$descriptor->describe($output, $object, $options);
66
+	}
67 67
 
68
-    /**
69
-     * Registers a descriptor.
70
-     *
71
-     * @return $this
72
-     */
73
-    public function register(string $format, DescriptorInterface $descriptor)
74
-    {
75
-        $this->descriptors[$format] = $descriptor;
68
+	/**
69
+	 * Registers a descriptor.
70
+	 *
71
+	 * @return $this
72
+	 */
73
+	public function register(string $format, DescriptorInterface $descriptor)
74
+	{
75
+		$this->descriptors[$format] = $descriptor;
76 76
 
77
-        return $this;
78
-    }
77
+		return $this;
78
+	}
79 79
 
80
-    /**
81
-     * {@inheritdoc}
82
-     */
83
-    public function getName()
84
-    {
85
-        return 'descriptor';
86
-    }
80
+	/**
81
+	 * {@inheritdoc}
82
+	 */
83
+	public function getName()
84
+	{
85
+		return 'descriptor';
86
+	}
87 87
 }
Please login to merge, or discard this patch.
vendor/symfony/console/Helper/Dumper.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -21,44 +21,44 @@
 block discarded – undo
21 21
  */
22 22
 final class Dumper
23 23
 {
24
-    private $output;
25
-    private $dumper;
26
-    private $cloner;
27
-    private $handler;
24
+	private $output;
25
+	private $dumper;
26
+	private $cloner;
27
+	private $handler;
28 28
 
29
-    public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
30
-    {
31
-        $this->output = $output;
32
-        $this->dumper = $dumper;
33
-        $this->cloner = $cloner;
29
+	public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
30
+	{
31
+		$this->output = $output;
32
+		$this->dumper = $dumper;
33
+		$this->cloner = $cloner;
34 34
 
35
-        if (class_exists(CliDumper::class)) {
36
-            $this->handler = function ($var): string {
37
-                $dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
38
-                $dumper->setColors($this->output->isDecorated());
35
+		if (class_exists(CliDumper::class)) {
36
+			$this->handler = function ($var): string {
37
+				$dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
38
+				$dumper->setColors($this->output->isDecorated());
39 39
 
40
-                return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true));
41
-            };
42
-        } else {
43
-            $this->handler = function ($var): string {
44
-                switch (true) {
45
-                    case null === $var:
46
-                        return 'null';
47
-                    case true === $var:
48
-                        return 'true';
49
-                    case false === $var:
50
-                        return 'false';
51
-                    case \is_string($var):
52
-                        return '"'.$var.'"';
53
-                    default:
54
-                        return rtrim(print_r($var, true));
55
-                }
56
-            };
57
-        }
58
-    }
40
+				return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true));
41
+			};
42
+		} else {
43
+			$this->handler = function ($var): string {
44
+				switch (true) {
45
+					case null === $var:
46
+						return 'null';
47
+					case true === $var:
48
+						return 'true';
49
+					case false === $var:
50
+						return 'false';
51
+					case \is_string($var):
52
+						return '"'.$var.'"';
53
+					default:
54
+						return rtrim(print_r($var, true));
55
+				}
56
+			};
57
+		}
58
+	}
59 59
 
60
-    public function __invoke($var): string
61
-    {
62
-        return ($this->handler)($var);
63
-    }
60
+	public function __invoke($var): string
61
+	{
62
+		return ($this->handler)($var);
63
+	}
64 64
 }
Please login to merge, or discard this patch.
vendor/symfony/console/Helper/QuestionHelper.php 1 patch
Indentation   +569 added lines, -569 removed lines patch added patch discarded remove patch
@@ -33,573 +33,573 @@
 block discarded – undo
33 33
  */
34 34
 class QuestionHelper extends Helper
35 35
 {
36
-    private $inputStream;
37
-    private static $shell;
38
-    private static $stty = true;
39
-    private static $stdinIsInteractive;
40
-
41
-    /**
42
-     * Asks a question to the user.
43
-     *
44
-     * @return mixed The user answer
45
-     *
46
-     * @throws RuntimeException If there is no data to read in the input stream
47
-     */
48
-    public function ask(InputInterface $input, OutputInterface $output, Question $question)
49
-    {
50
-        if ($output instanceof ConsoleOutputInterface) {
51
-            $output = $output->getErrorOutput();
52
-        }
53
-
54
-        if (!$input->isInteractive()) {
55
-            return $this->getDefaultAnswer($question);
56
-        }
57
-
58
-        if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
59
-            $this->inputStream = $stream;
60
-        }
61
-
62
-        try {
63
-            if (!$question->getValidator()) {
64
-                return $this->doAsk($output, $question);
65
-            }
66
-
67
-            $interviewer = function () use ($output, $question) {
68
-                return $this->doAsk($output, $question);
69
-            };
70
-
71
-            return $this->validateAttempts($interviewer, $output, $question);
72
-        } catch (MissingInputException $exception) {
73
-            $input->setInteractive(false);
74
-
75
-            if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
76
-                throw $exception;
77
-            }
78
-
79
-            return $fallbackOutput;
80
-        }
81
-    }
82
-
83
-    /**
84
-     * {@inheritdoc}
85
-     */
86
-    public function getName()
87
-    {
88
-        return 'question';
89
-    }
90
-
91
-    /**
92
-     * Prevents usage of stty.
93
-     */
94
-    public static function disableStty()
95
-    {
96
-        self::$stty = false;
97
-    }
98
-
99
-    /**
100
-     * Asks the question to the user.
101
-     *
102
-     * @return mixed
103
-     *
104
-     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
105
-     */
106
-    private function doAsk(OutputInterface $output, Question $question)
107
-    {
108
-        $this->writePrompt($output, $question);
109
-
110
-        $inputStream = $this->inputStream ?: \STDIN;
111
-        $autocomplete = $question->getAutocompleterCallback();
112
-
113
-        if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
114
-            $ret = false;
115
-            if ($question->isHidden()) {
116
-                try {
117
-                    $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
118
-                    $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
119
-                } catch (RuntimeException $e) {
120
-                    if (!$question->isHiddenFallback()) {
121
-                        throw $e;
122
-                    }
123
-                }
124
-            }
125
-
126
-            if (false === $ret) {
127
-                $ret = $this->readInput($inputStream, $question);
128
-                if (false === $ret) {
129
-                    throw new MissingInputException('Aborted.');
130
-                }
131
-                if ($question->isTrimmable()) {
132
-                    $ret = trim($ret);
133
-                }
134
-            }
135
-        } else {
136
-            $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
137
-            $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
138
-        }
139
-
140
-        if ($output instanceof ConsoleSectionOutput) {
141
-            $output->addContent($ret);
142
-        }
143
-
144
-        $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
145
-
146
-        if ($normalizer = $question->getNormalizer()) {
147
-            return $normalizer($ret);
148
-        }
149
-
150
-        return $ret;
151
-    }
152
-
153
-    /**
154
-     * @return mixed
155
-     */
156
-    private function getDefaultAnswer(Question $question)
157
-    {
158
-        $default = $question->getDefault();
159
-
160
-        if (null === $default) {
161
-            return $default;
162
-        }
163
-
164
-        if ($validator = $question->getValidator()) {
165
-            return \call_user_func($question->getValidator(), $default);
166
-        } elseif ($question instanceof ChoiceQuestion) {
167
-            $choices = $question->getChoices();
168
-
169
-            if (!$question->isMultiselect()) {
170
-                return $choices[$default] ?? $default;
171
-            }
172
-
173
-            $default = explode(',', $default);
174
-            foreach ($default as $k => $v) {
175
-                $v = $question->isTrimmable() ? trim($v) : $v;
176
-                $default[$k] = $choices[$v] ?? $v;
177
-            }
178
-        }
179
-
180
-        return $default;
181
-    }
182
-
183
-    /**
184
-     * Outputs the question prompt.
185
-     */
186
-    protected function writePrompt(OutputInterface $output, Question $question)
187
-    {
188
-        $message = $question->getQuestion();
189
-
190
-        if ($question instanceof ChoiceQuestion) {
191
-            $output->writeln(array_merge([
192
-                $question->getQuestion(),
193
-            ], $this->formatChoiceQuestionChoices($question, 'info')));
194
-
195
-            $message = $question->getPrompt();
196
-        }
197
-
198
-        $output->write($message);
199
-    }
200
-
201
-    /**
202
-     * @return string[]
203
-     */
204
-    protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag)
205
-    {
206
-        $messages = [];
207
-
208
-        $maxWidth = max(array_map('self::width', array_keys($choices = $question->getChoices())));
209
-
210
-        foreach ($choices as $key => $value) {
211
-            $padding = str_repeat(' ', $maxWidth - self::width($key));
212
-
213
-            $messages[] = sprintf("  [<$tag>%s$padding</$tag>] %s", $key, $value);
214
-        }
215
-
216
-        return $messages;
217
-    }
218
-
219
-    /**
220
-     * Outputs an error message.
221
-     */
222
-    protected function writeError(OutputInterface $output, \Exception $error)
223
-    {
224
-        if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
225
-            $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
226
-        } else {
227
-            $message = '<error>'.$error->getMessage().'</error>';
228
-        }
229
-
230
-        $output->writeln($message);
231
-    }
232
-
233
-    /**
234
-     * Autocompletes a question.
235
-     *
236
-     * @param resource $inputStream
237
-     */
238
-    private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
239
-    {
240
-        $cursor = new Cursor($output, $inputStream);
241
-
242
-        $fullChoice = '';
243
-        $ret = '';
244
-
245
-        $i = 0;
246
-        $ofs = -1;
247
-        $matches = $autocomplete($ret);
248
-        $numMatches = \count($matches);
249
-
250
-        $sttyMode = shell_exec('stty -g');
251
-
252
-        // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
253
-        shell_exec('stty -icanon -echo');
254
-
255
-        // Add highlighted text style
256
-        $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
257
-
258
-        // Read a keypress
259
-        while (!feof($inputStream)) {
260
-            $c = fread($inputStream, 1);
261
-
262
-            // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
263
-            if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
264
-                shell_exec(sprintf('stty %s', $sttyMode));
265
-                throw new MissingInputException('Aborted.');
266
-            } elseif ("\177" === $c) { // Backspace Character
267
-                if (0 === $numMatches && 0 !== $i) {
268
-                    --$i;
269
-                    $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));
270
-
271
-                    $fullChoice = self::substr($fullChoice, 0, $i);
272
-                }
273
-
274
-                if (0 === $i) {
275
-                    $ofs = -1;
276
-                    $matches = $autocomplete($ret);
277
-                    $numMatches = \count($matches);
278
-                } else {
279
-                    $numMatches = 0;
280
-                }
281
-
282
-                // Pop the last character off the end of our string
283
-                $ret = self::substr($ret, 0, $i);
284
-            } elseif ("\033" === $c) {
285
-                // Did we read an escape sequence?
286
-                $c .= fread($inputStream, 2);
287
-
288
-                // A = Up Arrow. B = Down Arrow
289
-                if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
290
-                    if ('A' === $c[2] && -1 === $ofs) {
291
-                        $ofs = 0;
292
-                    }
293
-
294
-                    if (0 === $numMatches) {
295
-                        continue;
296
-                    }
297
-
298
-                    $ofs += ('A' === $c[2]) ? -1 : 1;
299
-                    $ofs = ($numMatches + $ofs) % $numMatches;
300
-                }
301
-            } elseif (\ord($c) < 32) {
302
-                if ("\t" === $c || "\n" === $c) {
303
-                    if ($numMatches > 0 && -1 !== $ofs) {
304
-                        $ret = (string) $matches[$ofs];
305
-                        // Echo out remaining chars for current match
306
-                        $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
307
-                        $output->write($remainingCharacters);
308
-                        $fullChoice .= $remainingCharacters;
309
-                        $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);
310
-
311
-                        $matches = array_filter(
312
-                            $autocomplete($ret),
313
-                            function ($match) use ($ret) {
314
-                                return '' === $ret || str_starts_with($match, $ret);
315
-                            }
316
-                        );
317
-                        $numMatches = \count($matches);
318
-                        $ofs = -1;
319
-                    }
320
-
321
-                    if ("\n" === $c) {
322
-                        $output->write($c);
323
-                        break;
324
-                    }
325
-
326
-                    $numMatches = 0;
327
-                }
328
-
329
-                continue;
330
-            } else {
331
-                if ("\x80" <= $c) {
332
-                    $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
333
-                }
334
-
335
-                $output->write($c);
336
-                $ret .= $c;
337
-                $fullChoice .= $c;
338
-                ++$i;
339
-
340
-                $tempRet = $ret;
341
-
342
-                if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
343
-                    $tempRet = $this->mostRecentlyEnteredValue($fullChoice);
344
-                }
345
-
346
-                $numMatches = 0;
347
-                $ofs = 0;
348
-
349
-                foreach ($autocomplete($ret) as $value) {
350
-                    // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
351
-                    if (str_starts_with($value, $tempRet)) {
352
-                        $matches[$numMatches++] = $value;
353
-                    }
354
-                }
355
-            }
356
-
357
-            $cursor->clearLineAfter();
358
-
359
-            if ($numMatches > 0 && -1 !== $ofs) {
360
-                $cursor->savePosition();
361
-                // Write highlighted text, complete the partially entered response
362
-                $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
363
-                $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
364
-                $cursor->restorePosition();
365
-            }
366
-        }
367
-
368
-        // Reset stty so it behaves normally again
369
-        shell_exec(sprintf('stty %s', $sttyMode));
370
-
371
-        return $fullChoice;
372
-    }
373
-
374
-    private function mostRecentlyEnteredValue(string $entered): string
375
-    {
376
-        // Determine the most recent value that the user entered
377
-        if (!str_contains($entered, ',')) {
378
-            return $entered;
379
-        }
380
-
381
-        $choices = explode(',', $entered);
382
-        if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) {
383
-            return $lastChoice;
384
-        }
385
-
386
-        return $entered;
387
-    }
388
-
389
-    /**
390
-     * Gets a hidden response from user.
391
-     *
392
-     * @param resource $inputStream The handler resource
393
-     * @param bool     $trimmable   Is the answer trimmable
394
-     *
395
-     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
396
-     */
397
-    private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
398
-    {
399
-        if ('\\' === \DIRECTORY_SEPARATOR) {
400
-            $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
401
-
402
-            // handle code running from a phar
403
-            if ('phar:' === substr(__FILE__, 0, 5)) {
404
-                $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
405
-                copy($exe, $tmpExe);
406
-                $exe = $tmpExe;
407
-            }
408
-
409
-            $sExec = shell_exec('"'.$exe.'"');
410
-            $value = $trimmable ? rtrim($sExec) : $sExec;
411
-            $output->writeln('');
412
-
413
-            if (isset($tmpExe)) {
414
-                unlink($tmpExe);
415
-            }
416
-
417
-            return $value;
418
-        }
419
-
420
-        if (self::$stty && Terminal::hasSttyAvailable()) {
421
-            $sttyMode = shell_exec('stty -g');
422
-            shell_exec('stty -echo');
423
-        } elseif ($this->isInteractiveInput($inputStream)) {
424
-            throw new RuntimeException('Unable to hide the response.');
425
-        }
426
-
427
-        $value = fgets($inputStream, 4096);
428
-
429
-        if (self::$stty && Terminal::hasSttyAvailable()) {
430
-            shell_exec(sprintf('stty %s', $sttyMode));
431
-        }
432
-
433
-        if (false === $value) {
434
-            throw new MissingInputException('Aborted.');
435
-        }
436
-        if ($trimmable) {
437
-            $value = trim($value);
438
-        }
439
-        $output->writeln('');
440
-
441
-        return $value;
442
-    }
443
-
444
-    /**
445
-     * Validates an attempt.
446
-     *
447
-     * @param callable $interviewer A callable that will ask for a question and return the result
448
-     *
449
-     * @return mixed The validated response
450
-     *
451
-     * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
452
-     */
453
-    private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
454
-    {
455
-        $error = null;
456
-        $attempts = $question->getMaxAttempts();
457
-
458
-        while (null === $attempts || $attempts--) {
459
-            if (null !== $error) {
460
-                $this->writeError($output, $error);
461
-            }
462
-
463
-            try {
464
-                return $question->getValidator()($interviewer());
465
-            } catch (RuntimeException $e) {
466
-                throw $e;
467
-            } catch (\Exception $error) {
468
-            }
469
-        }
470
-
471
-        throw $error;
472
-    }
473
-
474
-    private function isInteractiveInput($inputStream): bool
475
-    {
476
-        if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
477
-            return false;
478
-        }
479
-
480
-        if (null !== self::$stdinIsInteractive) {
481
-            return self::$stdinIsInteractive;
482
-        }
483
-
484
-        if (\function_exists('stream_isatty')) {
485
-            return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r'));
486
-        }
487
-
488
-        if (\function_exists('posix_isatty')) {
489
-            return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r'));
490
-        }
491
-
492
-        if (!\function_exists('exec')) {
493
-            return self::$stdinIsInteractive = true;
494
-        }
495
-
496
-        exec('stty 2> /dev/null', $output, $status);
497
-
498
-        return self::$stdinIsInteractive = 1 !== $status;
499
-    }
500
-
501
-    /**
502
-     * Reads one or more lines of input and returns what is read.
503
-     *
504
-     * @param resource $inputStream The handler resource
505
-     * @param Question $question    The question being asked
506
-     *
507
-     * @return string|false The input received, false in case input could not be read
508
-     */
509
-    private function readInput($inputStream, Question $question)
510
-    {
511
-        if (!$question->isMultiline()) {
512
-            $cp = $this->setIOCodepage();
513
-            $ret = fgets($inputStream, 4096);
514
-
515
-            return $this->resetIOCodepage($cp, $ret);
516
-        }
517
-
518
-        $multiLineStreamReader = $this->cloneInputStream($inputStream);
519
-        if (null === $multiLineStreamReader) {
520
-            return false;
521
-        }
522
-
523
-        $ret = '';
524
-        $cp = $this->setIOCodepage();
525
-        while (false !== ($char = fgetc($multiLineStreamReader))) {
526
-            if (\PHP_EOL === "{$ret}{$char}") {
527
-                break;
528
-            }
529
-            $ret .= $char;
530
-        }
531
-
532
-        return $this->resetIOCodepage($cp, $ret);
533
-    }
534
-
535
-    /**
536
-     * Sets console I/O to the host code page.
537
-     *
538
-     * @return int Previous code page in IBM/EBCDIC format
539
-     */
540
-    private function setIOCodepage(): int
541
-    {
542
-        if (\function_exists('sapi_windows_cp_set')) {
543
-            $cp = sapi_windows_cp_get();
544
-            sapi_windows_cp_set(sapi_windows_cp_get('oem'));
545
-
546
-            return $cp;
547
-        }
548
-
549
-        return 0;
550
-    }
551
-
552
-    /**
553
-     * Sets console I/O to the specified code page and converts the user input.
554
-     *
555
-     * @param string|false $input
556
-     *
557
-     * @return string|false
558
-     */
559
-    private function resetIOCodepage(int $cp, $input)
560
-    {
561
-        if (0 !== $cp) {
562
-            sapi_windows_cp_set($cp);
563
-
564
-            if (false !== $input && '' !== $input) {
565
-                $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
566
-            }
567
-        }
568
-
569
-        return $input;
570
-    }
571
-
572
-    /**
573
-     * Clones an input stream in order to act on one instance of the same
574
-     * stream without affecting the other instance.
575
-     *
576
-     * @param resource $inputStream The handler resource
577
-     *
578
-     * @return resource|null The cloned resource, null in case it could not be cloned
579
-     */
580
-    private function cloneInputStream($inputStream)
581
-    {
582
-        $streamMetaData = stream_get_meta_data($inputStream);
583
-        $seekable = $streamMetaData['seekable'] ?? false;
584
-        $mode = $streamMetaData['mode'] ?? 'rb';
585
-        $uri = $streamMetaData['uri'] ?? null;
586
-
587
-        if (null === $uri) {
588
-            return null;
589
-        }
590
-
591
-        $cloneStream = fopen($uri, $mode);
592
-
593
-        // For seekable and writable streams, add all the same data to the
594
-        // cloned stream and then seek to the same offset.
595
-        if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
596
-            $offset = ftell($inputStream);
597
-            rewind($inputStream);
598
-            stream_copy_to_stream($inputStream, $cloneStream);
599
-            fseek($inputStream, $offset);
600
-            fseek($cloneStream, $offset);
601
-        }
602
-
603
-        return $cloneStream;
604
-    }
36
+	private $inputStream;
37
+	private static $shell;
38
+	private static $stty = true;
39
+	private static $stdinIsInteractive;
40
+
41
+	/**
42
+	 * Asks a question to the user.
43
+	 *
44
+	 * @return mixed The user answer
45
+	 *
46
+	 * @throws RuntimeException If there is no data to read in the input stream
47
+	 */
48
+	public function ask(InputInterface $input, OutputInterface $output, Question $question)
49
+	{
50
+		if ($output instanceof ConsoleOutputInterface) {
51
+			$output = $output->getErrorOutput();
52
+		}
53
+
54
+		if (!$input->isInteractive()) {
55
+			return $this->getDefaultAnswer($question);
56
+		}
57
+
58
+		if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
59
+			$this->inputStream = $stream;
60
+		}
61
+
62
+		try {
63
+			if (!$question->getValidator()) {
64
+				return $this->doAsk($output, $question);
65
+			}
66
+
67
+			$interviewer = function () use ($output, $question) {
68
+				return $this->doAsk($output, $question);
69
+			};
70
+
71
+			return $this->validateAttempts($interviewer, $output, $question);
72
+		} catch (MissingInputException $exception) {
73
+			$input->setInteractive(false);
74
+
75
+			if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
76
+				throw $exception;
77
+			}
78
+
79
+			return $fallbackOutput;
80
+		}
81
+	}
82
+
83
+	/**
84
+	 * {@inheritdoc}
85
+	 */
86
+	public function getName()
87
+	{
88
+		return 'question';
89
+	}
90
+
91
+	/**
92
+	 * Prevents usage of stty.
93
+	 */
94
+	public static function disableStty()
95
+	{
96
+		self::$stty = false;
97
+	}
98
+
99
+	/**
100
+	 * Asks the question to the user.
101
+	 *
102
+	 * @return mixed
103
+	 *
104
+	 * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
105
+	 */
106
+	private function doAsk(OutputInterface $output, Question $question)
107
+	{
108
+		$this->writePrompt($output, $question);
109
+
110
+		$inputStream = $this->inputStream ?: \STDIN;
111
+		$autocomplete = $question->getAutocompleterCallback();
112
+
113
+		if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
114
+			$ret = false;
115
+			if ($question->isHidden()) {
116
+				try {
117
+					$hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
118
+					$ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
119
+				} catch (RuntimeException $e) {
120
+					if (!$question->isHiddenFallback()) {
121
+						throw $e;
122
+					}
123
+				}
124
+			}
125
+
126
+			if (false === $ret) {
127
+				$ret = $this->readInput($inputStream, $question);
128
+				if (false === $ret) {
129
+					throw new MissingInputException('Aborted.');
130
+				}
131
+				if ($question->isTrimmable()) {
132
+					$ret = trim($ret);
133
+				}
134
+			}
135
+		} else {
136
+			$autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
137
+			$ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
138
+		}
139
+
140
+		if ($output instanceof ConsoleSectionOutput) {
141
+			$output->addContent($ret);
142
+		}
143
+
144
+		$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
145
+
146
+		if ($normalizer = $question->getNormalizer()) {
147
+			return $normalizer($ret);
148
+		}
149
+
150
+		return $ret;
151
+	}
152
+
153
+	/**
154
+	 * @return mixed
155
+	 */
156
+	private function getDefaultAnswer(Question $question)
157
+	{
158
+		$default = $question->getDefault();
159
+
160
+		if (null === $default) {
161
+			return $default;
162
+		}
163
+
164
+		if ($validator = $question->getValidator()) {
165
+			return \call_user_func($question->getValidator(), $default);
166
+		} elseif ($question instanceof ChoiceQuestion) {
167
+			$choices = $question->getChoices();
168
+
169
+			if (!$question->isMultiselect()) {
170
+				return $choices[$default] ?? $default;
171
+			}
172
+
173
+			$default = explode(',', $default);
174
+			foreach ($default as $k => $v) {
175
+				$v = $question->isTrimmable() ? trim($v) : $v;
176
+				$default[$k] = $choices[$v] ?? $v;
177
+			}
178
+		}
179
+
180
+		return $default;
181
+	}
182
+
183
+	/**
184
+	 * Outputs the question prompt.
185
+	 */
186
+	protected function writePrompt(OutputInterface $output, Question $question)
187
+	{
188
+		$message = $question->getQuestion();
189
+
190
+		if ($question instanceof ChoiceQuestion) {
191
+			$output->writeln(array_merge([
192
+				$question->getQuestion(),
193
+			], $this->formatChoiceQuestionChoices($question, 'info')));
194
+
195
+			$message = $question->getPrompt();
196
+		}
197
+
198
+		$output->write($message);
199
+	}
200
+
201
+	/**
202
+	 * @return string[]
203
+	 */
204
+	protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag)
205
+	{
206
+		$messages = [];
207
+
208
+		$maxWidth = max(array_map('self::width', array_keys($choices = $question->getChoices())));
209
+
210
+		foreach ($choices as $key => $value) {
211
+			$padding = str_repeat(' ', $maxWidth - self::width($key));
212
+
213
+			$messages[] = sprintf("  [<$tag>%s$padding</$tag>] %s", $key, $value);
214
+		}
215
+
216
+		return $messages;
217
+	}
218
+
219
+	/**
220
+	 * Outputs an error message.
221
+	 */
222
+	protected function writeError(OutputInterface $output, \Exception $error)
223
+	{
224
+		if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
225
+			$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
226
+		} else {
227
+			$message = '<error>'.$error->getMessage().'</error>';
228
+		}
229
+
230
+		$output->writeln($message);
231
+	}
232
+
233
+	/**
234
+	 * Autocompletes a question.
235
+	 *
236
+	 * @param resource $inputStream
237
+	 */
238
+	private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
239
+	{
240
+		$cursor = new Cursor($output, $inputStream);
241
+
242
+		$fullChoice = '';
243
+		$ret = '';
244
+
245
+		$i = 0;
246
+		$ofs = -1;
247
+		$matches = $autocomplete($ret);
248
+		$numMatches = \count($matches);
249
+
250
+		$sttyMode = shell_exec('stty -g');
251
+
252
+		// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
253
+		shell_exec('stty -icanon -echo');
254
+
255
+		// Add highlighted text style
256
+		$output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
257
+
258
+		// Read a keypress
259
+		while (!feof($inputStream)) {
260
+			$c = fread($inputStream, 1);
261
+
262
+			// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
263
+			if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
264
+				shell_exec(sprintf('stty %s', $sttyMode));
265
+				throw new MissingInputException('Aborted.');
266
+			} elseif ("\177" === $c) { // Backspace Character
267
+				if (0 === $numMatches && 0 !== $i) {
268
+					--$i;
269
+					$cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));
270
+
271
+					$fullChoice = self::substr($fullChoice, 0, $i);
272
+				}
273
+
274
+				if (0 === $i) {
275
+					$ofs = -1;
276
+					$matches = $autocomplete($ret);
277
+					$numMatches = \count($matches);
278
+				} else {
279
+					$numMatches = 0;
280
+				}
281
+
282
+				// Pop the last character off the end of our string
283
+				$ret = self::substr($ret, 0, $i);
284
+			} elseif ("\033" === $c) {
285
+				// Did we read an escape sequence?
286
+				$c .= fread($inputStream, 2);
287
+
288
+				// A = Up Arrow. B = Down Arrow
289
+				if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
290
+					if ('A' === $c[2] && -1 === $ofs) {
291
+						$ofs = 0;
292
+					}
293
+
294
+					if (0 === $numMatches) {
295
+						continue;
296
+					}
297
+
298
+					$ofs += ('A' === $c[2]) ? -1 : 1;
299
+					$ofs = ($numMatches + $ofs) % $numMatches;
300
+				}
301
+			} elseif (\ord($c) < 32) {
302
+				if ("\t" === $c || "\n" === $c) {
303
+					if ($numMatches > 0 && -1 !== $ofs) {
304
+						$ret = (string) $matches[$ofs];
305
+						// Echo out remaining chars for current match
306
+						$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
307
+						$output->write($remainingCharacters);
308
+						$fullChoice .= $remainingCharacters;
309
+						$i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);
310
+
311
+						$matches = array_filter(
312
+							$autocomplete($ret),
313
+							function ($match) use ($ret) {
314
+								return '' === $ret || str_starts_with($match, $ret);
315
+							}
316
+						);
317
+						$numMatches = \count($matches);
318
+						$ofs = -1;
319
+					}
320
+
321
+					if ("\n" === $c) {
322
+						$output->write($c);
323
+						break;
324
+					}
325
+
326
+					$numMatches = 0;
327
+				}
328
+
329
+				continue;
330
+			} else {
331
+				if ("\x80" <= $c) {
332
+					$c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
333
+				}
334
+
335
+				$output->write($c);
336
+				$ret .= $c;
337
+				$fullChoice .= $c;
338
+				++$i;
339
+
340
+				$tempRet = $ret;
341
+
342
+				if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
343
+					$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
344
+				}
345
+
346
+				$numMatches = 0;
347
+				$ofs = 0;
348
+
349
+				foreach ($autocomplete($ret) as $value) {
350
+					// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
351
+					if (str_starts_with($value, $tempRet)) {
352
+						$matches[$numMatches++] = $value;
353
+					}
354
+				}
355
+			}
356
+
357
+			$cursor->clearLineAfter();
358
+
359
+			if ($numMatches > 0 && -1 !== $ofs) {
360
+				$cursor->savePosition();
361
+				// Write highlighted text, complete the partially entered response
362
+				$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
363
+				$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
364
+				$cursor->restorePosition();
365
+			}
366
+		}
367
+
368
+		// Reset stty so it behaves normally again
369
+		shell_exec(sprintf('stty %s', $sttyMode));
370
+
371
+		return $fullChoice;
372
+	}
373
+
374
+	private function mostRecentlyEnteredValue(string $entered): string
375
+	{
376
+		// Determine the most recent value that the user entered
377
+		if (!str_contains($entered, ',')) {
378
+			return $entered;
379
+		}
380
+
381
+		$choices = explode(',', $entered);
382
+		if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) {
383
+			return $lastChoice;
384
+		}
385
+
386
+		return $entered;
387
+	}
388
+
389
+	/**
390
+	 * Gets a hidden response from user.
391
+	 *
392
+	 * @param resource $inputStream The handler resource
393
+	 * @param bool     $trimmable   Is the answer trimmable
394
+	 *
395
+	 * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
396
+	 */
397
+	private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
398
+	{
399
+		if ('\\' === \DIRECTORY_SEPARATOR) {
400
+			$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
401
+
402
+			// handle code running from a phar
403
+			if ('phar:' === substr(__FILE__, 0, 5)) {
404
+				$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
405
+				copy($exe, $tmpExe);
406
+				$exe = $tmpExe;
407
+			}
408
+
409
+			$sExec = shell_exec('"'.$exe.'"');
410
+			$value = $trimmable ? rtrim($sExec) : $sExec;
411
+			$output->writeln('');
412
+
413
+			if (isset($tmpExe)) {
414
+				unlink($tmpExe);
415
+			}
416
+
417
+			return $value;
418
+		}
419
+
420
+		if (self::$stty && Terminal::hasSttyAvailable()) {
421
+			$sttyMode = shell_exec('stty -g');
422
+			shell_exec('stty -echo');
423
+		} elseif ($this->isInteractiveInput($inputStream)) {
424
+			throw new RuntimeException('Unable to hide the response.');
425
+		}
426
+
427
+		$value = fgets($inputStream, 4096);
428
+
429
+		if (self::$stty && Terminal::hasSttyAvailable()) {
430
+			shell_exec(sprintf('stty %s', $sttyMode));
431
+		}
432
+
433
+		if (false === $value) {
434
+			throw new MissingInputException('Aborted.');
435
+		}
436
+		if ($trimmable) {
437
+			$value = trim($value);
438
+		}
439
+		$output->writeln('');
440
+
441
+		return $value;
442
+	}
443
+
444
+	/**
445
+	 * Validates an attempt.
446
+	 *
447
+	 * @param callable $interviewer A callable that will ask for a question and return the result
448
+	 *
449
+	 * @return mixed The validated response
450
+	 *
451
+	 * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
452
+	 */
453
+	private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
454
+	{
455
+		$error = null;
456
+		$attempts = $question->getMaxAttempts();
457
+
458
+		while (null === $attempts || $attempts--) {
459
+			if (null !== $error) {
460
+				$this->writeError($output, $error);
461
+			}
462
+
463
+			try {
464
+				return $question->getValidator()($interviewer());
465
+			} catch (RuntimeException $e) {
466
+				throw $e;
467
+			} catch (\Exception $error) {
468
+			}
469
+		}
470
+
471
+		throw $error;
472
+	}
473
+
474
+	private function isInteractiveInput($inputStream): bool
475
+	{
476
+		if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
477
+			return false;
478
+		}
479
+
480
+		if (null !== self::$stdinIsInteractive) {
481
+			return self::$stdinIsInteractive;
482
+		}
483
+
484
+		if (\function_exists('stream_isatty')) {
485
+			return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r'));
486
+		}
487
+
488
+		if (\function_exists('posix_isatty')) {
489
+			return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r'));
490
+		}
491
+
492
+		if (!\function_exists('exec')) {
493
+			return self::$stdinIsInteractive = true;
494
+		}
495
+
496
+		exec('stty 2> /dev/null', $output, $status);
497
+
498
+		return self::$stdinIsInteractive = 1 !== $status;
499
+	}
500
+
501
+	/**
502
+	 * Reads one or more lines of input and returns what is read.
503
+	 *
504
+	 * @param resource $inputStream The handler resource
505
+	 * @param Question $question    The question being asked
506
+	 *
507
+	 * @return string|false The input received, false in case input could not be read
508
+	 */
509
+	private function readInput($inputStream, Question $question)
510
+	{
511
+		if (!$question->isMultiline()) {
512
+			$cp = $this->setIOCodepage();
513
+			$ret = fgets($inputStream, 4096);
514
+
515
+			return $this->resetIOCodepage($cp, $ret);
516
+		}
517
+
518
+		$multiLineStreamReader = $this->cloneInputStream($inputStream);
519
+		if (null === $multiLineStreamReader) {
520
+			return false;
521
+		}
522
+
523
+		$ret = '';
524
+		$cp = $this->setIOCodepage();
525
+		while (false !== ($char = fgetc($multiLineStreamReader))) {
526
+			if (\PHP_EOL === "{$ret}{$char}") {
527
+				break;
528
+			}
529
+			$ret .= $char;
530
+		}
531
+
532
+		return $this->resetIOCodepage($cp, $ret);
533
+	}
534
+
535
+	/**
536
+	 * Sets console I/O to the host code page.
537
+	 *
538
+	 * @return int Previous code page in IBM/EBCDIC format
539
+	 */
540
+	private function setIOCodepage(): int
541
+	{
542
+		if (\function_exists('sapi_windows_cp_set')) {
543
+			$cp = sapi_windows_cp_get();
544
+			sapi_windows_cp_set(sapi_windows_cp_get('oem'));
545
+
546
+			return $cp;
547
+		}
548
+
549
+		return 0;
550
+	}
551
+
552
+	/**
553
+	 * Sets console I/O to the specified code page and converts the user input.
554
+	 *
555
+	 * @param string|false $input
556
+	 *
557
+	 * @return string|false
558
+	 */
559
+	private function resetIOCodepage(int $cp, $input)
560
+	{
561
+		if (0 !== $cp) {
562
+			sapi_windows_cp_set($cp);
563
+
564
+			if (false !== $input && '' !== $input) {
565
+				$input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
566
+			}
567
+		}
568
+
569
+		return $input;
570
+	}
571
+
572
+	/**
573
+	 * Clones an input stream in order to act on one instance of the same
574
+	 * stream without affecting the other instance.
575
+	 *
576
+	 * @param resource $inputStream The handler resource
577
+	 *
578
+	 * @return resource|null The cloned resource, null in case it could not be cloned
579
+	 */
580
+	private function cloneInputStream($inputStream)
581
+	{
582
+		$streamMetaData = stream_get_meta_data($inputStream);
583
+		$seekable = $streamMetaData['seekable'] ?? false;
584
+		$mode = $streamMetaData['mode'] ?? 'rb';
585
+		$uri = $streamMetaData['uri'] ?? null;
586
+
587
+		if (null === $uri) {
588
+			return null;
589
+		}
590
+
591
+		$cloneStream = fopen($uri, $mode);
592
+
593
+		// For seekable and writable streams, add all the same data to the
594
+		// cloned stream and then seek to the same offset.
595
+		if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
596
+			$offset = ftell($inputStream);
597
+			rewind($inputStream);
598
+			stream_copy_to_stream($inputStream, $cloneStream);
599
+			fseek($inputStream, $offset);
600
+			fseek($cloneStream, $offset);
601
+		}
602
+
603
+		return $cloneStream;
604
+	}
605 605
 }
Please login to merge, or discard this patch.
vendor/symfony/console/Helper/Table.php 1 patch
Indentation   +833 added lines, -833 removed lines patch added patch discarded remove patch
@@ -29,837 +29,837 @@
 block discarded – undo
29 29
  */
30 30
 class Table
31 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
-
39
-    private $headerTitle;
40
-    private $footerTitle;
41
-
42
-    /**
43
-     * Table headers.
44
-     */
45
-    private $headers = [];
46
-
47
-    /**
48
-     * Table rows.
49
-     */
50
-    private $rows = [];
51
-    private $horizontal = false;
52
-
53
-    /**
54
-     * Column widths cache.
55
-     */
56
-    private $effectiveColumnWidths = [];
57
-
58
-    /**
59
-     * Number of columns cache.
60
-     *
61
-     * @var int
62
-     */
63
-    private $numberOfColumns;
64
-
65
-    /**
66
-     * @var OutputInterface
67
-     */
68
-    private $output;
69
-
70
-    /**
71
-     * @var TableStyle
72
-     */
73
-    private $style;
74
-
75
-    /**
76
-     * @var array
77
-     */
78
-    private $columnStyles = [];
79
-
80
-    /**
81
-     * User set column widths.
82
-     *
83
-     * @var array
84
-     */
85
-    private $columnWidths = [];
86
-    private $columnMaxWidths = [];
87
-
88
-    private static $styles;
89
-
90
-    private $rendered = false;
91
-
92
-    public function __construct(OutputInterface $output)
93
-    {
94
-        $this->output = $output;
95
-
96
-        if (!self::$styles) {
97
-            self::$styles = self::initStyles();
98
-        }
99
-
100
-        $this->setStyle('default');
101
-    }
102
-
103
-    /**
104
-     * Sets a style definition.
105
-     */
106
-    public static function setStyleDefinition(string $name, TableStyle $style)
107
-    {
108
-        if (!self::$styles) {
109
-            self::$styles = self::initStyles();
110
-        }
111
-
112
-        self::$styles[$name] = $style;
113
-    }
114
-
115
-    /**
116
-     * Gets a style definition by name.
117
-     *
118
-     * @return TableStyle
119
-     */
120
-    public static function getStyleDefinition(string $name)
121
-    {
122
-        if (!self::$styles) {
123
-            self::$styles = self::initStyles();
124
-        }
125
-
126
-        if (isset(self::$styles[$name])) {
127
-            return self::$styles[$name];
128
-        }
129
-
130
-        throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
131
-    }
132
-
133
-    /**
134
-     * Sets table style.
135
-     *
136
-     * @param TableStyle|string $name The style name or a TableStyle instance
137
-     *
138
-     * @return $this
139
-     */
140
-    public function setStyle($name)
141
-    {
142
-        $this->style = $this->resolveStyle($name);
143
-
144
-        return $this;
145
-    }
146
-
147
-    /**
148
-     * Gets the current table style.
149
-     *
150
-     * @return TableStyle
151
-     */
152
-    public function getStyle()
153
-    {
154
-        return $this->style;
155
-    }
156
-
157
-    /**
158
-     * Sets table column style.
159
-     *
160
-     * @param TableStyle|string $name The style name or a TableStyle instance
161
-     *
162
-     * @return $this
163
-     */
164
-    public function setColumnStyle(int $columnIndex, $name)
165
-    {
166
-        $this->columnStyles[$columnIndex] = $this->resolveStyle($name);
167
-
168
-        return $this;
169
-    }
170
-
171
-    /**
172
-     * Gets the current style for a column.
173
-     *
174
-     * If style was not set, it returns the global table style.
175
-     *
176
-     * @return TableStyle
177
-     */
178
-    public function getColumnStyle(int $columnIndex)
179
-    {
180
-        return $this->columnStyles[$columnIndex] ?? $this->getStyle();
181
-    }
182
-
183
-    /**
184
-     * Sets the minimum width of a column.
185
-     *
186
-     * @return $this
187
-     */
188
-    public function setColumnWidth(int $columnIndex, int $width)
189
-    {
190
-        $this->columnWidths[$columnIndex] = $width;
191
-
192
-        return $this;
193
-    }
194
-
195
-    /**
196
-     * Sets the minimum width of all columns.
197
-     *
198
-     * @return $this
199
-     */
200
-    public function setColumnWidths(array $widths)
201
-    {
202
-        $this->columnWidths = [];
203
-        foreach ($widths as $index => $width) {
204
-            $this->setColumnWidth($index, $width);
205
-        }
206
-
207
-        return $this;
208
-    }
209
-
210
-    /**
211
-     * Sets the maximum width of a column.
212
-     *
213
-     * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
214
-     * formatted strings are preserved.
215
-     *
216
-     * @return $this
217
-     */
218
-    public function setColumnMaxWidth(int $columnIndex, int $width): self
219
-    {
220
-        if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
221
-            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())));
222
-        }
223
-
224
-        $this->columnMaxWidths[$columnIndex] = $width;
225
-
226
-        return $this;
227
-    }
228
-
229
-    public function setHeaders(array $headers)
230
-    {
231
-        $headers = array_values($headers);
232
-        if (!empty($headers) && !\is_array($headers[0])) {
233
-            $headers = [$headers];
234
-        }
235
-
236
-        $this->headers = $headers;
237
-
238
-        return $this;
239
-    }
240
-
241
-    public function setRows(array $rows)
242
-    {
243
-        $this->rows = [];
244
-
245
-        return $this->addRows($rows);
246
-    }
247
-
248
-    public function addRows(array $rows)
249
-    {
250
-        foreach ($rows as $row) {
251
-            $this->addRow($row);
252
-        }
253
-
254
-        return $this;
255
-    }
256
-
257
-    public function addRow($row)
258
-    {
259
-        if ($row instanceof TableSeparator) {
260
-            $this->rows[] = $row;
261
-
262
-            return $this;
263
-        }
264
-
265
-        if (!\is_array($row)) {
266
-            throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
267
-        }
268
-
269
-        $this->rows[] = array_values($row);
270
-
271
-        return $this;
272
-    }
273
-
274
-    /**
275
-     * Adds a row to the table, and re-renders the table.
276
-     */
277
-    public function appendRow($row): self
278
-    {
279
-        if (!$this->output instanceof ConsoleSectionOutput) {
280
-            throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
281
-        }
282
-
283
-        if ($this->rendered) {
284
-            $this->output->clear($this->calculateRowCount());
285
-        }
286
-
287
-        $this->addRow($row);
288
-        $this->render();
289
-
290
-        return $this;
291
-    }
292
-
293
-    public function setRow($column, array $row)
294
-    {
295
-        $this->rows[$column] = $row;
296
-
297
-        return $this;
298
-    }
299
-
300
-    public function setHeaderTitle(?string $title): self
301
-    {
302
-        $this->headerTitle = $title;
303
-
304
-        return $this;
305
-    }
306
-
307
-    public function setFooterTitle(?string $title): self
308
-    {
309
-        $this->footerTitle = $title;
310
-
311
-        return $this;
312
-    }
313
-
314
-    public function setHorizontal(bool $horizontal = true): self
315
-    {
316
-        $this->horizontal = $horizontal;
317
-
318
-        return $this;
319
-    }
320
-
321
-    /**
322
-     * Renders table to output.
323
-     *
324
-     * Example:
325
-     *
326
-     *     +---------------+-----------------------+------------------+
327
-     *     | ISBN          | Title                 | Author           |
328
-     *     +---------------+-----------------------+------------------+
329
-     *     | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
330
-     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
331
-     *     | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
332
-     *     +---------------+-----------------------+------------------+
333
-     */
334
-    public function render()
335
-    {
336
-        $divider = new TableSeparator();
337
-        if ($this->horizontal) {
338
-            $rows = [];
339
-            foreach ($this->headers[0] ?? [] as $i => $header) {
340
-                $rows[$i] = [$header];
341
-                foreach ($this->rows as $row) {
342
-                    if ($row instanceof TableSeparator) {
343
-                        continue;
344
-                    }
345
-                    if (isset($row[$i])) {
346
-                        $rows[$i][] = $row[$i];
347
-                    } elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
348
-                        // Noop, there is a "title"
349
-                    } else {
350
-                        $rows[$i][] = null;
351
-                    }
352
-                }
353
-            }
354
-        } else {
355
-            $rows = array_merge($this->headers, [$divider], $this->rows);
356
-        }
357
-
358
-        $this->calculateNumberOfColumns($rows);
359
-
360
-        $rows = $this->buildTableRows($rows);
361
-        $this->calculateColumnsWidth($rows);
362
-
363
-        $isHeader = !$this->horizontal;
364
-        $isFirstRow = $this->horizontal;
365
-        $hasTitle = (bool) $this->headerTitle;
366
-        foreach ($rows as $row) {
367
-            if ($divider === $row) {
368
-                $isHeader = false;
369
-                $isFirstRow = true;
370
-
371
-                continue;
372
-            }
373
-            if ($row instanceof TableSeparator) {
374
-                $this->renderRowSeparator();
375
-
376
-                continue;
377
-            }
378
-            if (!$row) {
379
-                continue;
380
-            }
381
-
382
-            if ($isHeader || $isFirstRow) {
383
-                $this->renderRowSeparator(
384
-                    $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
385
-                    $hasTitle ? $this->headerTitle : null,
386
-                    $hasTitle ? $this->style->getHeaderTitleFormat() : null
387
-                );
388
-                $isFirstRow = false;
389
-                $hasTitle = false;
390
-            }
391
-            if ($this->horizontal) {
392
-                $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
393
-            } else {
394
-                $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
395
-            }
396
-        }
397
-        $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
398
-
399
-        $this->cleanup();
400
-        $this->rendered = true;
401
-    }
402
-
403
-    /**
404
-     * Renders horizontal header separator.
405
-     *
406
-     * Example:
407
-     *
408
-     *     +-----+-----------+-------+
409
-     */
410
-    private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
411
-    {
412
-        if (0 === $count = $this->numberOfColumns) {
413
-            return;
414
-        }
415
-
416
-        $borders = $this->style->getBorderChars();
417
-        if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
418
-            return;
419
-        }
420
-
421
-        $crossings = $this->style->getCrossingChars();
422
-        if (self::SEPARATOR_MID === $type) {
423
-            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
424
-        } elseif (self::SEPARATOR_TOP === $type) {
425
-            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
426
-        } elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
427
-            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
428
-        } else {
429
-            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
430
-        }
431
-
432
-        $markup = $leftChar;
433
-        for ($column = 0; $column < $count; ++$column) {
434
-            $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
435
-            $markup .= $column === $count - 1 ? $rightChar : $midChar;
436
-        }
437
-
438
-        if (null !== $title) {
439
-            $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)));
440
-            $markupLength = Helper::width($markup);
441
-            if ($titleLength > $limit = $markupLength - 4) {
442
-                $titleLength = $limit;
443
-                $formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, '')));
444
-                $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
445
-            }
446
-
447
-            $titleStart = intdiv($markupLength - $titleLength, 2);
448
-            if (false === mb_detect_encoding($markup, null, true)) {
449
-                $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
450
-            } else {
451
-                $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
452
-            }
453
-        }
454
-
455
-        $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
456
-    }
457
-
458
-    /**
459
-     * Renders vertical column separator.
460
-     */
461
-    private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
462
-    {
463
-        $borders = $this->style->getBorderChars();
464
-
465
-        return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
466
-    }
467
-
468
-    /**
469
-     * Renders table row.
470
-     *
471
-     * Example:
472
-     *
473
-     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
474
-     */
475
-    private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
476
-    {
477
-        $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
478
-        $columns = $this->getRowColumns($row);
479
-        $last = \count($columns) - 1;
480
-        foreach ($columns as $i => $column) {
481
-            if ($firstCellFormat && 0 === $i) {
482
-                $rowContent .= $this->renderCell($row, $column, $firstCellFormat);
483
-            } else {
484
-                $rowContent .= $this->renderCell($row, $column, $cellFormat);
485
-            }
486
-            $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
487
-        }
488
-        $this->output->writeln($rowContent);
489
-    }
490
-
491
-    /**
492
-     * Renders table cell with padding.
493
-     */
494
-    private function renderCell(array $row, int $column, string $cellFormat): string
495
-    {
496
-        $cell = $row[$column] ?? '';
497
-        $width = $this->effectiveColumnWidths[$column];
498
-        if ($cell instanceof TableCell && $cell->getColspan() > 1) {
499
-            // add the width of the following columns(numbers of colspan).
500
-            foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
501
-                $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
502
-            }
503
-        }
504
-
505
-        // str_pad won't work properly with multi-byte strings, we need to fix the padding
506
-        if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
507
-            $width += \strlen($cell) - mb_strwidth($cell, $encoding);
508
-        }
509
-
510
-        $style = $this->getColumnStyle($column);
511
-
512
-        if ($cell instanceof TableSeparator) {
513
-            return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
514
-        }
515
-
516
-        $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
517
-        $content = sprintf($style->getCellRowContentFormat(), $cell);
518
-
519
-        $padType = $style->getPadType();
520
-        if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
521
-            $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
522
-            if ($isNotStyledByTag) {
523
-                $cellFormat = $cell->getStyle()->getCellFormat();
524
-                if (!\is_string($cellFormat)) {
525
-                    $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';');
526
-                    $cellFormat = '<'.$tag.'>%s</>';
527
-                }
528
-
529
-                if (strstr($content, '</>')) {
530
-                    $content = str_replace('</>', '', $content);
531
-                    $width -= 3;
532
-                }
533
-                if (strstr($content, '<fg=default;bg=default>')) {
534
-                    $content = str_replace('<fg=default;bg=default>', '', $content);
535
-                    $width -= \strlen('<fg=default;bg=default>');
536
-                }
537
-            }
538
-
539
-            $padType = $cell->getStyle()->getPadByAlign();
540
-        }
541
-
542
-        return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
543
-    }
544
-
545
-    /**
546
-     * Calculate number of columns for this table.
547
-     */
548
-    private function calculateNumberOfColumns(array $rows)
549
-    {
550
-        $columns = [0];
551
-        foreach ($rows as $row) {
552
-            if ($row instanceof TableSeparator) {
553
-                continue;
554
-            }
555
-
556
-            $columns[] = $this->getNumberOfColumns($row);
557
-        }
558
-
559
-        $this->numberOfColumns = max($columns);
560
-    }
561
-
562
-    private function buildTableRows(array $rows): TableRows
563
-    {
564
-        /** @var WrappableOutputFormatterInterface $formatter */
565
-        $formatter = $this->output->getFormatter();
566
-        $unmergedRows = [];
567
-        for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
568
-            $rows = $this->fillNextRows($rows, $rowKey);
569
-
570
-            // Remove any new line breaks and replace it with a new line
571
-            foreach ($rows[$rowKey] as $column => $cell) {
572
-                $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
573
-
574
-                if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) {
575
-                    $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
576
-                }
577
-                if (!strstr($cell ?? '', "\n")) {
578
-                    continue;
579
-                }
580
-                $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell)));
581
-                $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
582
-                $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
583
-                foreach ($lines as $lineKey => $line) {
584
-                    if ($colspan > 1) {
585
-                        $line = new TableCell($line, ['colspan' => $colspan]);
586
-                    }
587
-                    if (0 === $lineKey) {
588
-                        $rows[$rowKey][$column] = $line;
589
-                    } else {
590
-                        if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
591
-                            $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
592
-                        }
593
-                        $unmergedRows[$rowKey][$lineKey][$column] = $line;
594
-                    }
595
-                }
596
-            }
597
-        }
598
-
599
-        return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
600
-            foreach ($rows as $rowKey => $row) {
601
-                yield $row instanceof TableSeparator ? $row : $this->fillCells($row);
602
-
603
-                if (isset($unmergedRows[$rowKey])) {
604
-                    foreach ($unmergedRows[$rowKey] as $row) {
605
-                        yield $row instanceof TableSeparator ? $row : $this->fillCells($row);
606
-                    }
607
-                }
608
-            }
609
-        });
610
-    }
611
-
612
-    private function calculateRowCount(): int
613
-    {
614
-        $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
615
-
616
-        if ($this->headers) {
617
-            ++$numberOfRows; // Add row for header separator
618
-        }
619
-
620
-        if (\count($this->rows) > 0) {
621
-            ++$numberOfRows; // Add row for footer separator
622
-        }
623
-
624
-        return $numberOfRows;
625
-    }
626
-
627
-    /**
628
-     * fill rows that contains rowspan > 1.
629
-     *
630
-     * @throws InvalidArgumentException
631
-     */
632
-    private function fillNextRows(array $rows, int $line): array
633
-    {
634
-        $unmergedRows = [];
635
-        foreach ($rows[$line] as $column => $cell) {
636
-            if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
637
-                throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
638
-            }
639
-            if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
640
-                $nbLines = $cell->getRowspan() - 1;
641
-                $lines = [$cell];
642
-                if (strstr($cell, "\n")) {
643
-                    $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
644
-                    $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
645
-
646
-                    $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
647
-                    unset($lines[0]);
648
-                }
649
-
650
-                // create a two dimensional array (rowspan x colspan)
651
-                $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
652
-                foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
653
-                    $value = $lines[$unmergedRowKey - $line] ?? '';
654
-                    $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
655
-                    if ($nbLines === $unmergedRowKey - $line) {
656
-                        break;
657
-                    }
658
-                }
659
-            }
660
-        }
661
-
662
-        foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
663
-            // we need to know if $unmergedRow will be merged or inserted into $rows
664
-            if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
665
-                foreach ($unmergedRow as $cellKey => $cell) {
666
-                    // insert cell into row at cellKey position
667
-                    array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
668
-                }
669
-            } else {
670
-                $row = $this->copyRow($rows, $unmergedRowKey - 1);
671
-                foreach ($unmergedRow as $column => $cell) {
672
-                    if (!empty($cell)) {
673
-                        $row[$column] = $unmergedRow[$column];
674
-                    }
675
-                }
676
-                array_splice($rows, $unmergedRowKey, 0, [$row]);
677
-            }
678
-        }
679
-
680
-        return $rows;
681
-    }
682
-
683
-    /**
684
-     * fill cells for a row that contains colspan > 1.
685
-     */
686
-    private function fillCells(iterable $row)
687
-    {
688
-        $newRow = [];
689
-
690
-        foreach ($row as $column => $cell) {
691
-            $newRow[] = $cell;
692
-            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
693
-                foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
694
-                    // insert empty value at column position
695
-                    $newRow[] = '';
696
-                }
697
-            }
698
-        }
699
-
700
-        return $newRow ?: $row;
701
-    }
702
-
703
-    private function copyRow(array $rows, int $line): array
704
-    {
705
-        $row = $rows[$line];
706
-        foreach ($row as $cellKey => $cellValue) {
707
-            $row[$cellKey] = '';
708
-            if ($cellValue instanceof TableCell) {
709
-                $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
710
-            }
711
-        }
712
-
713
-        return $row;
714
-    }
715
-
716
-    /**
717
-     * Gets number of columns by row.
718
-     */
719
-    private function getNumberOfColumns(array $row): int
720
-    {
721
-        $columns = \count($row);
722
-        foreach ($row as $column) {
723
-            $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
724
-        }
725
-
726
-        return $columns;
727
-    }
728
-
729
-    /**
730
-     * Gets list of columns for the given row.
731
-     */
732
-    private function getRowColumns(array $row): array
733
-    {
734
-        $columns = range(0, $this->numberOfColumns - 1);
735
-        foreach ($row as $cellKey => $cell) {
736
-            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
737
-                // exclude grouped columns.
738
-                $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
739
-            }
740
-        }
741
-
742
-        return $columns;
743
-    }
744
-
745
-    /**
746
-     * Calculates columns widths.
747
-     */
748
-    private function calculateColumnsWidth(iterable $rows)
749
-    {
750
-        for ($column = 0; $column < $this->numberOfColumns; ++$column) {
751
-            $lengths = [];
752
-            foreach ($rows as $row) {
753
-                if ($row instanceof TableSeparator) {
754
-                    continue;
755
-                }
756
-
757
-                foreach ($row as $i => $cell) {
758
-                    if ($cell instanceof TableCell) {
759
-                        $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
760
-                        $textLength = Helper::width($textContent);
761
-                        if ($textLength > 0) {
762
-                            $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
763
-                            foreach ($contentColumns as $position => $content) {
764
-                                $row[$i + $position] = $content;
765
-                            }
766
-                        }
767
-                    }
768
-                }
769
-
770
-                $lengths[] = $this->getCellWidth($row, $column);
771
-            }
772
-
773
-            $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2;
774
-        }
775
-    }
776
-
777
-    private function getColumnSeparatorWidth(): int
778
-    {
779
-        return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
780
-    }
781
-
782
-    private function getCellWidth(array $row, int $column): int
783
-    {
784
-        $cellWidth = 0;
785
-
786
-        if (isset($row[$column])) {
787
-            $cell = $row[$column];
788
-            $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
789
-        }
790
-
791
-        $columnWidth = $this->columnWidths[$column] ?? 0;
792
-        $cellWidth = max($cellWidth, $columnWidth);
793
-
794
-        return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
795
-    }
796
-
797
-    /**
798
-     * Called after rendering to cleanup cache data.
799
-     */
800
-    private function cleanup()
801
-    {
802
-        $this->effectiveColumnWidths = [];
803
-        $this->numberOfColumns = null;
804
-    }
805
-
806
-    private static function initStyles(): array
807
-    {
808
-        $borderless = new TableStyle();
809
-        $borderless
810
-            ->setHorizontalBorderChars('=')
811
-            ->setVerticalBorderChars(' ')
812
-            ->setDefaultCrossingChar(' ')
813
-        ;
814
-
815
-        $compact = new TableStyle();
816
-        $compact
817
-            ->setHorizontalBorderChars('')
818
-            ->setVerticalBorderChars(' ')
819
-            ->setDefaultCrossingChar('')
820
-            ->setCellRowContentFormat('%s')
821
-        ;
822
-
823
-        $styleGuide = new TableStyle();
824
-        $styleGuide
825
-            ->setHorizontalBorderChars('-')
826
-            ->setVerticalBorderChars(' ')
827
-            ->setDefaultCrossingChar(' ')
828
-            ->setCellHeaderFormat('%s')
829
-        ;
830
-
831
-        $box = (new TableStyle())
832
-            ->setHorizontalBorderChars('─')
833
-            ->setVerticalBorderChars('│')
834
-            ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
835
-        ;
836
-
837
-        $boxDouble = (new TableStyle())
838
-            ->setHorizontalBorderChars('═', '─')
839
-            ->setVerticalBorderChars('║', '│')
840
-            ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
841
-        ;
842
-
843
-        return [
844
-            'default' => new TableStyle(),
845
-            'borderless' => $borderless,
846
-            'compact' => $compact,
847
-            'symfony-style-guide' => $styleGuide,
848
-            'box' => $box,
849
-            'box-double' => $boxDouble,
850
-        ];
851
-    }
852
-
853
-    private function resolveStyle($name): TableStyle
854
-    {
855
-        if ($name instanceof TableStyle) {
856
-            return $name;
857
-        }
858
-
859
-        if (isset(self::$styles[$name])) {
860
-            return self::$styles[$name];
861
-        }
862
-
863
-        throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
864
-    }
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
+
39
+	private $headerTitle;
40
+	private $footerTitle;
41
+
42
+	/**
43
+	 * Table headers.
44
+	 */
45
+	private $headers = [];
46
+
47
+	/**
48
+	 * Table rows.
49
+	 */
50
+	private $rows = [];
51
+	private $horizontal = false;
52
+
53
+	/**
54
+	 * Column widths cache.
55
+	 */
56
+	private $effectiveColumnWidths = [];
57
+
58
+	/**
59
+	 * Number of columns cache.
60
+	 *
61
+	 * @var int
62
+	 */
63
+	private $numberOfColumns;
64
+
65
+	/**
66
+	 * @var OutputInterface
67
+	 */
68
+	private $output;
69
+
70
+	/**
71
+	 * @var TableStyle
72
+	 */
73
+	private $style;
74
+
75
+	/**
76
+	 * @var array
77
+	 */
78
+	private $columnStyles = [];
79
+
80
+	/**
81
+	 * User set column widths.
82
+	 *
83
+	 * @var array
84
+	 */
85
+	private $columnWidths = [];
86
+	private $columnMaxWidths = [];
87
+
88
+	private static $styles;
89
+
90
+	private $rendered = false;
91
+
92
+	public function __construct(OutputInterface $output)
93
+	{
94
+		$this->output = $output;
95
+
96
+		if (!self::$styles) {
97
+			self::$styles = self::initStyles();
98
+		}
99
+
100
+		$this->setStyle('default');
101
+	}
102
+
103
+	/**
104
+	 * Sets a style definition.
105
+	 */
106
+	public static function setStyleDefinition(string $name, TableStyle $style)
107
+	{
108
+		if (!self::$styles) {
109
+			self::$styles = self::initStyles();
110
+		}
111
+
112
+		self::$styles[$name] = $style;
113
+	}
114
+
115
+	/**
116
+	 * Gets a style definition by name.
117
+	 *
118
+	 * @return TableStyle
119
+	 */
120
+	public static function getStyleDefinition(string $name)
121
+	{
122
+		if (!self::$styles) {
123
+			self::$styles = self::initStyles();
124
+		}
125
+
126
+		if (isset(self::$styles[$name])) {
127
+			return self::$styles[$name];
128
+		}
129
+
130
+		throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
131
+	}
132
+
133
+	/**
134
+	 * Sets table style.
135
+	 *
136
+	 * @param TableStyle|string $name The style name or a TableStyle instance
137
+	 *
138
+	 * @return $this
139
+	 */
140
+	public function setStyle($name)
141
+	{
142
+		$this->style = $this->resolveStyle($name);
143
+
144
+		return $this;
145
+	}
146
+
147
+	/**
148
+	 * Gets the current table style.
149
+	 *
150
+	 * @return TableStyle
151
+	 */
152
+	public function getStyle()
153
+	{
154
+		return $this->style;
155
+	}
156
+
157
+	/**
158
+	 * Sets table column style.
159
+	 *
160
+	 * @param TableStyle|string $name The style name or a TableStyle instance
161
+	 *
162
+	 * @return $this
163
+	 */
164
+	public function setColumnStyle(int $columnIndex, $name)
165
+	{
166
+		$this->columnStyles[$columnIndex] = $this->resolveStyle($name);
167
+
168
+		return $this;
169
+	}
170
+
171
+	/**
172
+	 * Gets the current style for a column.
173
+	 *
174
+	 * If style was not set, it returns the global table style.
175
+	 *
176
+	 * @return TableStyle
177
+	 */
178
+	public function getColumnStyle(int $columnIndex)
179
+	{
180
+		return $this->columnStyles[$columnIndex] ?? $this->getStyle();
181
+	}
182
+
183
+	/**
184
+	 * Sets the minimum width of a column.
185
+	 *
186
+	 * @return $this
187
+	 */
188
+	public function setColumnWidth(int $columnIndex, int $width)
189
+	{
190
+		$this->columnWidths[$columnIndex] = $width;
191
+
192
+		return $this;
193
+	}
194
+
195
+	/**
196
+	 * Sets the minimum width of all columns.
197
+	 *
198
+	 * @return $this
199
+	 */
200
+	public function setColumnWidths(array $widths)
201
+	{
202
+		$this->columnWidths = [];
203
+		foreach ($widths as $index => $width) {
204
+			$this->setColumnWidth($index, $width);
205
+		}
206
+
207
+		return $this;
208
+	}
209
+
210
+	/**
211
+	 * Sets the maximum width of a column.
212
+	 *
213
+	 * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
214
+	 * formatted strings are preserved.
215
+	 *
216
+	 * @return $this
217
+	 */
218
+	public function setColumnMaxWidth(int $columnIndex, int $width): self
219
+	{
220
+		if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
221
+			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())));
222
+		}
223
+
224
+		$this->columnMaxWidths[$columnIndex] = $width;
225
+
226
+		return $this;
227
+	}
228
+
229
+	public function setHeaders(array $headers)
230
+	{
231
+		$headers = array_values($headers);
232
+		if (!empty($headers) && !\is_array($headers[0])) {
233
+			$headers = [$headers];
234
+		}
235
+
236
+		$this->headers = $headers;
237
+
238
+		return $this;
239
+	}
240
+
241
+	public function setRows(array $rows)
242
+	{
243
+		$this->rows = [];
244
+
245
+		return $this->addRows($rows);
246
+	}
247
+
248
+	public function addRows(array $rows)
249
+	{
250
+		foreach ($rows as $row) {
251
+			$this->addRow($row);
252
+		}
253
+
254
+		return $this;
255
+	}
256
+
257
+	public function addRow($row)
258
+	{
259
+		if ($row instanceof TableSeparator) {
260
+			$this->rows[] = $row;
261
+
262
+			return $this;
263
+		}
264
+
265
+		if (!\is_array($row)) {
266
+			throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
267
+		}
268
+
269
+		$this->rows[] = array_values($row);
270
+
271
+		return $this;
272
+	}
273
+
274
+	/**
275
+	 * Adds a row to the table, and re-renders the table.
276
+	 */
277
+	public function appendRow($row): self
278
+	{
279
+		if (!$this->output instanceof ConsoleSectionOutput) {
280
+			throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
281
+		}
282
+
283
+		if ($this->rendered) {
284
+			$this->output->clear($this->calculateRowCount());
285
+		}
286
+
287
+		$this->addRow($row);
288
+		$this->render();
289
+
290
+		return $this;
291
+	}
292
+
293
+	public function setRow($column, array $row)
294
+	{
295
+		$this->rows[$column] = $row;
296
+
297
+		return $this;
298
+	}
299
+
300
+	public function setHeaderTitle(?string $title): self
301
+	{
302
+		$this->headerTitle = $title;
303
+
304
+		return $this;
305
+	}
306
+
307
+	public function setFooterTitle(?string $title): self
308
+	{
309
+		$this->footerTitle = $title;
310
+
311
+		return $this;
312
+	}
313
+
314
+	public function setHorizontal(bool $horizontal = true): self
315
+	{
316
+		$this->horizontal = $horizontal;
317
+
318
+		return $this;
319
+	}
320
+
321
+	/**
322
+	 * Renders table to output.
323
+	 *
324
+	 * Example:
325
+	 *
326
+	 *     +---------------+-----------------------+------------------+
327
+	 *     | ISBN          | Title                 | Author           |
328
+	 *     +---------------+-----------------------+------------------+
329
+	 *     | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
330
+	 *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
331
+	 *     | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
332
+	 *     +---------------+-----------------------+------------------+
333
+	 */
334
+	public function render()
335
+	{
336
+		$divider = new TableSeparator();
337
+		if ($this->horizontal) {
338
+			$rows = [];
339
+			foreach ($this->headers[0] ?? [] as $i => $header) {
340
+				$rows[$i] = [$header];
341
+				foreach ($this->rows as $row) {
342
+					if ($row instanceof TableSeparator) {
343
+						continue;
344
+					}
345
+					if (isset($row[$i])) {
346
+						$rows[$i][] = $row[$i];
347
+					} elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
348
+						// Noop, there is a "title"
349
+					} else {
350
+						$rows[$i][] = null;
351
+					}
352
+				}
353
+			}
354
+		} else {
355
+			$rows = array_merge($this->headers, [$divider], $this->rows);
356
+		}
357
+
358
+		$this->calculateNumberOfColumns($rows);
359
+
360
+		$rows = $this->buildTableRows($rows);
361
+		$this->calculateColumnsWidth($rows);
362
+
363
+		$isHeader = !$this->horizontal;
364
+		$isFirstRow = $this->horizontal;
365
+		$hasTitle = (bool) $this->headerTitle;
366
+		foreach ($rows as $row) {
367
+			if ($divider === $row) {
368
+				$isHeader = false;
369
+				$isFirstRow = true;
370
+
371
+				continue;
372
+			}
373
+			if ($row instanceof TableSeparator) {
374
+				$this->renderRowSeparator();
375
+
376
+				continue;
377
+			}
378
+			if (!$row) {
379
+				continue;
380
+			}
381
+
382
+			if ($isHeader || $isFirstRow) {
383
+				$this->renderRowSeparator(
384
+					$isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
385
+					$hasTitle ? $this->headerTitle : null,
386
+					$hasTitle ? $this->style->getHeaderTitleFormat() : null
387
+				);
388
+				$isFirstRow = false;
389
+				$hasTitle = false;
390
+			}
391
+			if ($this->horizontal) {
392
+				$this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
393
+			} else {
394
+				$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
395
+			}
396
+		}
397
+		$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
398
+
399
+		$this->cleanup();
400
+		$this->rendered = true;
401
+	}
402
+
403
+	/**
404
+	 * Renders horizontal header separator.
405
+	 *
406
+	 * Example:
407
+	 *
408
+	 *     +-----+-----------+-------+
409
+	 */
410
+	private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
411
+	{
412
+		if (0 === $count = $this->numberOfColumns) {
413
+			return;
414
+		}
415
+
416
+		$borders = $this->style->getBorderChars();
417
+		if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
418
+			return;
419
+		}
420
+
421
+		$crossings = $this->style->getCrossingChars();
422
+		if (self::SEPARATOR_MID === $type) {
423
+			[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
424
+		} elseif (self::SEPARATOR_TOP === $type) {
425
+			[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
426
+		} elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
427
+			[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
428
+		} else {
429
+			[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
430
+		}
431
+
432
+		$markup = $leftChar;
433
+		for ($column = 0; $column < $count; ++$column) {
434
+			$markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
435
+			$markup .= $column === $count - 1 ? $rightChar : $midChar;
436
+		}
437
+
438
+		if (null !== $title) {
439
+			$titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)));
440
+			$markupLength = Helper::width($markup);
441
+			if ($titleLength > $limit = $markupLength - 4) {
442
+				$titleLength = $limit;
443
+				$formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, '')));
444
+				$formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
445
+			}
446
+
447
+			$titleStart = intdiv($markupLength - $titleLength, 2);
448
+			if (false === mb_detect_encoding($markup, null, true)) {
449
+				$markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
450
+			} else {
451
+				$markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
452
+			}
453
+		}
454
+
455
+		$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
456
+	}
457
+
458
+	/**
459
+	 * Renders vertical column separator.
460
+	 */
461
+	private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
462
+	{
463
+		$borders = $this->style->getBorderChars();
464
+
465
+		return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
466
+	}
467
+
468
+	/**
469
+	 * Renders table row.
470
+	 *
471
+	 * Example:
472
+	 *
473
+	 *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
474
+	 */
475
+	private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
476
+	{
477
+		$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
478
+		$columns = $this->getRowColumns($row);
479
+		$last = \count($columns) - 1;
480
+		foreach ($columns as $i => $column) {
481
+			if ($firstCellFormat && 0 === $i) {
482
+				$rowContent .= $this->renderCell($row, $column, $firstCellFormat);
483
+			} else {
484
+				$rowContent .= $this->renderCell($row, $column, $cellFormat);
485
+			}
486
+			$rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
487
+		}
488
+		$this->output->writeln($rowContent);
489
+	}
490
+
491
+	/**
492
+	 * Renders table cell with padding.
493
+	 */
494
+	private function renderCell(array $row, int $column, string $cellFormat): string
495
+	{
496
+		$cell = $row[$column] ?? '';
497
+		$width = $this->effectiveColumnWidths[$column];
498
+		if ($cell instanceof TableCell && $cell->getColspan() > 1) {
499
+			// add the width of the following columns(numbers of colspan).
500
+			foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
501
+				$width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
502
+			}
503
+		}
504
+
505
+		// str_pad won't work properly with multi-byte strings, we need to fix the padding
506
+		if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
507
+			$width += \strlen($cell) - mb_strwidth($cell, $encoding);
508
+		}
509
+
510
+		$style = $this->getColumnStyle($column);
511
+
512
+		if ($cell instanceof TableSeparator) {
513
+			return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
514
+		}
515
+
516
+		$width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
517
+		$content = sprintf($style->getCellRowContentFormat(), $cell);
518
+
519
+		$padType = $style->getPadType();
520
+		if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
521
+			$isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
522
+			if ($isNotStyledByTag) {
523
+				$cellFormat = $cell->getStyle()->getCellFormat();
524
+				if (!\is_string($cellFormat)) {
525
+					$tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';');
526
+					$cellFormat = '<'.$tag.'>%s</>';
527
+				}
528
+
529
+				if (strstr($content, '</>')) {
530
+					$content = str_replace('</>', '', $content);
531
+					$width -= 3;
532
+				}
533
+				if (strstr($content, '<fg=default;bg=default>')) {
534
+					$content = str_replace('<fg=default;bg=default>', '', $content);
535
+					$width -= \strlen('<fg=default;bg=default>');
536
+				}
537
+			}
538
+
539
+			$padType = $cell->getStyle()->getPadByAlign();
540
+		}
541
+
542
+		return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
543
+	}
544
+
545
+	/**
546
+	 * Calculate number of columns for this table.
547
+	 */
548
+	private function calculateNumberOfColumns(array $rows)
549
+	{
550
+		$columns = [0];
551
+		foreach ($rows as $row) {
552
+			if ($row instanceof TableSeparator) {
553
+				continue;
554
+			}
555
+
556
+			$columns[] = $this->getNumberOfColumns($row);
557
+		}
558
+
559
+		$this->numberOfColumns = max($columns);
560
+	}
561
+
562
+	private function buildTableRows(array $rows): TableRows
563
+	{
564
+		/** @var WrappableOutputFormatterInterface $formatter */
565
+		$formatter = $this->output->getFormatter();
566
+		$unmergedRows = [];
567
+		for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
568
+			$rows = $this->fillNextRows($rows, $rowKey);
569
+
570
+			// Remove any new line breaks and replace it with a new line
571
+			foreach ($rows[$rowKey] as $column => $cell) {
572
+				$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
573
+
574
+				if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) {
575
+					$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
576
+				}
577
+				if (!strstr($cell ?? '', "\n")) {
578
+					continue;
579
+				}
580
+				$escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell)));
581
+				$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
582
+				$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
583
+				foreach ($lines as $lineKey => $line) {
584
+					if ($colspan > 1) {
585
+						$line = new TableCell($line, ['colspan' => $colspan]);
586
+					}
587
+					if (0 === $lineKey) {
588
+						$rows[$rowKey][$column] = $line;
589
+					} else {
590
+						if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
591
+							$unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
592
+						}
593
+						$unmergedRows[$rowKey][$lineKey][$column] = $line;
594
+					}
595
+				}
596
+			}
597
+		}
598
+
599
+		return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
600
+			foreach ($rows as $rowKey => $row) {
601
+				yield $row instanceof TableSeparator ? $row : $this->fillCells($row);
602
+
603
+				if (isset($unmergedRows[$rowKey])) {
604
+					foreach ($unmergedRows[$rowKey] as $row) {
605
+						yield $row instanceof TableSeparator ? $row : $this->fillCells($row);
606
+					}
607
+				}
608
+			}
609
+		});
610
+	}
611
+
612
+	private function calculateRowCount(): int
613
+	{
614
+		$numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
615
+
616
+		if ($this->headers) {
617
+			++$numberOfRows; // Add row for header separator
618
+		}
619
+
620
+		if (\count($this->rows) > 0) {
621
+			++$numberOfRows; // Add row for footer separator
622
+		}
623
+
624
+		return $numberOfRows;
625
+	}
626
+
627
+	/**
628
+	 * fill rows that contains rowspan > 1.
629
+	 *
630
+	 * @throws InvalidArgumentException
631
+	 */
632
+	private function fillNextRows(array $rows, int $line): array
633
+	{
634
+		$unmergedRows = [];
635
+		foreach ($rows[$line] as $column => $cell) {
636
+			if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
637
+				throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
638
+			}
639
+			if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
640
+				$nbLines = $cell->getRowspan() - 1;
641
+				$lines = [$cell];
642
+				if (strstr($cell, "\n")) {
643
+					$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
644
+					$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
645
+
646
+					$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
647
+					unset($lines[0]);
648
+				}
649
+
650
+				// create a two dimensional array (rowspan x colspan)
651
+				$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
652
+				foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
653
+					$value = $lines[$unmergedRowKey - $line] ?? '';
654
+					$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
655
+					if ($nbLines === $unmergedRowKey - $line) {
656
+						break;
657
+					}
658
+				}
659
+			}
660
+		}
661
+
662
+		foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
663
+			// we need to know if $unmergedRow will be merged or inserted into $rows
664
+			if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
665
+				foreach ($unmergedRow as $cellKey => $cell) {
666
+					// insert cell into row at cellKey position
667
+					array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
668
+				}
669
+			} else {
670
+				$row = $this->copyRow($rows, $unmergedRowKey - 1);
671
+				foreach ($unmergedRow as $column => $cell) {
672
+					if (!empty($cell)) {
673
+						$row[$column] = $unmergedRow[$column];
674
+					}
675
+				}
676
+				array_splice($rows, $unmergedRowKey, 0, [$row]);
677
+			}
678
+		}
679
+
680
+		return $rows;
681
+	}
682
+
683
+	/**
684
+	 * fill cells for a row that contains colspan > 1.
685
+	 */
686
+	private function fillCells(iterable $row)
687
+	{
688
+		$newRow = [];
689
+
690
+		foreach ($row as $column => $cell) {
691
+			$newRow[] = $cell;
692
+			if ($cell instanceof TableCell && $cell->getColspan() > 1) {
693
+				foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
694
+					// insert empty value at column position
695
+					$newRow[] = '';
696
+				}
697
+			}
698
+		}
699
+
700
+		return $newRow ?: $row;
701
+	}
702
+
703
+	private function copyRow(array $rows, int $line): array
704
+	{
705
+		$row = $rows[$line];
706
+		foreach ($row as $cellKey => $cellValue) {
707
+			$row[$cellKey] = '';
708
+			if ($cellValue instanceof TableCell) {
709
+				$row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
710
+			}
711
+		}
712
+
713
+		return $row;
714
+	}
715
+
716
+	/**
717
+	 * Gets number of columns by row.
718
+	 */
719
+	private function getNumberOfColumns(array $row): int
720
+	{
721
+		$columns = \count($row);
722
+		foreach ($row as $column) {
723
+			$columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
724
+		}
725
+
726
+		return $columns;
727
+	}
728
+
729
+	/**
730
+	 * Gets list of columns for the given row.
731
+	 */
732
+	private function getRowColumns(array $row): array
733
+	{
734
+		$columns = range(0, $this->numberOfColumns - 1);
735
+		foreach ($row as $cellKey => $cell) {
736
+			if ($cell instanceof TableCell && $cell->getColspan() > 1) {
737
+				// exclude grouped columns.
738
+				$columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
739
+			}
740
+		}
741
+
742
+		return $columns;
743
+	}
744
+
745
+	/**
746
+	 * Calculates columns widths.
747
+	 */
748
+	private function calculateColumnsWidth(iterable $rows)
749
+	{
750
+		for ($column = 0; $column < $this->numberOfColumns; ++$column) {
751
+			$lengths = [];
752
+			foreach ($rows as $row) {
753
+				if ($row instanceof TableSeparator) {
754
+					continue;
755
+				}
756
+
757
+				foreach ($row as $i => $cell) {
758
+					if ($cell instanceof TableCell) {
759
+						$textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
760
+						$textLength = Helper::width($textContent);
761
+						if ($textLength > 0) {
762
+							$contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
763
+							foreach ($contentColumns as $position => $content) {
764
+								$row[$i + $position] = $content;
765
+							}
766
+						}
767
+					}
768
+				}
769
+
770
+				$lengths[] = $this->getCellWidth($row, $column);
771
+			}
772
+
773
+			$this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2;
774
+		}
775
+	}
776
+
777
+	private function getColumnSeparatorWidth(): int
778
+	{
779
+		return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
780
+	}
781
+
782
+	private function getCellWidth(array $row, int $column): int
783
+	{
784
+		$cellWidth = 0;
785
+
786
+		if (isset($row[$column])) {
787
+			$cell = $row[$column];
788
+			$cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
789
+		}
790
+
791
+		$columnWidth = $this->columnWidths[$column] ?? 0;
792
+		$cellWidth = max($cellWidth, $columnWidth);
793
+
794
+		return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
795
+	}
796
+
797
+	/**
798
+	 * Called after rendering to cleanup cache data.
799
+	 */
800
+	private function cleanup()
801
+	{
802
+		$this->effectiveColumnWidths = [];
803
+		$this->numberOfColumns = null;
804
+	}
805
+
806
+	private static function initStyles(): array
807
+	{
808
+		$borderless = new TableStyle();
809
+		$borderless
810
+			->setHorizontalBorderChars('=')
811
+			->setVerticalBorderChars(' ')
812
+			->setDefaultCrossingChar(' ')
813
+		;
814
+
815
+		$compact = new TableStyle();
816
+		$compact
817
+			->setHorizontalBorderChars('')
818
+			->setVerticalBorderChars(' ')
819
+			->setDefaultCrossingChar('')
820
+			->setCellRowContentFormat('%s')
821
+		;
822
+
823
+		$styleGuide = new TableStyle();
824
+		$styleGuide
825
+			->setHorizontalBorderChars('-')
826
+			->setVerticalBorderChars(' ')
827
+			->setDefaultCrossingChar(' ')
828
+			->setCellHeaderFormat('%s')
829
+		;
830
+
831
+		$box = (new TableStyle())
832
+			->setHorizontalBorderChars('─')
833
+			->setVerticalBorderChars('│')
834
+			->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
835
+		;
836
+
837
+		$boxDouble = (new TableStyle())
838
+			->setHorizontalBorderChars('═', '─')
839
+			->setVerticalBorderChars('║', '│')
840
+			->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
841
+		;
842
+
843
+		return [
844
+			'default' => new TableStyle(),
845
+			'borderless' => $borderless,
846
+			'compact' => $compact,
847
+			'symfony-style-guide' => $styleGuide,
848
+			'box' => $box,
849
+			'box-double' => $boxDouble,
850
+		];
851
+	}
852
+
853
+	private function resolveStyle($name): TableStyle
854
+	{
855
+		if ($name instanceof TableStyle) {
856
+			return $name;
857
+		}
858
+
859
+		if (isset(self::$styles[$name])) {
860
+			return self::$styles[$name];
861
+		}
862
+
863
+		throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
864
+	}
865 865
 }
Please login to merge, or discard this patch.
vendor/symfony/console/Helper/TableSeparator.php 1 patch
Indentation   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -18,8 +18,8 @@
 block discarded – undo
18 18
  */
19 19
 class TableSeparator extends TableCell
20 20
 {
21
-    public function __construct(array $options = [])
22
-    {
23
-        parent::__construct('', $options);
24
-    }
21
+	public function __construct(array $options = [])
22
+	{
23
+		parent::__construct('', $options);
24
+	}
25 25
 }
Please login to merge, or discard this patch.
vendor/symfony/console/Helper/InputAwareHelper.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -21,13 +21,13 @@
 block discarded – undo
21 21
  */
22 22
 abstract class InputAwareHelper extends Helper implements InputAwareInterface
23 23
 {
24
-    protected $input;
24
+	protected $input;
25 25
 
26
-    /**
27
-     * {@inheritdoc}
28
-     */
29
-    public function setInput(InputInterface $input)
30
-    {
31
-        $this->input = $input;
32
-    }
26
+	/**
27
+	 * {@inheritdoc}
28
+	 */
29
+	public function setInput(InputInterface $input)
30
+	{
31
+		$this->input = $input;
32
+	}
33 33
 }
Please login to merge, or discard this patch.