|
1 # Text progress bar library, like curl or scp. |
|
2 |
|
3 from datetime import datetime, timedelta |
|
4 import math |
|
5 import sys |
|
6 |
|
7 if sys.platform.startswith('win'): |
|
8 from terminal_win import Terminal |
|
9 else: |
|
10 from terminal_unix import Terminal |
|
11 |
|
12 class NullProgressBar(object): |
|
13 def update(self, current, data): pass |
|
14 def poke(self): pass |
|
15 def finish(self, complete=True): pass |
|
16 def beginline(self): pass |
|
17 def message(self, msg): sys.stdout.write(msg + '\n') |
|
18 def update_granularity(self): return timedelta.max |
|
19 |
|
20 class ProgressBar(object): |
|
21 def __init__(self, limit, fmt): |
|
22 assert self.conservative_isatty() |
|
23 |
|
24 self.prior = None |
|
25 self.atLineStart = True |
|
26 self.counters_fmt = fmt # [{str:str}] Describtion of how to lay out each |
|
27 # field in the counters map. |
|
28 self.limit = limit # int: The value of 'current' equal to 100%. |
|
29 self.limit_digits = int(math.ceil(math.log10(self.limit))) # int: max digits in limit |
|
30 self.t0 = datetime.now() # datetime: The start time. |
|
31 |
|
32 # Compute the width of the counters and build the format string. |
|
33 self.counters_width = 1 # [ |
|
34 for layout in self.counters_fmt: |
|
35 self.counters_width += self.limit_digits |
|
36 self.counters_width += 1 # | (or ']' for the last one) |
|
37 |
|
38 self.barlen = 64 - self.counters_width |
|
39 |
|
40 def update_granularity(self): |
|
41 return timedelta(seconds=0.1) |
|
42 |
|
43 def update(self, current, data): |
|
44 # Record prior for poke. |
|
45 self.prior = (current, data) |
|
46 self.atLineStart = False |
|
47 |
|
48 # Build counters string. |
|
49 sys.stdout.write('\r[') |
|
50 for layout in self.counters_fmt: |
|
51 Terminal.set_color(layout['color']) |
|
52 sys.stdout.write(('%' + str(self.limit_digits) + 'd') % data[layout['value']]) |
|
53 Terminal.reset_color() |
|
54 if layout != self.counters_fmt[-1]: |
|
55 sys.stdout.write('|') |
|
56 else: |
|
57 sys.stdout.write('] ') |
|
58 |
|
59 # Build the bar. |
|
60 pct = int(100.0 * current / self.limit) |
|
61 sys.stdout.write('%3d%% ' % pct) |
|
62 |
|
63 barlen = int(1.0 * self.barlen * current / self.limit) - 1 |
|
64 bar = '=' * barlen + '>' + ' ' * (self.barlen - barlen - 1) |
|
65 sys.stdout.write(bar + '|') |
|
66 |
|
67 # Update the bar. |
|
68 dt = datetime.now() - self.t0 |
|
69 dt = dt.seconds + dt.microseconds * 1e-6 |
|
70 sys.stdout.write('%6.1fs' % dt) |
|
71 Terminal.clear_right() |
|
72 |
|
73 # Force redisplay, since we didn't write a \n. |
|
74 sys.stdout.flush() |
|
75 |
|
76 def poke(self): |
|
77 if not self.prior: |
|
78 return |
|
79 self.update(*self.prior) |
|
80 |
|
81 def finish(self, complete=True): |
|
82 final_count = self.limit if complete else self.prior[0] |
|
83 self.update(final_count, self.prior[1]) |
|
84 sys.stdout.write('\n') |
|
85 |
|
86 def beginline(self): |
|
87 if not self.atLineStart: |
|
88 sys.stdout.write('\n') |
|
89 self.atLineStart = True |
|
90 |
|
91 def message(self, msg): |
|
92 self.beginline() |
|
93 sys.stdout.write(msg) |
|
94 sys.stdout.write('\n') |
|
95 |
|
96 @staticmethod |
|
97 def conservative_isatty(): |
|
98 """ |
|
99 Prefer erring on the side of caution and not using terminal commands if |
|
100 the current output stream may be a file. We explicitly check for the |
|
101 Android platform because terminal commands work poorly over ADB's |
|
102 redirection. |
|
103 """ |
|
104 try: |
|
105 import android |
|
106 return False |
|
107 except ImportError: |
|
108 return sys.stdout.isatty() |
|
109 return False |