diff options
Diffstat (limited to 'progress.py')
-rw-r--r-- | progress.py | 223 |
1 files changed, 122 insertions, 101 deletions
diff --git a/progress.py b/progress.py index 526ce6c1..d1a7c543 100644 --- a/progress.py +++ b/progress.py | |||
@@ -22,115 +22,136 @@ _NOT_TTY = not os.isatty(2) | |||
22 | # This will erase all content in the current line (wherever the cursor is). | 22 | # This will erase all content in the current line (wherever the cursor is). |
23 | # It does not move the cursor, so this is usually followed by \r to move to | 23 | # It does not move the cursor, so this is usually followed by \r to move to |
24 | # column 0. | 24 | # column 0. |
25 | CSI_ERASE_LINE = '\x1b[2K' | 25 | CSI_ERASE_LINE = "\x1b[2K" |
26 | 26 | ||
27 | # This will erase all content in the current line after the cursor. This is | 27 | # This will erase all content in the current line after the cursor. This is |
28 | # useful for partial updates & progress messages as the terminal can display | 28 | # useful for partial updates & progress messages as the terminal can display |
29 | # it better. | 29 | # it better. |
30 | CSI_ERASE_LINE_AFTER = '\x1b[K' | 30 | CSI_ERASE_LINE_AFTER = "\x1b[K" |
31 | 31 | ||
32 | 32 | ||
33 | def duration_str(total): | 33 | def duration_str(total): |
34 | """A less noisy timedelta.__str__. | 34 | """A less noisy timedelta.__str__. |
35 | 35 | ||
36 | The default timedelta stringification contains a lot of leading zeros and | 36 | The default timedelta stringification contains a lot of leading zeros and |
37 | uses microsecond resolution. This makes for noisy output. | 37 | uses microsecond resolution. This makes for noisy output. |
38 | """ | 38 | """ |
39 | hours, rem = divmod(total, 3600) | 39 | hours, rem = divmod(total, 3600) |
40 | mins, secs = divmod(rem, 60) | 40 | mins, secs = divmod(rem, 60) |
41 | ret = '%.3fs' % (secs,) | 41 | ret = "%.3fs" % (secs,) |
42 | if mins: | 42 | if mins: |
43 | ret = '%im%s' % (mins, ret) | 43 | ret = "%im%s" % (mins, ret) |
44 | if hours: | 44 | if hours: |
45 | ret = '%ih%s' % (hours, ret) | 45 | ret = "%ih%s" % (hours, ret) |
46 | return ret | 46 | return ret |
47 | 47 | ||
48 | 48 | ||
49 | class Progress(object): | 49 | class Progress(object): |
50 | def __init__(self, title, total=0, units='', print_newline=False, delay=True, | 50 | def __init__( |
51 | quiet=False): | 51 | self, |
52 | self._title = title | 52 | title, |
53 | self._total = total | 53 | total=0, |
54 | self._done = 0 | 54 | units="", |
55 | self._start = time() | 55 | print_newline=False, |
56 | self._show = not delay | 56 | delay=True, |
57 | self._units = units | 57 | quiet=False, |
58 | self._print_newline = print_newline | 58 | ): |
59 | # Only show the active jobs section if we run more than one in parallel. | 59 | self._title = title |
60 | self._show_jobs = False | 60 | self._total = total |
61 | self._active = 0 | 61 | self._done = 0 |
62 | 62 | self._start = time() | |
63 | # When quiet, never show any output. It's a bit hacky, but reusing the | 63 | self._show = not delay |
64 | # existing logic that delays initial output keeps the rest of the class | 64 | self._units = units |
65 | # clean. Basically we set the start time to years in the future. | 65 | self._print_newline = print_newline |
66 | if quiet: | 66 | # Only show the active jobs section if we run more than one in parallel. |
67 | self._show = False | 67 | self._show_jobs = False |
68 | self._start += 2**32 | 68 | self._active = 0 |
69 | 69 | ||
70 | def start(self, name): | 70 | # When quiet, never show any output. It's a bit hacky, but reusing the |
71 | self._active += 1 | 71 | # existing logic that delays initial output keeps the rest of the class |
72 | if not self._show_jobs: | 72 | # clean. Basically we set the start time to years in the future. |
73 | self._show_jobs = self._active > 1 | 73 | if quiet: |
74 | self.update(inc=0, msg='started ' + name) | 74 | self._show = False |
75 | 75 | self._start += 2**32 | |
76 | def finish(self, name): | 76 | |
77 | self.update(msg='finished ' + name) | 77 | def start(self, name): |
78 | self._active -= 1 | 78 | self._active += 1 |
79 | 79 | if not self._show_jobs: | |
80 | def update(self, inc=1, msg=''): | 80 | self._show_jobs = self._active > 1 |
81 | self._done += inc | 81 | self.update(inc=0, msg="started " + name) |
82 | 82 | ||
83 | if _NOT_TTY or IsTraceToStderr(): | 83 | def finish(self, name): |
84 | return | 84 | self.update(msg="finished " + name) |
85 | 85 | self._active -= 1 | |
86 | if not self._show: | 86 | |
87 | if 0.5 <= time() - self._start: | 87 | def update(self, inc=1, msg=""): |
88 | self._show = True | 88 | self._done += inc |
89 | else: | 89 | |
90 | return | 90 | if _NOT_TTY or IsTraceToStderr(): |
91 | 91 | return | |
92 | if self._total <= 0: | 92 | |
93 | sys.stderr.write('\r%s: %d,%s' % ( | 93 | if not self._show: |
94 | self._title, | 94 | if 0.5 <= time() - self._start: |
95 | self._done, | 95 | self._show = True |
96 | CSI_ERASE_LINE_AFTER)) | 96 | else: |
97 | sys.stderr.flush() | 97 | return |
98 | else: | 98 | |
99 | p = (100 * self._done) / self._total | 99 | if self._total <= 0: |
100 | if self._show_jobs: | 100 | sys.stderr.write( |
101 | jobs = '[%d job%s] ' % (self._active, 's' if self._active > 1 else '') | 101 | "\r%s: %d,%s" % (self._title, self._done, CSI_ERASE_LINE_AFTER) |
102 | else: | 102 | ) |
103 | jobs = '' | 103 | sys.stderr.flush() |
104 | sys.stderr.write('\r%s: %2d%% %s(%d%s/%d%s)%s%s%s%s' % ( | 104 | else: |
105 | self._title, | 105 | p = (100 * self._done) / self._total |
106 | p, | 106 | if self._show_jobs: |
107 | jobs, | 107 | jobs = "[%d job%s] " % ( |
108 | self._done, self._units, | 108 | self._active, |
109 | self._total, self._units, | 109 | "s" if self._active > 1 else "", |
110 | ' ' if msg else '', msg, | 110 | ) |
111 | CSI_ERASE_LINE_AFTER, | 111 | else: |
112 | '\n' if self._print_newline else '')) | 112 | jobs = "" |
113 | sys.stderr.flush() | 113 | sys.stderr.write( |
114 | 114 | "\r%s: %2d%% %s(%d%s/%d%s)%s%s%s%s" | |
115 | def end(self): | 115 | % ( |
116 | if _NOT_TTY or IsTraceToStderr() or not self._show: | 116 | self._title, |
117 | return | 117 | p, |
118 | 118 | jobs, | |
119 | duration = duration_str(time() - self._start) | 119 | self._done, |
120 | if self._total <= 0: | 120 | self._units, |
121 | sys.stderr.write('\r%s: %d, done in %s%s\n' % ( | 121 | self._total, |
122 | self._title, | 122 | self._units, |
123 | self._done, | 123 | " " if msg else "", |
124 | duration, | 124 | msg, |
125 | CSI_ERASE_LINE_AFTER)) | 125 | CSI_ERASE_LINE_AFTER, |
126 | sys.stderr.flush() | 126 | "\n" if self._print_newline else "", |
127 | else: | 127 | ) |
128 | p = (100 * self._done) / self._total | 128 | ) |
129 | sys.stderr.write('\r%s: %3d%% (%d%s/%d%s), done in %s%s\n' % ( | 129 | sys.stderr.flush() |
130 | self._title, | 130 | |
131 | p, | 131 | def end(self): |
132 | self._done, self._units, | 132 | if _NOT_TTY or IsTraceToStderr() or not self._show: |
133 | self._total, self._units, | 133 | return |
134 | duration, | 134 | |
135 | CSI_ERASE_LINE_AFTER)) | 135 | duration = duration_str(time() - self._start) |
136 | sys.stderr.flush() | 136 | if self._total <= 0: |
137 | sys.stderr.write( | ||
138 | "\r%s: %d, done in %s%s\n" | ||
139 | % (self._title, self._done, duration, CSI_ERASE_LINE_AFTER) | ||
140 | ) | ||
141 | sys.stderr.flush() | ||
142 | else: | ||
143 | p = (100 * self._done) / self._total | ||
144 | sys.stderr.write( | ||
145 | "\r%s: %3d%% (%d%s/%d%s), done in %s%s\n" | ||
146 | % ( | ||
147 | self._title, | ||
148 | p, | ||
149 | self._done, | ||
150 | self._units, | ||
151 | self._total, | ||
152 | self._units, | ||
153 | duration, | ||
154 | CSI_ERASE_LINE_AFTER, | ||
155 | ) | ||
156 | ) | ||
157 | sys.stderr.flush() | ||