diff --git a/src/console/PhutilConsoleProgressBar.php b/src/console/PhutilConsoleProgressBar.php index 52e709c..89a3381 100644 --- a/src/console/PhutilConsoleProgressBar.php +++ b/src/console/PhutilConsoleProgressBar.php @@ -1,144 +1,152 @@ 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; 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 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->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); } }