8000 [Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawS… · symfony/symfony@cb08298 · GitHub
[go: up one dir, main page]

Skip to content

Commit cb08298

Browse files
ostroluckynicolas-grekas
authored andcommitted
[Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods
1 parent dca9325 commit cb08298

File tree

3 files changed

+109
-8
lines changed

3 files changed

+109
-8
lines changed

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.4.0
5+
-----
6+
7+
* added method `preventRedrawFasterThan()` and `forceRedrawSlowerThan()` on `ProgressBar`
8+
49
4.3.0
510
-----
611

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

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ final class ProgressBar
3232
private $format;
3333
private $internalFormat;
3434
private $redrawFreq = 1;
35+
private $writeCount;
36+
private $lastWriteTime;
37+
private $minSecondsBetweenRedraws = 0;
38+
private $maxSecondsBetweenRedraws = 1;
3539
private $output;
3640
private $step = 0;
3741
private $max;
@@ -51,7 +55,7 @@ final class ProgressBar
5155
* @param OutputInterface $output An OutputInterface instance
5256
* @param int $max Maximum steps (0 if unknown)
5357
*/
54-
public function __construct(OutputInterface $output, int $max = 0)
58+
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0)
5559
{
5660
if ($output instanceof ConsoleOutputInterface) {
5761
$output = $output->getErrorOutput();
@@ -61,12 +65,17 @@ public function __construct(OutputInterface $output, int $max = 0)
6165
$this->setMaxSteps($max);
6266
$this->terminal = new Terminal();
6367

68+
if (0 < $minSecondsBetweenRedraws) {
69+
$this->setRedrawFrequency(null);
70+
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
71+
}
72+
6473
if (!$this->output->isDecorated()) {
6574
// disable overwrite when output does not support ANSI codes.
6675
$this->overwrite = false;
6776

6877
// set a reasonable redraw frequency so output isn't flooded
69-
$this->setRedrawFrequency($max / 10);
78+
$this->setRedrawFrequency(null);
7079
}
7180

7281
$this->startTime = time();
@@ -183,6 +192,11 @@ public function getProgressPercent(): float
183192
return $this->percent;
184193
}
185194

195+
public function getBarOffset(): int
196+
{
197+
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? min(5, $this->barWidth / 15) * $this->writeCount : $this->step) % $this->barWidth);
198+
}
199+
186200
public function setBarWidth(int $size)
187201
{
188202
$this->barWidth = max(1, $size);
@@ -238,9 +252,19 @@ public function setFormat(string $format)
238252
*
239253
* @param int|float $freq The frequency in steps
240254
*/
241-
public function setRedrawFrequency(int $freq)
255+
public function setRedrawFrequency(?int $freq)
256+
{
257+
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
258+
}
259+
260+
public function preventRedrawFasterThan(float $intervalInSeconds): void
261+
{
262+
$this->minSecondsBetweenRedraws = $intervalInSeconds;
263+
}
264+
265+
public function forceRedrawSlowerThan(float $intervalInSeconds): void
242266
{
243-
$this->redrawFreq = max($freq, 1);
267+
$this->maxSecondsBetweenRedraws = $intervalInSeconds;
244268
}
245269

246270
/**
@@ -305,11 +329,27 @@ public function setProgress(int $step)
305329
$step = 0;
306330
}
307331

308-
$prevPeriod = (int) ($this->step / $this->redrawFreq);
309-
$currPeriod = (int) ($step / $this->redrawFreq);
332+
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
333+
$prevPeriod = (int) ($this->step / $redrawFreq);
334+
$currPeriod = (int) ($step / $redrawFreq);
310335
$this->step = $step;
311336
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
312-
if ($prevPeriod !== $currPeriod || $this->max === $step) {
337+
$timeInterval = microtime(true) - $this->lastWriteTime;
338+
339+
// Draw regardless of other limits
340+
if ($this->max === $step) {
341+
$this->display();
342+
343+
return;
344+
}
345+
346+
// Throttling
347+
if ($timeInterval < $this->minSecondsBetweenRedraws) {
348+
return;
349+
}
350+
351+
// Draw each step period, but not too late
352+
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
313353
$this->display();
314354
}
315355
}
@@ -413,8 +453,10 @@ private function overwrite(string $message): void
413453
}
414454

415455
$this->firstRun = false;
456+
$this->lastWriteTime = microtime(true);
416457

417458
$this->output->write($message);
459+
++$this->writeCount;
418460
}
419461

420462
private function determineBestFormat(): string
@@ -436,7 +478,7 @@ private static function initPlaceholderFormatters(): array
436478
{
437479
return [
438480
'bar' => function (self $bar, OutputInterface $output) {
439-
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
481+
$completeBars = $bar->getBarOffset();
440482
$display = str_repeat($bar->getBarCharacter(), $completeBars);
441483
if ($completeBars < $bar->getBarWidth()) {
442484
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,4 +944,58 @@ public function testBarWidthWithMultilineFormat()
944944
$this->assertEquals(5, $bar->getBarWidth(), stream_get_contents($output->getStream()));
945945
putenv('COLUMNS=120');
946946
}
947+
948+
public function testForceRedrawSlowerThan(): void
949+
{
950+
$bar = new ProgressBar($output = $this->getOutputStream());
951+
$bar->setRedrawFrequency(4); // disable step based redraws
952+
$bar->start();
953+
$bar->setProgress(1); // No treshold hit, no redraw
954+
$bar->forceRedrawSlowerThan(2);
955+
sleep(1);
956+
$bar->setProgress(2); // Still no redraw because redraw is forced after 2 seconds only
957+
sleep(1);
958+
$bar->setProgress(3); // 1+1 = 2 -> redraw finally
959+
$bar->setProgress(4); // step based redraw freq hit, redraw even without sleep
960+
$bar->setProgress(5); // No treshold hit, no redraw
961+
$bar->preventRedrawFasterThan(3);
962+
sleep(2);
963+
$bar->setProgress(6); // No redraw even though 2 seconds passed. Throttling has priority
964+
$bar->preventRedrawFasterThan(2);
965+
$bar->setProgress(7); // Throttling relaxed, draw
966+
967+
rewind($output->getStream());
968+
$this->assertEquals(
969+
' 0 [>---------------------------]'.
970+
$this->generateOutput(' 3 [--->------------------------]').
971+
$this->generateOutput(' 4 [---->-----------------------]').
972+
$this->generateOutput(' 7 [------->--------------------]'),
973+
stream_get_contents($output->getStream())
974+
);
975+
}
976+
977+
public function testPreventRedrawFasterThan()
978+
{
979+
$bar = new ProgressBar($output = $this->getOutputStream());
980+
$bar->setRedrawFrequency(1);
981+
$bar->preventRedrawFasterThan(1);
982+
$bar->start();
983+
$bar->setProgress(1); // Too fast, should not draw
984+
sleep(1);
985+
$bar->setProgress(2); // 1 second passed, draw
986+
$bar->preventRedrawFasterThan(2);
987+
sleep(1);
988+
$bar->setProgress(3); // 1 second passed but we changed threshold, should not draw
989+
sleep(1);
990+
$bar->setProgress(4); // 1+1 seconds = 2 seconds passed which conforms threshold, draw
991+
$bar->setProgress(5); // No treshold hit, no redraw
992+
993+
rewind($output->getStream());
994+
$this->assertEquals(
995+
' 0 [>---------------------------]'.
996+
$this->generateOutput(' 2 [-->-------------------------]').
997+
$this->generateOutput(' 4 [---->-----------------------]'),
998+
stream_get_contents($output->getStream())
999+
);
1000+
}
9471001
}

0 commit comments

Comments
 (0)
0