# Text progress bar library, like curl or scp. import math import sys from datetime import datetime, timedelta if sys.platform.startswith("win"): from .terminal_win import Terminal else: from .terminal_unix import Terminal class NullProgressBar(object): def update(self, current, data): pass def poke(self): pass def finish(self, complete=True): pass def beginline(self): pass def message(self, msg): sys.stdout.write(msg + "\n") @staticmethod def update_granularity(): return timedelta.max class ProgressBar(object): def __init__(self, limit, fmt): assert self.conservative_isatty() self.prior = None self.atLineStart = True # [{str:str}] Describtion of how to lay out each field in the counters map. self.counters_fmt = fmt # int: The value of 'current' equal to 100%. self.limit = limit # int: max digits in limit self.limit_digits = int(math.ceil(math.log10(self.limit))) # datetime: The start time. self.t0 = datetime.now() # datetime: Optional, the last time update() ran. self.last_update_time = None # Compute the width of the counters and build the format string. self.counters_width = 1 # [ for layout in self.counters_fmt: self.counters_width += self.limit_digits # | (or ']' for the last one) self.counters_width += 1 self.barlen = 64 - self.counters_width @staticmethod def update_granularity(): return timedelta(seconds=0.1) def update(self, current, data): # Record prior for poke. self.prior = (current, data) self.atLineStart = False # Build counters string. sys.stdout.write("\r[") for layout in self.counters_fmt: Terminal.set_color(layout["color"]) sys.stdout.write( ("{:" + str(self.limit_digits) + "d}").format(data[layout["value"]]) ) Terminal.reset_color() if layout != self.counters_fmt[-1]: sys.stdout.write("|") else: sys.stdout.write("] ") # Build the bar. pct = int(100.0 * current / self.limit) sys.stdout.write("{:3d}% ".format(pct)) barlen = int(1.0 * self.barlen * current / self.limit) - 1 bar = "=" * barlen + ">" + " " * (self.barlen - barlen - 1) sys.stdout.write(bar + "|") # Update the bar. now = datetime.now() dt = now - self.t0 dt = dt.seconds + dt.microseconds * 1e-6 sys.stdout.write("{:6.1f}s".format(dt)) Terminal.clear_right() # Force redisplay, since we didn't write a \n. sys.stdout.flush() self.last_update_time = now def poke(self): if not self.prior: return if datetime.now() - self.last_update_time < self.update_granularity(): return self.update(*self.prior) def finish(self, complete=True): if not self.prior: sys.stdout.write( "No test run... You can try adding" " --run-slow-tests or --run-skipped to run more tests\n" ) return final_count = self.limit if complete else self.prior[0] self.update(final_count, self.prior[1]) sys.stdout.write("\n") def beginline(self): if not self.atLineStart: sys.stdout.write("\n") self.atLineStart = True def message(self, msg): self.beginline() sys.stdout.write(msg) sys.stdout.write("\n") @staticmethod def conservative_isatty(): """ Prefer erring on the side of caution and not using terminal commands if the current output stream may be a file. """ return sys.stdout.isatty()