/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>
2
# Copyright (C) 2005 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
45
def _supports_progress(f):
695 by Martin Pool
- don't display progress bars on really dumb terminals
46
    if not hasattr(f, 'isatty'):
47
        return False
48
    if not f.isatty():
49
        return False
50
    if os.environ.get('TERM') == 'dumb':
51
        # e.g. emacs compile window
52
        return False
53
    return True
649 by Martin Pool
- some cleanups for the progressbar method
54
55
56
964 by Martin Pool
- show progress on dumb terminals by printing dots
57
def ProgressBar(to_file=sys.stderr, **kwargs):
58
    """Abstract factory"""
59
    if _supports_progress(to_file):
60
        return TTYProgressBar(to_file=to_file, **kwargs)
61
    else:
62
        return DotsProgressBar(to_file=to_file, **kwargs)
63
    
64
    
65
class _BaseProgressBar(object):
66
    def __init__(self,
67
                 to_file=sys.stderr,
68
                 show_pct=False,
69
                 show_spinner=False,
70
                 show_eta=True,
71
                 show_bar=True,
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
72
                 show_count=True,
73
                 to_messages_file=sys.stdout):
964 by Martin Pool
- show progress on dumb terminals by printing dots
74
        object.__init__(self)
75
        self.to_file = to_file
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
76
        self.to_messages_file = to_messages_file
964 by Martin Pool
- show progress on dumb terminals by printing dots
77
        self.last_msg = None
78
        self.last_cnt = None
79
        self.last_total = None
80
        self.show_pct = show_pct
81
        self.show_spinner = show_spinner
82
        self.show_eta = show_eta
83
        self.show_bar = show_bar
84
        self.show_count = show_count
1104 by Martin Pool
- Add a simple UIFactory
85
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
86
    def note(self, fmt_string, *args, **kwargs):
87
        """Record a note without disrupting the progress bar."""
88
        self.clear()
89
        self.to_messages_file.write(fmt_string % args)
90
        self.to_messages_file.write('\n')
1104 by Martin Pool
- Add a simple UIFactory
91
92
93
class DummyProgress(_BaseProgressBar):
94
    """Progress-bar standin that does nothing.
95
96
    This can be used as the default argument for methods that
97
    take an optional progress indicator."""
98
    def tick(self):
99
        pass
100
101
    def update(self, msg=None, current=None, total=None):
102
        pass
103
104
    def clear(self):
105
        pass
964 by Martin Pool
- show progress on dumb terminals by printing dots
106
        
1534.5.6 by Robert Collins
split out converter logic into per-format objects.
107
    def note(self, fmt_string, *args, **kwargs):
108
        """See _BaseProgressBar.note()."""
1534.5.9 by Robert Collins
Advise users running upgrade on a checkout to also run it on the branch.
109
110
964 by Martin Pool
- show progress on dumb terminals by printing dots
111
class DotsProgressBar(_BaseProgressBar):
112
    def __init__(self, **kwargs):
113
        _BaseProgressBar.__init__(self, **kwargs)
114
        self.last_msg = None
115
        self.need_nl = False
116
        
117
    def tick(self):
118
        self.update()
119
        
120
    def update(self, msg=None, current_cnt=None, total_cnt=None):
121
        if msg and msg != self.last_msg:
122
            if self.need_nl:
123
                self.to_file.write('\n')
124
            
125
            self.to_file.write(msg + ': ')
126
            self.last_msg = msg
127
        self.need_nl = True
128
        self.to_file.write('.')
129
        
130
    def clear(self):
131
        if self.need_nl:
132
            self.to_file.write('\n')
133
        
134
    
135
class TTYProgressBar(_BaseProgressBar):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
136
    """Progress bar display object.
137
138
    Several options are available to control the display.  These can
139
    be passed as parameters to the constructor or assigned at any time:
140
141
    show_pct
142
        Show percentage complete.
143
    show_spinner
144
        Show rotating baton.  This ticks over on every update even
145
        if the values don't change.
146
    show_eta
147
        Show predicted time-to-completion.
148
    show_bar
149
        Show bar graph.
150
    show_count
151
        Show numerical counts.
152
153
    The output file should be in line-buffered or unbuffered mode.
154
    """
155
    SPIN_CHARS = r'/-\|'
661 by Martin Pool
- limit rate at which progress bar is updated
156
    MIN_PAUSE = 0.1 # seconds
157
964 by Martin Pool
- show progress on dumb terminals by printing dots
158
159
    def __init__(self, **kwargs):
1185.33.60 by Martin Pool
Use full terminal width for verbose test output.
160
        from bzrlib.osutils import terminal_width
964 by Martin Pool
- show progress on dumb terminals by printing dots
161
        _BaseProgressBar.__init__(self, **kwargs)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
162
        self.spin_pos = 0
1185.33.60 by Martin Pool
Use full terminal width for verbose test output.
163
        self.width = terminal_width()
964 by Martin Pool
- show progress on dumb terminals by printing dots
164
        self.start_time = None
165
        self.last_update = None
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
166
        self.last_updates = deque()
964 by Martin Pool
- show progress on dumb terminals by printing dots
167
    
168
169
    def throttle(self):
170
        """Return True if the bar was updated too recently"""
171
        now = time.time()
172
        if self.start_time is None:
173
            self.start_time = self.last_update = now
174
            return False
175
        else:
176
            interval = now - self.last_update
177
            if interval > 0 and interval < self.MIN_PAUSE:
178
                return True
179
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
180
        self.last_updates.append(now - self.last_update)
964 by Martin Pool
- show progress on dumb terminals by printing dots
181
        self.last_update = now
182
        return False
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
183
        
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
184
185
    def tick(self):
186
        self.update(self.last_msg, self.last_cnt, self.last_total)
187
                 
188
189
667 by Martin Pool
- allow for progressbar updates with no count, only a message
190
    def update(self, msg, current_cnt=None, total_cnt=None):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
191
        """Update and redraw progress bar."""
192
1308 by Martin Pool
- make progress bar more tolerant of out-of-range values
193
        if current_cnt < 0:
194
            current_cnt = 0
195
            
196
        if current_cnt > total_cnt:
197
            total_cnt = current_cnt
1570.1.9 by Robert Collins
Do not throttle updates to progress bars that change the message.
198
        
199
        old_msg = self.last_msg
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
200
        # save these for the tick() function
201
        self.last_msg = msg
202
        self.last_cnt = current_cnt
203
        self.last_total = total_cnt
204
            
1570.1.9 by Robert Collins
Do not throttle updates to progress bars that change the message.
205
        if old_msg == self.last_msg and self.throttle():
964 by Martin Pool
- show progress on dumb terminals by printing dots
206
            return 
661 by Martin Pool
- limit rate at which progress bar is updated
207
        
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
208
        if self.show_eta and self.start_time and total_cnt:
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
209
            eta = get_eta(self.start_time, current_cnt, total_cnt,
210
                    last_updates = self.last_updates)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
211
            eta_str = " " + str_tdelta(eta)
212
        else:
213
            eta_str = ""
214
215
        if self.show_spinner:
216
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '            
217
        else:
218
            spin_str = ''
219
220
        # always update this; it's also used for the bar
221
        self.spin_pos += 1
222
223
        if self.show_pct and total_cnt and current_cnt:
224
            pct = 100.0 * current_cnt / total_cnt
225
            pct_str = ' (%5.1f%%)' % pct
226
        else:
227
            pct_str = ''
228
229
        if not self.show_count:
230
            count_str = ''
231
        elif current_cnt is None:
232
            count_str = ''
233
        elif total_cnt is None:
234
            count_str = ' %i' % (current_cnt)
235
        else:
236
            # make both fields the same size
237
            t = '%i' % (total_cnt)
238
            c = '%*i' % (len(t), current_cnt)
239
            count_str = ' ' + c + '/' + t 
240
241
        if self.show_bar:
242
            # progress bar, if present, soaks up all remaining space
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
243
            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
244
                   - len(eta_str) - len(count_str) - 3
245
246
            if total_cnt:
247
                # number of markers highlighted in bar
248
                markers = int(round(float(cols) * current_cnt / total_cnt))
249
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
669 by Martin Pool
- don't show progress bar unless completion is known
250
            elif False:
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
251
                # don't know total, so can't show completion.
252
                # so just show an expanded spinning thingy
253
                m = self.spin_pos % cols
668 by Martin Pool
- fix sweeping bar progress indicator
254
                ms = (' ' * m + '*').ljust(cols)
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
255
                
256
                bar_str = '[' + ms + '] '
669 by Martin Pool
- don't show progress bar unless completion is known
257
            else:
258
                bar_str = ''
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
259
        else:
260
            bar_str = ''
261
262
        m = spin_str + bar_str + msg + count_str + pct_str + eta_str
263
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
264
        assert len(m) < self.width
265
        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
266
        #self.to_file.flush()
267
            
964 by Martin Pool
- show progress on dumb terminals by printing dots
268
    def clear(self):        
929 by Martin Pool
- progress bar: avoid repeatedly checking screen width
269
        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
270
        #self.to_file.flush()        
649 by Martin Pool
- some cleanups for the progressbar method
271
648 by Martin Pool
- import aaron's progress-indicator code
272
        
273
def str_tdelta(delt):
274
    if delt is None:
275
        return "-:--:--"
660 by Martin Pool
- use plain unix time, not datetime module
276
    delt = int(round(delt))
277
    return '%d:%02d:%02d' % (delt/3600,
278
                             (delt/60) % 60,
279
                             delt % 60)
280
281
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
282
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
283
    if start_time is None:
284
        return None
285
286
    if not total:
287
        return None
288
289
    if current < enough_samples:
290
        return None
291
292
    if current > total:
293
        return None                     # wtf?
294
295
    elapsed = time.time() - start_time
296
297
    if elapsed < 2.0:                   # not enough time to estimate
298
        return None
299
    
300
    total_duration = float(elapsed) * float(total) / float(current)
301
302
    assert total_duration >= elapsed
303
1185.16.75 by Martin Pool
- improved eta estimation for progress bar
304
    if last_updates and len(last_updates) >= n_recent:
305
        while len(last_updates) > n_recent:
306
            last_updates.popleft()
307
        avg = sum(last_updates) / float(len(last_updates))
308
        time_left = avg * (total - current)
309
310
        old_time_left = total_duration - elapsed
311
312
        # We could return the average, or some other value here
313
        return (time_left + old_time_left) / 2
314
660 by Martin Pool
- use plain unix time, not datetime module
315
    return total_duration - elapsed
648 by Martin Pool
- import aaron's progress-indicator code
316
649 by Martin Pool
- some cleanups for the progressbar method
317
648 by Martin Pool
- import aaron's progress-indicator code
318
def run_tests():
319
    import doctest
320
    result = doctest.testmod()
321
    if result[1] > 0:
322
        if result[0] == 0:
323
            print "All tests passed"
324
    else:
325
        print "No tests to run"
649 by Martin Pool
- some cleanups for the progressbar method
326
327
328
def demo():
964 by Martin Pool
- show progress on dumb terminals by printing dots
329
    sleep = time.sleep
330
    
331
    print 'dumb-terminal test:'
332
    pb = DotsProgressBar()
333
    for i in range(100):
334
        pb.update('Leoparden', i, 99)
335
        sleep(0.1)
336
    sleep(1.5)
337
    pb.clear()
338
    sleep(1.5)
339
    
340
    print 'smart-terminal test:'
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
341
    pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
649 by Martin Pool
- some cleanups for the progressbar method
342
    for i in range(100):
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
343
        pb.update('Elephanten', i, 99)
344
        sleep(0.1)
345
    sleep(2)
346
    pb.clear()
347
    sleep(1)
964 by Martin Pool
- show progress on dumb terminals by printing dots
348
649 by Martin Pool
- some cleanups for the progressbar method
349
    print 'done!'
350
648 by Martin Pool
- import aaron's progress-indicator code
351
if __name__ == "__main__":
649 by Martin Pool
- some cleanups for the progressbar method
352
    demo()