Page MenuHomec4science

PhutilConsoleProgressBar.php
No OneTemporary

File Metadata

Created
Sun, May 26, 20:47

PhutilConsoleProgressBar.php

<?php
/**
* Show a progress bar on the console. Usage:
*
* // Create a progress bar, and configure the total amount of work that
* // needs to be done.
* $bar = id(new PhutilConsoleProgressBar())
* ->setTotal(count($stuff));
*
* // As you complete the work, update the progress bar.
* foreach ($stuff as $thing) {
* do_stuff($thing);
* $bar->update(1);
* }
*
* // When complete, mark the work done to clear the bar.
* $bar->done();
*
* The progress bar attempts to account for various special cases, notably:
*
* - If stderr is not a TTY, the bar will not be drawn (for example, if
* it is being piped to a log file).
* - If the Phutil log output is enabled (usually because `--trace` was
* specified), the bar will not be drawn.
* - The bar will be resized to the width of the console if possible.
*
*/
final class PhutilConsoleProgressBar extends Phobject {
private $work;
private $done;
private $drawn;
private $console;
private $finished;
private $lastUpdate;
private $quiet = false;
public function setConsole(PhutilConsole $console) {
$this->console = $console;
return $this;
}
private function getConsole() {
if ($this->console) {
return $this->console;
}
return PhutilConsole::getConsole();
}
public function setTotal($work) {
$this->work = $work;
$this->redraw();
return $this;
}
public function setQuiet($quiet) {
$this->quiet = $quiet;
return $this;
}
public function update($work) {
$this->done += $work;
$this->redraw();
return $this;
}
private function redraw() {
if ($this->lastUpdate + 0.1 > microtime(true)) {
// We redrew the bar very recently; skip this update.
return;
}
if ($this->quiet) {
return;
}
if ($this->finished) {
return;
}
if (!$this->work) {
// There's no work to be done, so don't draw the bar.
return;
}
$console = $this->getConsole();
if ($console->isErrATTY() === false) {
return;
}
if ($console->isLogEnabled()) {
return;
}
// Width of the stuff other than the progress bar itself.
$chrome_width = strlen('[] 100.0% ');
$char_width = $this->getWidth();
if ($char_width < $chrome_width) {
return;
}
$this->lastUpdate = microtime(true);
if (!$this->drawn) {
$this->drawn = true;
}
$percent = $this->done / $this->work;
$max_width = $char_width - $chrome_width;
$bar_width = $percent * $max_width;
$bar_int = floor($bar_width);
$bar_frac = $bar_width - $bar_int;
$frac_map = array(
'',
'-',
'~',
);
$frac_char = $frac_map[floor($bar_frac * count($frac_map))];
$pattern = "[%-{$max_width}.{$max_width}s] % 5s%%";
$out = sprintf(
$pattern,
str_repeat('=', $bar_int).$frac_char,
sprintf('%.1f', 100 * $percent));
$this->eraseLine();
$console->writeErr('%s', $out);
}
public function done($clean_exit = true) {
$console = $this->getConsole();
if ($this->drawn) {
$this->eraseLine();
if ($clean_exit) {
$console->writeErr("%s\n", 'Done.');
}
}
$this->finished = true;
}
private function eraseLine() {
$string = str_repeat(' ', $this->getWidth());
$console = $this->getConsole();
$console->writeErr("\r%s\r", $string);
}
private function getWidth() {
$console = $this->getConsole();
$width = $console->getErrCols();
return min(nonempty($width, 78), 78);
}
public function __destruct() {
$this->done($clean_exit = false);
}
}

Event Timeline