From 647c51ff073300a432a4a504e29323cf0d5e0571 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 28 Oct 2020 08:52:32 +0100 Subject: [PATCH 1/6] Use short array deconstruction syntax. --- Helper/Table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Helper/Table.php b/Helper/Table.php index 82aeb3fc8..facffa683 100644 --- a/Helper/Table.php +++ b/Helper/Table.php @@ -430,13 +430,13 @@ private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $tit $crossings = $this->style->getCrossingChars(); if (self::SEPARATOR_MID === $type) { - list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; } elseif (self::SEPARATOR_TOP === $type) { - list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { - list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; } else { - list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; + [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; } $markup = $leftChar; From 95f70e6ff95947208a19d0aa85811a3d2460cb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Wed, 4 Nov 2020 21:14:54 +0100 Subject: [PATCH 2/6] Fix ANSI when stdErr is not a tty --- Output/ConsoleOutput.php | 7 +++++++ Tests/Output/ConsoleOutputTest.php | 13 +++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Output/ConsoleOutput.php b/Output/ConsoleOutput.php index ff02f8694..966fca099 100644 --- a/Output/ConsoleOutput.php +++ b/Output/ConsoleOutput.php @@ -41,6 +41,13 @@ public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decor { parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + if (null === $formatter) { + // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); + + return; + } + $actualDecorated = $this->isDecorated(); $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); diff --git a/Tests/Output/ConsoleOutputTest.php b/Tests/Output/ConsoleOutputTest.php index db39a02b8..33a5371d6 100644 --- a/Tests/Output/ConsoleOutputTest.php +++ b/Tests/Output/ConsoleOutputTest.php @@ -18,11 +18,19 @@ class ConsoleOutputTest extends TestCase { - public function testConstructor() + public function testConstructorWithoutFormatter() { $output = new ConsoleOutput(Output::VERBOSITY_QUIET, true); $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument'); - $this->assertSame($output->getFormatter(), $output->getErrorOutput()->getFormatter(), '__construct() takes a formatter or null as the third argument'); + $this->assertNotSame($output->getFormatter(), $output->getErrorOutput()->getFormatter(), 'ErrorOutput should use it own formatter'); + } + + public function testConstructorWithFormatter() + { + $output = new ConsoleOutput(Output::VERBOSITY_QUIET, true, $formatter = new OutputFormatter()); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument'); + $this->assertSame($formatter, $output->getFormatter()); + $this->assertSame($formatter, $output->getErrorOutput()->getFormatter(), 'Output and ErrorOutput should use the same provided formatter'); } public function testSetFormatter() @@ -31,6 +39,7 @@ public function testSetFormatter() $outputFormatter = new OutputFormatter(); $output->setFormatter($outputFormatter); $this->assertSame($outputFormatter, $output->getFormatter()); + $this->assertSame($outputFormatter, $output->getErrorOutput()->getFormatter()); } public function testSetVerbosity() From 05240f0be359a76733a4c8a10ab7a15713f52769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Wed, 25 Nov 2020 01:21:23 +0100 Subject: [PATCH 3/6] Fix console closing tag --- Formatter/OutputFormatter.php | 8 ++++++++ .../Style/SymfonyStyle/command/command_20.php | 13 +++++++++++++ .../Style/SymfonyStyle/output/output_20.txt | 1 + 3 files changed, 22 insertions(+) create mode 100644 Tests/Fixtures/Style/SymfonyStyle/command/command_20.php create mode 100644 Tests/Fixtures/Style/SymfonyStyle/output/output_20.txt diff --git a/Formatter/OutputFormatter.php b/Formatter/OutputFormatter.php index 5d52896ac..26288ce62 100644 --- a/Formatter/OutputFormatter.php +++ b/Formatter/OutputFormatter.php @@ -25,6 +25,14 @@ class OutputFormatter implements WrappableOutputFormatterInterface private $styles = []; private $styleStack; + public function __clone() + { + $this->styleStack = clone $this->styleStack; + foreach ($this->styles as $key => $value) { + $this->styles[$key] = clone $value; + } + } + /** * Escapes "<" special char in given text. * diff --git a/Tests/Fixtures/Style/SymfonyStyle/command/command_20.php b/Tests/Fixtures/Style/SymfonyStyle/command/command_20.php new file mode 100644 index 000000000..6b47969ee --- /dev/null +++ b/Tests/Fixtures/Style/SymfonyStyle/command/command_20.php @@ -0,0 +1,13 @@ +setDecorated(true); + $output = new SymfonyStyle($input, $output); + $output->write('do you want something'); + $output->writeln('?'); +}; diff --git a/Tests/Fixtures/Style/SymfonyStyle/output/output_20.txt b/Tests/Fixtures/Style/SymfonyStyle/output/output_20.txt new file mode 100644 index 000000000..c08298530 --- /dev/null +++ b/Tests/Fixtures/Style/SymfonyStyle/output/output_20.txt @@ -0,0 +1 @@ +do you want something? From 061d2c71b9bdee2e3ad284e63f7fc4aaaa81d4f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Tue, 24 Nov 2020 13:45:24 +0100 Subject: [PATCH 4/6] Use a partial buffer in SymfonyStyle --- Output/TrimmedBufferOutput.php | 67 ++++++++++++++++++++++++++++++++ Style/SymfonyStyle.php | 9 ++--- Tests/Style/SymfonyStyleTest.php | 15 +++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 Output/TrimmedBufferOutput.php diff --git a/Output/TrimmedBufferOutput.php b/Output/TrimmedBufferOutput.php new file mode 100644 index 000000000..c014d4363 --- /dev/null +++ b/Output/TrimmedBufferOutput.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * A BufferedOutput that keeps only the last N chars. + * + * @author Jérémy Derussé + */ +class TrimmedBufferOutput extends Output +{ + private $maxLength; + private $buffer = ''; + + public function __construct( + ?int $verbosity = self::VERBOSITY_NORMAL, + bool $decorated = false, + OutputFormatterInterface $formatter = null, + int $maxLength + ) { + if ($maxLength <= 0) { + throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); + } + + parent::__construct($verbosity, $decorated, $formatter); + $this->maxLength = $maxLength; + } + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + + $this->buffer = substr($this->buffer, 0 - $this->maxLength); + } +} diff --git a/Style/SymfonyStyle.php b/Style/SymfonyStyle.php index b40c16e99..a5edac3c4 100644 --- a/Style/SymfonyStyle.php +++ b/Style/SymfonyStyle.php @@ -21,8 +21,8 @@ use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\TrimmedBufferOutput; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; @@ -46,7 +46,7 @@ class SymfonyStyle extends OutputStyle public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; - $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); + $this->bufferedOutput = new TrimmedBufferOutput($output->getVerbosity(), false, clone $output->getFormatter(), \DIRECTORY_SEPARATOR === '\\' ? 4 : 2); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); @@ -449,9 +449,8 @@ private function autoPrependText(): void private function writeBuffer(string $message, bool $newLine, int $type): void { - // We need to know if the two last chars are PHP_EOL - // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer - $this->bufferedOutput->write(substr($message, -4), $newLine, $type); + // We need to know if the last chars are PHP_EOL + $this->bufferedOutput->write($message, $newLine, $type); } private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array diff --git a/Tests/Style/SymfonyStyleTest.php b/Tests/Style/SymfonyStyleTest.php index 943b94172..2444d89ba 100644 --- a/Tests/Style/SymfonyStyleTest.php +++ b/Tests/Style/SymfonyStyleTest.php @@ -14,8 +14,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Tester\CommandTester; @@ -115,4 +117,17 @@ public function testGetErrorStyleUsesTheCurrentOutputIfNoErrorOutputIsAvailable( $this->assertInstanceOf(SymfonyStyle::class, $style->getErrorStyle()); } + + public function testMemoryConsumption() + { + $io = new SymfonyStyle(new ArrayInput([]), new NullOutput()); + $str = 'teststr'; + $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); + $start = memory_get_usage(); + for ($i = 0; $i < 100; ++$i) { + $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); + } + + $this->assertSame(0, memory_get_usage() - $start); + } } From e5e14767ea292eabd703e72bced68cc1dd52272c Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 27 Nov 2020 01:30:48 +0100 Subject: [PATCH 5/6] Fix test. --- Tests/Style/SymfonyStyleTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Style/SymfonyStyleTest.php b/Tests/Style/SymfonyStyleTest.php index 2444d89ba..16bb2baec 100644 --- a/Tests/Style/SymfonyStyleTest.php +++ b/Tests/Style/SymfonyStyleTest.php @@ -123,6 +123,7 @@ public function testMemoryConsumption() $io = new SymfonyStyle(new ArrayInput([]), new NullOutput()); $str = 'teststr'; $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); + $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); $start = memory_get_usage(); for ($i = 0; $i < 100; ++$i) { $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); From c8e37f6928c19816437a4dd7bf16e3bd79941470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 28 Nov 2020 11:15:42 +0100 Subject: [PATCH 6/6] Fix parameter order --- Output/TrimmedBufferOutput.php | 4 ++-- Style/SymfonyStyle.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Output/TrimmedBufferOutput.php b/Output/TrimmedBufferOutput.php index c014d4363..a03aa835f 100644 --- a/Output/TrimmedBufferOutput.php +++ b/Output/TrimmedBufferOutput.php @@ -25,10 +25,10 @@ class TrimmedBufferOutput extends Output private $buffer = ''; public function __construct( + int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, - OutputFormatterInterface $formatter = null, - int $maxLength + OutputFormatterInterface $formatter = null ) { if ($maxLength <= 0) { throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); diff --git a/Style/SymfonyStyle.php b/Style/SymfonyStyle.php index a5edac3c4..0400a26cf 100644 --- a/Style/SymfonyStyle.php +++ b/Style/SymfonyStyle.php @@ -46,7 +46,7 @@ class SymfonyStyle extends OutputStyle public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; - $this->bufferedOutput = new TrimmedBufferOutput($output->getVerbosity(), false, clone $output->getFormatter(), \DIRECTORY_SEPARATOR === '\\' ? 4 : 2); + $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);