Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e6573b7

Browse files
committed
Add section output to console
1 parent d79c528 commit e6573b7

File tree

8 files changed

+530
-8
lines changed

8 files changed

+530
-8
lines changed

src/Symfony/Component/Console/Helper/ProgressBar.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Console\Helper;
1313

1414
use Symfony\Component\Console\Output\ConsoleOutputInterface;
15+
use Symfony\Component\Console\Output\ConsoleSectionOutput;
1516
use Symfony\Component\Console\Output\OutputInterface;
1617
use Symfony\Component\Console\Exception\LogicException;
1718
use Symfony\Component\Console\Terminal;
@@ -375,15 +376,20 @@ private function overwrite(string $message): void
375376
{
376377
if ($this->overwrite) {
377378
if (!$this->firstRun) {
378-
// Move the cursor to the beginning of the line
379-
$this->output->write("\x0D");
379+
if ($this->output instanceof ConsoleSectionOutput) {
380+
$lines = floor(Helper::strlen($message) / $this->terminal->getWidth()) + $this->formatLineCount + 1;
381+
$this->output->clear($lines);
382+
} else {
383+
// Move the cursor to the beginning of the line
384+
$this->output->write("\x0D");
380385

381-
// Erase the line
382-
$this->output->write("\x1B[2K");
386+
// Erase the line
387+
$this->output->write("\x1B[2K");
383388

384-
// Erase previous lines
385-
if ($this->formatLineCount > 0) {
386-
$this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
389+
// Erase previous lines
390+
if ($this->formatLineCount > 0) {
391+
$this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
392+
}
387393
}
388394
}
389395
} elseif ($this->step > 0) {

src/Symfony/Component/Console/Helper/Table.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
namespace Symfony\Component\Console\Helper;
1313

14+
use Symfony\Component\Console\Output\ConsoleSectionOutput;
1415
use Symfony\Component\Console\Output\OutputInterface;
1516
use Symfony\Component\Console\Exception\InvalidArgumentException;
17+
use Symfony\Component\Console\Exception\RuntimeException;
1618

1719
/**
1820
* Provides helpers to display a table.
@@ -70,6 +72,8 @@ class Table
7072

7173
private static $styles;
7274

75+
private $rendered = false;
76+
7377
public function __construct(OutputInterface $output)
7478
{
7579
$this->output = $output;
@@ -252,6 +256,25 @@ public function addRow($row)
252256
return $this;
253257
}
254258

259+
/**
260+
* Add a row to the table, and re-render the table.
261+
*/
262+
public function appendRow($row): self
263+
{
264+
if (!$this->output instanceof ConsoleSectionOutput) {
265+
throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
266+
}
267+
268+
if ($this->rendered) {
269+
$this->output->clear($this->calculateRowCount());
270+
}
271+
272+
$this->addRow($row);
273+
$this->render();
274+
275+
return $this;
276+
}
277+
255278
public function setRow($column, array $row)
256279
{
257280
$this->rows[$column] = $row;
@@ -311,6 +334,7 @@ public function render()
311334
$this->renderRowSeparator();
312335

313336
$this->cleanup();
337+
$this->rendered = true;
314338
}
315339

316340
/**
@@ -445,6 +469,19 @@ private function buildTableRows($rows)
445469
});
446470
}
447471

472+
private function calculateRowCount(): int
473+
{
474+
$numberOfRows = count(iterator_to_array($this->buildTableRows(array_merge($this->headers, array(new TableSeparator()), $this->rows))));
475+
476+
if ($this->headers) {
477+
++$numberOfRows; // Add row for header separator
478+
}
479+
480+
++$numberOfRows; // Add row for footer separator
481+
482+
return $numberOfRows;
483+
}
484+
448485
/**
449486
* fill rows that contains rowspan > 1.
450487
*

src/Symfony/Component/Console/Output/ConsoleOutput.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
3131
{
3232
private $stderr;
33+
private $consoleSectionOutputs = array();
3334

3435
/**
3536
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
@@ -48,6 +49,14 @@ public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decor
4849
}
4950
}
5051

52+
/**
53+
* Creates a new output section.
54+
*/
55+
public function section(): ConsoleSectionOutput
56+
{
57+
return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
58+
}
59+
5160
/**
5261
* {@inheritdoc}
5362
*/

src/Symfony/Component/Console/Output/ConsoleOutputInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313

1414
/**
1515
* ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
16-
* This adds information about stderr output stream.
16+
* This adds information about stderr and section output stream.
1717
*
1818
* @author Dariusz Górecki <[email protected]>
19+
*
20+
* @method ConsoleSectionOutput section() Creates a new output section
1921
*/
2022
interface ConsoleOutputInterface extends OutputInterface
2123
{
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Output;
13+
14+
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
15+
use Symfony\Component\Console\Helper\Helper;
16+
use Symfony\Component\Console\Terminal;
17+
18+
/**
19+
* @author Pierre du Plessis <[email protected]>
20+
* @author Gabriel Ostrolucký <[email protected]>
21+
*/
22+
class ConsoleSectionOutput extends StreamOutput
23+
{
24+
private $content = array();
25+
private $lines = 0;
26+
private $sections;
27+
private $terminal;
28+
29+
/**
30+
* @param resource $stream
31+
* @param ConsoleSectionOutput[] $sections
32+
*/
33+
public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
34+
{
35+
parent::__construct($stream, $verbosity, $decorated, $formatter);
36+
array_unshift($sections, $this);
37+
$this->sections = &$sections;
38+
$this->terminal = new Terminal();
39+
}
40+
41+
/**
42+
* Clears previous output for this section.
43+
*
44+
* @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
45+
*/
46+
public function clear(int $lines = null)
47+
{
48+
if (empty($this->content) || !$this->isDecorated()) {
49+
return;
50+
}
51+
52+
if ($lines) {
53+
\array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
54+
} else {
55+
$lines = $this->lines;
56+
$this->content = array();
57+
}
58+
59+
$this->lines -= $lines;
60+
61+
parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
62+
}
63+
64+
/**
65+
* Overwrites the previous output with a new message.
66+
*
67+
* @param array|string $message
68+
*/
69+
public function overwrite($message)
70+
{
71+
$this->clear();
72+
$this->writeln($message);
73+
}
74+
75+
public function getContent(): string
76+
{
77+
return implode('', $this->content);
78+
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
protected function doWrite($message, $newline)
84+
{
85+
if (!$this->isDecorated()) {
86+
return parent::doWrite($message, $newline);
87+
}
88+
89+
$erasedContent = $this->popStreamContentUntilCurrentSection();
90+
91+
foreach (explode(PHP_EOL, $message) as $lineContent) {
92+
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
93+
$this->content[] = $lineContent;
94+
$this->content[] = PHP_EOL;
95+
}
96+
97+
parent::doWrite($message, true);
98+
parent::doWrite($erasedContent, false);
99+
}
100+
101+
/**
102+
* At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
103+
* current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
104+
*/
105+
private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
106+
{
107+
$numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
108+
$erasedContent = array();
109+
110+
foreach ($this->sections as $section) {
111+
if ($section === $this) {
112+
break;
113+
}
114+
115+
$numberOfLinesToClear += $section->lines;
116+
$erasedContent[] = $section->getContent();
117+
}
118+
119+
if ($numberOfLinesToClear > 0) {
120+
// Move cursor up n lines
121+
parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false);
122+
// erase to end of screen
123+
parent::doWrite("\x1b[0J", false);
124+
}
125+
126+
return implode('', array_reverse($erasedContent));
127+
}
128+
129+
private function getDisplayLength(string $text): string
130+
{
131+
return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text));
132+
}
133+
}

src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
namespace Symfony\Component\Console\Tests\Helper;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Console\Formatter\OutputFormatter;
1516
use Symfony\Component\Console\Helper\ProgressBar;
1617
use Symfony\Component\Console\Helper\Helper;
18+
use Symfony\Component\Console\Output\ConsoleSectionOutput;
1719
use Symfony\Component\Console\Output\StreamOutput;
1820

1921
/**
@@ -310,6 +312,88 @@ public function testOverwriteWithShorterLine()
310312
);
311313
}
312314

315+
public function testOverwriteWithSectionOutput()
316+
{
317+
$sections = array();
318+
$stream = $this->getOutputStream(true);
319+
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
320+
321+
$bar = new ProgressBar($output, 50);
322+
$bar->start();
323+
$bar->display();
324+
$bar->advance();
325+
$bar->advance();
326+
327+
rewind($output->getStream());
328+
$this->assertEquals(
329+
' 0/50 [>---------------------------] 0%'.PHP_EOL.
330+
"\x1b[1A\x1b[0J".' 0/50 [>---------------------------] 0%'.PHP_EOL.
331+
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
332+
"\x1b[1A\x1b[0J".' 2/50 [=>--------------------------] 4%'.PHP_EOL,
333+
stream_get_contents($output->getStream())
334+
);
335+
}
336+
337+
public function testOverwriteMultipleProgressBarsWithSectionOutputs()
338+
{
339+
$sections = array();
340+
$stream = $this->getOutputStream(true);
341+
$output1 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
342+
$output2 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
343+
344+
$progress = new ProgressBar($output1, 50);
345+
$progress2 = new ProgressBar($output2, 50);
346+
347+
$progress->start();
348+
$progress2->start();
349+
350+
$progress2->advance();
351+
$progress->advance();
352+
353+
rewind($stream->getStream());
354+
355+
$this->assertEquals(
356+
' 0/50 [>---------------------------] 0%'.PHP_EOL.
357+
' 0/50 [>---------------------------] 0%'.PHP_EOL.
358+
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
359+
"\x1b[2A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
360+
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
361+
' 1/50 [>---------------------------] 2%'.PHP_EOL,
362+
stream_get_contents($stream->getStream())
363+
);
364+
}
365+
366+
public function testMultipleSectionsWithCustomFormat()
367+
{
368+
$sections = array();
369+
$stream = $this->getOutputStream(true);
370+
$output1 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
371+
$output2 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
372+
373+
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.');
374+
375+
$progress = new ProgressBar($output1, 50);
376+
$progress2 = new ProgressBar($output2, 50);
377+
$progress2->setFormat('test');
378+
379+
$progress->start();
380+
$progress2->start();
381+
382+
$progress->advance();
383+
$progress2->advance();
384+
385+
rewind($stream->getStream());
386+
387+
$this->assertEquals(' 0/50 [>---------------------------] 0%'.PHP_EOL.
388+
' 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.
389+
"\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.
390+
"\x1b[3A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
391+
' 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.
392+
"\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,
393+
stream_get_contents($stream->getStream())
394+
);
395+
}
396+
313397
public function testStartWithMax()
314398
{
315399
$bar = new ProgressBar($output = $this->getOutputStream());

0 commit comments

Comments
 (0)