~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: Robert Collins
  • Date: 2005-10-16 22:31:25 UTC
  • mto: This revision was merged to the branch mainline in revision 1458.
  • Revision ID: robertc@lifelesslap.robertcollins.net-20051016223125-26d4401cb94b7b82
Branch.relpath has been moved to WorkingTree.relpath.

WorkingTree no no longer takes an inventory, rather it takes an optional branch
parameter, and if None is given will open the branch at basedir implicitly.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
18
 
19
 
"""
20
 
Simple text-mode progress indicator.
21
 
 
22
 
Everyone loves ascii art!
 
19
"""Simple text-mode progress indicator.
23
20
 
24
21
To display an indicator, create a ProgressBar object.  Call it,
25
22
passing Progress objects indicating the current state.  When done,
29
26
not to clutter log files.
30
27
"""
31
28
 
32
 
# TODO: remove functions in favour of keeping everything in one class
33
 
 
34
29
# TODO: should be a global option e.g. --silent that disables progress
35
30
# indicators, preferably without needing to adjust all code that
36
31
# potentially calls them.
37
32
 
38
 
# TODO: Perhaps don't write updates faster than a certain rate, say
39
 
# 5/second.
 
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
40
37
 
41
38
 
42
39
import sys
43
40
import time
 
41
import os
44
42
 
45
43
 
46
44
def _width():
51
49
    TODO: Is there anything that gets a better update when the window
52
50
          is resized while the program is running?
53
51
    """
54
 
    import os
55
52
    try:
56
53
        return int(os.environ['COLUMNS'])
57
54
    except (IndexError, KeyError, ValueError):
63
60
        return False
64
61
    if not f.isatty():
65
62
        return False
66
 
    import os
67
63
    if os.environ.get('TERM') == 'dumb':
68
64
        # e.g. emacs compile window
69
65
        return False
71
67
 
72
68
 
73
69
 
74
 
class ProgressBar(object):
 
70
def ProgressBar(to_file=sys.stderr, **kwargs):
 
71
    """Abstract factory"""
 
72
    if _supports_progress(to_file):
 
73
        return TTYProgressBar(to_file=to_file, **kwargs)
 
74
    else:
 
75
        return DotsProgressBar(to_file=to_file, **kwargs)
 
76
    
 
77
    
 
78
class _BaseProgressBar(object):
 
79
    def __init__(self,
 
80
                 to_file=sys.stderr,
 
81
                 show_pct=False,
 
82
                 show_spinner=False,
 
83
                 show_eta=True,
 
84
                 show_bar=True,
 
85
                 show_count=True):
 
86
        object.__init__(self)
 
87
        self.to_file = to_file
 
88
 
 
89
        self.last_msg = None
 
90
        self.last_cnt = None
 
91
        self.last_total = None
 
92
        self.show_pct = show_pct
 
93
        self.show_spinner = show_spinner
 
94
        self.show_eta = show_eta
 
95
        self.show_bar = show_bar
 
96
        self.show_count = show_count
 
97
 
 
98
 
 
99
 
 
100
class DummyProgress(_BaseProgressBar):
 
101
    """Progress-bar standin that does nothing.
 
102
 
 
103
    This can be used as the default argument for methods that
 
104
    take an optional progress indicator."""
 
105
    def tick(self):
 
106
        pass
 
107
 
 
108
    def update(self, msg=None, current=None, total=None):
 
109
        pass
 
110
 
 
111
    def clear(self):
 
112
        pass
 
113
        
 
114
    
 
115
class DotsProgressBar(_BaseProgressBar):
 
116
    def __init__(self, **kwargs):
 
117
        _BaseProgressBar.__init__(self, **kwargs)
 
118
        self.last_msg = None
 
119
        self.need_nl = False
 
120
        
 
121
    def tick(self):
 
122
        self.update()
 
123
        
 
124
    def update(self, msg=None, current_cnt=None, total_cnt=None):
 
125
        if msg and msg != self.last_msg:
 
126
            if self.need_nl:
 
127
                self.to_file.write('\n')
 
128
            
 
129
            self.to_file.write(msg + ': ')
 
130
            self.last_msg = msg
 
131
        self.need_nl = True
 
132
        self.to_file.write('.')
 
133
        
 
134
    def clear(self):
 
135
        if self.need_nl:
 
136
            self.to_file.write('\n')
 
137
        
 
138
    
 
139
class TTYProgressBar(_BaseProgressBar):
75
140
    """Progress bar display object.
76
141
 
77
142
    Several options are available to control the display.  These can
94
159
    SPIN_CHARS = r'/-\|'
95
160
    MIN_PAUSE = 0.1 # seconds
96
161
 
97
 
    start_time = None
98
 
    last_update = None
99
 
    
100
 
    def __init__(self,
101
 
                 to_file=sys.stderr,
102
 
                 show_pct=False,
103
 
                 show_spinner=False,
104
 
                 show_eta=True,
105
 
                 show_bar=True,
106
 
                 show_count=True):
107
 
        object.__init__(self)
108
 
        self.to_file = to_file
109
 
        self.suppressed = not _supports_progress(self.to_file)
 
162
 
 
163
    def __init__(self, **kwargs):
 
164
        _BaseProgressBar.__init__(self, **kwargs)
110
165
        self.spin_pos = 0
111
 
 
112
 
        self.last_msg = None
113
 
        self.last_cnt = None
114
 
        self.last_total = None
115
 
        self.show_pct = show_pct
116
 
        self.show_spinner = show_spinner
117
 
        self.show_eta = show_eta
118
 
        self.show_bar = show_bar
119
 
        self.show_count = show_count
120
 
 
 
166
        self.width = _width()
 
167
        self.start_time = None
 
168
        self.last_update = None
 
169
    
 
170
 
 
171
    def throttle(self):
 
172
        """Return True if the bar was updated too recently"""
 
173
        now = time.time()
 
174
        if self.start_time is None:
 
175
            self.start_time = self.last_update = now
 
176
            return False
 
177
        else:
 
178
            interval = now - self.last_update
 
179
            if interval > 0 and interval < self.MIN_PAUSE:
 
180
                return True
 
181
 
 
182
        self.last_update = now
 
183
        return False
 
184
        
121
185
 
122
186
    def tick(self):
123
187
        self.update(self.last_msg, self.last_cnt, self.last_total)
126
190
 
127
191
    def update(self, msg, current_cnt=None, total_cnt=None):
128
192
        """Update and redraw progress bar."""
129
 
        if self.suppressed:
130
 
            return
 
193
 
 
194
        if current_cnt < 0:
 
195
            current_cnt = 0
 
196
            
 
197
        if current_cnt > total_cnt:
 
198
            total_cnt = current_cnt
131
199
 
132
200
        # save these for the tick() function
133
201
        self.last_msg = msg
134
202
        self.last_cnt = current_cnt
135
203
        self.last_total = total_cnt
136
204
            
137
 
        now = time.time()
138
 
        if self.start_time is None:
139
 
            self.start_time = now
140
 
        else:
141
 
            interval = now - self.last_update
142
 
            if interval > 0 and interval < self.MIN_PAUSE:
143
 
                return
144
 
 
145
 
        self.last_update = now
146
 
        
147
 
        width = _width()
148
 
 
149
 
        if total_cnt:
150
 
            assert current_cnt <= total_cnt
151
 
        if current_cnt:
152
 
            assert current_cnt >= 0
 
205
        if self.throttle():
 
206
            return 
153
207
        
154
208
        if self.show_eta and self.start_time and total_cnt:
155
209
            eta = get_eta(self.start_time, current_cnt, total_cnt)
185
239
 
186
240
        if self.show_bar:
187
241
            # progress bar, if present, soaks up all remaining space
188
 
            cols = width - 1 - len(msg) - len(spin_str) - len(pct_str) \
 
242
            cols = self.width - 1 - len(msg) - len(spin_str) - len(pct_str) \
189
243
                   - len(eta_str) - len(count_str) - 3
190
244
 
191
245
            if total_cnt:
206
260
 
207
261
        m = spin_str + bar_str + msg + count_str + pct_str + eta_str
208
262
 
209
 
        assert len(m) < width
210
 
        self.to_file.write('\r' + m.ljust(width - 1))
 
263
        assert len(m) < self.width
 
264
        self.to_file.write('\r' + m.ljust(self.width - 1))
211
265
        #self.to_file.flush()
212
266
            
213
267
 
214
 
    def clear(self):
215
 
        if self.suppressed:
216
 
            return
217
 
        
218
 
        self.to_file.write('\r%s\r' % (' ' * (_width() - 1)))
 
268
    def clear(self):        
 
269
        self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
219
270
        #self.to_file.flush()        
220
271
    
221
272
 
265
316
 
266
317
 
267
318
def demo():
268
 
    from time import sleep
 
319
    sleep = time.sleep
 
320
    
 
321
    print 'dumb-terminal test:'
 
322
    pb = DotsProgressBar()
 
323
    for i in range(100):
 
324
        pb.update('Leoparden', i, 99)
 
325
        sleep(0.1)
 
326
    sleep(1.5)
 
327
    pb.clear()
 
328
    sleep(1.5)
 
329
    
 
330
    print 'smart-terminal test:'
269
331
    pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
270
332
    for i in range(100):
271
333
        pb.update('Elephanten', i, 99)
273
335
    sleep(2)
274
336
    pb.clear()
275
337
    sleep(1)
 
338
 
276
339
    print 'done!'
277
340
 
278
341
if __name__ == "__main__":