~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: Vincent Ladeuil
  • Date: 2007-10-09 20:32:29 UTC
  • mto: (2903.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 2904.
  • Revision ID: v.ladeuil+lp@free.fr-20071009203229-5k200m1g7mf4jf9l
Fix 149019 by using a proper line number when reporting errors.

* bzrlib/util/configobj/configobj.py:
(ConfigObj._handle_error): Trivial fix. Since cur_index is
0-based, line number was off by one.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
2
3
#
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
12
13
#
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
16
 
 
17
 
 
18
 
"""Progress indicators.
19
 
 
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
 
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.
22
27
"""
23
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
 
 
35
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
 
36
# when the rate is unpredictable
24
37
 
25
38
import sys
26
39
import time
27
40
import os
28
41
 
29
 
 
 
42
from bzrlib.lazy_import import lazy_import
 
43
lazy_import(globals(), """
30
44
from bzrlib import (
31
45
    errors,
32
46
    )
 
47
""")
 
48
 
33
49
from bzrlib.trace import mutter
34
 
from bzrlib.symbol_versioning import (
35
 
    deprecated_function,
36
 
    deprecated_in,
37
 
    )
38
50
 
39
51
 
40
52
def _supports_progress(f):
41
 
    """Detect if we can use pretty progress bars on file F.
 
53
    """Detect if we can use pretty progress bars on the output stream f.
42
54
 
43
 
    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 
44
56
    output, and that we can repaint a line to update it.
45
 
 
46
 
    This doesn't check the policy for whether we *should* use them.
47
57
    """
48
58
    isatty = getattr(f, 'isatty', None)
49
59
    if isatty is None:
50
60
        return False
51
61
    if not isatty():
52
62
        return False
53
 
    # The following case also handles Win32 - on that platform $TERM is
54
 
    # typically never set, so the case None is treated as a smart terminal,
55
 
    # not dumb.  <https://bugs.launchpad.net/bugs/334808>  win32 files do have
56
 
    # isatty methods that return true.
57
63
    if os.environ.get('TERM') == 'dumb':
58
64
        # e.g. emacs compile window
59
65
        return False
60
66
    return True
61
67
 
62
68
 
63
 
class ProgressTask(object):
64
 
    """Model component of a progress indicator.
65
 
 
66
 
    Most code that needs to indicate progress should update one of these,
67
 
    and it will in turn update the display, if one is present.
68
 
 
69
 
    Code updating the task may also set fields as hints about how to display
70
 
    it: show_pct, show_spinner, show_eta, show_count, show_bar.  UIs
71
 
    will not necessarily respect all these fields.
72
 
    """
73
 
 
74
 
    def __init__(self, parent_task=None, ui_factory=None, progress_view=None):
75
 
        """Construct a new progress task.
76
 
 
77
 
        :param parent_task: Enclosing ProgressTask or None.
78
 
 
79
 
        :param progress_view: ProgressView to display this ProgressTask.
80
 
 
81
 
        :param ui_factory: The UI factory that will display updates; 
82
 
            deprecated in favor of passing progress_view directly.
83
 
 
84
 
        Normally you should not call this directly but rather through
85
 
        `ui_factory.nested_progress_bar`.
86
 
        """
87
 
        self._parent_task = parent_task
88
 
        self._last_update = 0
89
 
        self.total_cnt = None
90
 
        self.current_cnt = None
91
 
        self.msg = ''
92
 
        # TODO: deprecate passing ui_factory
93
 
        self.ui_factory = ui_factory
94
 
        self.progress_view = progress_view
95
 
        self.show_pct = False
96
 
        self.show_spinner = True
97
 
        self.show_eta = False,
98
 
        self.show_count = True
99
 
        self.show_bar = True
100
 
 
101
 
    def __repr__(self):
102
 
        return '%s(%r/%r, msg=%r)' % (
103
 
            self.__class__.__name__,
104
 
            self.current_cnt,
105
 
            self.total_cnt,
106
 
            self.msg)
107
 
 
108
 
    def update(self, msg, current_cnt=None, total_cnt=None):
109
 
        self.msg = msg
110
 
        self.current_cnt = current_cnt
111
 
        if total_cnt:
112
 
            self.total_cnt = total_cnt
113
 
        if self.progress_view:
114
 
            self.progress_view.show_progress(self)
115
 
        else:
116
 
            self.ui_factory._progress_updated(self)
117
 
 
118
 
    def tick(self):
119
 
        self.update(self.msg)
120
 
 
121
 
    def finished(self):
122
 
        if self.progress_view:
123
 
            self.progress_view.task_finished(self)
124
 
        else:
125
 
            self.ui_factory._progress_finished(self)
126
 
 
127
 
    def make_sub_task(self):
128
 
        return ProgressTask(self, ui_factory=self.ui_factory,
129
 
            progress_view=self.progress_view)
130
 
 
131
 
    def _overall_completion_fraction(self, child_fraction=0.0):
132
 
        """Return fractional completion of this task and its parents
133
 
 
134
 
        Returns None if no completion can be computed."""
135
 
        if self.current_cnt is not None and self.total_cnt:
136
 
            own_fraction = (float(self.current_cnt) + child_fraction) / self.total_cnt
137
 
        else:
138
 
            # if this task has no estimation, it just passes on directly
139
 
            # whatever the child has measured...
140
 
            own_fraction = child_fraction
141
 
        if self._parent_task is None:
142
 
            return own_fraction
143
 
        else:
144
 
            if own_fraction is None:
145
 
                own_fraction = 0.0
146
 
            return self._parent_task._overall_completion_fraction(own_fraction)
147
 
 
148
 
    def note(self, fmt_string, *args):
149
 
        """Record a note without disrupting the progress bar."""
150
 
        # XXX: shouldn't be here; put it in mutter or the ui instead
151
 
        if args:
152
 
            self.ui_factory.note(fmt_string % args)
153
 
        else:
154
 
            self.ui_factory.note(fmt_string)
155
 
 
156
 
    def clear(self):
157
 
        # XXX: shouldn't be here; put it in mutter or the ui instead
158
 
        if self.progress_view:
159
 
            self.progress_view.clear()
160
 
        else:
161
 
            self.ui_factory.clear_term()
162
 
 
163
 
 
164
 
@deprecated_function(deprecated_in((1, 16, 0)))
 
69
_progress_bar_types = {}
 
70
 
 
71
 
165
72
def ProgressBar(to_file=None, **kwargs):
166
73
    """Abstract factory"""
167
74
    if to_file is None:
184
91
                                                _progress_bar_types.keys())
185
92
        return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
186
93
 
187
 
 
188
 
# NOTE: This is also deprecated; you should provide a ProgressView instead.
 
94
 
 
95
class ProgressBarStack(object):
 
96
    """A stack of progress bars."""
 
97
 
 
98
    def __init__(self,
 
99
                 to_file=None,
 
100
                 show_pct=False,
 
101
                 show_spinner=True,
 
102
                 show_eta=False,
 
103
                 show_bar=True,
 
104
                 show_count=True,
 
105
                 to_messages_file=None,
 
106
                 klass=None):
 
107
        """Setup the stack with the parameters the progress bars should have."""
 
108
        if to_file is None:
 
109
            to_file = sys.stderr
 
110
        if to_messages_file is None:
 
111
            to_messages_file = sys.stdout
 
112
        self._to_file = to_file
 
113
        self._show_pct = show_pct
 
114
        self._show_spinner = show_spinner
 
115
        self._show_eta = show_eta
 
116
        self._show_bar = show_bar
 
117
        self._show_count = show_count
 
118
        self._to_messages_file = to_messages_file
 
119
        self._stack = []
 
120
        self._klass = klass or ProgressBar
 
121
 
 
122
    def top(self):
 
123
        if len(self._stack) != 0:
 
124
            return self._stack[-1]
 
125
        else:
 
126
            return None
 
127
 
 
128
    def bottom(self):
 
129
        if len(self._stack) != 0:
 
130
            return self._stack[0]
 
131
        else:
 
132
            return None
 
133
 
 
134
    def get_nested(self):
 
135
        """Return a nested progress bar."""
 
136
        if len(self._stack) == 0:
 
137
            func = self._klass
 
138
        else:
 
139
            func = self.top().child_progress
 
140
        new_bar = func(to_file=self._to_file,
 
141
                       show_pct=self._show_pct,
 
142
                       show_spinner=self._show_spinner,
 
143
                       show_eta=self._show_eta,
 
144
                       show_bar=self._show_bar,
 
145
                       show_count=self._show_count,
 
146
                       to_messages_file=self._to_messages_file,
 
147
                       _stack=self)
 
148
        self._stack.append(new_bar)
 
149
        return new_bar
 
150
 
 
151
    def return_pb(self, bar):
 
152
        """Return bar after its been used."""
 
153
        if bar is not self._stack[-1]:
 
154
            raise errors.MissingProgressBarFinish()
 
155
        self._stack.pop()
 
156
 
 
157
 
189
158
class _BaseProgressBar(object):
190
159
 
191
160
    def __init__(self,
224
193
    def finished(self):
225
194
        """Return this bar to its progress stack."""
226
195
        self.clear()
 
196
        assert self._stack is not None
227
197
        self._stack.return_pb(self)
228
198
 
229
199
    def note(self, fmt_string, *args, **kwargs):
232
202
        self.to_messages_file.write(fmt_string % args)
233
203
        self.to_messages_file.write('\n')
234
204
 
235
 
    @deprecated_function(deprecated_in((1, 16, 0)))
236
205
    def child_progress(self, **kwargs):
237
206
        return ChildProgress(**kwargs)
238
207
 
242
211
 
243
212
    This can be used as the default argument for methods that
244
213
    take an optional progress indicator."""
245
 
 
246
214
    def tick(self):
247
215
        pass
248
216
 
254
222
 
255
223
    def clear(self):
256
224
        pass
257
 
 
 
225
        
258
226
    def note(self, fmt_string, *args, **kwargs):
259
227
        """See _BaseProgressBar.note()."""
260
228
 
262
230
        return DummyProgress(**kwargs)
263
231
 
264
232
 
 
233
_progress_bar_types['dummy'] = DummyProgress
 
234
_progress_bar_types['none'] = DummyProgress
 
235
 
 
236
 
265
237
class DotsProgressBar(_BaseProgressBar):
266
238
 
267
 
    @deprecated_function(deprecated_in((1, 16, 0)))
268
239
    def __init__(self, **kwargs):
269
240
        _BaseProgressBar.__init__(self, **kwargs)
270
241
        self.last_msg = None
271
242
        self.need_nl = False
272
 
 
 
243
        
273
244
    def tick(self):
274
245
        self.update()
275
 
 
 
246
        
276
247
    def update(self, msg=None, current_cnt=None, total_cnt=None):
277
248
        if msg and msg != self.last_msg:
278
249
            if self.need_nl:
281
252
            self.last_msg = msg
282
253
        self.need_nl = True
283
254
        self.to_file.write('.')
284
 
 
 
255
        
285
256
    def clear(self):
286
257
        if self.need_nl:
287
258
            self.to_file.write('\n')
288
259
        self.need_nl = False
289
 
 
 
260
        
290
261
    def child_update(self, message, current, total):
291
262
        self.tick()
292
263
 
293
264
 
 
265
_progress_bar_types['dots'] = DotsProgressBar
 
266
 
 
267
    
294
268
class TTYProgressBar(_BaseProgressBar):
295
269
    """Progress bar display object.
296
270
 
313
287
    """
314
288
    SPIN_CHARS = r'/-\|'
315
289
 
316
 
    @deprecated_function(deprecated_in((1, 16, 0)))
 
290
 
317
291
    def __init__(self, **kwargs):
318
292
        from bzrlib.osutils import terminal_width
319
293
        _BaseProgressBar.__init__(self, **kwargs)
323
297
        self._max_last_updates = 10
324
298
        self.child_fraction = 0
325
299
        self._have_output = False
 
300
    
326
301
 
327
302
    def throttle(self, old_msg):
328
303
        """Return True if the bar was updated too recently"""
343
318
        self.last_updates = self.last_updates[-self._max_last_updates:]
344
319
        self.last_update = now
345
320
        return False
346
 
 
 
321
        
347
322
    def tick(self):
348
 
        self.update(self.last_msg, self.last_cnt, self.last_total,
 
323
        self.update(self.last_msg, self.last_cnt, self.last_total, 
349
324
                    self.child_fraction)
350
325
 
351
326
    def child_update(self, message, current, total):
355
330
                pass
356
331
            elif self.last_cnt + child_fraction <= self.last_total:
357
332
                self.child_fraction = child_fraction
 
333
            else:
 
334
                mutter('not updating child fraction')
358
335
        if self.last_msg is None:
359
336
            self.last_msg = ''
360
337
        self.tick()
361
338
 
362
 
    def update(self, msg, current_cnt=None, total_cnt=None,
363
 
            child_fraction=0):
364
 
        """Update and redraw progress bar.
365
 
        """
 
339
    def update(self, msg, current_cnt=None, total_cnt=None, 
 
340
               child_fraction=0):
 
341
        """Update and redraw progress bar."""
366
342
        if msg is None:
367
343
            msg = self.last_msg
368
344
 
371
347
 
372
348
        if current_cnt < 0:
373
349
            current_cnt = 0
374
 
 
 
350
            
375
351
        if current_cnt > total_cnt:
376
352
            total_cnt = current_cnt
377
 
 
378
 
        ## # optional corner case optimisation
 
353
        
 
354
        ## # optional corner case optimisation 
379
355
        ## # currently does not seem to fire so costs more than saved.
380
356
        ## # trivial optimal case:
381
357
        ## # NB if callers are doing a clear and restore with
388
364
        ##     self.child_fraction == child_fraction):
389
365
        ##     return
390
366
 
391
 
        if msg is None:
392
 
            msg = ''
393
 
 
394
367
        old_msg = self.last_msg
395
368
        # save these for the tick() function
396
369
        self.last_msg = msg
398
371
        self.last_total = total_cnt
399
372
        self.child_fraction = child_fraction
400
373
 
401
 
        # each function call takes 20ms/4000 = 0.005 ms,
 
374
        # each function call takes 20ms/4000 = 0.005 ms, 
402
375
        # but multiple that by 4000 calls -> starts to cost.
403
376
        # so anything to make this function call faster
404
377
        # will improve base 'diff' time by up to 0.1 seconds.
406
379
            return
407
380
 
408
381
        if self.show_eta and self.start_time and self.last_total:
409
 
            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, 
410
383
                    self.last_total, last_updates = self.last_updates)
411
384
            eta_str = " " + str_tdelta(eta)
412
385
        else:
413
386
            eta_str = ""
414
387
 
415
388
        if self.show_spinner:
416
 
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '
 
389
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '            
417
390
        else:
418
391
            spin_str = ''
419
392
 
436
409
            # make both fields the same size
437
410
            t = '%i' % (self.last_total)
438
411
            c = '%*i' % (len(t), self.last_cnt)
439
 
            count_str = ' ' + c + '/' + t
 
412
            count_str = ' ' + c + '/' + t 
440
413
 
441
414
        if self.show_bar:
442
415
            # progress bar, if present, soaks up all remaining space
445
418
 
446
419
            if self.last_total:
447
420
                # number of markers highlighted in bar
448
 
                markers = int(round(float(cols) *
 
421
                markers = int(round(float(cols) * 
449
422
                              (self.last_cnt + self.child_fraction) / self.last_total))
450
423
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
451
424
            elif False:
453
426
                # so just show an expanded spinning thingy
454
427
                m = self.spin_pos % cols
455
428
                ms = (' ' * m + '*').ljust(cols)
456
 
 
 
429
                
457
430
                bar_str = '[' + ms + '] '
458
431
            else:
459
432
                bar_str = ''
460
433
        else:
461
434
            bar_str = ''
462
435
 
463
 
        m = spin_str + bar_str + self.last_msg + count_str \
464
 
            + pct_str + eta_str
 
436
        m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
465
437
        self.to_file.write('\r%-*.*s' % (self.width - 1, self.width - 1, m))
466
438
        self._have_output = True
467
439
        #self.to_file.flush()
468
 
 
469
 
    def clear(self):
 
440
            
 
441
    def clear(self):        
470
442
        if self._have_output:
471
443
            self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
472
444
        self._have_output = False
473
 
        #self.to_file.flush()
474
 
 
475
 
 
476
 
 
477
 
# DEPRECATED
 
445
        #self.to_file.flush()        
 
446
 
 
447
 
 
448
_progress_bar_types['tty'] = TTYProgressBar
 
449
 
 
450
 
478
451
class ChildProgress(_BaseProgressBar):
479
452
    """A progress indicator that pushes its data to the parent"""
480
453
 
481
 
    @deprecated_function(deprecated_in((1, 16, 0)))
482
454
    def __init__(self, _stack, **kwargs):
483
455
        _BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
484
456
        self.parent = _stack.top()
489
461
 
490
462
    def update(self, msg, current_cnt=None, total_cnt=None):
491
463
        self.current = current_cnt
492
 
        if total_cnt is not None:
493
 
            self.total = total_cnt
 
464
        self.total = total_cnt
494
465
        self.message = msg
495
466
        self.child_fraction = 0
496
467
        self.tick()
519
490
    def note(self, *args, **kwargs):
520
491
        self.parent.note(*args, **kwargs)
521
492
 
522
 
 
 
493
 
523
494
def str_tdelta(delt):
524
495
    if delt is None:
525
496
        return "-:--:--"
546
517
 
547
518
    if elapsed < 2.0:                   # not enough time to estimate
548
519
        return None
549
 
 
 
520
    
550
521
    total_duration = float(elapsed) * float(total) / float(current)
551
522
 
 
523
    assert total_duration >= elapsed
 
524
 
552
525
    if last_updates and len(last_updates) >= n_recent:
553
526
        avg = sum(last_updates) / float(len(last_updates))
554
527
        time_left = avg * (total - current)
575
548
            self.cur_phase = 0
576
549
        else:
577
550
            self.cur_phase += 1
 
551
        assert self.cur_phase < self.total 
578
552
        self.pb.update(self.message, self.cur_phase, self.total)
579
553
 
580
554
 
581
 
_progress_bar_types = {}
582
 
_progress_bar_types['dummy'] = DummyProgress
583
 
_progress_bar_types['none'] = DummyProgress
584
 
_progress_bar_types['tty'] = TTYProgressBar
585
 
_progress_bar_types['dots'] = DotsProgressBar
 
555
def run_tests():
 
556
    import doctest
 
557
    result = doctest.testmod()
 
558
    if result[1] > 0:
 
559
        if result[0] == 0:
 
560
            print "All tests passed"
 
561
    else:
 
562
        print "No tests to run"
 
563
 
 
564
 
 
565
def demo():
 
566
    sleep = time.sleep
 
567
    
 
568
    print 'dumb-terminal test:'
 
569
    pb = DotsProgressBar()
 
570
    for i in range(100):
 
571
        pb.update('Leoparden', i, 99)
 
572
        sleep(0.1)
 
573
    sleep(1.5)
 
574
    pb.clear()
 
575
    sleep(1.5)
 
576
    
 
577
    print 'smart-terminal test:'
 
578
    pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
 
579
    for i in range(100):
 
580
        pb.update('Elephanten', i, 99)
 
581
        sleep(0.1)
 
582
    sleep(2)
 
583
    pb.clear()
 
584
    sleep(1)
 
585
 
 
586
    print 'done!'
 
587
 
 
588
if __name__ == "__main__":
 
589
    demo()