bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 649
by Martin Pool - some cleanups for the progressbar method | 1 | # Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
 | 
| 2 | # Copyright (C) 2005 Canonical <canonical.com>
 | |
| 648
by Martin Pool - import aaron's progress-indicator code | 3 | #
 | 
| 4 | #    This program is free software; you can redistribute it and/or modify
 | |
| 5 | #    it under the terms of the GNU General Public License as published by
 | |
| 6 | #    the Free Software Foundation; either version 2 of the License, or
 | |
| 7 | #    (at your option) any later version.
 | |
| 8 | #
 | |
| 9 | #    This program is distributed in the hope that it will be useful,
 | |
| 10 | #    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| 11 | #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| 12 | #    GNU General Public License for more details.
 | |
| 13 | #
 | |
| 14 | #    You should have received a copy of the GNU General Public License
 | |
| 15 | #    along with this program; if not, write to the Free Software
 | |
| 16 | #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | |
| 17 | ||
| 649
by Martin Pool - some cleanups for the progressbar method | 18 | |
| 889
by Martin Pool - show progress bar during inventory conversion to weave, and make profiling optional | 19 | """Simple text-mode progress indicator.
 | 
| 649
by Martin Pool - some cleanups for the progressbar method | 20 | |
| 21 | To display an indicator, create a ProgressBar object.  Call it,
 | |
| 22 | passing Progress objects indicating the current state.  When done,
 | |
| 23 | call clear().
 | |
| 24 | ||
| 25 | Progress is suppressed when output is not sent to a terminal, so as
 | |
| 26 | not to clutter log files.
 | |
| 27 | """
 | |
| 28 | ||
| 652
by Martin Pool doc | 29 | # TODO: should be a global option e.g. --silent that disables progress
 | 
| 30 | # indicators, preferably without needing to adjust all code that
 | |
| 31 | # potentially calls them.
 | |
| 32 | ||
| 962
by Martin Pool todo | 33 | # TODO: If not on a tty perhaps just print '......' for the benefit of IDEs, etc
 | 
| 655
by Martin Pool - better calculation of progress bar position | 34 | |
| 934
by Martin Pool todo | 35 | # TODO: Optionally show elapsed time instead/as well as ETA; nicer
 | 
| 36 | # when the rate is unpredictable
 | |
| 37 | ||
| 649
by Martin Pool - some cleanups for the progressbar method | 38 | |
| 648
by Martin Pool - import aaron's progress-indicator code | 39 | import sys | 
| 660
by Martin Pool - use plain unix time, not datetime module | 40 | import time | 
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 41 | import os | 
| 1185.16.75
by Martin Pool - improved eta estimation for progress bar | 42 | from collections import deque | 
| 648
by Martin Pool - import aaron's progress-indicator code | 43 | |
| 649
by Martin Pool - some cleanups for the progressbar method | 44 | |
| 45 | def _supports_progress(f): | |
| 695
by Martin Pool - don't display progress bars on really dumb terminals | 46 | if not hasattr(f, 'isatty'): | 
| 47 | return False | |
| 48 | if not f.isatty(): | |
| 49 | return False | |
| 50 | if os.environ.get('TERM') == 'dumb': | |
| 51 |         # e.g. emacs compile window
 | |
| 52 | return False | |
| 53 | return True | |
| 649
by Martin Pool - some cleanups for the progressbar method | 54 | |
| 55 | ||
| 56 | ||
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 57 | def ProgressBar(to_file=sys.stderr, **kwargs): | 
| 58 | """Abstract factory""" | |
| 59 | if _supports_progress(to_file): | |
| 60 | return TTYProgressBar(to_file=to_file, **kwargs) | |
| 61 | else: | |
| 62 | return DotsProgressBar(to_file=to_file, **kwargs) | |
| 63 | ||
| 64 | ||
| 65 | class _BaseProgressBar(object): | |
| 66 | def __init__(self, | |
| 67 | to_file=sys.stderr, | |
| 68 | show_pct=False, | |
| 69 | show_spinner=False, | |
| 70 | show_eta=True, | |
| 71 | show_bar=True, | |
| 1534.5.6
by Robert Collins split out converter logic into per-format objects. | 72 | show_count=True, | 
| 73 | to_messages_file=sys.stdout): | |
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 74 | object.__init__(self) | 
| 75 | self.to_file = to_file | |
| 1534.5.6
by Robert Collins split out converter logic into per-format objects. | 76 | self.to_messages_file = to_messages_file | 
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 77 | self.last_msg = None | 
| 78 | self.last_cnt = None | |
| 79 | self.last_total = None | |
| 80 | self.show_pct = show_pct | |
| 81 | self.show_spinner = show_spinner | |
| 82 | self.show_eta = show_eta | |
| 83 | self.show_bar = show_bar | |
| 84 | self.show_count = show_count | |
| 1104
by Martin Pool - Add a simple UIFactory | 85 | |
| 1534.5.6
by Robert Collins split out converter logic into per-format objects. | 86 | def note(self, fmt_string, *args, **kwargs): | 
| 87 | """Record a note without disrupting the progress bar.""" | |
| 88 | self.clear() | |
| 89 | self.to_messages_file.write(fmt_string % args) | |
| 90 | self.to_messages_file.write('\n') | |
| 1104
by Martin Pool - Add a simple UIFactory | 91 | |
| 92 | ||
| 93 | class DummyProgress(_BaseProgressBar): | |
| 94 | """Progress-bar standin that does nothing. | |
| 95 | ||
| 96 |     This can be used as the default argument for methods that
 | |
| 97 |     take an optional progress indicator."""
 | |
| 98 | def tick(self): | |
| 99 |         pass
 | |
| 100 | ||
| 101 | def update(self, msg=None, current=None, total=None): | |
| 102 |         pass
 | |
| 103 | ||
| 104 | def clear(self): | |
| 105 |         pass
 | |
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 106 | |
| 1534.5.6
by Robert Collins split out converter logic into per-format objects. | 107 | def note(self, fmt_string, *args, **kwargs): | 
| 108 | """See _BaseProgressBar.note().""" | |
| 1534.5.9
by Robert Collins Advise users running upgrade on a checkout to also run it on the branch. | 109 | |
| 110 | ||
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 111 | class DotsProgressBar(_BaseProgressBar): | 
| 112 | def __init__(self, **kwargs): | |
| 113 | _BaseProgressBar.__init__(self, **kwargs) | |
| 114 | self.last_msg = None | |
| 115 | self.need_nl = False | |
| 116 | ||
| 117 | def tick(self): | |
| 118 | self.update() | |
| 119 | ||
| 120 | def update(self, msg=None, current_cnt=None, total_cnt=None): | |
| 121 | if msg and msg != self.last_msg: | |
| 122 | if self.need_nl: | |
| 123 | self.to_file.write('\n') | |
| 124 | ||
| 125 | self.to_file.write(msg + ': ') | |
| 126 | self.last_msg = msg | |
| 127 | self.need_nl = True | |
| 128 | self.to_file.write('.') | |
| 129 | ||
| 130 | def clear(self): | |
| 131 | if self.need_nl: | |
| 132 | self.to_file.write('\n') | |
| 133 | ||
| 134 | ||
| 135 | class TTYProgressBar(_BaseProgressBar): | |
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 136 | """Progress bar display object. | 
| 137 | ||
| 138 |     Several options are available to control the display.  These can
 | |
| 139 |     be passed as parameters to the constructor or assigned at any time:
 | |
| 140 | ||
| 141 |     show_pct
 | |
| 142 |         Show percentage complete.
 | |
| 143 |     show_spinner
 | |
| 144 |         Show rotating baton.  This ticks over on every update even
 | |
| 145 |         if the values don't change.
 | |
| 146 |     show_eta
 | |
| 147 |         Show predicted time-to-completion.
 | |
| 148 |     show_bar
 | |
| 149 |         Show bar graph.
 | |
| 150 |     show_count
 | |
| 151 |         Show numerical counts.
 | |
| 152 | ||
| 153 |     The output file should be in line-buffered or unbuffered mode.
 | |
| 154 |     """
 | |
| 155 | SPIN_CHARS = r'/-\|' | |
| 661
by Martin Pool - limit rate at which progress bar is updated | 156 | MIN_PAUSE = 0.1 # seconds | 
| 157 | ||
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 158 | |
| 159 | def __init__(self, **kwargs): | |
| 1185.33.60
by Martin Pool Use full terminal width for verbose test output. | 160 | from bzrlib.osutils import terminal_width | 
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 161 | _BaseProgressBar.__init__(self, **kwargs) | 
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 162 | self.spin_pos = 0 | 
| 1185.33.60
by Martin Pool Use full terminal width for verbose test output. | 163 | self.width = terminal_width() | 
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 164 | self.start_time = None | 
| 165 | self.last_update = None | |
| 1185.16.75
by Martin Pool - improved eta estimation for progress bar | 166 | self.last_updates = deque() | 
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 167 | |
| 168 | ||
| 169 | def throttle(self): | |
| 170 | """Return True if the bar was updated too recently""" | |
| 171 | now = time.time() | |
| 172 | if self.start_time is None: | |
| 173 | self.start_time = self.last_update = now | |
| 174 | return False | |
| 175 | else: | |
| 176 | interval = now - self.last_update | |
| 177 | if interval > 0 and interval < self.MIN_PAUSE: | |
| 178 | return True | |
| 179 | ||
| 1185.16.75
by Martin Pool - improved eta estimation for progress bar | 180 | self.last_updates.append(now - self.last_update) | 
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 181 | self.last_update = now | 
| 182 | return False | |
| 929
by Martin Pool - progress bar: avoid repeatedly checking screen width | 183 | |
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 184 | |
| 185 | def tick(self): | |
| 186 | self.update(self.last_msg, self.last_cnt, self.last_total) | |
| 187 | ||
| 188 | ||
| 189 | ||
| 667
by Martin Pool - allow for progressbar updates with no count, only a message | 190 | def update(self, msg, current_cnt=None, total_cnt=None): | 
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 191 | """Update and redraw progress bar.""" | 
| 192 | ||
| 1308
by Martin Pool - make progress bar more tolerant of out-of-range values | 193 | if current_cnt < 0: | 
| 194 | current_cnt = 0 | |
| 195 | ||
| 196 | if current_cnt > total_cnt: | |
| 197 | total_cnt = current_cnt | |
| 198 | ||
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 199 |         # save these for the tick() function
 | 
| 200 | self.last_msg = msg | |
| 201 | self.last_cnt = current_cnt | |
| 202 | self.last_total = total_cnt | |
| 203 | ||
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 204 | if self.throttle(): | 
| 205 |             return 
 | |
| 661
by Martin Pool - limit rate at which progress bar is updated | 206 | |
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 207 | if self.show_eta and self.start_time and total_cnt: | 
| 1185.16.75
by Martin Pool - improved eta estimation for progress bar | 208 | eta = get_eta(self.start_time, current_cnt, total_cnt, | 
| 209 | last_updates = self.last_updates) | |
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 210 | eta_str = " " + str_tdelta(eta) | 
| 211 | else: | |
| 212 | eta_str = "" | |
| 213 | ||
| 214 | if self.show_spinner: | |
| 215 | spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' ' | |
| 216 | else: | |
| 217 | spin_str = '' | |
| 218 | ||
| 219 |         # always update this; it's also used for the bar
 | |
| 220 | self.spin_pos += 1 | |
| 221 | ||
| 222 | if self.show_pct and total_cnt and current_cnt: | |
| 223 | pct = 100.0 * current_cnt / total_cnt | |
| 224 | pct_str = ' (%5.1f%%)' % pct | |
| 225 | else: | |
| 226 | pct_str = '' | |
| 227 | ||
| 228 | if not self.show_count: | |
| 229 | count_str = '' | |
| 230 | elif current_cnt is None: | |
| 231 | count_str = '' | |
| 232 | elif total_cnt is None: | |
| 233 | count_str = ' %i' % (current_cnt) | |
| 234 | else: | |
| 235 |             # make both fields the same size
 | |
| 236 | t = '%i' % (total_cnt) | |
| 237 | c = '%*i' % (len(t), current_cnt) | |
| 238 | count_str = ' ' + c + '/' + t | |
| 239 | ||
| 240 | if self.show_bar: | |
| 241 |             # progress bar, if present, soaks up all remaining space
 | |
| 929
by Martin Pool - progress bar: avoid repeatedly checking screen width | 242 | cols = self.width - 1 - len(msg) - len(spin_str) - len(pct_str) \ | 
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 243 | - len(eta_str) - len(count_str) - 3 | 
| 244 | ||
| 245 | if total_cnt: | |
| 246 |                 # number of markers highlighted in bar
 | |
| 247 | markers = int(round(float(cols) * current_cnt / total_cnt)) | |
| 248 | bar_str = '[' + ('=' * markers).ljust(cols) + '] ' | |
| 669
by Martin Pool - don't show progress bar unless completion is known | 249 | elif False: | 
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 250 |                 # don't know total, so can't show completion.
 | 
| 251 |                 # so just show an expanded spinning thingy
 | |
| 252 | m = self.spin_pos % cols | |
| 668
by Martin Pool - fix sweeping bar progress indicator | 253 | ms = (' ' * m + '*').ljust(cols) | 
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 254 | |
| 255 | bar_str = '[' + ms + '] ' | |
| 669
by Martin Pool - don't show progress bar unless completion is known | 256 | else: | 
| 257 | bar_str = '' | |
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 258 | else: | 
| 259 | bar_str = '' | |
| 260 | ||
| 261 | m = spin_str + bar_str + msg + count_str + pct_str + eta_str | |
| 262 | ||
| 929
by Martin Pool - progress bar: avoid repeatedly checking screen width | 263 | assert len(m) < self.width | 
| 264 | self.to_file.write('\r' + m.ljust(self.width - 1)) | |
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 265 |         #self.to_file.flush()
 | 
| 266 | ||
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 267 | def clear(self): | 
| 929
by Martin Pool - progress bar: avoid repeatedly checking screen width | 268 | self.to_file.write('\r%s\r' % (' ' * (self.width - 1))) | 
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 269 |         #self.to_file.flush()        
 | 
| 649
by Martin Pool - some cleanups for the progressbar method | 270 | |
| 648
by Martin Pool - import aaron's progress-indicator code | 271 | |
| 272 | def str_tdelta(delt): | |
| 273 | if delt is None: | |
| 274 | return "-:--:--" | |
| 660
by Martin Pool - use plain unix time, not datetime module | 275 | delt = int(round(delt)) | 
| 276 | return '%d:%02d:%02d' % (delt/3600, | |
| 277 | (delt/60) % 60, | |
| 278 | delt % 60) | |
| 279 | ||
| 280 | ||
| 1185.16.75
by Martin Pool - improved eta estimation for progress bar | 281 | def get_eta(start_time, current, total, enough_samples=3, last_updates=None, n_recent=10): | 
| 660
by Martin Pool - use plain unix time, not datetime module | 282 | if start_time is None: | 
| 283 | return None | |
| 284 | ||
| 285 | if not total: | |
| 286 | return None | |
| 287 | ||
| 288 | if current < enough_samples: | |
| 289 | return None | |
| 290 | ||
| 291 | if current > total: | |
| 292 | return None # wtf? | |
| 293 | ||
| 294 | elapsed = time.time() - start_time | |
| 295 | ||
| 296 | if elapsed < 2.0: # not enough time to estimate | |
| 297 | return None | |
| 298 | ||
| 299 | total_duration = float(elapsed) * float(total) / float(current) | |
| 300 | ||
| 301 | assert total_duration >= elapsed | |
| 302 | ||
| 1185.16.75
by Martin Pool - improved eta estimation for progress bar | 303 | if last_updates and len(last_updates) >= n_recent: | 
| 304 | while len(last_updates) > n_recent: | |
| 305 | last_updates.popleft() | |
| 306 | avg = sum(last_updates) / float(len(last_updates)) | |
| 307 | time_left = avg * (total - current) | |
| 308 | ||
| 309 | old_time_left = total_duration - elapsed | |
| 310 | ||
| 311 |         # We could return the average, or some other value here
 | |
| 312 | return (time_left + old_time_left) / 2 | |
| 313 | ||
| 660
by Martin Pool - use plain unix time, not datetime module | 314 | return total_duration - elapsed | 
| 648
by Martin Pool - import aaron's progress-indicator code | 315 | |
| 649
by Martin Pool - some cleanups for the progressbar method | 316 | |
| 648
by Martin Pool - import aaron's progress-indicator code | 317 | def run_tests(): | 
| 318 | import doctest | |
| 319 | result = doctest.testmod() | |
| 320 | if result[1] > 0: | |
| 321 | if result[0] == 0: | |
| 322 | print "All tests passed" | |
| 323 | else: | |
| 324 | print "No tests to run" | |
| 649
by Martin Pool - some cleanups for the progressbar method | 325 | |
| 326 | ||
| 327 | def demo(): | |
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 328 | sleep = time.sleep | 
| 329 | ||
| 330 | print 'dumb-terminal test:' | |
| 331 | pb = DotsProgressBar() | |
| 332 | for i in range(100): | |
| 333 | pb.update('Leoparden', i, 99) | |
| 334 | sleep(0.1) | |
| 335 | sleep(1.5) | |
| 336 | pb.clear() | |
| 337 | sleep(1.5) | |
| 338 | ||
| 339 | print 'smart-terminal test:' | |
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 340 | pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False) | 
| 649
by Martin Pool - some cleanups for the progressbar method | 341 | for i in range(100): | 
| 658
by Martin Pool - clean up and add a bunch of options to the progress indicator | 342 | pb.update('Elephanten', i, 99) | 
| 343 | sleep(0.1) | |
| 344 | sleep(2) | |
| 345 | pb.clear() | |
| 346 | sleep(1) | |
| 964
by Martin Pool - show progress on dumb terminals by printing dots | 347 | |
| 649
by Martin Pool - some cleanups for the progressbar method | 348 | print 'done!' | 
| 349 | ||
| 648
by Martin Pool - import aaron's progress-indicator code | 350 | if __name__ == "__main__": | 
| 649
by Martin Pool - some cleanups for the progressbar method | 351 | demo() |