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