Skip to content

[Console] Added support for labels to ProgressHelper #10187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
CHANGELOG
=========

* added support for labels to ProgressHelper

2.5.0
-----

Expand Down
110 changes: 96 additions & 14 deletions src/Symfony/Component/Console/Helper/ProgressHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class ProgressHelper extends Helper
const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]';
const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%';

const LABEL_TOP = 1;
const LABEL_BOTTOM = 2;
const LABEL_LEFT = STR_PAD_LEFT;
const LABEL_CENTER = STR_PAD_BOTH;
const LABEL_RIGHT = STR_PAD_RIGHT;

// options
private $barWidth = 28;
private $barChar = '=';
Expand All @@ -37,6 +43,18 @@ class ProgressHelper extends Helper
private $format = null;
private $redrawFreq = 1;

// Label options
private $label = '';
private $labelPosition = self::LABEL_BOTTOM;
private $labelAlignment = self::LABEL_CENTER;

/**
* Whether or not to show the label based on verbosity level.
*
* @var boolean
*/
private $showLabel = true;

private $lastMessagesLength;
private $barCharOriginal;

Expand Down Expand Up @@ -115,6 +133,13 @@ class ProgressHelper extends Helper
array(604800, 'days', 86400),
);

/**
* Whether or not overwrite() has yet been called.
*
* @var boolean
*/
private $haveWritten = false;

/**
* Sets the progress bar width.
*
Expand Down Expand Up @@ -175,6 +200,30 @@ public function setRedrawFrequency($freq)
$this->redrawFreq = (int) $freq;
}

/**
* Add a label to the progress bar.
*
* @param string $label The label to add
* @param int $position Where to show the label
* @param int $align How to align the label
*/
public function setLabel($label, $position = self::LABEL_BOTTOM, $align = self::LABEL_CENTER)
{
$this->label = $label;
$this->labelPosition = $position;
$this->labelAlignment = $align;
}

/**
* Update current label.
*
* @param string $label Label's text
*/
public function updateLabel($label)
{
$this->label = $label;
}

/**
* Starts the progress output.
*
Expand All @@ -199,6 +248,7 @@ public function start(OutputInterface $output, $max = null)
if ($this->max > 0) {
$this->format = self::FORMAT_QUIET;
}
$this->showLabel = false;
break;
case OutputInterface::VERBOSITY_VERBOSE:
case OutputInterface::VERBOSITY_VERY_VERBOSE:
Expand Down Expand Up @@ -284,9 +334,31 @@ public function display($finish = false)
foreach ($this->generate($finish) as $name => $value) {
$message = str_replace("%{$name}%", $value, $message);
}
$this->overwrite($this->output, $message);

$length = $this->strlen($message);

// append whitespace to match the last line's length
if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
$message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
}

$this->lastMessagesLength = $this->strlen($message);

$lines[] = $message;

if (true === $this->showLabel && !empty($this->label)) {
$label = str_pad($this->label, $this->lastMessagesLength, "\x20", $this->labelAlignment);
if (self::LABEL_TOP === $this->labelPosition) {
array_unshift($lines, $label);
} else {
$lines[] = $label;
}
}

$this->overwrite($this->output, $lines);
}


/**
* Removes the progress bar from the current line.
*
Expand All @@ -296,7 +368,12 @@ public function display($finish = false)
*/
public function clear()
{
$this->overwrite($this->output, '');
$emptyLine = str_repeat("\x20", $this->lastMessagesLength);
$lines = array($emptyLine);
if (true === $this->showLabel && !empty($this->label)) {
$lines[] = $emptyLine;
}
$this->overwrite($this->output, $lines);
}

/**
Expand Down Expand Up @@ -427,22 +504,27 @@ private function humaneTime($secs)
* Overwrites a previous message to the output.
*
* @param OutputInterface $output An Output instance
* @param string $message The message
* @param array $lines Lines to output
*/
private function overwrite(OutputInterface $output, $message)
private function overwrite(OutputInterface $output, array $lines)
{
$length = $this->strlen($message);

// append whitespace to match the last line's length
if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
$message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
$numberOfLines = count($lines);
$glue = $numberOfLines > 1 ? PHP_EOL : '';
if (true === $this->haveWritten) {
// carriage return
$output->write("\x0D");

// Move the cursor up if we are outputting more than one line
if ($numberOfLines > 1) {
// We move up number of lines - 1 because \r removes a line
$line = $numberOfLines - 1;
$output->write("\033[{$line}A");
}
} else {
$this->haveWritten = true;
}

// carriage return
$output->write("\x0D");
$output->write($message);

$this->lastMessagesLength = $this->strlen($message);
$output->write(join($glue, $lines));
}

/**
Expand Down
80 changes: 71 additions & 9 deletions src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public function testClear()

rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 25/50 [==============>-------------] 50%') . $this->generateOutput(''),
$this->generateOutput(' 25/50 [==============>-------------] 50%') . $this->generateOutput(str_repeat("\x20", 42)),
stream_get_contents($output->getStream())
);
}
Expand All @@ -202,23 +202,85 @@ public function testNonDecoratedOutput()
$this->assertEquals('', stream_get_contents($output->getStream()));
}

public function testLabelDefault()
{
$progress = new ProgressHelper();
$progress->setLabel('label');
$progress->start($output = $this->getOutputStream(), 10);
$progress->display();

rewind($output->getStream());
$this->assertEquals($this->generateOutput(' 0/10 [>---------------------------] 0%'.PHP_EOL.' label '), stream_get_contents($output->getStream()));
}

public function testLabelTop()
{
$progress = new ProgressHelper();
$progress->setLabel('label', ProgressHelper::LABEL_TOP);
$progress->start($output = $this->getOutputStream(), 10);
$progress->display();

rewind($output->getStream());
$this->assertEquals($this->generateOutput(' label '.PHP_EOL.' 0/10 [>---------------------------] 0%'), stream_get_contents($output->getStream()));
}

public function testLabelAlignLeft()
{
$progress = new ProgressHelper();
$progress->setLabel('label', ProgressHelper::LABEL_BOTTOM, ProgressHelper::LABEL_LEFT);
$progress->start($output = $this->getOutputStream(), 10);
$progress->display();

rewind($output->getStream());
$this->assertEquals($this->generateOutput(' 0/10 [>---------------------------] 0%'.PHP_EOL.' label'), stream_get_contents($output->getStream()));
}

public function testLabelAlignRight()
{
$progress = new ProgressHelper();
$progress->setLabel('label', ProgressHelper::LABEL_BOTTOM, ProgressHelper::LABEL_RIGHT);
$progress->start($output = $this->getOutputStream(), 10);

$progress->display();

rewind($output->getStream());
$this->assertEquals($this->generateOutput(' 0/10 [>---------------------------] 0%'.PHP_EOL.'label '), stream_get_contents($output->getStream()));
}

public function testLabelUpdate()
{
$progress = new ProgressHelper();
$progress->setLabel('label');
$progress->start($output = $this->getOutputStream(), 10);
$progress->display();
$progress->updateLabel('label 1');
$progress->advance();


rewind($output->getStream());
$this->assertEquals($this->generateOutput(' 0/10 [>---------------------------] 0%'.PHP_EOL.' label ').$this->generateOutput(' 1/10 [==>-------------------------] 10%'.PHP_EOL.' label 1 '), stream_get_contents($output->getStream()));
}

protected function getOutputStream($decorated = true)
{
return new StreamOutput(fopen('php://memory', 'r+', false), StreamOutput::VERBOSITY_NORMAL, $decorated);
}

protected $lastMessagesLength;
protected $haveWritten = false;

protected function generateOutput($expected)
{
$expectedout = $expected;

if ($this->lastMessagesLength !== null) {
$expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
$expectedout = '';
if (true === $this->haveWritten) {
$expectedout .= "\x0D";
if ($count = substr_count($expected, PHP_EOL)) {
$expectedout .= "\033[{$count}A";
}
} else {
$this->haveWritten = true;
}
$expectedout .= $expected;

$this->lastMessagesLength = strlen($expectedout);

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