1
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
# Copyright (C) 2005, 2006 Canonical <canonical.com>
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.
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.
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
19
"""Simple text-mode progress indicator.
21
To display an indicator, create a ProgressBar object. Call it,
22
passing Progress objects indicating the current state. When done,
25
Progress is suppressed when output is not sent to a terminal, so as
26
not to clutter log files.
2
# Copyright (C) 2005, 2006 Canonical Ltd
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.
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.
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
19
"""Progress indicators.
21
The usual way to use this is via bzrlib.ui.ui_factory.nested_progress_bar which
22
will maintain a ProgressBarStack for you.
24
For direct use, the factory ProgressBar will return an auto-detected progress
25
bar that should match your terminal type. You can manually create a
26
ProgressBarStack too if you need multiple levels of cooperating progress bars.
27
Note that bzrlib's internal functions use the ui module, so if you are using
28
bzrlib it really is best to use bzrlib.ui.ui_factory.
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.
33
# TODO: If not on a tty perhaps just print '......' for the benefit of IDEs, etc
35
31
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
36
32
# when the rate is unpredictable
42
from collections import deque
45
import bzrlib.errors as errors
46
from bzrlib.trace import mutter
38
from bzrlib.lazy_import import lazy_import
39
lazy_import(globals(), """
45
from bzrlib.trace import mutter
49
48
def _supports_progress(f):
50
if not hasattr(f, 'isatty'):
49
"""Detect if we can use pretty progress bars on the output stream f.
51
If this returns true we expect that a human may be looking at that
52
output, and that we can repaint a line to update it.
54
isatty = getattr(f, 'isatty', None)
54
59
if os.environ.get('TERM') == 'dumb':
55
60
# e.g. emacs compile window
61
def ProgressBar(to_file=sys.stderr, **kwargs):
65
_progress_bar_types = {}
68
def ProgressBar(to_file=None, **kwargs):
62
69
"""Abstract factory"""
63
if _supports_progress(to_file):
64
return TTYProgressBar(to_file=to_file, **kwargs)
72
requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
73
# An value of '' or not set reverts to standard processing
74
if requested_bar_type in (None, ''):
75
if _supports_progress(to_file):
76
return TTYProgressBar(to_file=to_file, **kwargs)
78
return DummyProgress(to_file=to_file, **kwargs)
66
return DotsProgressBar(to_file=to_file, **kwargs)
80
# Minor sanitation to prevent spurious errors
81
requested_bar_type = requested_bar_type.lower().strip()
82
# TODO: jam 20060710 Arguably we shouldn't raise an exception
83
# but should instead just disable progress bars if we
84
# don't recognize the type
85
if requested_bar_type not in _progress_bar_types:
86
raise errors.InvalidProgressBarType(requested_bar_type,
87
_progress_bar_types.keys())
88
return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
69
91
class ProgressBarStack(object):
70
92
"""A stack of progress bars."""
79
to_messages_file=sys.stdout,
101
to_messages_file=None,
81
103
"""Setup the stack with the parameters the progress bars should have."""
106
if to_messages_file is None:
107
to_messages_file = sys.stdout
82
108
self._to_file = to_file
83
109
self._show_pct = show_pct
84
110
self._show_spinner = show_spinner
250
289
_BaseProgressBar.__init__(self, **kwargs)
251
290
self.spin_pos = 0
252
291
self.width = terminal_width()
253
self.start_time = None
254
self.last_updates = deque()
292
self.last_updates = []
293
self._max_last_updates = 10
255
294
self.child_fraction = 0
295
self._have_output = False
297
def throttle(self, old_msg):
259
298
"""Return True if the bar was updated too recently"""
260
299
# time.time consistently takes 40/4000 ms = 0.01 ms.
261
# but every single update to the pb invokes it.
262
# so we use time.clock which takes 20/4000 ms = 0.005ms
263
# on the downside, time.clock() appears to have approximately
264
# 10ms granularity, so we treat a zero-time change as 'throttled.'
300
# time.clock() is faster, but gives us CPU time, not wall-clock time
302
if self.start_time is not None and (now - self.start_time) < 1:
304
if old_msg != self.last_msg:
267
306
interval = now - self.last_update
268
307
# if interval > 0
269
308
if interval < self.MIN_PAUSE:
272
311
self.last_updates.append(now - self.last_update)
312
# Don't let the queue grow without bound
313
self.last_updates = self.last_updates[-self._max_last_updates:]
273
314
self.last_update = now
278
self.update(self.last_msg, self.last_cnt, self.last_total,
318
self.update(self.last_msg, self.last_cnt, self.last_total,
279
319
self.child_fraction)
281
321
def child_update(self, message, current, total):
286
326
elif self.last_cnt + child_fraction <= self.last_total:
287
327
self.child_fraction = child_fraction
289
mutter('not updating child fraction')
290
328
if self.last_msg is None:
291
329
self.last_msg = ''
295
def update(self, msg, current_cnt=None, total_cnt=None,
332
def update(self, msg, current_cnt=None, total_cnt=None,
296
333
child_fraction=0):
297
334
"""Update and redraw progress bar."""
338
if total_cnt is None:
339
total_cnt = self.last_total
299
341
if current_cnt < 0:
387
429
m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
389
assert len(m) < self.width
390
self.to_file.write('\r' + m.ljust(self.width - 1))
430
self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
431
self._have_output = True
391
432
#self.to_file.flush()
394
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
435
if self._have_output:
436
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
437
self._have_output = False
395
438
#self.to_file.flush()
441
_progress_bar_types['tty'] = TTYProgressBar
398
444
class ChildProgress(_BaseProgressBar):
399
445
"""A progress indicator that pushes its data to the parent"""
400
447
def __init__(self, _stack, **kwargs):
401
448
_BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
402
449
self.parent = _stack.top()
436
484
def note(self, *args, **kwargs):
437
485
self.parent.note(*args, **kwargs)
488
class InstrumentedProgress(TTYProgressBar):
489
"""TTYProgress variant that tracks outcomes"""
491
def __init__(self, *args, **kwargs):
492
self.always_throttled = True
493
self.never_throttle = False
494
TTYProgressBar.__init__(self, *args, **kwargs)
496
def throttle(self, old_message):
497
if self.never_throttle:
500
result = TTYProgressBar.throttle(self, old_message)
502
self.always_throttled = False
440
505
def str_tdelta(delt):
496
559
self.cur_phase = 0
498
561
self.cur_phase += 1
499
assert self.cur_phase < self.total
562
assert self.cur_phase < self.total
500
563
self.pb.update(self.message, self.cur_phase, self.total)
505
result = doctest.testmod()
508
print "All tests passed"
510
print "No tests to run"
516
print 'dumb-terminal test:'
517
pb = DotsProgressBar()
519
pb.update('Leoparden', i, 99)
525
print 'smart-terminal test:'
526
pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
528
pb.update('Elephanten', i, 99)
536
if __name__ == "__main__":