/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
649 by Martin Pool
- some cleanups for the progressbar method
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
2
# Copyright (C) 2005, 2006 Canonical <canonical.com>
648 by Martin Pool
- import aaron's progress-indicator code
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
649 by Martin Pool
- some cleanups for the progressbar method
18
889 by Martin Pool
- show progress bar during inventory conversion to weave, and make profiling optional
19
"""Simple text-mode progress indicator.
649 by Martin Pool
- some cleanups for the progressbar method
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.
27
"""
28
652 by Martin Pool
doc
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
962 by Martin Pool
todo
33
# TODO: If not on a tty perhaps just print '......' for the benefit of IDEs, etc
655 by Martin Pool
- better calculation of progress bar position
34
934 by Martin Pool
todo
35
# TODO: Optionally show elapsed time instead/as well as ETA; nicer
36
# when the rate is unpredictable
37
649 by Martin Pool
- some cleanups for the progressbar method
38
648 by Martin Pool
- import aaron's progress-indicator code
39
import sys
660 by Martin Pool
- use plain unix time, not datetime module
40
import time
964 by Martin Pool
- show progress on dumb terminals by printing dots
41
import os
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
42
from collections import deque
648 by Martin Pool
- import aaron's progress-indicator code
43
649 by Martin Pool
- some cleanups for the progressbar method
44
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
45
import bzrlib.errors as errors
1551.2.27 by Aaron Bentley
Got propogation under test
46
from bzrlib.trace import mutter 
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
47
48
649 by Martin Pool
- some cleanups for the progressbar method
49
def _supports_progress(f):
695 by Martin Pool
- don't display progress bars on really dumb terminals
50
    if not hasattr(f, 'isatty'):
51
        return False
52
    if not f.isatty():
53
        return False
54
    if os.environ.get('TERM') == 'dumb':
55
        # e.g. emacs compile window
56
        return False
57
    return True
649 by Martin Pool
- some cleanups for the progressbar method
58
59
60
964 by Martin Pool
- show progress on dumb terminals by printing dots
61
def ProgressBar(to_file=sys.stderr, **kwargs):
62
    """Abstract factory"""
63
    if _supports_progress(to_file):
64
        return TTYProgressBar(to_file=to_file, **kwargs)
65
    else:
66
        return DotsProgressBar(to_file=to_file, **kwargs)
67
    
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
68
 
69
class ProgressBarStack(object):
70
    """A stack of progress bars."""
71
72
    def __init__(self,
73
                 to_file=sys.stderr,
74
                 show_pct=False,
75
                 show_spinner=False,
76
                 show_eta=True,
77
                 show_bar=True,
78
                 show_count=True,
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
79
                 to_messages_file=sys.stdout,
80
                 klass=None):
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
81
        """Setup the stack with the parameters the progress bars should have."""
82
        self._to_file = to_file
83
        self._show_pct = show_pct
84
        self._show_spinner = show_spinner
85
        self._show_eta = show_eta
86
        self._show_bar = show_bar
87
        self._show_count = show_count
88
        self._to_messages_file = to_messages_file
89
        self._stack = []
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
90
        self._klass = klass or TTYProgressBar
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
91
1551.2.29 by Aaron Bentley
Got stack handling under test
92
    def top(self):
93
        if len(self._stack) != 0:
94
            return self._stack[-1]
95
        else:
96
            return None
97
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
98
    def get_nested(self):
99
        """Return a nested progress bar."""
1551.2.29 by Aaron Bentley
Got stack handling under test
100
        if len(self._stack) == 0:
101
            func = self._klass
102
        else:
103
            func = self.top().child_progress
104
        new_bar = func(to_file=self._to_file,
105
                       show_pct=self._show_pct,
106
                       show_spinner=self._show_spinner,
107
                       show_eta=self._show_eta,
108
                       show_bar=self._show_bar,
109
                       show_count=self._show_count,
110
                       to_messages_file=self._to_messages_file,
111
                       _stack=self)
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
112
        self._stack.append(new_bar)
113
        return new_bar
114
115
    def return_pb(self, bar):
116
        """Return bar after its been used."""
1594.1.4 by Robert Collins
Fix identity test in ProgressBarStack.return_pb
117
        if bar is not self._stack[-1]:
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
118
            raise errors.MissingProgressBarFinish()
119
        self._stack.pop()
120
121
 
964 by Martin Pool
- show progress on dumb terminals by printing dots
122
class _BaseProgressBar(object):
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
123
964 by Martin Pool
- show progress on dumb terminals by printing dots
124
    def __init__(self,
125
                 to_file=sys.stderr,
126
                 show_pct=False,
127
                 show_spinner=False,
128
                 show_eta=True,
129
                 show_bar=True,
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
130
                 show_count=True,
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
131
                 to_messages_file=sys.stdout,
132
                 _stack=None):
964 by Martin Pool
- show progress on dumb terminals by printing dots
133
        object.__init__(self)
134
        self.to_file = to_file
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
135
        self.to_messages_file = to_messages_file
964 by Martin Pool
- show progress on dumb terminals by printing dots
136
        self.last_msg = None
137
        self.last_cnt = None
138
        self.last_total = None
139
        self.show_pct = show_pct
140
        self.show_spinner = show_spinner
141
        self.show_eta = show_eta
142
        self.show_bar = show_bar
143
        self.show_count = show_count
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
144
        self._stack = _stack
145
146
    def finished(self):
147
        """Return this bar to its progress stack."""
148
        self.clear()
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
149
        assert self._stack is not None
1594.1.1 by Robert Collins
Introduce new bzr progress bar api. ui_factory.nested_progress_bar.
150
        self._stack.return_pb(self)
1104 by Martin Pool
- Add a simple UIFactory
151
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
152
    def note(self, fmt_string, *args, **kwargs):
153
        """Record a note without disrupting the progress bar."""
154
        self.clear()
155
        self.to_messages_file.write(fmt_string % args)
156
        self.to_messages_file.write('\n')
1104 by Martin Pool
- Add a simple UIFactory
157
1551.2.29 by Aaron Bentley
Got stack handling under test
158
    def child_progress(self, **kwargs):
159
        return ChildProgress(**kwargs)
160
1104 by Martin Pool
- Add a simple UIFactory
161
162
class DummyProgress(_BaseProgressBar):
163
    """Progress-bar standin that does nothing.
164
165
    This can be used as the default argument for methods that
166
    take an optional progress indicator."""
167
    def tick(self):
168
        pass
169
170
    def update(self, msg=None, current=None, total=None):
171
        pass
172
1551.2.27 by Aaron Bentley
Got propogation under test
173
    def child_update(self, message, current, total):
174
        pass
175
1104 by Martin Pool
- Add a simple UIFactory
176
    def clear(self):
177
        pass
964 by Martin Pool
- show progress on dumb terminals by printing dots
178
        
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
179
    def note(self, fmt_string, *args, **kwargs):
180
        """See _BaseProgressBar.note()."""
1534.5.9 by Robert Collins
Advise users running upgrade on a checkout to also run it on the branch.
181
1551.2.29 by Aaron Bentley
Got stack handling under test
182
    def child_progress(self, **kwargs):
183
        return DummyProgress(**kwargs)
1534.5.9 by Robert Collins
Advise users running upgrade on a checkout to also run it on the branch.
184
964 by Martin Pool
- show progress on dumb terminals by printing dots
185
class DotsProgressBar(_BaseProgressBar):
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
186
964 by Martin Pool
- show progress on dumb terminals by printing dots
187
    def __init__(self, **kwargs):
188
        _BaseProgressBar.__init__(self, **kwargs)
189
        self.last_msg = None
190
        self.need_nl = False
191
        
192
    def tick(self):
193
        self.update()
194
        
195
    def update(self, msg=None, current_cnt=None, total_cnt=None):
196
        if msg and msg != self.last_msg:
197
            if self.need_nl:
198
                self.to_file.write('\n')
199
            
200
            self.to_file.write(msg + ': ')
201
            self.last_msg = msg
202
        self.need_nl = True
203
        self.to_file.write('.')
204
        
205
    def clear(self):
206
        if self.need_nl:
207
            self.to_file.write('\n')
208
        
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
209
    def child_update(self, message, current, total):
210
        self.tick()
964 by Martin Pool
- show progress on dumb terminals by printing dots
211
    
212
class TTYProgressBar(_BaseProgressBar):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
213
    """Progress bar display object.
214
215
    Several options are available to control the display.  These can
216
    be passed as parameters to the constructor or assigned at any time:
217
218
    show_pct
219
        Show percentage complete.
220
    show_spinner
221
        Show rotating baton.  This ticks over on every update even
222
        if the values don't change.
223
    show_eta
224
        Show predicted time-to-completion.
225
    show_bar
226
        Show bar graph.
227
    show_count
228
        Show numerical counts.
229
230
    The output file should be in line-buffered or unbuffered mode.
231
    """
232
    SPIN_CHARS = r'/-\|'
661 by Martin Pool
- limit rate at which progress bar is updated
233
    MIN_PAUSE = 0.1 # seconds
234
964 by Martin Pool
- show progress on dumb terminals by printing dots
235
236
    def __init__(self, **kwargs):
1185.33.60 by Martin Pool
Use full terminal width for verbose test output.
237
        from bzrlib.osutils import terminal_width
964 by Martin Pool
- show progress on dumb terminals by printing dots
238
        _BaseProgressBar.__init__(self, **kwargs)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
239
        self.spin_pos = 0
1185.33.60 by Martin Pool
Use full terminal width for verbose test output.
240
        self.width = terminal_width()
964 by Martin Pool
- show progress on dumb terminals by printing dots
241
        self.start_time = None
242
        self.last_update = None
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
243
        self.last_updates = deque()
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
244
        self.child_fraction = 0
964 by Martin Pool
- show progress on dumb terminals by printing dots
245
    
246
247
    def throttle(self):
248
        """Return True if the bar was updated too recently"""
249
        now = time.time()
250
        if self.start_time is None:
251
            self.start_time = self.last_update = now
252
            return False
253
        else:
254
            interval = now - self.last_update
255
            if interval > 0 and interval < self.MIN_PAUSE:
256
                return True
257
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
258
        self.last_updates.append(now - self.last_update)
964 by Martin Pool
- show progress on dumb terminals by printing dots
259
        self.last_update = now
260
        return False
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
261
        
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
262
263
    def tick(self):
1551.2.27 by Aaron Bentley
Got propogation under test
264
        self.update(self.last_msg, self.last_cnt, self.last_total, 
265
                    self.child_fraction)
266
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
267
    def child_update(self, message, current, total):
268
        child_fraction = float(current) / total
269
        if self.last_cnt is None:
270
            pass
271
        elif self.last_cnt + child_fraction <= total:
272
            self.child_fraction = child_fraction
273
        else:
274
            mutter('not updating child fraction')
275
        self.tick()
276
277
1551.2.27 by Aaron Bentley
Got propogation under test
278
    def update(self, msg, current_cnt=None, total_cnt=None, 
279
               child_fraction=0):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
280
        """Update and redraw progress bar."""
1551.2.27 by Aaron Bentley
Got propogation under test
281
        self.child_fraction = child_fraction
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
282
1308 by Martin Pool
- make progress bar more tolerant of out-of-range values
283
        if current_cnt < 0:
284
            current_cnt = 0
285
            
286
        if current_cnt > total_cnt:
287
            total_cnt = current_cnt
1570.1.9 by Robert Collins
Do not throttle updates to progress bars that change the message.
288
        
289
        old_msg = self.last_msg
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
290
        # save these for the tick() function
291
        self.last_msg = msg
292
        self.last_cnt = current_cnt
293
        self.last_total = total_cnt
294
            
1570.1.9 by Robert Collins
Do not throttle updates to progress bars that change the message.
295
        if old_msg == self.last_msg and self.throttle():
964 by Martin Pool
- show progress on dumb terminals by printing dots
296
            return 
661 by Martin Pool
- limit rate at which progress bar is updated
297
        
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
298
        if self.show_eta and self.start_time and total_cnt:
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
299
            eta = get_eta(self.start_time, current_cnt, total_cnt,
300
                    last_updates = self.last_updates)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
301
            eta_str = " " + str_tdelta(eta)
302
        else:
303
            eta_str = ""
304
305
        if self.show_spinner:
306
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '            
307
        else:
308
            spin_str = ''
309
310
        # always update this; it's also used for the bar
311
        self.spin_pos += 1
312
313
        if self.show_pct and total_cnt and current_cnt:
1551.2.27 by Aaron Bentley
Got propogation under test
314
            pct = 100.0 * (current_cnt / total_cnt + child_fraction)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
315
            pct_str = ' (%5.1f%%)' % pct
316
        else:
317
            pct_str = ''
318
319
        if not self.show_count:
320
            count_str = ''
321
        elif current_cnt is None:
322
            count_str = ''
323
        elif total_cnt is None:
324
            count_str = ' %i' % (current_cnt)
325
        else:
326
            # make both fields the same size
327
            t = '%i' % (total_cnt)
328
            c = '%*i' % (len(t), current_cnt)
329
            count_str = ' ' + c + '/' + t 
330
331
        if self.show_bar:
332
            # progress bar, if present, soaks up all remaining space
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
333
            cols = self.width - 1 - len(msg) - len(spin_str) - len(pct_str) \
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
334
                   - len(eta_str) - len(count_str) - 3
335
336
            if total_cnt:
337
                # number of markers highlighted in bar
338
                markers = int(round(float(cols) * current_cnt / total_cnt))
339
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
669 by Martin Pool
- don't show progress bar unless completion is known
340
            elif False:
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
341
                # don't know total, so can't show completion.
342
                # so just show an expanded spinning thingy
343
                m = self.spin_pos % cols
668 by Martin Pool
- fix sweeping bar progress indicator
344
                ms = (' ' * m + '*').ljust(cols)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
345
                
346
                bar_str = '[' + ms + '] '
669 by Martin Pool
- don't show progress bar unless completion is known
347
            else:
348
                bar_str = ''
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
349
        else:
350
            bar_str = ''
351
352
        m = spin_str + bar_str + msg + count_str + pct_str + eta_str
353
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
354
        assert len(m) < self.width
355
        self.to_file.write('\r' + m.ljust(self.width - 1))
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
356
        #self.to_file.flush()
357
            
964 by Martin Pool
- show progress on dumb terminals by printing dots
358
    def clear(self):        
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
359
        self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
360
        #self.to_file.flush()        
649 by Martin Pool
- some cleanups for the progressbar method
361
1551.2.27 by Aaron Bentley
Got propogation under test
362
1551.2.28 by Aaron Bentley
Ensure all ProgressBar implementations can be used as parents
363
class ChildProgress(_BaseProgressBar):
1551.2.27 by Aaron Bentley
Got propogation under test
364
    """A progress indicator that pushes its data to the parent"""
1551.2.29 by Aaron Bentley
Got stack handling under test
365
    def __init__(self, _stack, **kwargs):
366
        _BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
367
        self.parent = _stack.top()
1551.2.27 by Aaron Bentley
Got propogation under test
368
        self.current = None
369
        self.total = None
370
        self.child_fraction = 0
371
        self.message = None
372
373
    def update(self, msg, current_cnt=None, total_cnt=None):
374
        self.current = current_cnt
375
        self.total = total_cnt
376
        self.message = msg
377
        self.child_fraction = 0
378
        self.tick()
379
380
    def child_update(self, message, current, total):
381
        self.child_fraction = float(current) / total
382
        self.tick()
383
384
    def tick(self):
385
        count = self.current+self.child_fraction
386
        if count > self.total:
387
            mutter('clamping count of %d to %d' % (count, self.total))
388
            count = self.total
389
        self.parent.child_update(self.message, count, self.total)
390
1551.2.29 by Aaron Bentley
Got stack handling under test
391
    def clear(self):
392
        self.parent.clear()
393
1551.2.27 by Aaron Bentley
Got propogation under test
394
 
648 by Martin Pool
- import aaron's progress-indicator code
395
def str_tdelta(delt):
396
    if delt is None:
397
        return "-:--:--"
660 by Martin Pool
- use plain unix time, not datetime module
398
    delt = int(round(delt))
399
    return '%d:%02d:%02d' % (delt/3600,
400
                             (delt/60) % 60,
401
                             delt % 60)
402
403
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
404
def get_eta(start_time, current, total, enough_samples=3, last_updates=None, n_recent=10):
660 by Martin Pool
- use plain unix time, not datetime module
405
    if start_time is None:
406
        return None
407
408
    if not total:
409
        return None
410
411
    if current < enough_samples:
412
        return None
413
414
    if current > total:
415
        return None                     # wtf?
416
417
    elapsed = time.time() - start_time
418
419
    if elapsed < 2.0:                   # not enough time to estimate
420
        return None
421
    
422
    total_duration = float(elapsed) * float(total) / float(current)
423
424
    assert total_duration >= elapsed
425
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
426
    if last_updates and len(last_updates) >= n_recent:
427
        while len(last_updates) > n_recent:
428
            last_updates.popleft()
429
        avg = sum(last_updates) / float(len(last_updates))
430
        time_left = avg * (total - current)
431
432
        old_time_left = total_duration - elapsed
433
434
        # We could return the average, or some other value here
435
        return (time_left + old_time_left) / 2
436
660 by Martin Pool
- use plain unix time, not datetime module
437
    return total_duration - elapsed
648 by Martin Pool
- import aaron's progress-indicator code
438
649 by Martin Pool
- some cleanups for the progressbar method
439
648 by Martin Pool
- import aaron's progress-indicator code
440
def run_tests():
441
    import doctest
442
    result = doctest.testmod()
443
    if result[1] > 0:
444
        if result[0] == 0:
445
            print "All tests passed"
446
    else:
447
        print "No tests to run"
649 by Martin Pool
- some cleanups for the progressbar method
448
449
450
def demo():
964 by Martin Pool
- show progress on dumb terminals by printing dots
451
    sleep = time.sleep
452
    
453
    print 'dumb-terminal test:'
454
    pb = DotsProgressBar()
455
    for i in range(100):
456
        pb.update('Leoparden', i, 99)
457
        sleep(0.1)
458
    sleep(1.5)
459
    pb.clear()
460
    sleep(1.5)
461
    
462
    print 'smart-terminal test:'
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
463
    pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
649 by Martin Pool
- some cleanups for the progressbar method
464
    for i in range(100):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
465
        pb.update('Elephanten', i, 99)
466
        sleep(0.1)
467
    sleep(2)
468
    pb.clear()
469
    sleep(1)
964 by Martin Pool
- show progress on dumb terminals by printing dots
470
649 by Martin Pool
- some cleanups for the progressbar method
471
    print 'done!'
472
648 by Martin Pool
- import aaron's progress-indicator code
473
if __name__ == "__main__":
649 by Martin Pool
- some cleanups for the progressbar method
474
    demo()