mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-18 06:48:50 +02:00
N°8834 - Add compatibility with PHP 8.4 (#819)
* N°8834 - Add compatibility with PHP 8.4 * Rollback of scssphp/scssphp version upgrade due to compilation error
This commit is contained in:
@@ -31,7 +31,7 @@ class DebugFormatterHelper extends Helper
|
||||
{
|
||||
$this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)];
|
||||
|
||||
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
|
||||
return \sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,22 +47,22 @@ class DebugFormatterHelper extends Helper
|
||||
unset($this->started[$id]['out']);
|
||||
}
|
||||
if (!isset($this->started[$id]['err'])) {
|
||||
$message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
|
||||
$message .= \sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
|
||||
$this->started[$id]['err'] = true;
|
||||
}
|
||||
|
||||
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
|
||||
$message .= str_replace("\n", \sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
|
||||
} else {
|
||||
if (isset($this->started[$id]['err'])) {
|
||||
$message .= "\n";
|
||||
unset($this->started[$id]['err']);
|
||||
}
|
||||
if (!isset($this->started[$id]['out'])) {
|
||||
$message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
|
||||
$message .= \sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
|
||||
$this->started[$id]['out'] = true;
|
||||
}
|
||||
|
||||
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
|
||||
$message .= str_replace("\n", \sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
|
||||
}
|
||||
|
||||
return $message;
|
||||
@@ -76,10 +76,10 @@ class DebugFormatterHelper extends Helper
|
||||
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
|
||||
|
||||
if ($successful) {
|
||||
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
|
||||
return \sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
|
||||
}
|
||||
|
||||
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
|
||||
$message = \sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
|
||||
|
||||
unset($this->started[$id]['out'], $this->started[$id]['err']);
|
||||
|
||||
@@ -88,7 +88,7 @@ class DebugFormatterHelper extends Helper
|
||||
|
||||
private function getBorder(string $id): string
|
||||
{
|
||||
return sprintf('<bg=%s> </>', self::COLORS[$this->started[$id]['border']]);
|
||||
return \sprintf('<bg=%s> </>', self::COLORS[$this->started[$id]['border']]);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
|
||||
@@ -62,7 +62,7 @@ class DescriptorHelper extends Helper
|
||||
], $options);
|
||||
|
||||
if (!isset($this->descriptors[$options['format']])) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
|
||||
throw new InvalidArgumentException(\sprintf('Unsupported format "%s".', $options['format']));
|
||||
}
|
||||
|
||||
$descriptor = $this->descriptors[$options['format']];
|
||||
|
||||
@@ -26,7 +26,7 @@ final class Dumper
|
||||
private ?ClonerInterface $cloner;
|
||||
private \Closure $handler;
|
||||
|
||||
public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
|
||||
public function __construct(OutputInterface $output, ?CliDumper $dumper = null, ?ClonerInterface $cloner = null)
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->dumper = $dumper;
|
||||
|
||||
@@ -25,7 +25,7 @@ class FormatterHelper extends Helper
|
||||
*/
|
||||
public function formatSection(string $section, string $message, string $style = 'info'): string
|
||||
{
|
||||
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
|
||||
return \sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ class FormatterHelper extends Helper
|
||||
$lines = [];
|
||||
foreach ($messages as $message) {
|
||||
$message = OutputFormatter::escape($message);
|
||||
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
|
||||
$lines[] = \sprintf($large ? ' %s ' : ' %s ', $message);
|
||||
$len = max(self::width($message) + ($large ? 4 : 2), $len);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class FormatterHelper extends Helper
|
||||
}
|
||||
|
||||
for ($i = 0; isset($messages[$i]); ++$i) {
|
||||
$messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
|
||||
$messages[$i] = \sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
|
||||
}
|
||||
|
||||
return implode("\n", $messages);
|
||||
|
||||
@@ -26,7 +26,7 @@ abstract class Helper implements HelperInterface
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setHelperSet(HelperSet $helperSet = null)
|
||||
public function setHelperSet(?HelperSet $helperSet = null)
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
@@ -48,7 +48,9 @@ abstract class Helper implements HelperInterface
|
||||
$string ??= '';
|
||||
|
||||
if (preg_match('//u', $string)) {
|
||||
return (new UnicodeString($string))->width(false);
|
||||
$string = preg_replace('/[\p{Cc}\x7F]++/u', '', $string, -1, $count);
|
||||
|
||||
return (new UnicodeString($string))->width(false) + $count;
|
||||
}
|
||||
|
||||
if (false === $encoding = mb_detect_encoding($string, null, true)) {
|
||||
@@ -80,10 +82,14 @@ abstract class Helper implements HelperInterface
|
||||
/**
|
||||
* Returns the subset of a string, using mb_substr if it is available.
|
||||
*/
|
||||
public static function substr(?string $string, int $from, int $length = null): string
|
||||
public static function substr(?string $string, int $from, ?int $length = null): string
|
||||
{
|
||||
$string ??= '';
|
||||
|
||||
if (preg_match('//u', $string)) {
|
||||
return (new UnicodeString($string))->slice($from, $length);
|
||||
}
|
||||
|
||||
if (false === $encoding = mb_detect_encoding($string, null, true)) {
|
||||
return substr($string, $from, $length);
|
||||
}
|
||||
@@ -140,18 +146,18 @@ abstract class Helper implements HelperInterface
|
||||
public static function formatMemory(int $memory)
|
||||
{
|
||||
if ($memory >= 1024 * 1024 * 1024) {
|
||||
return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
|
||||
return \sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
|
||||
}
|
||||
|
||||
if ($memory >= 1024 * 1024) {
|
||||
return sprintf('%.1f MiB', $memory / 1024 / 1024);
|
||||
return \sprintf('%.1f MiB', $memory / 1024 / 1024);
|
||||
}
|
||||
|
||||
if ($memory >= 1024) {
|
||||
return sprintf('%d KiB', $memory / 1024);
|
||||
return \sprintf('%d KiB', $memory / 1024);
|
||||
}
|
||||
|
||||
return sprintf('%d B', $memory);
|
||||
return \sprintf('%d B', $memory);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ class HelperSet implements \IteratorAggregate
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set(HelperInterface $helper, string $alias = null)
|
||||
public function set(HelperInterface $helper, ?string $alias = null)
|
||||
{
|
||||
$this->helpers[$helper->getName()] = $helper;
|
||||
if (null !== $alias) {
|
||||
@@ -64,7 +64,7 @@ class HelperSet implements \IteratorAggregate
|
||||
public function get(string $name): HelperInterface
|
||||
{
|
||||
if (!$this->has($name)) {
|
||||
throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
|
||||
throw new InvalidArgumentException(\sprintf('The helper "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
return $this->helpers[$name];
|
||||
|
||||
@@ -49,7 +49,7 @@ final class OutputWrapper
|
||||
private const URL_PATTERN = 'https?://\S+';
|
||||
|
||||
public function __construct(
|
||||
private bool $allowCutUrls = false
|
||||
private bool $allowCutUrls = false,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ final class OutputWrapper
|
||||
return $text;
|
||||
}
|
||||
|
||||
$tagPattern = sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
|
||||
$tagPattern = \sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
|
||||
$limitPattern = "{1,$width}";
|
||||
$patternBlocks = [$tagPattern];
|
||||
if (!$this->allowCutUrls) {
|
||||
@@ -68,7 +68,7 @@ final class OutputWrapper
|
||||
$patternBlocks[] = '.';
|
||||
$blocks = implode('|', $patternBlocks);
|
||||
$rowPattern = "(?:$blocks)$limitPattern";
|
||||
$pattern = sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
|
||||
$pattern = \sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
|
||||
$output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);
|
||||
|
||||
return str_replace(' '.$break, $break, $output);
|
||||
|
||||
@@ -32,7 +32,7 @@ class ProcessHelper extends Helper
|
||||
* @param callable|null $callback A PHP callback to run whenever there is some
|
||||
* output available on STDOUT or STDERR
|
||||
*/
|
||||
public function run(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
|
||||
public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
|
||||
{
|
||||
if (!class_exists(Process::class)) {
|
||||
throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
|
||||
@@ -55,7 +55,7 @@ class ProcessHelper extends Helper
|
||||
$process = $cmd[0];
|
||||
unset($cmd[0]);
|
||||
} else {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
|
||||
}
|
||||
|
||||
if ($verbosity <= $output->getVerbosity()) {
|
||||
@@ -69,12 +69,12 @@ class ProcessHelper extends Helper
|
||||
$process->run($callback, $cmd);
|
||||
|
||||
if ($verbosity <= $output->getVerbosity()) {
|
||||
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
|
||||
$message = $process->isSuccessful() ? 'Command ran successfully' : \sprintf('%s Command did not run successfully', $process->getExitCode());
|
||||
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful() && null !== $error) {
|
||||
$output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
|
||||
$output->writeln(\sprintf('<error>%s</error>', $this->escapeString($error)));
|
||||
}
|
||||
|
||||
return $process;
|
||||
@@ -94,7 +94,7 @@ class ProcessHelper extends Helper
|
||||
*
|
||||
* @see run()
|
||||
*/
|
||||
public function mustRun(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null): Process
|
||||
public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process
|
||||
{
|
||||
$process = $this->run($output, $cmd, $error, $callback);
|
||||
|
||||
@@ -108,7 +108,7 @@ class ProcessHelper extends Helper
|
||||
/**
|
||||
* Wraps a Process callback to add debugging output.
|
||||
*/
|
||||
public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable
|
||||
public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable
|
||||
{
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
|
||||
@@ -183,9 +183,9 @@ final class ProgressBar
|
||||
$this->messages[$name] = $message;
|
||||
}
|
||||
|
||||
public function getMessage(string $name = 'message'): string
|
||||
public function getMessage(string $name = 'message'): ?string
|
||||
{
|
||||
return $this->messages[$name];
|
||||
return $this->messages[$name] ?? null;
|
||||
}
|
||||
|
||||
public function getStartTime(): int
|
||||
@@ -229,7 +229,7 @@ final class ProgressBar
|
||||
|
||||
public function getRemaining(): float
|
||||
{
|
||||
if (!$this->step) {
|
||||
if (0 === $this->step || $this->step === $this->startingStep) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ final class ProgressBar
|
||||
*
|
||||
* @return iterable<TKey, TValue>
|
||||
*/
|
||||
public function iterate(iterable $iterable, int $max = null): iterable
|
||||
public function iterate(iterable $iterable, ?int $max = null): iterable
|
||||
{
|
||||
$this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));
|
||||
|
||||
@@ -332,7 +332,7 @@ final class ProgressBar
|
||||
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
|
||||
* @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar)
|
||||
*/
|
||||
public function start(int $max = null, int $startAt = 0): void
|
||||
public function start(?int $max = null, int $startAt = 0): void
|
||||
{
|
||||
$this->startTime = time();
|
||||
$this->step = $startAt;
|
||||
@@ -486,12 +486,21 @@ final class ProgressBar
|
||||
if ($this->output instanceof ConsoleSectionOutput) {
|
||||
$messageLines = explode("\n", $this->previousMessage);
|
||||
$lineCount = \count($messageLines);
|
||||
|
||||
$lastLineWithoutDecoration = Helper::removeDecoration($this->output->getFormatter(), end($messageLines) ?? '');
|
||||
|
||||
// When the last previous line is empty (without formatting) it is already cleared by the section output, so we don't need to clear it again
|
||||
if ('' === $lastLineWithoutDecoration) {
|
||||
--$lineCount;
|
||||
}
|
||||
|
||||
foreach ($messageLines as $messageLine) {
|
||||
$messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine));
|
||||
if ($messageLineLength > $this->terminal->getWidth()) {
|
||||
$lineCount += floor($messageLineLength / $this->terminal->getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->clear($lineCount);
|
||||
} else {
|
||||
$lineCount = substr_count($this->previousMessage, "\n");
|
||||
@@ -594,7 +603,7 @@ final class ProgressBar
|
||||
}
|
||||
|
||||
if (isset($matches[2])) {
|
||||
$text = sprintf('%'.$matches[2], $text);
|
||||
$text = \sprintf('%'.$matches[2], $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
|
||||
@@ -50,7 +50,7 @@ class ProgressIndicator
|
||||
* @param int $indicatorChangeInterval Change interval in milliseconds
|
||||
* @param array|null $indicatorValues Animated indicator characters
|
||||
*/
|
||||
public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
|
||||
public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ class QuestionHelper extends Helper
|
||||
foreach ($choices as $key => $value) {
|
||||
$padding = str_repeat(' ', $maxWidth - self::width($key));
|
||||
|
||||
$messages[] = sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value);
|
||||
$messages[] = \sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value);
|
||||
}
|
||||
|
||||
return $messages;
|
||||
@@ -258,11 +258,7 @@ class QuestionHelper extends Helper
|
||||
$ofs = -1;
|
||||
$matches = $autocomplete($ret);
|
||||
$numMatches = \count($matches);
|
||||
|
||||
$sttyMode = shell_exec('stty -g');
|
||||
$isStdin = 'php://stdin' === (stream_get_meta_data($inputStream)['uri'] ?? null);
|
||||
$r = [$inputStream];
|
||||
$w = [];
|
||||
$inputHelper = new TerminalInputHelper($inputStream);
|
||||
|
||||
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
|
||||
shell_exec('stty -icanon -echo');
|
||||
@@ -272,15 +268,13 @@ class QuestionHelper extends Helper
|
||||
|
||||
// Read a keypress
|
||||
while (!feof($inputStream)) {
|
||||
while ($isStdin && 0 === @stream_select($r, $w, $w, 0, 100)) {
|
||||
// Give signal handlers a chance to run
|
||||
$r = [$inputStream];
|
||||
}
|
||||
$inputHelper->waitForInput();
|
||||
$c = fread($inputStream, 1);
|
||||
|
||||
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
|
||||
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
|
||||
shell_exec('stty '.$sttyMode);
|
||||
// Restore the terminal so it behaves normally again
|
||||
$inputHelper->finish();
|
||||
throw new MissingInputException('Aborted.');
|
||||
} elseif ("\177" === $c) { // Backspace Character
|
||||
if (0 === $numMatches && 0 !== $i) {
|
||||
@@ -317,12 +311,12 @@ class QuestionHelper extends Helper
|
||||
$ofs += ('A' === $c[2]) ? -1 : 1;
|
||||
$ofs = ($numMatches + $ofs) % $numMatches;
|
||||
}
|
||||
} elseif (\ord($c) < 32) {
|
||||
} elseif ('' === $c || \ord($c) < 32) {
|
||||
if ("\t" === $c || "\n" === $c) {
|
||||
if ($numMatches > 0 && -1 !== $ofs) {
|
||||
$ret = (string) $matches[$ofs];
|
||||
// Echo out remaining chars for current match
|
||||
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
|
||||
$remainingCharacters = substr($ret, \strlen($this->mostRecentlyEnteredValue($fullChoice)));
|
||||
$output->write($remainingCharacters);
|
||||
$fullChoice .= $remainingCharacters;
|
||||
$i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);
|
||||
@@ -376,14 +370,14 @@ class QuestionHelper extends Helper
|
||||
if ($numMatches > 0 && -1 !== $ofs) {
|
||||
$cursor->savePosition();
|
||||
// Write highlighted text, complete the partially entered response
|
||||
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
|
||||
$charactersEntered = \strlen($this->mostRecentlyEnteredValue($fullChoice));
|
||||
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
|
||||
$cursor->restorePosition();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset stty so it behaves normally again
|
||||
shell_exec('stty '.$sttyMode);
|
||||
// Restore the terminal so it behaves normally again
|
||||
$inputHelper->finish();
|
||||
|
||||
return $fullChoice;
|
||||
}
|
||||
@@ -434,27 +428,25 @@ class QuestionHelper extends Helper
|
||||
return $value;
|
||||
}
|
||||
|
||||
$inputHelper = null;
|
||||
|
||||
if (self::$stty && Terminal::hasSttyAvailable()) {
|
||||
$sttyMode = shell_exec('stty -g');
|
||||
$inputHelper = new TerminalInputHelper($inputStream);
|
||||
shell_exec('stty -echo');
|
||||
} elseif ($this->isInteractiveInput($inputStream)) {
|
||||
throw new RuntimeException('Unable to hide the response.');
|
||||
}
|
||||
|
||||
$value = fgets($inputStream, 4096);
|
||||
$value = $this->doReadInput($inputStream, helper: $inputHelper);
|
||||
|
||||
if (4095 === \strlen($value)) {
|
||||
$errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
|
||||
$errOutput->warning('The value was possibly truncated by your shell or terminal emulator');
|
||||
}
|
||||
|
||||
if (self::$stty && Terminal::hasSttyAvailable()) {
|
||||
shell_exec('stty '.$sttyMode);
|
||||
}
|
||||
// Restore the terminal so it behaves normally again
|
||||
$inputHelper?->finish();
|
||||
|
||||
if (false === $value) {
|
||||
throw new MissingInputException('Aborted.');
|
||||
}
|
||||
if ($trimmable) {
|
||||
$value = trim($value);
|
||||
}
|
||||
@@ -501,19 +493,7 @@ class QuestionHelper extends Helper
|
||||
return self::$stdinIsInteractive;
|
||||
}
|
||||
|
||||
if (\function_exists('stream_isatty')) {
|
||||
return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
|
||||
}
|
||||
|
||||
if (\function_exists('posix_isatty')) {
|
||||
return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r'));
|
||||
}
|
||||
|
||||
if (!\function_exists('shell_exec')) {
|
||||
return self::$stdinIsInteractive = true;
|
||||
}
|
||||
|
||||
return self::$stdinIsInteractive = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
|
||||
return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -526,7 +506,7 @@ class QuestionHelper extends Helper
|
||||
{
|
||||
if (!$question->isMultiline()) {
|
||||
$cp = $this->setIOCodepage();
|
||||
$ret = fgets($inputStream, 4096);
|
||||
$ret = $this->doReadInput($inputStream);
|
||||
|
||||
return $this->resetIOCodepage($cp, $ret);
|
||||
}
|
||||
@@ -536,13 +516,11 @@ class QuestionHelper extends Helper
|
||||
return false;
|
||||
}
|
||||
|
||||
$ret = '';
|
||||
$cp = $this->setIOCodepage();
|
||||
while (false !== ($char = fgetc($multiLineStreamReader))) {
|
||||
if (\PHP_EOL === "{$ret}{$char}") {
|
||||
break;
|
||||
}
|
||||
$ret .= $char;
|
||||
$ret = $this->doReadInput($multiLineStreamReader, "\x4");
|
||||
|
||||
if (stream_get_meta_data($inputStream)['seekable']) {
|
||||
fseek($inputStream, ftell($multiLineStreamReader));
|
||||
}
|
||||
|
||||
return $this->resetIOCodepage($cp, $ret);
|
||||
@@ -609,4 +587,35 @@ class QuestionHelper extends Helper
|
||||
|
||||
return $cloneStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $inputStream
|
||||
*/
|
||||
private function doReadInput($inputStream, ?string $exitChar = null, ?TerminalInputHelper $helper = null): string
|
||||
{
|
||||
$ret = '';
|
||||
$helper ??= new TerminalInputHelper($inputStream, false);
|
||||
|
||||
while (!feof($inputStream)) {
|
||||
$helper->waitForInput();
|
||||
$char = fread($inputStream, 1);
|
||||
|
||||
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
|
||||
if (false === $char || ('' === $ret && '' === $char)) {
|
||||
throw new MissingInputException('Aborted.');
|
||||
}
|
||||
|
||||
if (\PHP_EOL === "{$ret}{$char}" || $exitChar === $char) {
|
||||
break;
|
||||
}
|
||||
|
||||
$ret .= $char;
|
||||
|
||||
if (null === $exitChar && "\n" === $char) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,17 +34,17 @@ class SymfonyQuestionHelper extends QuestionHelper
|
||||
$default = $question->getDefault();
|
||||
|
||||
if ($question->isMultiline()) {
|
||||
$text .= sprintf(' (press %s to continue)', $this->getEofShortcut());
|
||||
$text .= \sprintf(' (press %s to continue)', $this->getEofShortcut($output));
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case null === $default:
|
||||
$text = sprintf(' <info>%s</info>:', $text);
|
||||
$text = \sprintf(' <info>%s</info>:', $text);
|
||||
|
||||
break;
|
||||
|
||||
case $question instanceof ConfirmationQuestion:
|
||||
$text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
|
||||
$text = \sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
|
||||
|
||||
break;
|
||||
|
||||
@@ -56,18 +56,18 @@ class SymfonyQuestionHelper extends QuestionHelper
|
||||
$default[$key] = $choices[trim($value)];
|
||||
}
|
||||
|
||||
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));
|
||||
$text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));
|
||||
|
||||
break;
|
||||
|
||||
case $question instanceof ChoiceQuestion:
|
||||
$choices = $question->getChoices();
|
||||
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));
|
||||
$text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
|
||||
$text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
|
||||
}
|
||||
|
||||
$output->writeln($text);
|
||||
@@ -98,9 +98,9 @@ class SymfonyQuestionHelper extends QuestionHelper
|
||||
parent::writeError($output, $error);
|
||||
}
|
||||
|
||||
private function getEofShortcut(): string
|
||||
private function getEofShortcut(OutputInterface $output): string
|
||||
{
|
||||
if ('Windows' === \PHP_OS_FAMILY) {
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && !$output->isDecorated()) {
|
||||
return '<comment>Ctrl+Z</comment> then <comment>Enter</comment>';
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class Table
|
||||
{
|
||||
self::$styles ??= self::initStyles();
|
||||
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +168,7 @@ class Table
|
||||
public function setColumnMaxWidth(int $columnIndex, int $width): static
|
||||
{
|
||||
if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
|
||||
throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
|
||||
throw new \LogicException(\sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
|
||||
}
|
||||
|
||||
$this->columnMaxWidths[$columnIndex] = $width;
|
||||
@@ -237,7 +237,7 @@ class Table
|
||||
public function appendRow(TableSeparator|array $row): static
|
||||
{
|
||||
if (!$this->output instanceof ConsoleSectionOutput) {
|
||||
throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
|
||||
throw new RuntimeException(\sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
|
||||
}
|
||||
|
||||
if ($this->rendered) {
|
||||
@@ -365,17 +365,19 @@ class Table
|
||||
for ($i = 0; $i < $maxRows; ++$i) {
|
||||
$cell = (string) ($row[$i] ?? '');
|
||||
|
||||
$parts = explode("\n", $cell);
|
||||
$eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
|
||||
$parts = explode($eol, $cell);
|
||||
foreach ($parts as $idx => $part) {
|
||||
if ($headers && !$containsColspan) {
|
||||
if (0 === $idx) {
|
||||
$rows[] = [sprintf(
|
||||
'<comment>%s</>: %s',
|
||||
str_pad($headers[$i] ?? '', $maxHeaderLength, ' ', \STR_PAD_LEFT),
|
||||
$rows[] = [\sprintf(
|
||||
'<comment>%s%s</>: %s',
|
||||
str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
|
||||
$headers[$i] ?? '',
|
||||
$part
|
||||
)];
|
||||
} else {
|
||||
$rows[] = [sprintf(
|
||||
$rows[] = [\sprintf(
|
||||
'%s %s',
|
||||
str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT),
|
||||
$part
|
||||
@@ -466,7 +468,7 @@ class Table
|
||||
*
|
||||
* +-----+-----------+-------+
|
||||
*/
|
||||
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null): void
|
||||
private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void
|
||||
{
|
||||
if (!$count = $this->numberOfColumns) {
|
||||
return;
|
||||
@@ -495,12 +497,12 @@ class Table
|
||||
}
|
||||
|
||||
if (null !== $title) {
|
||||
$titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title)));
|
||||
$titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = \sprintf($titleFormat, $title)));
|
||||
$markupLength = Helper::width($markup);
|
||||
if ($titleLength > $limit = $markupLength - 4) {
|
||||
$titleLength = $limit;
|
||||
$formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, '')));
|
||||
$formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
|
||||
$formatLength = Helper::width(Helper::removeDecoration($formatter, \sprintf($titleFormat, '')));
|
||||
$formattedTitle = \sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
|
||||
}
|
||||
|
||||
$titleStart = intdiv($markupLength - $titleLength, 2);
|
||||
@@ -511,7 +513,7 @@ class Table
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
|
||||
$this->output->writeln(\sprintf($this->style->getBorderFormat(), $markup));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,7 +523,7 @@ class Table
|
||||
{
|
||||
$borders = $this->style->getBorderChars();
|
||||
|
||||
return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
|
||||
return \sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -531,7 +533,7 @@ class Table
|
||||
*
|
||||
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
|
||||
*/
|
||||
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null): void
|
||||
private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void
|
||||
{
|
||||
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
|
||||
$columns = $this->getRowColumns($row);
|
||||
@@ -562,18 +564,15 @@ class Table
|
||||
}
|
||||
|
||||
// str_pad won't work properly with multi-byte strings, we need to fix the padding
|
||||
if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
|
||||
$width += \strlen($cell) - mb_strwidth($cell, $encoding);
|
||||
}
|
||||
|
||||
$width += \strlen($cell) - Helper::width($cell) - substr_count($cell, "\0");
|
||||
$style = $this->getColumnStyle($column);
|
||||
|
||||
if ($cell instanceof TableSeparator) {
|
||||
return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
|
||||
return \sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
|
||||
}
|
||||
|
||||
$width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
|
||||
$content = sprintf($style->getCellRowContentFormat(), $cell);
|
||||
$content = \sprintf($style->getCellRowContentFormat(), $cell);
|
||||
|
||||
$padType = $style->getPadType();
|
||||
if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
|
||||
@@ -598,7 +597,7 @@ class Table
|
||||
$padType = $cell->getStyle()->getPadByAlign();
|
||||
}
|
||||
|
||||
return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
|
||||
return \sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -630,15 +629,56 @@ class Table
|
||||
foreach ($rows[$rowKey] as $column => $cell) {
|
||||
$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
|
||||
|
||||
if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) {
|
||||
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
|
||||
$minWrappedWidth = 0;
|
||||
$widthApplied = [];
|
||||
$lengthColumnBorder = $this->getColumnSeparatorWidth() + Helper::width($this->style->getCellRowContentFormat()) - 2;
|
||||
for ($i = $column; $i < ($column + $colspan); ++$i) {
|
||||
if (isset($this->columnMaxWidths[$i])) {
|
||||
$minWrappedWidth += $this->columnMaxWidths[$i];
|
||||
$widthApplied[] = ['type' => 'max', 'column' => $i];
|
||||
} elseif (($this->columnWidths[$i] ?? 0) > 0 && $colspan > 1) {
|
||||
$minWrappedWidth += $this->columnWidths[$i];
|
||||
$widthApplied[] = ['type' => 'min', 'column' => $i];
|
||||
}
|
||||
}
|
||||
if (1 === \count($widthApplied)) {
|
||||
if ($colspan > 1) {
|
||||
$minWrappedWidth *= $colspan; // previous logic
|
||||
}
|
||||
} elseif (\count($widthApplied) > 1) {
|
||||
$minWrappedWidth += (\count($widthApplied) - 1) * $lengthColumnBorder;
|
||||
}
|
||||
|
||||
$cellWidth = Helper::width(Helper::removeDecoration($formatter, $cell));
|
||||
if ($minWrappedWidth && $cellWidth > $minWrappedWidth) {
|
||||
$cell = $formatter->formatAndWrap($cell, $minWrappedWidth);
|
||||
}
|
||||
// update minimal columnWidths for spanned columns
|
||||
if ($colspan > 1 && $minWrappedWidth > 0) {
|
||||
$columnsMinWidthProcessed = [];
|
||||
$cellWidth = min($cellWidth, $minWrappedWidth);
|
||||
foreach ($widthApplied as $item) {
|
||||
if ('max' === $item['type'] && $cellWidth >= $this->columnMaxWidths[$item['column']]) {
|
||||
$minWidthColumn = $this->columnMaxWidths[$item['column']];
|
||||
$this->columnWidths[$item['column']] = $minWidthColumn;
|
||||
$columnsMinWidthProcessed[$item['column']] = true;
|
||||
$cellWidth -= $minWidthColumn + $lengthColumnBorder;
|
||||
}
|
||||
}
|
||||
for ($i = $column; $i < ($column + $colspan); ++$i) {
|
||||
if (isset($columnsMinWidthProcessed[$i])) {
|
||||
continue;
|
||||
}
|
||||
$this->columnWidths[$i] = $cellWidth + $lengthColumnBorder;
|
||||
}
|
||||
}
|
||||
if (!str_contains($cell ?? '', "\n")) {
|
||||
continue;
|
||||
}
|
||||
$escaped = implode("\n", array_map(OutputFormatter::escapeTrailingBackslash(...), explode("\n", $cell)));
|
||||
$eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n";
|
||||
$escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell)));
|
||||
$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
|
||||
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default></>\n", $cell));
|
||||
$lines = explode($eol, str_replace($eol, '<fg=default;bg=default></>'.$eol, $cell));
|
||||
foreach ($lines as $lineKey => $line) {
|
||||
if ($colspan > 1) {
|
||||
$line = new TableCell($line, ['colspan' => $colspan]);
|
||||
@@ -694,14 +734,15 @@ class Table
|
||||
$unmergedRows = [];
|
||||
foreach ($rows[$line] as $column => $cell) {
|
||||
if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) {
|
||||
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
|
||||
throw new InvalidArgumentException(\sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
|
||||
}
|
||||
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
|
||||
$nbLines = $cell->getRowspan() - 1;
|
||||
$lines = [$cell];
|
||||
if (str_contains($cell, "\n")) {
|
||||
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
|
||||
$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
|
||||
$eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
|
||||
$lines = explode($eol, str_replace($eol, '<fg=default;bg=default>'.$eol.'</>', $cell));
|
||||
$nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines;
|
||||
|
||||
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
|
||||
unset($lines[0]);
|
||||
@@ -838,7 +879,7 @@ class Table
|
||||
|
||||
private function getColumnSeparatorWidth(): int
|
||||
{
|
||||
return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
|
||||
return Helper::width(\sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
|
||||
}
|
||||
|
||||
private function getCellWidth(array $row, int $column): int
|
||||
@@ -921,6 +962,6 @@ class Table
|
||||
return $name;
|
||||
}
|
||||
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
|
||||
return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class TableCell
|
||||
|
||||
// check option names
|
||||
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
|
||||
throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
|
||||
throw new InvalidArgumentException(\sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
|
||||
}
|
||||
|
||||
if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
|
||||
|
||||
@@ -43,11 +43,11 @@ class TableCellStyle
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
|
||||
throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
|
||||
throw new InvalidArgumentException(\sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
|
||||
}
|
||||
|
||||
if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) {
|
||||
throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP))));
|
||||
throw new InvalidArgumentException(\sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP))));
|
||||
}
|
||||
|
||||
$this->options = array_merge($this->options, $options);
|
||||
|
||||
@@ -77,30 +77,6 @@ class TableStyle
|
||||
*
|
||||
* <code>
|
||||
* ╔═══════════════╤══════════════════════════╤══════════════════╗
|
||||
* 1 ISBN 2 Title │ Author ║
|
||||
* ╠═══════════════╪══════════════════════════╪══════════════════╣
|
||||
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
|
||||
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
|
||||
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
|
||||
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
|
||||
* ╚═══════════════╧══════════════════════════╧══════════════════╝
|
||||
* </code>
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHorizontalBorderChars(string $outside, string $inside = null): static
|
||||
{
|
||||
$this->horizontalOutsideBorderChar = $outside;
|
||||
$this->horizontalInsideBorderChar = $inside ?? $outside;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets vertical border characters.
|
||||
*
|
||||
* <code>
|
||||
* ╔═══════════════╤══════════════════════════╤══════════════════╗
|
||||
* ║ ISBN │ Title │ Author ║
|
||||
* ╠═══════1═══════╪══════════════════════════╪══════════════════╣
|
||||
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
|
||||
@@ -113,7 +89,31 @@ class TableStyle
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setVerticalBorderChars(string $outside, string $inside = null): static
|
||||
public function setHorizontalBorderChars(string $outside, ?string $inside = null): static
|
||||
{
|
||||
$this->horizontalOutsideBorderChar = $outside;
|
||||
$this->horizontalInsideBorderChar = $inside ?? $outside;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets vertical border characters.
|
||||
*
|
||||
* <code>
|
||||
* ╔═══════════════╤══════════════════════════╤══════════════════╗
|
||||
* 1 ISBN 2 Title │ Author ║
|
||||
* ╠═══════════════╪══════════════════════════╪══════════════════╣
|
||||
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
|
||||
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
|
||||
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
|
||||
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
|
||||
* ╚═══════════════╧══════════════════════════╧══════════════════╝
|
||||
* </code>
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setVerticalBorderChars(string $outside, ?string $inside = null): static
|
||||
{
|
||||
$this->verticalOutsideBorderChar = $outside;
|
||||
$this->verticalInsideBorderChar = $inside ?? $outside;
|
||||
@@ -167,7 +167,7 @@ class TableStyle
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): static
|
||||
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static
|
||||
{
|
||||
$this->crossingChar = $cross;
|
||||
$this->crossingTopLeftChar = $topLeft;
|
||||
|
||||
156
lib/symfony/console/Helper/TerminalInputHelper.php
Normal file
156
lib/symfony/console/Helper/TerminalInputHelper.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
/**
|
||||
* TerminalInputHelper stops Ctrl-C and similar signals from leaving the terminal in
|
||||
* an unusable state if its settings have been modified when reading user input.
|
||||
* This can be an issue on non-Windows platforms.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* $inputHelper = new TerminalInputHelper($inputStream);
|
||||
*
|
||||
* ...change terminal settings
|
||||
*
|
||||
* // Wait for input before all input reads
|
||||
* $inputHelper->waitForInput();
|
||||
*
|
||||
* ...read input
|
||||
*
|
||||
* // Call finish to restore terminal settings and signal handlers
|
||||
* $inputHelper->finish()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TerminalInputHelper
|
||||
{
|
||||
/** @var resource */
|
||||
private $inputStream;
|
||||
private bool $isStdin;
|
||||
private string $initialState = '';
|
||||
private int $signalToKill = 0;
|
||||
private array $signalHandlers = [];
|
||||
private array $targetSignals = [];
|
||||
private bool $withStty;
|
||||
|
||||
/**
|
||||
* @param resource $inputStream
|
||||
*
|
||||
* @throws \RuntimeException If unable to read terminal settings
|
||||
*/
|
||||
public function __construct($inputStream, bool $withStty = true)
|
||||
{
|
||||
$this->inputStream = $inputStream;
|
||||
$this->isStdin = 'php://stdin' === stream_get_meta_data($inputStream)['uri'];
|
||||
$this->withStty = $withStty;
|
||||
|
||||
if ($withStty) {
|
||||
if (!\is_string($state = shell_exec('stty -g'))) {
|
||||
throw new \RuntimeException('Unable to read the terminal settings.');
|
||||
}
|
||||
|
||||
$this->initialState = $state;
|
||||
|
||||
$this->createSignalHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for input.
|
||||
*/
|
||||
public function waitForInput(): void
|
||||
{
|
||||
if ($this->isStdin) {
|
||||
$r = [$this->inputStream];
|
||||
$w = [];
|
||||
|
||||
// Allow signal handlers to run
|
||||
while (0 === @stream_select($r, $w, $w, 0, 100)) {
|
||||
$r = [$this->inputStream];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->withStty) {
|
||||
$this->checkForKillSignal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores terminal state and signal handlers.
|
||||
*/
|
||||
public function finish(): void
|
||||
{
|
||||
if (!$this->withStty) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Safeguard in case an unhandled kill signal exists
|
||||
$this->checkForKillSignal();
|
||||
shell_exec('stty '.$this->initialState);
|
||||
$this->signalToKill = 0;
|
||||
|
||||
foreach ($this->signalHandlers as $signal => $originalHandler) {
|
||||
pcntl_signal($signal, $originalHandler);
|
||||
}
|
||||
$this->signalHandlers = [];
|
||||
$this->targetSignals = [];
|
||||
}
|
||||
|
||||
private function createSignalHandlers(): void
|
||||
{
|
||||
if (!\function_exists('pcntl_async_signals') || !\function_exists('pcntl_signal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
pcntl_async_signals(true);
|
||||
$this->targetSignals = [\SIGINT, \SIGQUIT, \SIGTERM];
|
||||
|
||||
foreach ($this->targetSignals as $signal) {
|
||||
$this->signalHandlers[$signal] = pcntl_signal_get_handler($signal);
|
||||
|
||||
pcntl_signal($signal, function ($signal) {
|
||||
// Save current state, then restore to initial state
|
||||
$currentState = shell_exec('stty -g');
|
||||
shell_exec('stty '.$this->initialState);
|
||||
$originalHandler = $this->signalHandlers[$signal];
|
||||
|
||||
if (\is_callable($originalHandler)) {
|
||||
$originalHandler($signal);
|
||||
// Handler did not exit, so restore to current state
|
||||
shell_exec('stty '.$currentState);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a callable, so SIG_DFL or SIG_IGN
|
||||
if (\SIG_DFL === $originalHandler) {
|
||||
$this->signalToKill = $signal;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function checkForKillSignal(): void
|
||||
{
|
||||
if (\in_array($this->signalToKill, $this->targetSignals, true)) {
|
||||
// Try posix_kill
|
||||
if (\function_exists('posix_kill')) {
|
||||
pcntl_signal($this->signalToKill, \SIG_DFL);
|
||||
posix_kill(getmypid(), $this->signalToKill);
|
||||
}
|
||||
|
||||
// Best attempt fallback
|
||||
exit(128 + $this->signalToKill);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user