diff --git a/src/Symfony/Component/Console/Helper/ProgressHelper.php b/src/Symfony/Component/Console/Helper/ProgressHelper.php index 25736af634e23..5f145bb4094cd 100644 --- a/src/Symfony/Component/Console/Helper/ProgressHelper.php +++ b/src/Symfony/Component/Console/Helper/ProgressHelper.php @@ -177,8 +177,8 @@ public function setRedrawFrequency($freq) /** * Starts the progress output. * - * @param OutputInterface $output An Output instance - * @param integer $max Maximum steps + * @param OutputInterface $output An Output instance + * @param integer $max Maximum steps */ public function start(OutputInterface $output, $max = null) { @@ -236,6 +236,36 @@ public function advance($step = 1, $redraw = false) } } + /** + * Sets the current progress. + * + * @param integer $current The current progress + * @param Boolean $redraw Whether to redraw or not + * + * @throws \LogicException + */ + public function setCurrent($current, $redraw = false) + { + if (null === $this->startTime) { + throw new \LogicException('You must start the progress bar before calling setCurrent().'); + } + + $current = (int) $current; + + if ($current < $this->current) { + throw new \LogicException('You can\'t regress the progress bar'); + } + + if ($this->current === 0) { + $redraw = true; + } + + $this->current = $current; + if ($redraw || $this->current % $this->redrawFreq === 0) { + $this->display(); + } + } + /** * Outputs the current progress string. * @@ -289,7 +319,7 @@ private function initialize() } if ($this->max > 0) { - $this->widths['max'] = strlen($this->max); + $this->widths['max'] = $this->getLength($this->max); $this->widths['current'] = $this->widths['max']; } else { $this->barCharOriginal = $this->barChar; @@ -325,7 +355,7 @@ private function generate($finish = false) } } - $emptyBars = $this->barWidth - $completeBars - strlen($this->progressChar); + $emptyBars = $this->barWidth - $completeBars - $this->getLength($this->progressChar); $bar = str_repeat($this->barChar, $completeBars); if ($completeBars < $this->barWidth) { $bar .= $this->progressChar; @@ -384,21 +414,41 @@ private function humaneTime($secs) * Overwrites a previous message to the output. * * @param OutputInterface $output An Output instance - * @param string|array $messages The message as an array of lines or a single string + * @param string $messages The message */ - private function overwrite(OutputInterface $output, $messages) + private function overwrite(OutputInterface $output, $message) { + $length = $this->getLength($message); + + // append whitespace to match the last line's length + if (($this->lastMessagesLength !== null) && ($this->lastMessagesLength > $length)) { + $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + } + // carriage return $output->write("\x0D"); - if ($this->lastMessagesLength!==null) { - // clear the line with the length of the last message - $output->write(str_repeat("\x20", $this->lastMessagesLength)); - // carriage return - $output->write("\x0D"); + $output->write($message); + + $this->lastMessagesLength = $this->getLength($message); + } + + /** + * Wrapper arround strlen: uses multi-byte function if available + * + * @param string $string + * @return integer + */ + private function getLength($string) + { + if (!function_exists('mb_strlen')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); } - $output->write($messages); - $this->lastMessagesLength=strlen($messages); + return mb_strlen($string, $encoding); } /** diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php index b5560263f6a07..abb8d0b681566 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php @@ -74,6 +74,83 @@ public function testPercent() $this->assertEquals($this->generateOutput(' 0/50 [>---------------------------] 0%').$this->generateOutput(' 1/50 [>---------------------------] 2%').$this->generateOutput(' 2/50 [=>--------------------------] 4%'), stream_get_contents($output->getStream())); } + public function testOverwriteWithShorterLine() + { + $progress = new ProgressHelper(); + $progress->setFormat(' %current%/%max% [%bar%] %percent%%'); + $progress->start($output = $this->getOutputStream(), 50); + $progress->display(); + $progress->advance(); + + // set shorter format + $progress->setFormat(' %current%/%max% [%bar%]'); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%') . + $this->generateOutput(' 1/50 [>---------------------------] 2%') . + $this->generateOutput(' 2/50 [=>--------------------------] '), + stream_get_contents($output->getStream()) + ); + } + + public function testSetCurrentProgress() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->display(); + $progress->advance(); + $progress->setCurrent(15); + $progress->setCurrent(25); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%') . + $this->generateOutput(' 1/50 [>---------------------------] 2%') . + $this->generateOutput(' 15/50 [========>-------------------] 30%') . + $this->generateOutput(' 25/50 [==============>-------------] 50%'), + stream_get_contents($output->getStream()) + ); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You must start the progress bar + */ + public function testSetCurrentBeforeStarting() + { + $progress = new ProgressHelper(); + $progress->setCurrent(15); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You can't regress the progress bar + */ + public function testRegressProgress() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->setCurrent(15); + $progress->setCurrent(10); + } + + public function testMultiByteSupport() + { + if (!function_exists('mb_strlen') || (false === $encoding = mb_detect_encoding('■'))) { + $this->markTestSkipped('The mbstring extension is needed for multi-byte support'); + } + + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->setBarCharacter('■'); + $progress->advance(3); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 3 [■■■>------------------------]'), stream_get_contents($output->getStream())); + } + protected function getOutputStream() { return new StreamOutput(fopen('php://memory', 'r+', false)); @@ -86,10 +163,10 @@ protected function generateOutput($expected) $expectedout = $expected; if ($this->lastMessagesLength !== null) { - $expectedout = str_repeat("\x20", $this->lastMessagesLength)."\x0D".$expected; + $expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); } - $this->lastMessagesLength = strlen($expected); + $this->lastMessagesLength = strlen($expectedout); return "\x0D".$expectedout; }