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.
1
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
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
43
import bzrlib.errors as errors
44
37
from bzrlib.trace import mutter
38
from bzrlib.symbol_versioning import (
47
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.
48
50
isatty = getattr(f, 'isatty', None)
59
_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
"""Construct a new progress task.
75
Normally you should not call this directly but rather through
76
`ui_factory.nested_progress_bar`.
78
self._parent_task = parent_task
81
self.current_cnt = None
83
self.ui_factory = ui_factory
85
self.show_spinner = True
86
self.show_eta = False,
87
self.show_count = True
91
return '%s(%r/%r, msg=%r)' % (
92
self.__class__.__name__,
97
def update(self, msg, current_cnt=None, total_cnt=None):
99
self.current_cnt = current_cnt
101
self.total_cnt = total_cnt
102
self.ui_factory._progress_updated(self)
105
self.update(self.msg)
108
self.ui_factory._progress_finished(self)
110
def make_sub_task(self):
111
return ProgressTask(self, self.ui_factory)
113
def _overall_completion_fraction(self, child_fraction=0.0):
114
"""Return fractional completion of this task and its parents
116
Returns None if no completion can be computed."""
117
if self.current_cnt is not None and self.total_cnt:
118
own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
120
# if this task has no estimation, it just passes on directly
121
# whatever the child has measured...
122
own_fraction = child_fraction
123
if self._parent_task is None:
126
if own_fraction is None:
128
return self._parent_task._overall_completion_fraction(own_fraction)
130
def note(self, fmt_string, *args):
131
"""Record a note without disrupting the progress bar."""
132
# XXX: shouldn't be here; put it in mutter or the ui instead
134
self.ui_factory.note(fmt_string % args)
136
self.ui_factory.note(fmt_string)
139
# XXX: shouldn't be here; put it in mutter or the ui instead
140
self.ui_factory.clear_term()
62
143
def ProgressBar(to_file=None, **kwargs):
81
162
_progress_bar_types.keys())
82
163
return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
85
166
class ProgressBarStack(object):
86
"""A stack of progress bars."""
167
"""A stack of progress bars.
169
This class is deprecated: instead, ask the ui factory for a new progress
170
task and finish it when it's done.
173
@deprecated_method(deprecated_in((1, 12, 0)))
88
174
def __init__(self,
220
307
return DummyProgress(**kwargs)
223
_progress_bar_types['dummy'] = DummyProgress
224
_progress_bar_types['none'] = DummyProgress
227
310
class DotsProgressBar(_BaseProgressBar):
229
312
def __init__(self, **kwargs):
230
313
_BaseProgressBar.__init__(self, **kwargs)
231
314
self.last_msg = None
232
315
self.need_nl = False
237
320
def update(self, msg=None, current_cnt=None, total_cnt=None):
238
321
if msg and msg != self.last_msg:
287
369
self._max_last_updates = 10
288
370
self.child_fraction = 0
289
371
self._have_output = False
292
373
def throttle(self, old_msg):
293
374
"""Return True if the bar was updated too recently"""
294
375
# time.time consistently takes 40/4000 ms = 0.01 ms.
295
# but every single update to the pb invokes it.
296
# so we use time.clock which takes 20/4000 ms = 0.005ms
297
# on the downside, time.clock() appears to have approximately
298
# 10ms granularity, so we treat a zero-time change as 'throttled.'
376
# time.clock() is faster, but gives us CPU time, not wall-clock time
300
378
if self.start_time is not None and (now - self.start_time) < 1:
302
380
if old_msg != self.last_msg:
324
402
elif self.last_cnt + child_fraction <= self.last_total:
325
403
self.child_fraction = child_fraction
327
mutter('not updating child fraction')
328
404
if self.last_msg is None:
329
405
self.last_msg = ''
332
def update(self, msg, current_cnt=None, total_cnt=None,
334
"""Update and redraw progress bar."""
408
def update(self, msg, current_cnt=None, total_cnt=None,
410
"""Update and redraw progress bar.
336
413
msg = self.last_msg
374
454
if self.show_eta and self.start_time and self.last_total:
375
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
455
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
376
456
self.last_total, last_updates = self.last_updates)
377
457
eta_str = " " + str_tdelta(eta)
381
461
if self.show_spinner:
382
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
462
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
419
499
# so just show an expanded spinning thingy
420
500
m = self.spin_pos % cols
421
501
ms = (' ' * m + '*').ljust(cols)
423
503
bar_str = '[' + ms + '] '
429
m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
431
assert len(m) < self.width
432
self.to_file.write('\r' + m.ljust(self.width - 1))
509
m = spin_str + bar_str + self.last_msg + count_str \
511
self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
433
512
self._have_output = True
434
513
#self.to_file.flush()
437
516
if self._have_output:
438
517
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
439
518
self._have_output = False
440
#self.to_file.flush()
443
_progress_bar_types['tty'] = TTYProgressBar
519
#self.to_file.flush()
446
524
class ChildProgress(_BaseProgressBar):
485
564
def note(self, *args, **kwargs):
486
565
self.parent.note(*args, **kwargs)
568
class InstrumentedProgress(TTYProgressBar):
569
"""TTYProgress variant that tracks outcomes"""
571
def __init__(self, *args, **kwargs):
572
self.always_throttled = True
573
self.never_throttle = False
574
TTYProgressBar.__init__(self, *args, **kwargs)
576
def throttle(self, old_message):
577
if self.never_throttle:
580
result = TTYProgressBar.throttle(self, old_message)
582
self.always_throttled = False
489
585
def str_tdelta(delt):
543
637
self.cur_phase = 0
545
639
self.cur_phase += 1
546
assert self.cur_phase < self.total
547
640
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__":
643
_progress_bar_types = {}
644
_progress_bar_types['dummy'] = DummyProgress
645
_progress_bar_types['none'] = DummyProgress
646
_progress_bar_types['tty'] = TTYProgressBar
647
_progress_bar_types['dots'] = DotsProgressBar