~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

  • Committer: Martin Pool
  • Date: 2005-06-10 08:40:33 UTC
  • Revision ID: mbp@sourcefrog.net-20050610084033-22955aa7533654f3
- clean up and add a bunch of options to the progress indicator

Show diffs side-by-side

added added

removed removed

Lines of Context:
63
63
 
64
64
 
65
65
 
66
 
class Progress(object):
67
 
    """Description of progress through a task.
68
 
 
69
 
    Basically just a fancy tuple holding:
70
 
 
71
 
    units
72
 
        noun string describing what is being traversed, e.g.
73
 
        "balloons", "kB"
74
 
 
75
 
    current
76
 
        how many objects have been processed so far
77
 
 
78
 
    total
79
 
        total number of objects to process, if known.
80
 
    """
81
 
    
82
 
    def __init__(self, units, current, total=None):
83
 
        self.units = units
84
 
        self.current = current
85
 
        self.total = total
86
 
 
87
 
    def _get_percent(self):
88
 
        if self.total is not None and self.current is not None:
89
 
            return 100.0 * self.current / self.total
90
 
 
91
 
    percent = property(_get_percent)
92
 
 
93
 
    def __str__(self):
94
 
        if self.total is not None:
95
 
            return "%i of %i %s %.1f%%" % (self.current, self.total, self.units,
96
 
                                         self.percent)
97
 
        else:
98
 
            return "%i %s" (self.current, self.units)
99
 
 
100
 
 
101
 
 
102
66
class ProgressBar(object):
103
 
    def __init__(self, to_file=sys.stderr):
 
67
    """Progress bar display object.
 
68
 
 
69
    Several options are available to control the display.  These can
 
70
    be passed as parameters to the constructor or assigned at any time:
 
71
 
 
72
    show_pct
 
73
        Show percentage complete.
 
74
    show_spinner
 
75
        Show rotating baton.  This ticks over on every update even
 
76
        if the values don't change.
 
77
    show_eta
 
78
        Show predicted time-to-completion.
 
79
    show_bar
 
80
        Show bar graph.
 
81
    show_count
 
82
        Show numerical counts.
 
83
 
 
84
    The output file should be in line-buffered or unbuffered mode.
 
85
    """
 
86
    SPIN_CHARS = r'/-\|'
 
87
    
 
88
    def __init__(self,
 
89
                 to_file=sys.stderr,
 
90
                 show_pct=False,
 
91
                 show_spinner=False,
 
92
                 show_eta=True,
 
93
                 show_bar=True,
 
94
                 show_count=True):
104
95
        object.__init__(self)
105
 
        self.start = None
 
96
        self.start_time = None
106
97
        self.to_file = to_file
107
98
        self.suppressed = not _supports_progress(self.to_file)
108
 
 
109
 
 
110
 
    def __call__(self, progress):
111
 
        if self.start is None:
112
 
            self.start = datetime.datetime.now()
113
 
        if not self.suppressed:
114
 
            draw_progress_bar(progress, start_time=self.start,
115
 
                              to_file=self.to_file)
 
99
        self.spin_pos = 0
 
100
 
 
101
        self.show_pct = show_pct
 
102
        self.show_spinner = show_spinner
 
103
        self.show_eta = show_eta
 
104
        self.show_bar = show_bar
 
105
        self.show_count = show_count
 
106
 
 
107
 
 
108
    def tick(self):
 
109
        self.update(self.last_msg, self.last_cnt, self.last_total)
 
110
                 
 
111
 
 
112
 
 
113
    def update(self, msg, current_cnt, total_cnt=None):
 
114
        """Update and redraw progress bar."""
 
115
        if self.start_time is None:
 
116
            self.start_time = datetime.datetime.now()
 
117
 
 
118
        # save these for the tick() function
 
119
        self.last_msg = msg
 
120
        self.last_cnt = current_cnt
 
121
        self.last_total = total_cnt
 
122
            
 
123
        if self.suppressed:
 
124
            return
 
125
 
 
126
        width = _width()
 
127
 
 
128
        if total_cnt:
 
129
            assert current_cnt <= total_cnt
 
130
        if current_cnt:
 
131
            assert current_cnt >= 0
 
132
        
 
133
        if self.show_eta and self.start_time and total_cnt:
 
134
            eta = get_eta(self.start_time, current_cnt, total_cnt)
 
135
            eta_str = " " + str_tdelta(eta)
 
136
        else:
 
137
            eta_str = ""
 
138
 
 
139
        if self.show_spinner:
 
140
            spin_str = self.SPIN_CHARS[self.spin_pos % 4] + ' '            
 
141
        else:
 
142
            spin_str = ''
 
143
 
 
144
        # always update this; it's also used for the bar
 
145
        self.spin_pos += 1
 
146
 
 
147
        if self.show_pct and total_cnt and current_cnt:
 
148
            pct = 100.0 * current_cnt / total_cnt
 
149
            pct_str = ' (%5.1f%%)' % pct
 
150
        else:
 
151
            pct_str = ''
 
152
 
 
153
        if not self.show_count:
 
154
            count_str = ''
 
155
        elif current_cnt is None:
 
156
            count_str = ''
 
157
        elif total_cnt is None:
 
158
            count_str = ' %i' % (current_cnt)
 
159
        else:
 
160
            # make both fields the same size
 
161
            t = '%i' % (total_cnt)
 
162
            c = '%*i' % (len(t), current_cnt)
 
163
            count_str = ' ' + c + '/' + t 
 
164
 
 
165
        if self.show_bar:
 
166
            # progress bar, if present, soaks up all remaining space
 
167
            cols = width - 1 - len(msg) - len(spin_str) - len(pct_str) \
 
168
                   - len(eta_str) - len(count_str) - 3
 
169
 
 
170
            if total_cnt:
 
171
                # number of markers highlighted in bar
 
172
                markers = int(round(float(cols) * current_cnt / total_cnt))
 
173
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
 
174
            else:
 
175
                # don't know total, so can't show completion.
 
176
                # so just show an expanded spinning thingy
 
177
                m = self.spin_pos % cols
 
178
                ms = ' ' * cols
 
179
                ms[m] = '*'
 
180
                
 
181
                bar_str = '[' + ms + '] '
 
182
        else:
 
183
            bar_str = ''
 
184
 
 
185
        m = spin_str + bar_str + msg + count_str + pct_str + eta_str
 
186
 
 
187
        assert len(m) < width
 
188
        self.to_file.write('\r' + m.ljust(width - 1))
 
189
        #self.to_file.flush()
 
190
            
116
191
 
117
192
    def clear(self):
118
 
        if not self.suppressed:
119
 
            clear_progress_bar(self.to_file)
 
193
        if self.suppressed:
 
194
            return
 
195
        
 
196
        self.to_file.write('\r%s\r' % (' ' * (_width() - 1)))
 
197
        #self.to_file.flush()        
120
198
    
121
199
 
122
200
        
132
210
    return str(datetime.timedelta(delt.days, delt.seconds))
133
211
 
134
212
 
135
 
def get_eta(start_time, progress, enough_samples=20):
136
 
    if start_time is None or progress.current == 0:
 
213
def get_eta(start_time, current, total, enough_samples=20):
 
214
    if start_time is None or current == 0:
137
215
        return None
138
 
    elif progress.current < enough_samples:
 
216
    elif current < enough_samples:
 
217
        # FIXME: No good if it's not a count
139
218
        return None
140
219
    elapsed = datetime.datetime.now() - start_time
141
 
    total_duration = divide_timedelta((elapsed) * long(progress.total), 
142
 
                                      progress.current)
 
220
    total_duration = divide_timedelta(elapsed * long(total), 
 
221
                                      current)
143
222
    if elapsed < total_duration:
144
223
        eta = total_duration - elapsed
145
224
    else:
147
226
    return eta
148
227
 
149
228
 
150
 
def draw_progress_bar(progress, start_time=None, to_file=sys.stderr):
151
 
    eta = get_eta(start_time, progress)
152
 
    if start_time is not None:
153
 
        eta_str = " "+str_tdelta(eta)
154
 
    else:
155
 
        eta_str = ""
156
 
 
157
 
    fmt = " %i of %i %s (%.1f%%)"
158
 
    f = fmt % (progress.total, progress.total, progress.units, 100.0)
159
 
    cols = _width() - 3 - len(f)
160
 
    if start_time is not None:
161
 
        cols -= len(eta_str)
162
 
    markers = int(round(float(cols) * progress.current / progress.total))
163
 
    txt = fmt % (progress.current, progress.total, progress.units,
164
 
                 progress.percent)
165
 
    to_file.write("\r[%s%s]%s%s" % ('='*markers, ' '*(cols-markers), txt, 
166
 
                                       eta_str))
167
 
 
168
 
def clear_progress_bar(to_file=sys.stderr):
169
 
    to_file.write('\r%s\r' % (' '*79))
170
 
 
171
 
 
172
 
def spinner_str(progress, show_text=False):
173
 
    """
174
 
    Produces the string for a textual "spinner" progress indicator
175
 
    :param progress: an object represinting current progress
176
 
    :param show_text: If true, show progress text as well
177
 
    :return: The spinner string
178
 
 
179
 
    >>> spinner_str(Progress("baloons", 0))
180
 
    '|'
181
 
    >>> spinner_str(Progress("baloons", 5))
182
 
    '/'
183
 
    >>> spinner_str(Progress("baloons", 6), show_text=True)
184
 
    '- 6 baloons'
185
 
    """
186
 
    positions = ('|', '/', '-', '\\')
187
 
    text = positions[progress.current % 4]
188
 
    if show_text:
189
 
        text+=" %i %s" % (progress.current, progress.units)
190
 
    return text
191
 
 
192
 
 
193
 
def spinner(progress, show_text=False, output=sys.stderr):
194
 
    """
195
 
    Update a spinner progress indicator on an output
196
 
    :param progress: The progress to display
197
 
    :param show_text: If true, show text as well as spinner
198
 
    :param output: The output to write to
199
 
 
200
 
    >>> spinner(Progress("baloons", 6), show_text=True, output=sys.stdout)
201
 
    \r- 6 baloons
202
 
    """
203
 
    output.write('\r%s' % spinner_str(progress, show_text))
204
 
 
205
 
 
206
229
def run_tests():
207
230
    import doctest
208
231
    result = doctest.testmod()
215
238
 
216
239
def demo():
217
240
    from time import sleep
218
 
    pb = ProgressBar()
 
241
    pb = ProgressBar(show_pct=True, show_bar=True, show_spinner=False)
219
242
    for i in range(100):
220
 
        pb(Progress('Elephanten', i, 100))
221
 
        sleep(0.3)
 
243
        pb.update('Elephanten', i, 99)
 
244
        sleep(0.1)
 
245
    sleep(2)
 
246
    pb.clear()
 
247
    sleep(1)
222
248
    print 'done!'
223
249
 
224
250
if __name__ == "__main__":