~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 02:21:14 UTC
  • Revision ID: mbp@sourcefrog.net-20050610022114-8f49c9244a5b8e2f
- improved external-command patch from john

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
 
# Copyright (C) 2005 Canonical <canonical.com>
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
 
 
18
 
 
19
 
"""
20
 
Simple text-mode progress indicator.
21
 
 
22
 
Everyone loves ascii art!
23
 
 
24
 
To display an indicator, create a ProgressBar object.  Call it,
25
 
passing Progress objects indicating the current state.  When done,
26
 
call clear().
27
 
 
28
 
Progress is suppressed when output is not sent to a terminal, so as
29
 
not to clutter log files.
30
 
"""
31
 
 
32
 
# TODO: remove functions in favour of keeping everything in one class
33
 
 
34
 
# TODO: should be a global option e.g. --silent that disables progress
35
 
# indicators, preferably without needing to adjust all code that
36
 
# potentially calls them.
37
 
 
38
 
# TODO: Perhaps don't write updates faster than a certain rate, say
39
 
# 5/second.
40
 
 
41
 
 
42
 
import sys
43
 
import datetime
44
 
 
45
 
 
46
 
def _width():
47
 
    """Return estimated terminal width.
48
 
 
49
 
    TODO: Do something smart on Windows?
50
 
 
51
 
    TODO: Is there anything that gets a better update when the window
52
 
          is resized while the program is running?
53
 
    """
54
 
    import os
55
 
    try:
56
 
        return int(os.environ['COLUMNS'])
57
 
    except (IndexError, KeyError, ValueError):
58
 
        return 80
59
 
 
60
 
 
61
 
def _supports_progress(f):
62
 
    return hasattr(f, 'isatty') and f.isatty()
63
 
 
64
 
 
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
 
class ProgressBar(object):
103
 
    def __init__(self, to_file=sys.stderr):
104
 
        object.__init__(self)
105
 
        self.start = None
106
 
        self.to_file = to_file
107
 
        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)
116
 
 
117
 
    def clear(self):
118
 
        if not self.suppressed:
119
 
            clear_progress_bar(self.to_file)
120
 
    
121
 
 
122
 
        
123
 
def divide_timedelta(delt, divisor):
124
 
    """Divides a timedelta object"""
125
 
    return datetime.timedelta(float(delt.days)/divisor, 
126
 
                              float(delt.seconds)/divisor, 
127
 
                              float(delt.microseconds)/divisor)
128
 
 
129
 
def str_tdelta(delt):
130
 
    if delt is None:
131
 
        return "-:--:--"
132
 
    return str(datetime.timedelta(delt.days, delt.seconds))
133
 
 
134
 
 
135
 
def get_eta(start_time, progress, enough_samples=20):
136
 
    if start_time is None or progress.current == 0:
137
 
        return None
138
 
    elif progress.current < enough_samples:
139
 
        return None
140
 
    elapsed = datetime.datetime.now() - start_time
141
 
    total_duration = divide_timedelta((elapsed) * long(progress.total), 
142
 
                                      progress.current)
143
 
    if elapsed < total_duration:
144
 
        eta = total_duration - elapsed
145
 
    else:
146
 
        eta = total_duration - total_duration
147
 
    return eta
148
 
 
149
 
 
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
 
def run_tests():
207
 
    import doctest
208
 
    result = doctest.testmod()
209
 
    if result[1] > 0:
210
 
        if result[0] == 0:
211
 
            print "All tests passed"
212
 
    else:
213
 
        print "No tests to run"
214
 
 
215
 
 
216
 
def demo():
217
 
    from time import sleep
218
 
    pb = ProgressBar()
219
 
    for i in range(100):
220
 
        pb(Progress('Elephanten', i, 100))
221
 
        sleep(0.3)
222
 
    print 'done!'
223
 
 
224
 
if __name__ == "__main__":
225
 
    demo()