2015-08-18 00:00:26 +00:00
< ? 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 ;
2015-10-08 18:40:12 +00:00
use Symfony\Component\Console\Output\ConsoleOutputInterface ;
2015-08-18 00:00:26 +00:00
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 = '>' ;
2015-11-17 21:42:33 +00:00
private $format ;
private $internalFormat ;
2015-08-18 00:00:26 +00:00
private $redrawFreq = 1 ;
/**
* @ var OutputInterface
*/
private $output ;
private $step = 0 ;
private $max ;
private $startTime ;
private $stepWidth ;
private $percent = 0.0 ;
private $lastMessagesLength = 0 ;
private $formatLineCount ;
private $messages ;
private $overwrite = true ;
private static $formatters ;
private static $formats ;
/**
* Constructor .
*
* @ param OutputInterface $output An OutputInterface instance
* @ param int $max Maximum steps ( 0 if unknown )
*/
public function __construct ( OutputInterface $output , $max = 0 )
{
2015-10-08 18:40:12 +00:00
if ( $output instanceof ConsoleOutputInterface ) {
$output = $output -> getErrorOutput ();
}
2015-08-18 00:00:26 +00:00
$this -> output = $output ;
$this -> setMaxSteps ( $max );
if ( ! $this -> output -> isDecorated ()) {
// disable overwrite when output does not support ANSI codes.
$this -> overwrite = false ;
if ( $this -> max > 10 ) {
// set a reasonable redraw frequency so output isn't flooded
$this -> setRedrawFrequency ( $max / 10 );
}
}
$this -> startTime = time ();
}
/**
* 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 ;
}
/**
* 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 )
{
if ( ! self :: $formatters ) {
self :: $formatters = self :: initPlaceholderFormatters ();
}
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 ;
}
/**
* Gets the format for a given name .
*
* @ param string $name The format name
*
* @ return string | null A format string
*/
public static function getFormatDefinition ( $name )
{
if ( ! self :: $formats ) {
self :: $formats = self :: initFormats ();
}
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 .
*
* @ return int The progress bar start time
*/
public function getStartTime ()
{
return $this -> startTime ;
}
/**
* Gets the progress bar maximal steps .
*
* @ return int The progress bar max steps
*/
public function getMaxSteps ()
{
return $this -> max ;
}
/**
* Gets the progress bar step .
*
* @ deprecated since version 2.6 , to be removed in 3.0 . Use { @ link getProgress ()} instead .
*
* @ return int The progress bar step
*/
public function getStep ()
{
2015-08-27 19:03:05 +00:00
@ trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the getProgress() method instead.' , E_USER_DEPRECATED );
2015-08-18 00:00:26 +00:00
return $this -> getProgress ();
}
/**
* Gets the current step position .
*
* @ return int The progress bar step
*/
public function getProgress ()
{
return $this -> step ;
}
/**
* Gets the progress bar step width .
*
* @ internal This method is public for PHP 5.3 compatibility , it should not be used .
*
* @ return int The progress bar step width
*/
public function getStepWidth ()
{
return $this -> stepWidth ;
}
/**
* Gets the current progress bar percent .
*
* @ return float The current progress bar percent
*/
public function getProgressPercent ()
{
return $this -> percent ;
}
/**
* Sets the progress bar width .
*
* @ param int $size The progress bar size
*/
public function setBarWidth ( $size )
{
$this -> barWidth = ( int ) $size ;
}
/**
* Gets the progress bar width .
*
* @ return int 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 ()
{
if ( null === $this -> barChar ) {
return $this -> max ? '=' : $this -> emptyBarChar ;
}
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 )
{
2015-11-17 21:42:33 +00:00
$this -> format = null ;
$this -> internalFormat = $format ;
2015-08-18 00:00:26 +00:00
}
/**
* Sets the redraw frequency .
*
* @ param int $freq The frequency in steps
*/
public function setRedrawFrequency ( $freq )
{
$this -> redrawFreq = ( int ) $freq ;
}
/**
* Starts the progress output .
*
* @ param int | null $max Number of steps to complete the bar ( 0 if indeterminate ), null to leave unchanged
*/
public function start ( $max = null )
{
$this -> startTime = time ();
$this -> step = 0 ;
$this -> percent = 0.0 ;
if ( null !== $max ) {
$this -> setMaxSteps ( $max );
}
$this -> display ();
}
/**
* Advances the progress output X steps .
*
* @ param int $step Number of steps to advance
*
* @ throws \LogicException
*/
public function advance ( $step = 1 )
{
$this -> setProgress ( $this -> step + $step );
}
/**
* Sets the current progress .
*
* @ deprecated since version 2.6 , to be removed in 3.0 . Use { @ link setProgress ()} instead .
*
* @ param int $step The current progress
*
* @ throws \LogicException
*/
public function setCurrent ( $step )
{
2015-08-27 19:03:05 +00:00
@ trigger_error ( 'The ' . __METHOD__ . ' method is deprecated since version 2.6 and will be removed in 3.0. Use the setProgress() method instead.' , E_USER_DEPRECATED );
2015-08-18 00:00:26 +00:00
$this -> setProgress ( $step );
}
/**
2015-08-27 19:03:05 +00:00
* Sets whether to overwrite the progressbar , false for new line .
2015-08-18 00:00:26 +00:00
*
* @ param bool $overwrite
*/
public function setOverwrite ( $overwrite )
{
$this -> overwrite = ( bool ) $overwrite ;
}
/**
* Sets the current progress .
*
* @ param int $step The current progress
*
* @ throws \LogicException
*/
public function setProgress ( $step )
{
$step = ( int ) $step ;
if ( $step < $this -> step ) {
throw new \LogicException ( 'You can\'t regress the progress bar.' );
}
if ( $this -> max && $step > $this -> max ) {
$this -> max = $step ;
}
2015-08-27 19:03:05 +00:00
$prevPeriod = ( int ) ( $this -> step / $this -> redrawFreq );
$currPeriod = ( int ) ( $step / $this -> redrawFreq );
2015-08-18 00:00:26 +00:00
$this -> step = $step ;
$this -> percent = $this -> max ? ( float ) $this -> step / $this -> max : 0 ;
if ( $prevPeriod !== $currPeriod || $this -> max === $step ) {
$this -> display ();
}
}
/**
* Finishes the progress output .
*/
public function finish ()
{
if ( ! $this -> max ) {
$this -> max = $this -> step ;
}
if ( $this -> step === $this -> max && ! $this -> overwrite ) {
// prevent double 100% output
return ;
}
$this -> setProgress ( $this -> max );
}
/**
* Outputs the current progress string .
*/
public function display ()
{
if ( OutputInterface :: VERBOSITY_QUIET === $this -> output -> getVerbosity ()) {
return ;
}
2015-11-17 21:42:33 +00:00
if ( null === $this -> format ) {
$this -> setRealFormat ( $this -> internalFormat ? : $this -> determineBestFormat ());
}
2015-08-18 00:00:26 +00:00
// these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped.
$self = $this ;
$output = $this -> output ;
$messages = $this -> messages ;
$this -> overwrite ( preg_replace_callback ( " { %([a-z \ -_]+)(?: \ :([^%]+))?%}i " , function ( $matches ) use ( $self , $output , $messages ) {
if ( $formatter = $self :: getPlaceholderFormatterDefinition ( $matches [ 1 ])) {
$text = call_user_func ( $formatter , $self , $output );
} elseif ( isset ( $messages [ $matches [ 1 ]])) {
$text = $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 ()
{
if ( ! $this -> overwrite ) {
return ;
}
2015-11-17 21:42:33 +00:00
if ( null === $this -> format ) {
$this -> setRealFormat ( $this -> internalFormat ? : $this -> determineBestFormat ());
}
2015-08-18 00:00:26 +00:00
$this -> overwrite ( str_repeat ( " \n " , $this -> formatLineCount ));
}
2015-11-17 21:42:33 +00:00
/**
* Sets the progress bar format .
*
* @ param string $format The format
*/
private function setRealFormat ( $format )
{
// try to use the _nomax variant if available
if ( ! $this -> max && null !== self :: getFormatDefinition ( $format . '_nomax' )) {
$this -> format = self :: getFormatDefinition ( $format . '_nomax' );
} elseif ( null !== self :: getFormatDefinition ( $format )) {
$this -> format = self :: getFormatDefinition ( $format );
} else {
$this -> format = $format ;
}
$this -> formatLineCount = substr_count ( $this -> format , " \n " );
}
2015-08-18 00:00:26 +00:00
/**
* Sets the progress bar maximal steps .
*
2015-10-08 18:40:12 +00:00
* @ param int $max The progress bar max steps
2015-08-18 00:00:26 +00:00
*/
private function setMaxSteps ( $max )
{
$this -> max = max ( 0 , ( int ) $max );
$this -> stepWidth = $this -> max ? Helper :: strlen ( $this -> max ) : 4 ;
}
/**
* Overwrites a previous message to the output .
*
* @ param string $message The message
*/
private function overwrite ( $message )
{
$lines = explode ( " \n " , $message );
// append whitespace to match the line's length
if ( null !== $this -> lastMessagesLength ) {
foreach ( $lines as $i => $line ) {
if ( $this -> lastMessagesLength > Helper :: strlenWithoutDecoration ( $this -> output -> getFormatter (), $line )) {
$lines [ $i ] = str_pad ( $line , $this -> lastMessagesLength , " \x20 " , STR_PAD_RIGHT );
}
}
}
if ( $this -> overwrite ) {
// move back to the beginning of the progress bar before redrawing it
$this -> output -> write ( " \x0D " );
} elseif ( $this -> step > 0 ) {
// move to new line
$this -> output -> writeln ( '' );
}
if ( $this -> formatLineCount ) {
$this -> output -> write ( sprintf ( " \033 [%dA " , $this -> formatLineCount ));
}
$this -> output -> write ( implode ( " \n " , $lines ));
$this -> lastMessagesLength = 0 ;
foreach ( $lines as $line ) {
$len = Helper :: strlenWithoutDecoration ( $this -> output -> getFormatter (), $line );
if ( $len > $this -> lastMessagesLength ) {
$this -> lastMessagesLength = $len ;
}
}
}
private function determineBestFormat ()
{
switch ( $this -> output -> getVerbosity ()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface :: VERBOSITY_VERBOSE :
return $this -> max ? 'verbose' : 'verbose_nomax' ;
case OutputInterface :: VERBOSITY_VERY_VERBOSE :
return $this -> max ? 'very_verbose' : 'very_verbose_nomax' ;
case OutputInterface :: VERBOSITY_DEBUG :
return $this -> max ? 'debug' : 'debug_nomax' ;
default :
return $this -> max ? 'normal' : 'normal_nomax' ;
}
}
private static function initPlaceholderFormatters ()
{
return array (
'bar' => function ( ProgressBar $bar , OutputInterface $output ) {
$completeBars = floor ( $bar -> getMaxSteps () > 0 ? $bar -> getProgressPercent () * $bar -> getBarWidth () : $bar -> getProgress () % $bar -> getBarWidth ());
$display = str_repeat ( $bar -> getBarCharacter (), $completeBars );
if ( $completeBars < $bar -> getBarWidth ()) {
$emptyBars = $bar -> getBarWidth () - $completeBars - Helper :: strlenWithoutDecoration ( $output -> getFormatter (), $bar -> getProgressCharacter ());
$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 -> getProgress ()) {
$remaining = 0 ;
} else {
$remaining = round (( time () - $bar -> getStartTime ()) / $bar -> getProgress () * ( $bar -> getMaxSteps () - $bar -> getProgress ()));
}
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 -> getProgress ()) {
$estimated = 0 ;
} else {
$estimated = round (( time () - $bar -> getStartTime ()) / $bar -> getProgress () * $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 -> getProgress (), $bar -> getStepWidth (), ' ' , STR_PAD_LEFT );
},
'max' => function ( ProgressBar $bar ) {
return $bar -> getMaxSteps ();
},
'percent' => function ( ProgressBar $bar ) {
return floor ( $bar -> getProgressPercent () * 100 );
},
);
}
private static function initFormats ()
{
return array (
'normal' => ' %current%/%max% [%bar%] %percent:3s%%' ,
'normal_nomax' => ' %current% [%bar%]' ,
'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%' ,
'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%' ,
'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%' ,
'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%' ,
'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%' ,
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%' ,
);
}
}