1
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
# Copyright (C) 2005, 2006 Canonical Ltd
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
13
14
# You should have received a copy of the GNU General Public License
14
15
# 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.
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.
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(), """
31
44
from bzrlib import (
37
49
from bzrlib.trace import mutter
38
from bzrlib.symbol_versioning import (
44
52
def _supports_progress(f):
45
53
"""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
55
If this returns true we expect that a human may be looking at that
48
56
output, and that we can repaint a line to update it.
50
58
isatty = getattr(f, 'isatty', None)
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()
69
_progress_bar_types = {}
143
72
def ProgressBar(to_file=None, **kwargs):
162
91
_progress_bar_types.keys())
163
92
return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
166
95
class ProgressBarStack(object):
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)))
96
"""A stack of progress bars."""
174
98
def __init__(self,
227
151
def return_pb(self, bar):
228
152
"""Return bar after its been used."""
229
153
if bar is not self._stack[-1]:
230
warnings.warn("%r is not currently active" % (bar,))
154
raise errors.MissingProgressBarFinish()
235
158
class _BaseProgressBar(object):
237
160
def __init__(self,
307
230
return DummyProgress(**kwargs)
233
_progress_bar_types['dummy'] = DummyProgress
234
_progress_bar_types['none'] = DummyProgress
310
237
class DotsProgressBar(_BaseProgressBar):
312
239
def __init__(self, **kwargs):
313
240
_BaseProgressBar.__init__(self, **kwargs)
314
241
self.last_msg = None
315
242
self.need_nl = False
320
247
def update(self, msg=None, current_cnt=None, total_cnt=None):
321
248
if msg and msg != self.last_msg:
389
318
self.last_updates = self.last_updates[-self._max_last_updates:]
390
319
self.last_update = now
394
self.update(self.last_msg, self.last_cnt, self.last_total,
323
self.update(self.last_msg, self.last_cnt, self.last_total,
395
324
self.child_fraction)
397
326
def child_update(self, message, current, total):
402
331
elif self.last_cnt + child_fraction <= self.last_total:
403
332
self.child_fraction = child_fraction
334
mutter('not updating child fraction')
404
335
if self.last_msg is None:
405
336
self.last_msg = ''
408
def update(self, msg, current_cnt=None, total_cnt=None,
410
"""Update and redraw progress bar.
339
def update(self, msg, current_cnt=None, total_cnt=None,
341
"""Update and redraw progress bar."""
413
343
msg = self.last_msg
418
348
if current_cnt < 0:
421
351
if current_cnt > total_cnt:
422
352
total_cnt = current_cnt
424
## # optional corner case optimisation
354
## # optional corner case optimisation
425
355
## # currently does not seem to fire so costs more than saved.
426
356
## # trivial optimal case:
427
357
## # NB if callers are doing a clear and restore with
444
371
self.last_total = total_cnt
445
372
self.child_fraction = child_fraction
447
# each function call takes 20ms/4000 = 0.005 ms,
374
# each function call takes 20ms/4000 = 0.005 ms,
448
375
# but multiple that by 4000 calls -> starts to cost.
449
376
# so anything to make this function call faster
450
377
# will improve base 'diff' time by up to 0.1 seconds.
454
381
if self.show_eta and self.start_time and self.last_total:
455
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
382
eta = get_eta(self.start_time, self.last_cnt + self.child_fraction,
456
383
self.last_total, last_updates = self.last_updates)
457
384
eta_str = " " + str_tdelta(eta)
461
388
if self.show_spinner:
462
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
389
spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
482
409
# make both fields the same size
483
410
t = '%i' % (self.last_total)
484
411
c = '%*i' % (len(t), self.last_cnt)
485
count_str = ' ' + c + '/' + t
412
count_str = ' ' + c + '/' + t
487
414
if self.show_bar:
488
415
# progress bar, if present, soaks up all remaining space
499
426
# so just show an expanded spinning thingy
500
427
m = self.spin_pos % cols
501
428
ms = (' ' * m + '*').ljust(cols)
503
430
bar_str = '[' + ms + '] '
509
m = spin_str + bar_str + self.last_msg + count_str \
436
m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
511
437
self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
512
438
self._have_output = True
513
439
#self.to_file.flush()
516
442
if self._have_output:
517
443
self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
518
444
self._have_output = False
519
#self.to_file.flush()
445
#self.to_file.flush()
448
_progress_bar_types['tty'] = TTYProgressBar
524
451
class ChildProgress(_BaseProgressBar):
564
490
def note(self, *args, **kwargs):
565
491
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
585
494
def str_tdelta(delt):
609
518
if elapsed < 2.0: # not enough time to estimate
612
521
total_duration = float(elapsed) * float(total) / float(current)
523
assert total_duration >= elapsed
614
525
if last_updates and len(last_updates) >= n_recent:
615
526
avg = sum(last_updates) / float(len(last_updates))
616
527
time_left = avg * (total - current)
637
548
self.cur_phase = 0
639
550
self.cur_phase += 1
551
assert self.cur_phase < self.total
640
552
self.pb.update(self.message, self.cur_phase, self.total)
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
557
result = doctest.testmod()
560
print "All tests passed"
562
print "No tests to run"
568
print 'dumb-terminal test:'
569
pb = DotsProgressBar()
571
pb.update('Leoparden', i, 99)
577
print 'smart-terminal test:'
578
pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
580
pb.update('Elephanten', i, 99)
588
if __name__ == "__main__":