This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/src/Symfony/Component/Console/Helper/ProgressBar.php

529 lines
14 KiB
PHP
Raw Normal View History

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* The ProgressBar provides helpers to display progress output.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Chris Jones <leeked@gmail.com>
*/
class ProgressBar
{
// options
private $barWidth = 28;
private $barChar = '=';
private $emptyBarChar = '-';
private $progressChar = '>';
private $format = null;
private $redrawFreq = 1;
/**
* @var OutputInterface
*/
private $output;
private $step;
private $max;
private $startTime;
private $stepWidth;
private $percent;
private $lastMessagesLength;
private $barCharOriginal;
private $formatLineCount;
private $messages;
2014-03-01 17:08:18 +00:00
private static $formatters;
private static $formats;
/**
* Constructor.
*
* @param OutputInterface $output An OutputInterface instance
* @param integer $max Maximum steps (0 if unknown)
*/
public function __construct(OutputInterface $output, $max = 0)
{
// Disabling output when it does not support ANSI codes as it would result in a broken display anyway.
$this->output = $output->isDecorated() ? $output : new NullOutput();
$this->max = (int) $max;
$this->stepWidth = $this->max > 0 ? Helper::strlen($this->max) : 4;
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
if (!self::$formats) {
self::$formats = self::initFormats();
}
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable
*/
public static function setPlaceholderFormatterDefinition($name, $callable)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
2014-03-01 17:08:18 +00:00
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition($name)
{
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
}
/**
* Sets a format for a given name.
*
* This method also allow you to override an existing format.
*
* @param string $name The format name
* @param string $format A format string
*/
public static function setFormatDefinition($name, $format)
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
self::$formats[$name] = $format;
}
2014-03-01 17:08:18 +00:00
/**
* Gets the format for a given name.
*
* @param string $name The format name
*
* @return string|null A format string
*/
public static function getFormatDefinition($name)
{
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
}
public function setMessage($message, $name = 'message')
{
$this->messages[$name] = $message;
}
public function getMessage($name = 'message')
{
return $this->messages[$name];
}
/**
* Gets the progress bar start time.
*
2014-03-01 17:08:18 +00:00
* @return integer The progress bar start time
*/
public function getStartTime()
{
return $this->startTime;
}
/**
* Gets the progress bar maximal steps.
*
2014-03-01 17:08:18 +00:00
* @return integer The progress bar max steps
*/
public function getMaxSteps()
{
return $this->max;
}
/**
* Gets the progress bar step.
*
2014-03-01 17:08:18 +00:00
* @return integer The progress bar step
*/
public function getStep()
{
return $this->step;
}
/**
* Gets the progress bar step width.
*
2014-03-01 17:08:18 +00:00
* @return integer The progress bar step width
*/
public function getStepWidth()
{
return $this->stepWidth;
}
/**
* Gets the current progress bar percent.
*
2014-03-01 17:08:18 +00:00
* @return integer The current progress bar percent
*/
public function getProgressPercent()
{
return $this->percent;
}
/**
* Sets the progress bar width.
*
2014-03-01 17:08:18 +00:00
* @param integer $size The progress bar size
*/
public function setBarWidth($size)
{
$this->barWidth = (int) $size;
}
/**
* Gets the progress bar width.
*
2014-03-01 17:08:18 +00:00
* @return integer The progress bar size
*/
public function getBarWidth()
{
return $this->barWidth;
}
/**
* Sets the bar character.
*
* @param string $char A character
*/
public function setBarCharacter($char)
{
$this->barChar = $char;
}
/**
* Gets the bar character.
*
* @return string A character
*/
public function getBarCharacter()
{
return $this->barChar;
}
/**
* Sets the empty bar character.
*
* @param string $char A character
*/
public function setEmptyBarCharacter($char)
{
$this->emptyBarChar = $char;
}
/**
* Gets the empty bar character.
*
* @return string A character
*/
public function getEmptyBarCharacter()
{
return $this->emptyBarChar;
}
/**
* Sets the progress bar character.
*
* @param string $char A character
*/
public function setProgressCharacter($char)
{
$this->progressChar = $char;
}
/**
* Gets the progress bar character.
*
* @return string A character
*/
public function getProgressCharacter()
{
return $this->progressChar;
}
/**
* Sets the progress bar format.
*
* @param string $format The format
*/
public function setFormat($format)
{
$this->format = isset(self::$formats[$format]) ? self::$formats[$format] : $format;
$this->formatLineCount = substr_count($this->format, "\n");
}
/**
* Sets the redraw frequency.
*
2014-03-01 17:08:18 +00:00
* @param integer $freq The frequency in steps
*/
public function setRedrawFrequency($freq)
{
$this->redrawFreq = (int) $freq;
}
/**
* Starts the progress output.
*/
public function start()
{
$this->startTime = time();
$this->step = 0;
$this->percent = 0;
$this->lastMessagesLength = 0;
$this->barCharOriginal = '';
if (null === $this->format) {
$this->setFormat($this->determineBestFormat());
}
if (!$this->max) {
$this->barCharOriginal = $this->barChar;
$this->barChar = $this->emptyBarChar;
}
$this->display();
}
/**
* Advances the progress output X steps.
*
* @param integer $step Number of steps to advance
*
* @throws \LogicException
*/
public function advance($step = 1)
{
$this->setCurrent($this->step + $step);
}
/**
* Sets the current progress.
*
* @param integer $step The current progress
*
* @throws \LogicException
*/
public function setCurrent($step)
{
if (null === $this->startTime) {
throw new \LogicException('You must start the progress bar before calling setCurrent().');
}
$step = (int) $step;
if ($step < $this->step) {
throw new \LogicException('You can\'t regress the progress bar.');
}
if ($this->max > 0 && $step > $this->max) {
throw new \LogicException('You can\'t advance the progress bar past the max value.');
}
$prevPeriod = intval($this->step / $this->redrawFreq);
$currPeriod = intval($step / $this->redrawFreq);
$this->step = $step;
$this->percent = $this->max > 0 ? (float) $this->step / $this->max : 0;
if ($prevPeriod !== $currPeriod || $this->max === $step) {
$this->display();
}
}
/**
* Finishes the progress output.
*/
public function finish()
{
if (null === $this->startTime) {
throw new \LogicException('You must start the progress bar before calling finish().');
}
if (!$this->max) {
$this->barChar = $this->barCharOriginal;
$this->max = $this->step;
$this->setCurrent($this->max);
$this->max = 0;
$this->barChar = $this->emptyBarChar;
} else {
$this->setCurrent($this->max);
}
$this->startTime = null;
}
/**
* Outputs the current progress string.
*
* @throws \LogicException
*/
public function display()
{
if (null === $this->startTime) {
throw new \LogicException('You must start the progress bar before calling display().');
}
$self = $this;
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
2014-03-01 17:08:18 +00:00
if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
$text = call_user_func($formatter, $self);
} elseif (isset($this->messages[$matches[1]])) {
$text = $this->messages[$matches[1]];
} else {
return $matches[0];
}
if (isset($matches[2])) {
$text = sprintf('%'.$matches[2], $text);
}
return $text;
}, $this->format));
}
/**
* Removes the progress bar from the current line.
*
* This is useful if you wish to write some output
* while a progress bar is running.
* Call display() to show the progress bar again.
*/
public function clear()
{
$this->overwrite(str_repeat("\n", $this->formatLineCount));
}
/**
* Overwrites a previous message to the output.
*
* @param string $message The message
*/
private function overwrite($message)
{
$length = Helper::strlen($message);
// append whitespace to match the last line's length
// FIXME: on each line!!!!!
// FIXME: max of each line for lastMessagesLength or an array?
if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
$message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
}
// move back to the beginning of the progress bar before redrawing it
$this->output->write("\x0D");
if ($this->formatLineCount) {
$this->output->write(sprintf("\033[%dA", $this->formatLineCount));
}
$this->output->write($message);
$this->lastMessagesLength = Helper::strlen($message);
}
private function determineBestFormat()
{
switch ($this->output->getVerbosity()) {
case OutputInterface::VERBOSITY_QUIET:
return $this->max > 0 ? 'quiet' : 'quiet_nomax';
case OutputInterface::VERBOSITY_VERBOSE:
case OutputInterface::VERBOSITY_VERY_VERBOSE:
case OutputInterface::VERBOSITY_DEBUG:
return $this->max > 0 ? 'verbose' : 'verbose_nomax';
default:
return $this->max > 0 ? 'normal' : 'normal_nomax';
}
}
2014-03-01 17:08:18 +00:00
private static function initPlaceholderFormatters()
{
return array(
'bar' => function (ProgressBar $bar) {
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getStep() % $bar->getBarWidth());
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlen($bar->getProgressCharacter());
$display = str_repeat($bar->getBarCharacter(), $completeBars);
if ($completeBars < $bar->getBarWidth()) {
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
}
return $display;
},
'elapsed' => function (ProgressBar $bar) {
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (ProgressBar $bar) {
if (!$bar->getMaxSteps()) {
throw new \LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
}
if (!$bar->getStep()) {
$remaining = 0;
} else {
$remaining = round((time() - $bar->getStartTime()) / $bar->getStep() * ($bar->getMaxSteps() - $bar->getStep()));
}
return Helper::formatTime($remaining);
},
'estimated' => function (ProgressBar $bar) {
if (!$bar->getMaxSteps()) {
throw new \LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
}
if (!$bar->getStep()) {
$estimated = 0;
} else {
$estimated = round((time() - $bar->getStartTime()) / $bar->getStep() * $bar->getMaxSteps());
}
return Helper::formatTime($estimated);
},
'memory' => function (ProgressBar $bar) {
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (ProgressBar $bar) {
return str_pad($bar->getStep(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
},
'max' => function (ProgressBar $bar) {
return $bar->getMaxSteps();
},
'percent' => function (ProgressBar $bar) {
return floor($bar->getProgressPercent() * 100);
},
);
}
2014-03-01 17:08:18 +00:00
private static function initFormats()
{
return array(
'quiet' => ' %percent%%',
'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
'verbose' => ' %current%/%max% [%bar%] %percent:3s%% Elapsed: %elapsed:6s%',
'quiet_nomax' => ' %current%',
'normal_nomax' => ' %current% [%bar%]',
'verbose_nomax' => ' %current% [%bar%] Elapsed: %elapsed:6s%',
);
}
}