Skip to content

Commit b95995f

Browse files
committed
merged branch pkruithof/progress-helper-enhancements (PR #7300)
This PR was squashed before being merged into the master branch (closes #7300). Discussion ---------- [Console] Progress helper enhancements | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | ~ | License | MIT | Doc PR | ~ Two enhancements: 1. The progress bar clears the current line before writing the current progress. This can cause some flickering in the terminal. I've modified the write method to append whitespace to the line to be written, so it matches the previous line's length. 2. Added a `setCurrent` method to set the current progress. Rather than advancing by 1 or more steps, sometimes you want to just set the current state. For example if you are downloading a file, and a callback provides you with the current download status. A workaround for this could be to keep track of the previous event, calculate the difference, and advancing by the diff. But it's easier to just set the current progress. Sidenotes: * The [`overwrite`](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Console/Helper/ProgressHelper.php#L387) method copied documentation of the Output's [`write`](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Console/Output/Output.php#L139) method. The difference is, the overwrite method does not handle an array of messages. I've updated the documentation for this. * The helper uses `strlen` to calculate line lengths. This could cause a problem when using multibyte strings. I'd change it to `mb_strlen`, but I'm not sure if the `mb_string` extension is required by Symfony. Commits ------- 5ae76f0 [Console] Progress helper enhancements
2 parents 4285104 + f4f1569 commit b95995f

File tree

2 files changed

+142
-15
lines changed

2 files changed

+142
-15
lines changed

Helper/ProgressHelper.php

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ public function setRedrawFrequency($freq)
177177
/**
178178
* Starts the progress output.
179179
*
180-
* @param OutputInterface $output An Output instance
181-
* @param integer $max Maximum steps
180+
* @param OutputInterface $output An Output instance
181+
* @param integer $max Maximum steps
182182
*/
183183
public function start(OutputInterface $output, $max = null)
184184
{
@@ -236,6 +236,36 @@ public function advance($step = 1, $redraw = false)
236236
}
237237
}
238238

239+
/**
240+
* Sets the current progress.
241+
*
242+
* @param integer $current The current progress
243+
* @param Boolean $redraw Whether to redraw or not
244+
*
245+
* @throws \LogicException
246+
*/
247+
public function setCurrent($current, $redraw = false)
248+
{
249+
if (null === $this->startTime) {
250+
throw new \LogicException('You must start the progress bar before calling setCurrent().');
251+
}
252+
253+
$current = (int) $current;
254+
255+
if ($current < $this->current) {
256+
throw new \LogicException('You can\'t regress the progress bar');
257+
}
258+
259+
if ($this->current === 0) {
260+
$redraw = true;
261+
}
262+
263+
$this->current = $current;
264+
if ($redraw || $this->current % $this->redrawFreq === 0) {
265+
$this->display();
266+
}
267+
}
268+
239269
/**
240270
* Outputs the current progress string.
241271
*
@@ -289,7 +319,7 @@ private function initialize()
289319
}
290320

291321
if ($this->max > 0) {
292-
$this->widths['max'] = strlen($this->max);
322+
$this->widths['max'] = $this->getLength($this->max);
293323
$this->widths['current'] = $this->widths['max'];
294324
} else {
295325
$this->barCharOriginal = $this->barChar;
@@ -325,7 +355,7 @@ private function generate($finish = false)
325355
}
326356
}
327357

328-
$emptyBars = $this->barWidth - $completeBars - strlen($this->progressChar);
358+
$emptyBars = $this->barWidth - $completeBars - $this->getLength($this->progressChar);
329359
$bar = str_repeat($this->barChar, $completeBars);
330360
if ($completeBars < $this->barWidth) {
331361
$bar .= $this->progressChar;
@@ -384,21 +414,41 @@ private function humaneTime($secs)
384414
* Overwrites a previous message to the output.
385415
*
386416
* @param OutputInterface $output An Output instance
387-
* @param string|array $messages The message as an array of lines or a single string
417+
* @param string $messages The message
388418
*/
389-
private function overwrite(OutputInterface $output, $messages)
419+
private function overwrite(OutputInterface $output, $message)
390420
{
421+
$length = $this->getLength($message);
422+
423+
// append whitespace to match the last line's length
424+
if (($this->lastMessagesLength !== null) && ($this->lastMessagesLength > $length)) {
425+
$message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
426+
}
427+
391428
// carriage return
392429
$output->write("\x0D");
393-
if ($this->lastMessagesLength!==null) {
394-
// clear the line with the length of the last message
395-
$output->write(str_repeat("\x20", $this->lastMessagesLength));
396-
// carriage return
397-
$output->write("\x0D");
430+
$output->write($message);
431+
432+
$this->lastMessagesLength = $this->getLength($message);
433+
}
434+
435+
/**
436+
* Wrapper arround strlen: uses multi-byte function if available
437+
*
438+
* @param string $string
439+
* @return integer
440+
*/
441+
private function getLength($string)
442+
{
443+
if (!function_exists('mb_strlen')) {
444+
return strlen($string);
445+
}
446+
447+
if (false === $encoding = mb_detect_encoding($string)) {
448+
return strlen($string);
398449
}
399-
$output->write($messages);
400450

401-
$this->lastMessagesLength=strlen($messages);
451+
return mb_strlen($string, $encoding);
402452
}
403453

404454
/**

Tests/Helper/ProgressHelperTest.php

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,83 @@ public function testPercent()
7474
$this->assertEquals($this->generateOutput(' 0/50 [>---------------------------] 0%').$this->generateOutput(' 1/50 [>---------------------------] 2%').$this->generateOutput(' 2/50 [=>--------------------------] 4%'), stream_get_contents($output->getStream()));
7575
}
7676

77+
public function testOverwriteWithShorterLine()
78+
{
79+
$progress = new ProgressHelper();
80+
$progress->setFormat(' %current%/%max% [%bar%] %percent%%');
81+
$progress->start($output = $this->getOutputStream(), 50);
82+
$progress->display();
83+
$progress->advance();
84+
85+
// set shorter format
86+
$progress->setFormat(' %current%/%max% [%bar%]');
87+
$progress->advance();
88+
89+
rewind($output->getStream());
90+
$this->assertEquals(
91+
$this->generateOutput(' 0/50 [>---------------------------] 0%') .
92+
$this->generateOutput(' 1/50 [>---------------------------] 2%') .
93+
$this->generateOutput(' 2/50 [=>--------------------------] '),
94+
stream_get_contents($output->getStream())
95+
);
96+
}
97+
98+
public function testSetCurrentProgress()
99+
{
100+
$progress = new ProgressHelper();
101+
$progress->start($output = $this->getOutputStream(), 50);
102+
$progress->display();
103+
$progress->advance();
104+
$progress->setCurrent(15);
105+
$progress->setCurrent(25);
106+
107+
rewind($output->getStream());
108+
$this->assertEquals(
109+
$this->generateOutput(' 0/50 [>---------------------------] 0%') .
110+
$this->generateOutput(' 1/50 [>---------------------------] 2%') .
111+
$this->generateOutput(' 15/50 [========>-------------------] 30%') .
112+
$this->generateOutput(' 25/50 [==============>-------------] 50%'),
113+
stream_get_contents($output->getStream())
114+
);
115+
}
116+
117+
/**
118+
* @expectedException \LogicException
119+
* @expectedExceptionMessage You must start the progress bar
120+
*/
121+
public function testSetCurrentBeforeStarting()
122+
{
123+
$progress = new ProgressHelper();
124+
$progress->setCurrent(15);
125+
}
126+
127+
/**
128+
* @expectedException \LogicException
129+
* @expectedExceptionMessage You can't regress the progress bar
130+
*/
131+
public function testRegressProgress()
132+
{
133+
$progress = new ProgressHelper();
134+
$progress->start($output = $this->getOutputStream(), 50);
135+
$progress->setCurrent(15);
136+
$progress->setCurrent(10);
137+
}
138+
139+
public function testMultiByteSupport()
140+
{
141+
if (!function_exists('mb_strlen') || (false === $encoding = mb_detect_encoding(''))) {
142+
$this->markTestSkipped('The mbstring extension is needed for multi-byte support');
143+
}
144+
145+
$progress = new ProgressHelper();
146+
$progress->start($output = $this->getOutputStream());
147+
$progress->setBarCharacter('');
148+
$progress->advance(3);
149+
150+
rewind($output->getStream());
151+
$this->assertEquals($this->generateOutput(' 3 [■■■>------------------------]'), stream_get_contents($output->getStream()));
152+
}
153+
77154
protected function getOutputStream()
78155
{
79156
return new StreamOutput(fopen('php://memory', 'r+', false));
@@ -86,10 +163,10 @@ protected function generateOutput($expected)
86163
$expectedout = $expected;
87164

88165
if ($this->lastMessagesLength !== null) {
89-
$expectedout = str_repeat("\x20", $this->lastMessagesLength)."\x0D".$expected;
166+
$expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
90167
}
91168

92-
$this->lastMessagesLength = strlen($expected);
169+
$this->lastMessagesLength = strlen($expectedout);
93170

94171
return "\x0D".$expectedout;
95172
}

0 commit comments

Comments
 (0)