Commit be911e19 by Carsten Brandt

created proper progress bar

fixes #1293
parent 41fbfcbd
......@@ -5,6 +5,7 @@ Yii Framework 2 Change Log
----------------------------
- Bug #1446: Logging while logs are processed causes infinite loop (qiangxue)
- Enh #1293: Replaced Console::showProgress() with a better approach. See Console::startProgress() for details (cebe)
- Enh #1406: DB Schema support for Oracle Database (p0larbeer, qiangxue)
- Enh #1437: Added ListView::viewParams (qiangxue)
- New extension #1438: [MongoDB integration](https://github.com/yiisoft/yii2-mongodb) ActiveRecord and Query (klimov-paul)
......
......@@ -618,7 +618,7 @@ class BaseConsole
* Gets input from STDIN and returns a string right-trimmed for EOLs.
*
* @param bool $raw If set to true, returns the raw string without trimming
* @return string
* @return string the string read from stdin
*/
public static function stdin($raw = false)
{
......@@ -651,7 +651,7 @@ class BaseConsole
* Asks the user for input. Ends when the user types a carriage return (PHP_EOL). Optionally, It also provides a
* prompt.
*
* @param string $prompt the prompt (optional)
* @param string $prompt the prompt to display before waiting for input (optional)
* @return string the user's input
*/
public static function input($prompt = null)
......@@ -776,60 +776,147 @@ class BaseConsole
return $input;
}
private static $_progressStart;
private static $_progressWidth;
private static $_progressPrefix;
/**
* Displays and updates a simple progress bar on screen.
* Starts display of a progress bar on screen.
*
* This bar will be updated by [[updateProgress()]] and my be ended by [[endProgress()]].
*
* The following example shows a simple usage of a progress bar:
*
* ```php
* Console::startProgress(0, 1000);
* for ($n = 1; $n <= 1000; $n++) {
* usleep(1000);
* Console::updateProgress($n, 1000);
* }
* Console::endProgress();
* ```
*
* Git clone like progress (showing only status information):
* ```php
* Console::startProgress(0, 1000, 'Counting objects: ', false);
* for ($n = 1; $n <= 1000; $n++) {
* usleep(1000);
* Console::updateProgress($n, 1000);
* }
* Console::endProgress("done." . PHP_EOL);
* ```
*
* @param integer $done the number of items that are completed
* @param integer $total the total value of items that are to be done
* @param integer $size the size of the status bar (optional)
* @see http://snipplr.com/view/29548/
* @param integer $done the number of items that are completed.
* @param integer $total the total value of items that are to be done.
* @param string $prefix an optional string to display before the progress bar.
* Default to empty string which results in no prefix to be displayed.
* @param integer|boolean $width optional width of the progressbar. This can be an integer representing
* the number of characters to display for the progress bar or a float between 0 and 1 representing the
* percentage of screen with the progress bar may take. It can also be set to false to disable the
* bar and only show progress information like percent, number of items and ETA.
* If not set, the bar will be as wide as the screen. Screen size will be detected using [[getScreenSize()]].
* @see startProgress
* @see updateProgress
* @see endProgress
*/
public static function showProgress($done, $total, $size = 30)
public static function startProgress($done, $total, $prefix = '', $width = null)
{
static $start;
self::$_progressStart = time();
self::$_progressWidth = $width;
self::$_progressPrefix = $prefix;
// if we go over our bound, just ignore it
if ($done > $total) {
return;
}
static::updateProgress($done, $total);
}
if (empty($start)) {
$start = time();
/**
* Updates a progress bar that has been started by [[startProgress()]].
*
* @param integer $done the number of items that are completed.
* @param integer $total the total value of items that are to be done.
* @param string $prefix an optional string to display before the progress bar.
* Defaults to null meaning the prefix specified by [[startProgress()]] will be used.
* If prefix is specified it will update the prefix that will be used by later calls.
* @see startProgress
* @see endProgress
*/
public static function updateProgress($done, $total, $prefix = null)
{
$width = self::$_progressWidth;
if ($width === false) {
$width = 0;
} else {
$screenSize = static::getScreenSize(true);
if ($screenSize === false && $width < 1) {
$width = 0;
} elseif ($width === null) {
$width = $screenSize[0];
} elseif ($width > 0 && $width < 1) {
$width = floor($screenSize[0] * $width);
}
}
$now = time();
$percent = (double)($done / $total);
$bar = floor($percent * $size);
$status = "\r[";
$status .= str_repeat("=", $bar);
if ($bar < $size) {
$status .= ">";
$status .= str_repeat(" ", $size - $bar);
if ($prefix === null) {
$prefix = self::$_progressPrefix;
} else {
$status .= "=";
self::$_progressPrefix = $prefix;
}
$width -= mb_strlen($prefix);
$display = number_format($percent * 100, 0);
$status .= "] $display% $done/$total";
$rate = ($now - $start) / $done;
$left = $total - $done;
$eta = round($rate * $left, 2);
$elapsed = $now - $start;
$status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec.";
$percent = $done / $total;
$info = sprintf("%d%% (%d/%d)", $percent * 100, $done, $total);
static::stdout("$status ");
if ($done > $total || $done == 0) {
$info .= ' ETA: n/a';
} elseif ($done < $total) {
$rate = (time() - self::$_progressStart) / $done;
$info .= sprintf(' ETA: %d sec.', $rate * ($total - $done));
}
$width -= 3 + mb_strlen($info);
// skipping progress bar on very small display or if forced to skip
if ($width < 5) {
static::stdout("\r$prefix$info ");
} else {
if ($percent < 0) {
$percent = 0;
} elseif ($percent > 1) {
$percent = 1;
}
$bar = floor($percent * $width);
$status = str_repeat("=", $bar);
if ($bar < $width) {
$status .= ">";
$status .= str_repeat(" ", $width - $bar - 1);
}
static::stdout("\r$prefix" . "[$status] $info");
}
flush();
}
// when done, send a newline
if ($done == $total) {
echo "\n";
/**
* Ends a progress bar that has been started by [[startProgress()]].
*
* @param string|boolean $remove This can be `false` to leave the progress bar on screen and just print a newline.
* If set to `true`, the line of the progress bar will be cleared. This may also be a string to be displayed instead
* of the progress bar.
* @param bool $keepPrefix whether to keep the prefix that has been specified for the progressbar when progressbar
* gets removed. Defaults to true.
* @see startProgress
* @see updateProgress
*/
public static function endProgress($remove = false, $keepPrefix = true)
{
if ($remove === false) {
static::stdout(PHP_EOL);
} else {
if (static::streamSupportsAnsiColors(STDOUT)) {
static::clearLine();
}
static::stdout("\r" . ($keepPrefix ? self::$_progressPrefix : '') . (is_string($remove) ? $remove : ''));
}
flush();
self::$_progressStart = null;
self::$_progressWidth = null;
self::$_progressPrefix = '';
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment