1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
# Copyright (C) 2005, 2006 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
16
15
# 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.
18
"""Progress indicators.
20
The usual way to use this is via bzrlib.ui.ui_factory.nested_progress_bar which
21
will manage a conceptual stack of nested activities.
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
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
36
# when the rate is unpredictable
42
from bzrlib.lazy_import import lazy_import
43
lazy_import(globals(), """
44
31
from bzrlib import (
49
37
from bzrlib.trace import mutter
38
from bzrlib.symbol_versioning import (
52
44
def _supports_progress(f):
45
"""Detect if we can use pretty progress bars on the output stream f.
47
If this returns true we expect that a human may be looking at that
48
output, and that we can repaint a line to update it.
53
50
isatty = getattr(f, 'isatty', None)
64
_progress_bar_types = {}
61
class ProgressTask(object):
62
"""Model component of a progress indicator.
64
Most code that needs to indicate progress should update one of these,
65
and it will in turn update the display, if one is present.
67
Code updating the task may also set fields as hints about how to display
68
it: show_pct, show_spinner, show_eta, show_count, show_bar. UIs
69
will not necessarily respect all these fields.
72
def __init__(self, parent_task=None, ui_factory=None):
73
self._parent_task = parent_task
76
self.current_cnt = None
78
self.ui_factory = ui_factory
80
self.show_spinner = True
81
self.show_eta = False,
82
self.show_count = True
86
return '%s(%r/%r, msg=%r)' % (
87
self.__class__.__name__,
92
def update(self, msg, current_cnt=None, total_cnt=None):
94
self.current_cnt = current_cnt
96
self.total_cnt = total_cnt
97
self.ui_factory._progress_updated(self)
100
self.update(self.msg)
103
self.ui_factory._progress_finished(self)
105
def make_sub_task(self):
106
return ProgressTask(self, self.ui_factory)
108
def _overall_completion_fraction(self, child_fraction=0.0):
109
"""Return fractional completion of this task and its parents
111
Returns None if no completion can be computed."""
113
own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
116
if self._parent_task is None:
119
if own_fraction is None:
121
return self._parent_task._overall_completion_fraction(own_fraction)
123
def note(self, fmt_string, *args):
124
"""Record a note without disrupting the progress bar."""
125
# XXX: shouldn't be here; put it in mutter or the ui instead
127
self.ui_factory.note(fmt_string % args)
129
self.ui_factory.note(fmt_string)
132
# XXX: shouldn't be here; put it in mutter or the ui instead
133
self.ui_factory.clear_term()
67
136
def ProgressBar(to_file=None, **kwargs):
74
143
if _supports_progress(to_file):
75
144
return TTYProgressBar(to_file=to_file, **kwargs)
77
return DotsProgressBar(to_file=to_file, **kwargs)
146
return DummyProgress(to_file=to_file, **kwargs)
79
148
# Minor sanitation to prevent spurious errors
80
149
requested_bar_type = requested_bar_type.lower().strip()
86
155
_progress_bar_types.keys())
87
156
return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
90
159
class ProgressBarStack(object):
91
"""A stack of progress bars."""
160
"""A stack of progress bars.
162
This class is deprecated: instead, ask the ui factory for a new progress
163
task and finish it when it's done.
166
@deprecated_method(deprecated_in((1, 12, 0)))
93
167
def __init__(self,
146
220
def return_pb(self, bar):
147
221
"""Return bar after its been used."""
148
222
if bar is not self._stack[-1]:
149
raise errors.MissingProgressBarFinish()
223
warnings.warn("%r is not currently active" % (bar,))
153
228
class _BaseProgressBar(object):
188
263
def finished(self):
189
264
"""Return this bar to its progress stack."""
191
assert self._stack is not None
192
266
self._stack.return_pb(self)
194
268
def note(self, fmt_string, *args, **kwargs):
225
300
return DummyProgress(**kwargs)
228
_progress_bar_types['dummy'] = DummyProgress
229
_progress_bar_types['none'] = DummyProgress
232
303
class DotsProgressBar(_BaseProgressBar):
234
305
def __init__(self, **kwargs):
318
self.update(self.last_msg, self.last_cnt, self.last_total,
387
self.update(self.last_msg, self.last_cnt, self.last_total,
319
388
self.child_fraction)
321
390
def child_update(self, message, current, total):
326
395
elif self.last_cnt + child_fraction <= self.last_total:
327
396
self.child_fraction = child_fraction
329
mutter('not updating child fraction')
330
397
if self.last_msg is None:
331
398
self.last_msg = ''
334
def update(self, msg, current_cnt=None, total_cnt=None,
336
"""Update and redraw progress bar."""
401
def update(self, msg, current_cnt=None, total_cnt=None,
403
"""Update and redraw progress bar.
338
406
msg = self.last_msg
404
475
# make both fields the same size
405
476
t = '%i' % (self.last_total)
406
477
c = '%*i' % (len(t), self.last_cnt)
407
count_str = ' ' + c + '/' + t
478
count_str = ' ' + c + '/' + t
409
480
if self.show_bar:
410
481
# progress bar, if present, soaks up all remaining space
431
m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
502
m = spin_str + bar_str + self.last_msg + count_str \
432
504
self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
433
505
self._have_output = True
434
506
#self.to_file.flush()
437
509
if self._have_output:
438
510
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
439
511
self._have_output = False
440
512
#self.to_file.flush()
443
_progress_bar_types['tty'] = TTYProgressBar
446
517
class ChildProgress(_BaseProgressBar):
485
557
def note(self, *args, **kwargs):
486
558
self.parent.note(*args, **kwargs)
561
class InstrumentedProgress(TTYProgressBar):
562
"""TTYProgress variant that tracks outcomes"""
564
def __init__(self, *args, **kwargs):
565
self.always_throttled = True
566
self.never_throttle = False
567
TTYProgressBar.__init__(self, *args, **kwargs)
569
def throttle(self, old_message):
570
if self.never_throttle:
573
result = TTYProgressBar.throttle(self, old_message)
575
self.always_throttled = False
489
578
def str_tdelta(delt):
516
605
total_duration = float(elapsed) * float(total) / float(current)
518
assert total_duration >= elapsed
520
607
if last_updates and len(last_updates) >= n_recent:
521
608
avg = sum(last_updates) / float(len(last_updates))
522
609
time_left = avg * (total - current)
543
630
self.cur_phase = 0
545
632
self.cur_phase += 1
546
assert self.cur_phase < self.total
547
633
self.pb.update(self.message, self.cur_phase, self.total)
552
result = doctest.testmod()
555
print "All tests passed"
557
print "No tests to run"
563
print 'dumb-terminal test:'
564
pb = DotsProgressBar()
566
pb.update('Leoparden', i, 99)
572
print 'smart-terminal test:'
573
pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
575
pb.update('Elephanten', i, 99)
583
if __name__ == "__main__":
636
_progress_bar_types = {}
637
_progress_bar_types['dummy'] = DummyProgress
638
_progress_bar_types['none'] = DummyProgress
639
_progress_bar_types['tty'] = TTYProgressBar
640
_progress_bar_types['dots'] = DotsProgressBar