~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: Wouter van Heyst
  • Date: 2006-06-06 22:37:30 UTC
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: larstiq@larstiq.dyndns.org-20060606223730-a308c5429fc6c617
change branch.{get,set}_parent to store a relative path but return full urls

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
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
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
 
 
18
 
 
19
 
"""Progress indicators.
20
 
 
21
 
The usual way to use this is via bzrlib.ui.ui_factory.nested_progress_bar which
22
 
will maintain a ProgressBarStack for you.
23
 
 
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.
 
2
# Copyright (C) 2005, 2006 Canonical <canonical.com>
 
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
 
 
18
 
 
19
"""Simple text-mode progress indicator.
 
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.
29
27
"""
30
28
 
 
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
 
 
33
# TODO: If not on a tty perhaps just print '......' for the benefit of IDEs, etc
 
34
 
31
35
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
32
36
# when the rate is unpredictable
33
37
 
 
38
 
34
39
import sys
35
40
import time
36
41
import os
37
 
 
38
 
from bzrlib.lazy_import import lazy_import
39
 
lazy_import(globals(), """
40
 
from bzrlib import (
41
 
    errors,
42
 
    )
43
 
""")
44
 
 
45
 
from bzrlib.trace import mutter
 
42
from collections import deque
 
43
 
 
44
 
 
45
import bzrlib.errors as errors
 
46
from bzrlib.trace import mutter 
46
47
 
47
48
 
48
49
def _supports_progress(f):
49
 
    """Detect if we can use pretty progress bars on the output stream f.
50
 
 
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.
53
 
    """
54
 
    isatty = getattr(f, 'isatty', None)
55
 
    if isatty is None:
 
50
    if not hasattr(f, 'isatty'):
56
51
        return False
57
 
    if not isatty():
 
52
    if not f.isatty():
58
53
        return False
59
54
    if os.environ.get('TERM') == 'dumb':
60
55
        # e.g. emacs compile window
62
57
    return True
63
58
 
64
59
 
65
 
_progress_bar_types = {}
66
 
 
67
60
 
68
61
def ProgressBar(to_file=None, **kwargs):
69
62
    """Abstract factory"""
70
63
    if to_file is None:
71
64
        to_file = sys.stderr
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)
77
 
        else:
78
 
            return DummyProgress(to_file=to_file, **kwargs)
 
65
    if _supports_progress(to_file):
 
66
        return TTYProgressBar(to_file=to_file, **kwargs)
79
67
    else:
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)
89
 
 
 
68
        return DotsProgressBar(to_file=to_file, **kwargs)
 
69
    
90
70
 
91
71
class ProgressBarStack(object):
92
72
    """A stack of progress bars."""
113
93
        self._show_count = show_count
114
94
        self._to_messages_file = to_messages_file
115
95
        self._stack = []
116
 
        self._klass = klass or ProgressBar
 
96
        self._klass = klass or TTYProgressBar
117
97
 
118
98
    def top(self):
119
99
        if len(self._stack) != 0:
157
137
                 to_file=None,
158
138
                 show_pct=False,
159
139
                 show_spinner=False,
160
 
                 show_eta=False,
 
140
                 show_eta=True,
161
141
                 show_bar=True,
162
142
                 show_count=True,
163
143
                 to_messages_file=None,
180
160
        self._stack = _stack
181
161
        # seed throttler
182
162
        self.MIN_PAUSE = 0.1 # seconds
183
 
        now = time.time()
 
163
        now = time.clock()
184
164
        # starting now
185
165
        self.start_time = now
186
166
        # next update should not throttle
189
169
    def finished(self):
190
170
        """Return this bar to its progress stack."""
191
171
        self.clear()
 
172
        assert self._stack is not None
192
173
        self._stack.return_pb(self)
193
174
 
194
175
    def note(self, fmt_string, *args, **kwargs):
225
206
        return DummyProgress(**kwargs)
226
207
 
227
208
 
228
 
_progress_bar_types['dummy'] = DummyProgress
229
 
_progress_bar_types['none'] = DummyProgress
230
 
 
231
 
 
232
209
class DotsProgressBar(_BaseProgressBar):
233
210
 
234
211
    def __init__(self, **kwargs):
256
233
    def child_update(self, message, current, total):
257
234
        self.tick()
258
235
 
259
 
 
260
 
_progress_bar_types['dots'] = DotsProgressBar
261
 
 
262
236
    
263
237
class TTYProgressBar(_BaseProgressBar):
264
238
    """Progress bar display object.
288
262
        _BaseProgressBar.__init__(self, **kwargs)
289
263
        self.spin_pos = 0
290
264
        self.width = terminal_width()
291
 
        self.last_updates = []
292
 
        self._max_last_updates = 10
 
265
        self.start_time = None
 
266
        self.last_updates = deque()
293
267
        self.child_fraction = 0
294
 
        self._have_output = False
295
268
    
296
 
    def throttle(self, old_msg):
 
269
 
 
270
    def throttle(self):
297
271
        """Return True if the bar was updated too recently"""
298
272
        # time.time consistently takes 40/4000 ms = 0.01 ms.
299
 
        # time.clock() is faster, but gives us CPU time, not wall-clock time
300
 
        now = time.time()
301
 
        if self.start_time is not None and (now - self.start_time) < 1:
302
 
            return True
303
 
        if old_msg != self.last_msg:
304
 
            return False
 
273
        # but every single update to the pb invokes it.
 
274
        # so we use time.clock which takes 20/4000 ms = 0.005ms
 
275
        # on the downside, time.clock() appears to have approximately
 
276
        # 10ms granularity, so we treat a zero-time change as 'throttled.'
 
277
        
 
278
        now = time.clock()
305
279
        interval = now - self.last_update
306
280
        # if interval > 0
307
281
        if interval < self.MIN_PAUSE:
308
282
            return True
309
283
 
310
284
        self.last_updates.append(now - self.last_update)
311
 
        # Don't let the queue grow without bound
312
 
        self.last_updates = self.last_updates[-self._max_last_updates:]
313
285
        self.last_update = now
314
286
        return False
315
287
        
316
288
    def tick(self):
317
 
        self.update(self.last_msg, self.last_cnt, self.last_total,
 
289
        self.update(self.last_msg, self.last_cnt, self.last_total, 
318
290
                    self.child_fraction)
319
291
 
320
292
    def child_update(self, message, current, total):
324
296
                pass
325
297
            elif self.last_cnt + child_fraction <= self.last_total:
326
298
                self.child_fraction = child_fraction
 
299
            else:
 
300
                mutter('not updating child fraction')
327
301
        if self.last_msg is None:
328
302
            self.last_msg = ''
329
303
        self.tick()
330
304
 
331
 
    def update(self, msg, current_cnt=None, total_cnt=None,
 
305
    def update(self, msg, current_cnt=None, total_cnt=None, 
332
306
               child_fraction=0):
333
307
        """Update and redraw progress bar."""
334
308
        if msg is None:
367
341
        # but multiple that by 4000 calls -> starts to cost.
368
342
        # so anything to make this function call faster
369
343
        # will improve base 'diff' time by up to 0.1 seconds.
370
 
        if self.throttle(old_msg):
 
344
        if old_msg == self.last_msg and self.throttle():
371
345
            return
372
346
 
373
347
        if self.show_eta and self.start_time and self.last_total:
426
400
            bar_str = ''
427
401
 
428
402
        m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
429
 
        self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
430
 
        self._have_output = True
 
403
 
 
404
        assert len(m) < self.width
 
405
        self.to_file.write('\r' + m.ljust(self.width - 1))
431
406
        #self.to_file.flush()
432
407
            
433
 
    def clear(self):
434
 
        if self._have_output:
435
 
            self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
436
 
        self._have_output = False
 
408
    def clear(self):        
 
409
        self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
437
410
        #self.to_file.flush()        
438
411
 
439
412
 
440
 
_progress_bar_types['tty'] = TTYProgressBar
441
 
 
442
 
 
443
413
class ChildProgress(_BaseProgressBar):
444
414
    """A progress indicator that pushes its data to the parent"""
445
415
 
453
423
 
454
424
    def update(self, msg, current_cnt=None, total_cnt=None):
455
425
        self.current = current_cnt
456
 
        if total_cnt is not None:
457
 
            self.total = total_cnt
 
426
        self.total = total_cnt
458
427
        self.message = msg
459
428
        self.child_fraction = 0
460
429
        self.tick()
483
452
    def note(self, *args, **kwargs):
484
453
        self.parent.note(*args, **kwargs)
485
454
 
486
 
 
487
 
class InstrumentedProgress(TTYProgressBar):
488
 
    """TTYProgress variant that tracks outcomes"""
489
 
 
490
 
    def __init__(self, *args, **kwargs):
491
 
        self.always_throttled = True
492
 
        self.never_throttle = False
493
 
        TTYProgressBar.__init__(self, *args, **kwargs)
494
 
 
495
 
    def throttle(self, old_message):
496
 
        if self.never_throttle:
497
 
            result =  False
498
 
        else:
499
 
            result = TTYProgressBar.throttle(self, old_message)
500
 
        if result is False:
501
 
            self.always_throttled = False
502
 
 
503
 
 
 
455
 
504
456
def str_tdelta(delt):
505
457
    if delt is None:
506
458
        return "-:--:--"
523
475
    if current > total:
524
476
        return None                     # wtf?
525
477
 
526
 
    elapsed = time.time() - start_time
 
478
    elapsed = time.clock() - start_time
527
479
 
528
480
    if elapsed < 2.0:                   # not enough time to estimate
529
481
        return None
530
482
    
531
483
    total_duration = float(elapsed) * float(total) / float(current)
532
484
 
 
485
    assert total_duration >= elapsed
 
486
 
533
487
    if last_updates and len(last_updates) >= n_recent:
 
488
        while len(last_updates) > n_recent:
 
489
            last_updates.popleft()
534
490
        avg = sum(last_updates) / float(len(last_updates))
535
491
        time_left = avg * (total - current)
536
492
 
556
512
            self.cur_phase = 0
557
513
        else:
558
514
            self.cur_phase += 1
 
515
        assert self.cur_phase < self.total 
559
516
        self.pb.update(self.message, self.cur_phase, self.total)
 
517
 
 
518
 
 
519
def run_tests():
 
520
    import doctest
 
521
    result = doctest.testmod()
 
522
    if result[1] > 0:
 
523
        if result[0] == 0:
 
524
            print "All tests passed"
 
525
    else:
 
526
        print "No tests to run"
 
527
 
 
528
 
 
529
def demo():
 
530
    sleep = time.sleep
 
531
    
 
532
    print 'dumb-terminal test:'
 
533
    pb = DotsProgressBar()
 
534
    for i in range(100):
 
535
        pb.update('Leoparden', i, 99)
 
536
        sleep(0.1)
 
537
    sleep(1.5)
 
538
    pb.clear()
 
539
    sleep(1.5)
 
540
    
 
541
    print 'smart-terminal test:'
 
542
    pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
 
543
    for i in range(100):
 
544
        pb.update('Elephanten', i, 99)
 
545
        sleep(0.1)
 
546
    sleep(2)
 
547
    pb.clear()
 
548
    sleep(1)
 
549
 
 
550
    print 'done!'
 
551
 
 
552
if __name__ == "__main__":
 
553
    demo()