[Console] Modify console output and print multiple modifyable sections
This commit is contained in:
parent
d79c528b40
commit
9ec51a1797
@ -5,6 +5,7 @@ CHANGELOG
|
|||||||
-----
|
-----
|
||||||
|
|
||||||
* added option to run suggested command if command is not found and only 1 alternative is available
|
* added option to run suggested command if command is not found and only 1 alternative is available
|
||||||
|
* added option to modify console output and print multiple modifiable sections
|
||||||
|
|
||||||
4.0.0
|
4.0.0
|
||||||
-----
|
-----
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\Console\Helper;
|
namespace Symfony\Component\Console\Helper;
|
||||||
|
|
||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Exception\LogicException;
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
use Symfony\Component\Console\Terminal;
|
use Symfony\Component\Console\Terminal;
|
||||||
@ -375,15 +376,20 @@ final class ProgressBar
|
|||||||
{
|
{
|
||||||
if ($this->overwrite) {
|
if ($this->overwrite) {
|
||||||
if (!$this->firstRun) {
|
if (!$this->firstRun) {
|
||||||
// Move the cursor to the beginning of the line
|
if ($this->output instanceof ConsoleSectionOutput) {
|
||||||
$this->output->write("\x0D");
|
$lines = floor(Helper::strlen($message) / $this->terminal->getWidth()) + $this->formatLineCount + 1;
|
||||||
|
$this->output->clear($lines);
|
||||||
|
} else {
|
||||||
|
// Move the cursor to the beginning of the line
|
||||||
|
$this->output->write("\x0D");
|
||||||
|
|
||||||
// Erase the line
|
// Erase the line
|
||||||
$this->output->write("\x1B[2K");
|
$this->output->write("\x1B[2K");
|
||||||
|
|
||||||
// Erase previous lines
|
// Erase previous lines
|
||||||
if ($this->formatLineCount > 0) {
|
if ($this->formatLineCount > 0) {
|
||||||
$this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
|
$this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($this->step > 0) {
|
} elseif ($this->step > 0) {
|
||||||
|
@ -11,8 +11,10 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Console\Helper;
|
namespace Symfony\Component\Console\Helper;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides helpers to display a table.
|
* Provides helpers to display a table.
|
||||||
@ -70,6 +72,8 @@ class Table
|
|||||||
|
|
||||||
private static $styles;
|
private static $styles;
|
||||||
|
|
||||||
|
private $rendered = false;
|
||||||
|
|
||||||
public function __construct(OutputInterface $output)
|
public function __construct(OutputInterface $output)
|
||||||
{
|
{
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
@ -252,6 +256,25 @@ class Table
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a row to the table, and re-renders the table.
|
||||||
|
*/
|
||||||
|
public function appendRow($row): self
|
||||||
|
{
|
||||||
|
if (!$this->output instanceof ConsoleSectionOutput) {
|
||||||
|
throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->rendered) {
|
||||||
|
$this->output->clear($this->calculateRowCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addRow($row);
|
||||||
|
$this->render();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function setRow($column, array $row)
|
public function setRow($column, array $row)
|
||||||
{
|
{
|
||||||
$this->rows[$column] = $row;
|
$this->rows[$column] = $row;
|
||||||
@ -311,6 +334,7 @@ class Table
|
|||||||
$this->renderRowSeparator();
|
$this->renderRowSeparator();
|
||||||
|
|
||||||
$this->cleanup();
|
$this->cleanup();
|
||||||
|
$this->rendered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -445,6 +469,19 @@ class Table
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function calculateRowCount(): int
|
||||||
|
{
|
||||||
|
$numberOfRows = count(iterator_to_array($this->buildTableRows(array_merge($this->headers, array(new TableSeparator()), $this->rows))));
|
||||||
|
|
||||||
|
if ($this->headers) {
|
||||||
|
++$numberOfRows; // Add row for header separator
|
||||||
|
}
|
||||||
|
|
||||||
|
++$numberOfRows; // Add row for footer separator
|
||||||
|
|
||||||
|
return $numberOfRows;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fill rows that contains rowspan > 1.
|
* fill rows that contains rowspan > 1.
|
||||||
*
|
*
|
||||||
|
@ -30,6 +30,7 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
|
|||||||
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
|
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
|
||||||
{
|
{
|
||||||
private $stderr;
|
private $stderr;
|
||||||
|
private $consoleSectionOutputs = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
|
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
|
||||||
@ -48,6 +49,14 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new output section.
|
||||||
|
*/
|
||||||
|
public function section(): ConsoleSectionOutput
|
||||||
|
{
|
||||||
|
return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -13,9 +13,11 @@ namespace Symfony\Component\Console\Output;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
|
* ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
|
||||||
* This adds information about stderr output stream.
|
* This adds information about stderr and section output stream.
|
||||||
*
|
*
|
||||||
* @author Dariusz Górecki <darek.krk@gmail.com>
|
* @author Dariusz Górecki <darek.krk@gmail.com>
|
||||||
|
*
|
||||||
|
* @method ConsoleSectionOutput section() Creates a new output section
|
||||||
*/
|
*/
|
||||||
interface ConsoleOutputInterface extends OutputInterface
|
interface ConsoleOutputInterface extends OutputInterface
|
||||||
{
|
{
|
||||||
|
133
src/Symfony/Component/Console/Output/ConsoleSectionOutput.php
Normal file
133
src/Symfony/Component/Console/Output/ConsoleSectionOutput.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?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\Output;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
|
||||||
|
use Symfony\Component\Console\Helper\Helper;
|
||||||
|
use Symfony\Component\Console\Terminal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pierre du Plessis <pdples@gmail.com>
|
||||||
|
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
|
||||||
|
*/
|
||||||
|
class ConsoleSectionOutput extends StreamOutput
|
||||||
|
{
|
||||||
|
private $content = array();
|
||||||
|
private $lines = 0;
|
||||||
|
private $sections;
|
||||||
|
private $terminal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $stream
|
||||||
|
* @param ConsoleSectionOutput[] $sections
|
||||||
|
*/
|
||||||
|
public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
|
||||||
|
{
|
||||||
|
parent::__construct($stream, $verbosity, $decorated, $formatter);
|
||||||
|
array_unshift($sections, $this);
|
||||||
|
$this->sections = &$sections;
|
||||||
|
$this->terminal = new Terminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears previous output for this section.
|
||||||
|
*
|
||||||
|
* @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
|
||||||
|
*/
|
||||||
|
public function clear(int $lines = null)
|
||||||
|
{
|
||||||
|
if (empty($this->content) || !$this->isDecorated()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($lines) {
|
||||||
|
\array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
|
||||||
|
} else {
|
||||||
|
$lines = $this->lines;
|
||||||
|
$this->content = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lines -= $lines;
|
||||||
|
|
||||||
|
parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrites the previous output with a new message.
|
||||||
|
*
|
||||||
|
* @param array|string $message
|
||||||
|
*/
|
||||||
|
public function overwrite($message)
|
||||||
|
{
|
||||||
|
$this->clear();
|
||||||
|
$this->writeln($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return implode('', $this->content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doWrite($message, $newline)
|
||||||
|
{
|
||||||
|
if (!$this->isDecorated()) {
|
||||||
|
return parent::doWrite($message, $newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
$erasedContent = $this->popStreamContentUntilCurrentSection();
|
||||||
|
|
||||||
|
foreach (explode(PHP_EOL, $message) as $lineContent) {
|
||||||
|
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
|
||||||
|
$this->content[] = $lineContent;
|
||||||
|
$this->content[] = PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::doWrite($message, true);
|
||||||
|
parent::doWrite($erasedContent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
|
||||||
|
* current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
|
||||||
|
*/
|
||||||
|
private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
|
||||||
|
{
|
||||||
|
$numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
|
||||||
|
$erasedContent = array();
|
||||||
|
|
||||||
|
foreach ($this->sections as $section) {
|
||||||
|
if ($section === $this) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$numberOfLinesToClear += $section->lines;
|
||||||
|
$erasedContent[] = $section->getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($numberOfLinesToClear > 0) {
|
||||||
|
// move cursor up n lines
|
||||||
|
parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false);
|
||||||
|
// erase to end of screen
|
||||||
|
parent::doWrite("\x1b[0J", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('', array_reverse($erasedContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDisplayLength(string $text): string
|
||||||
|
{
|
||||||
|
return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text));
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,10 @@
|
|||||||
namespace Symfony\Component\Console\Tests\Helper;
|
namespace Symfony\Component\Console\Tests\Helper;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
use Symfony\Component\Console\Helper\ProgressBar;
|
use Symfony\Component\Console\Helper\ProgressBar;
|
||||||
use Symfony\Component\Console\Helper\Helper;
|
use Symfony\Component\Console\Helper\Helper;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||||
use Symfony\Component\Console\Output\StreamOutput;
|
use Symfony\Component\Console\Output\StreamOutput;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,6 +312,88 @@ class ProgressBarTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testOverwriteWithSectionOutput()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$stream = $this->getOutputStream(true);
|
||||||
|
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
|
||||||
|
|
||||||
|
$bar = new ProgressBar($output, 50);
|
||||||
|
$bar->start();
|
||||||
|
$bar->display();
|
||||||
|
$bar->advance();
|
||||||
|
$bar->advance();
|
||||||
|
|
||||||
|
rewind($output->getStream());
|
||||||
|
$this->assertEquals(
|
||||||
|
' 0/50 [>---------------------------] 0%'.PHP_EOL.
|
||||||
|
"\x1b[1A\x1b[0J".' 0/50 [>---------------------------] 0%'.PHP_EOL.
|
||||||
|
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
|
||||||
|
"\x1b[1A\x1b[0J".' 2/50 [=>--------------------------] 4%'.PHP_EOL,
|
||||||
|
stream_get_contents($output->getStream())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOverwriteMultipleProgressBarsWithSectionOutputs()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$stream = $this->getOutputStream(true);
|
||||||
|
$output1 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
|
||||||
|
$output2 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
|
||||||
|
|
||||||
|
$progress = new ProgressBar($output1, 50);
|
||||||
|
$progress2 = new ProgressBar($output2, 50);
|
||||||
|
|
||||||
|
$progress->start();
|
||||||
|
$progress2->start();
|
||||||
|
|
||||||
|
$progress2->advance();
|
||||||
|
$progress->advance();
|
||||||
|
|
||||||
|
rewind($stream->getStream());
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
' 0/50 [>---------------------------] 0%'.PHP_EOL.
|
||||||
|
' 0/50 [>---------------------------] 0%'.PHP_EOL.
|
||||||
|
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
|
||||||
|
"\x1b[2A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
|
||||||
|
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
|
||||||
|
' 1/50 [>---------------------------] 2%'.PHP_EOL,
|
||||||
|
stream_get_contents($stream->getStream())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleSectionsWithCustomFormat()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$stream = $this->getOutputStream(true);
|
||||||
|
$output1 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
|
||||||
|
$output2 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
|
||||||
|
|
||||||
|
ProgressBar::setFormatDefinition('test', '%current%/%max% [%bar%] %percent:3s%% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.');
|
||||||
|
|
||||||
|
$progress = new ProgressBar($output1, 50);
|
||||||
|
$progress2 = new ProgressBar($output2, 50);
|
||||||
|
$progress2->setFormat('test');
|
||||||
|
|
||||||
|
$progress->start();
|
||||||
|
$progress2->start();
|
||||||
|
|
||||||
|
$progress->advance();
|
||||||
|
$progress2->advance();
|
||||||
|
|
||||||
|
rewind($stream->getStream());
|
||||||
|
|
||||||
|
$this->assertEquals(' 0/50 [>---------------------------] 0%'.PHP_EOL.
|
||||||
|
' 0/50 [>] 0% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.PHP_EOL.
|
||||||
|
"\x1b[4A\x1b[0J".' 0/50 [>] 0% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.PHP_EOL.
|
||||||
|
"\x1b[3A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
|
||||||
|
' 0/50 [>] 0% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.PHP_EOL.
|
||||||
|
"\x1b[3A\x1b[0J".' 1/50 [>] 2% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.PHP_EOL,
|
||||||
|
stream_get_contents($stream->getStream())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testStartWithMax()
|
public function testStartWithMax()
|
||||||
{
|
{
|
||||||
$bar = new ProgressBar($output = $this->getOutputStream());
|
$bar = new ProgressBar($output = $this->getOutputStream());
|
||||||
|
@ -12,10 +12,12 @@
|
|||||||
namespace Symfony\Component\Console\Tests\Helper;
|
namespace Symfony\Component\Console\Tests\Helper;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
use Symfony\Component\Console\Helper\Table;
|
use Symfony\Component\Console\Helper\Table;
|
||||||
use Symfony\Component\Console\Helper\TableStyle;
|
use Symfony\Component\Console\Helper\TableStyle;
|
||||||
use Symfony\Component\Console\Helper\TableSeparator;
|
use Symfony\Component\Console\Helper\TableSeparator;
|
||||||
use Symfony\Component\Console\Helper\TableCell;
|
use Symfony\Component\Console\Helper\TableCell;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||||
use Symfony\Component\Console\Output\StreamOutput;
|
use Symfony\Component\Console\Output\StreamOutput;
|
||||||
|
|
||||||
class TableTest extends TestCase
|
class TableTest extends TestCase
|
||||||
@ -821,6 +823,115 @@ TABLE;
|
|||||||
$this->assertEquals($expected, $this->getOutputContent($output));
|
$this->assertEquals($expected, $this->getOutputContent($output));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSectionOutput()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$stream = $this->getOutputStream(true);
|
||||||
|
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
|
||||||
|
$table = new Table($output);
|
||||||
|
$table
|
||||||
|
->setHeaders(array('ISBN', 'Title', 'Author', 'Price'))
|
||||||
|
->setRows(array(
|
||||||
|
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$table->render();
|
||||||
|
|
||||||
|
$table->appendRow(array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25'));
|
||||||
|
|
||||||
|
$expected =
|
||||||
|
<<<TABLE
|
||||||
|
+---------------+---------------+-----------------+-------+
|
||||||
|
|\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
|
||||||
|
+---------------+---------------+-----------------+-------+
|
||||||
|
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
|
||||||
|
+---------------+---------------+-----------------+-------+
|
||||||
|
\x1b[5A\x1b[0J+---------------+----------------------+-----------------+--------+
|
||||||
|
|\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
|
||||||
|
+---------------+----------------------+-----------------+--------+
|
||||||
|
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
|
||||||
|
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
|
||||||
|
+---------------+----------------------+-----------------+--------+
|
||||||
|
|
||||||
|
TABLE;
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $this->getOutputContent($output));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSectionOutputDoesntClearIfTableIsntRendered()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$stream = $this->getOutputStream(true);
|
||||||
|
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
|
||||||
|
$table = new Table($output);
|
||||||
|
$table
|
||||||
|
->setHeaders(array('ISBN', 'Title', 'Author', 'Price'))
|
||||||
|
->setRows(array(
|
||||||
|
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$table->appendRow(array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25'));
|
||||||
|
|
||||||
|
$expected =
|
||||||
|
<<<TABLE
|
||||||
|
+---------------+----------------------+-----------------+--------+
|
||||||
|
|\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
|
||||||
|
+---------------+----------------------+-----------------+--------+
|
||||||
|
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
|
||||||
|
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
|
||||||
|
+---------------+----------------------+-----------------+--------+
|
||||||
|
|
||||||
|
TABLE;
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $this->getOutputContent($output));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSectionOutputWithoutDecoration()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$stream = $this->getOutputStream();
|
||||||
|
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
|
||||||
|
$table = new Table($output);
|
||||||
|
$table
|
||||||
|
->setHeaders(array('ISBN', 'Title', 'Author', 'Price'))
|
||||||
|
->setRows(array(
|
||||||
|
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$table->render();
|
||||||
|
|
||||||
|
$table->appendRow(array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25'));
|
||||||
|
|
||||||
|
$expected =
|
||||||
|
<<<TABLE
|
||||||
|
+---------------+---------------+-----------------+-------+
|
||||||
|
| ISBN | Title | Author | Price |
|
||||||
|
+---------------+---------------+-----------------+-------+
|
||||||
|
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
|
||||||
|
+---------------+---------------+-----------------+-------+
|
||||||
|
+---------------+----------------------+-----------------+--------+
|
||||||
|
| ISBN | Title | Author | Price |
|
||||||
|
+---------------+----------------------+-----------------+--------+
|
||||||
|
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
|
||||||
|
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
|
||||||
|
+---------------+----------------------+-----------------+--------+
|
||||||
|
|
||||||
|
TABLE;
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $this->getOutputContent($output));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Console\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage Output should be an instance of "Symfony\Component\Console\Output\ConsoleSectionOutput" when calling "Symfony\Component\Console\Helper\Table::appendRow".
|
||||||
|
*/
|
||||||
|
public function testAppendRowWithoutSectionOutput()
|
||||||
|
{
|
||||||
|
$table = new Table($this->getOutputStream());
|
||||||
|
|
||||||
|
$table->appendRow(array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
|
* @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
|
||||||
* @expectedExceptionMessage Style "absent" is not defined.
|
* @expectedExceptionMessage Style "absent" is not defined.
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
<?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\Tests\Output;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Output\StreamOutput;
|
||||||
|
|
||||||
|
class ConsoleSectionOutputTest extends TestCase
|
||||||
|
{
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
$this->stream = fopen('php://memory', 'r+', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown()
|
||||||
|
{
|
||||||
|
$this->stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearAll()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$output = new ConsoleSectionOutput($this->stream, $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
|
||||||
|
$output->writeln('Foo'.PHP_EOL.'Bar');
|
||||||
|
$output->clear();
|
||||||
|
|
||||||
|
rewind($output->getStream());
|
||||||
|
$this->assertEquals('Foo'.PHP_EOL.'Bar'.PHP_EOL.sprintf("\x1b[%dA", 2)."\x1b[0J", stream_get_contents($output->getStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearNumberOfLines()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$output = new ConsoleSectionOutput($this->stream, $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
|
||||||
|
$output->writeln("Foo\nBar\nBaz\nFooBar");
|
||||||
|
$output->clear(2);
|
||||||
|
|
||||||
|
rewind($output->getStream());
|
||||||
|
$this->assertEquals("Foo\nBar\nBaz\nFooBar".PHP_EOL.sprintf("\x1b[%dA", 2)."\x1b[0J", stream_get_contents($output->getStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearNumberOfLinesWithMultipleSections()
|
||||||
|
{
|
||||||
|
$output = new StreamOutput($this->stream);
|
||||||
|
$sections = array();
|
||||||
|
$output1 = new ConsoleSectionOutput($output->getStream(), $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
$output2 = new ConsoleSectionOutput($output->getStream(), $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
|
||||||
|
$output2->writeln('Foo');
|
||||||
|
$output2->writeln('Bar');
|
||||||
|
$output2->clear(1);
|
||||||
|
$output1->writeln('Baz');
|
||||||
|
|
||||||
|
rewind($output->getStream());
|
||||||
|
|
||||||
|
$this->assertEquals('Foo'.PHP_EOL.'Bar'.PHP_EOL."\x1b[1A\x1b[0J\e[1A\e[0J".'Baz'.PHP_EOL.'Foo'.PHP_EOL, stream_get_contents($output->getStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testClearPreservingEmptyLines()
|
||||||
|
{
|
||||||
|
$output = new StreamOutput($this->stream);
|
||||||
|
$sections = array();
|
||||||
|
$output1 = new ConsoleSectionOutput($output->getStream(), $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
$output2 = new ConsoleSectionOutput($output->getStream(), $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
|
||||||
|
$output2->writeln(PHP_EOL.'foo');
|
||||||
|
$output2->clear(1);
|
||||||
|
$output1->writeln('bar');
|
||||||
|
|
||||||
|
rewind($output->getStream());
|
||||||
|
|
||||||
|
$this->assertEquals(PHP_EOL.'foo'.PHP_EOL."\x1b[1A\x1b[0J\x1b[1A\x1b[0J".'bar'.PHP_EOL.PHP_EOL, stream_get_contents($output->getStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOverwrite()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$output = new ConsoleSectionOutput($this->stream, $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
|
||||||
|
$output->writeln('Foo');
|
||||||
|
$output->overwrite('Bar');
|
||||||
|
|
||||||
|
rewind($output->getStream());
|
||||||
|
$this->assertEquals('Foo'.PHP_EOL."\x1b[1A\x1b[0JBar".PHP_EOL, stream_get_contents($output->getStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOverwriteMultipleLines()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$output = new ConsoleSectionOutput($this->stream, $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
|
||||||
|
$output->writeln('Foo'.PHP_EOL.'Bar'.PHP_EOL.'Baz');
|
||||||
|
$output->overwrite('Bar');
|
||||||
|
|
||||||
|
rewind($output->getStream());
|
||||||
|
$this->assertEquals('Foo'.PHP_EOL.'Bar'.PHP_EOL.'Baz'.PHP_EOL.sprintf("\x1b[%dA", 3)."\x1b[0J".'Bar'.PHP_EOL, stream_get_contents($output->getStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddingMultipleSections()
|
||||||
|
{
|
||||||
|
$sections = array();
|
||||||
|
$output1 = new ConsoleSectionOutput($this->stream, $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
$output2 = new ConsoleSectionOutput($this->stream, $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
|
||||||
|
$this->assertCount(2, $sections);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleSectionsOutput()
|
||||||
|
{
|
||||||
|
$output = new StreamOutput($this->stream);
|
||||||
|
$sections = array();
|
||||||
|
$output1 = new ConsoleSectionOutput($output->getStream(), $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
$output2 = new ConsoleSectionOutput($output->getStream(), $sections, OutputInterface::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||||
|
|
||||||
|
$output1->writeln('Foo');
|
||||||
|
$output2->writeln('Bar');
|
||||||
|
|
||||||
|
$output1->overwrite('Baz');
|
||||||
|
$output2->overwrite('Foobar');
|
||||||
|
|
||||||
|
rewind($output->getStream());
|
||||||
|
$this->assertEquals('Foo'.PHP_EOL.'Bar'.PHP_EOL."\x1b[2A\x1b[0JBar".PHP_EOL."\x1b[1A\x1b[0JBaz".PHP_EOL.'Bar'.PHP_EOL."\x1b[1A\x1b[0JFoobar".PHP_EOL, stream_get_contents($output->getStream()));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user