| 
 | 1 | +#!/usr/bin/python  | 
 | 2 | +# -*- coding: iso-8859-1 -*-  | 
 | 3 | +#  | 
 | 4 | +# progressbar  - Text progressbar library for python.  | 
 | 5 | +# Copyright (c) 2005 Nilton Volpato  | 
 | 6 | +#   | 
 | 7 | +# This library is free software; you can redistribute it and/or  | 
 | 8 | +# modify it under the terms of the GNU Lesser General Public  | 
 | 9 | +# License as published by the Free Software Foundation; either  | 
 | 10 | +# version 2.1 of the License, or (at your option) any later version.  | 
 | 11 | +#   | 
 | 12 | +# This library is distributed in the hope that it will be useful,  | 
 | 13 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of  | 
 | 14 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  | 
 | 15 | +# Lesser General Public License for more details.  | 
 | 16 | +#   | 
 | 17 | +# You should have received a copy of the GNU Lesser General Public  | 
 | 18 | +# License along with this library; if not, write to the Free Software  | 
 | 19 | +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  | 
 | 20 | + | 
 | 21 | + | 
 | 22 | +"""Text progressbar library for python.  | 
 | 23 | +
  | 
 | 24 | +This library provides a text mode progressbar. This is tipically used  | 
 | 25 | +to display the progress of a long running operation, providing a  | 
 | 26 | +visual clue that processing is underway.  | 
 | 27 | +
  | 
 | 28 | +The ProgressBar class manages the progress, and the format of the line  | 
 | 29 | +is given by a number of widgets. A widget is an object that may  | 
 | 30 | +display diferently depending on the state of the progress. There are  | 
 | 31 | +three types of widget:  | 
 | 32 | +- a string, which always shows itself;  | 
 | 33 | +- a ProgressBarWidget, which may return a diferent value every time  | 
 | 34 | +it's update method is called; and  | 
 | 35 | +- a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it  | 
 | 36 | +expands to fill the remaining width of the line.  | 
 | 37 | +
  | 
 | 38 | +The progressbar module is very easy to use, yet very powerful. And  | 
 | 39 | +automatically supports features like auto-resizing when available.  | 
 | 40 | +"""  | 
 | 41 | + | 
 | 42 | +__author__ = "Nilton Volpato"  | 
 | 43 | +__author_email__ = "first-name dot last-name @ gmail.com"  | 
 | 44 | +__date__ = "2006-05-07"  | 
 | 45 | +__version__ = "2.2"  | 
 | 46 | + | 
 | 47 | +# Changelog  | 
 | 48 | +#  | 
 | 49 | +# 2006-05-07: v2.2 fixed bug in windows  | 
 | 50 | +# 2005-12-04: v2.1 autodetect terminal width, added start method  | 
 | 51 | +# 2005-12-04: v2.0 everything is now a widget (wow!)  | 
 | 52 | +# 2005-12-03: v1.0 rewrite using widgets  | 
 | 53 | +# 2005-06-02: v0.5 rewrite  | 
 | 54 | +# 2004-??-??: v0.1 first version  | 
 | 55 | + | 
 | 56 | + | 
 | 57 | +import sys, time, os  | 
 | 58 | +from array import array  | 
 | 59 | +try:  | 
 | 60 | +    from fcntl import ioctl  | 
 | 61 | +    import termios  | 
 | 62 | +except ImportError:  | 
 | 63 | +    pass  | 
 | 64 | +import signal  | 
 | 65 | + | 
 | 66 | +class ProgressBarWidget(object):  | 
 | 67 | +    """This is an element of ProgressBar formatting.  | 
 | 68 | +
  | 
 | 69 | +    The ProgressBar object will call it's update value when an update  | 
 | 70 | +    is needed. It's size may change between call, but the results will  | 
 | 71 | +    not be good if the size changes drastically and repeatedly.  | 
 | 72 | +    """  | 
 | 73 | +    def update(self, pbar):  | 
 | 74 | +        """Returns the string representing the widget.  | 
 | 75 | +
  | 
 | 76 | +        The parameter pbar is a reference to the calling ProgressBar,  | 
 | 77 | +        where one can access attributes of the class for knowing how  | 
 | 78 | +        the update must be made.  | 
 | 79 | +
  | 
 | 80 | +        At least this function must be overriden."""  | 
 | 81 | +        pass  | 
 | 82 | + | 
 | 83 | +class ProgressBarWidgetHFill(object):  | 
 | 84 | +    """This is a variable width element of ProgressBar formatting.  | 
 | 85 | +
  | 
 | 86 | +    The ProgressBar object will call it's update value, informing the  | 
 | 87 | +    width this object must the made. This is like TeX \\hfill, it will  | 
 | 88 | +    expand to fill the line. You can use more than one in the same  | 
 | 89 | +    line, and they will all have the same width, and together will  | 
 | 90 | +    fill the line.  | 
 | 91 | +    """  | 
 | 92 | +    def update(self, pbar, width):  | 
 | 93 | +        """Returns the string representing the widget.  | 
 | 94 | +
  | 
 | 95 | +        The parameter pbar is a reference to the calling ProgressBar,  | 
 | 96 | +        where one can access attributes of the class for knowing how  | 
 | 97 | +        the update must be made. The parameter width is the total  | 
 | 98 | +        horizontal width the widget must have.  | 
 | 99 | +
  | 
 | 100 | +        At least this function must be overriden."""  | 
 | 101 | +        pass  | 
 | 102 | + | 
 | 103 | + | 
 | 104 | +class ETA(ProgressBarWidget):  | 
 | 105 | +    "Widget for the Estimated Time of Arrival"  | 
 | 106 | +    def format_time(self, seconds):  | 
 | 107 | +        return time.strftime('%H:%M:%S', time.gmtime(seconds))  | 
 | 108 | +    def update(self, pbar):  | 
 | 109 | +        if pbar.currval == 0:  | 
 | 110 | +            return 'ETA:  --:--:--'  | 
 | 111 | +        elif pbar.finished:  | 
 | 112 | +            return 'Time: %s' % self.format_time(pbar.seconds_elapsed)  | 
 | 113 | +        else:  | 
 | 114 | +            elapsed = pbar.seconds_elapsed  | 
 | 115 | +            eta = elapsed * pbar.maxval / pbar.currval - elapsed  | 
 | 116 | +            return 'ETA:  %s' % self.format_time(eta)  | 
 | 117 | + | 
 | 118 | +class FileTransferSpeed(ProgressBarWidget):  | 
 | 119 | +    "Widget for showing the transfer speed (useful for file transfers)."  | 
 | 120 | +    def __init__(self):  | 
 | 121 | +        self.fmt = '%6.2f %s'  | 
 | 122 | +        self.units = ['B','K','M','G','T','P']  | 
 | 123 | +    def update(self, pbar):  | 
 | 124 | +        if pbar.seconds_elapsed < 2e-6:#== 0:  | 
 | 125 | +            bps = 0.0  | 
 | 126 | +        else:  | 
 | 127 | +            bps = float(pbar.currval) / pbar.seconds_elapsed  | 
 | 128 | +        spd = bps  | 
 | 129 | +        for u in self.units:  | 
 | 130 | +            if spd < 1000:  | 
 | 131 | +                break  | 
 | 132 | +            spd /= 1000  | 
 | 133 | +        return self.fmt % (spd, u+'/s')  | 
 | 134 | + | 
 | 135 | +class RotatingMarker(ProgressBarWidget):  | 
 | 136 | +    "A rotating marker for filling the bar of progress."  | 
 | 137 | +    def __init__(self, markers='|/-\\'):  | 
 | 138 | +        self.markers = markers  | 
 | 139 | +        self.curmark = -1  | 
 | 140 | +    def update(self, pbar):  | 
 | 141 | +        if pbar.finished:  | 
 | 142 | +            return self.markers[0]  | 
 | 143 | +        self.curmark = (self.curmark + 1)%len(self.markers)  | 
 | 144 | +        return self.markers[self.curmark]  | 
 | 145 | + | 
 | 146 | +class Percentage(ProgressBarWidget):  | 
 | 147 | +    "Just the percentage done."  | 
 | 148 | +    def update(self, pbar):  | 
 | 149 | +        return '%3d%%' % pbar.percentage()  | 
 | 150 | + | 
 | 151 | +class Bar(ProgressBarWidgetHFill):  | 
 | 152 | +    "The bar of progress. It will strech to fill the line."  | 
 | 153 | +    def __init__(self, marker='#', left='|', right='|'):  | 
 | 154 | +        self.marker = marker  | 
 | 155 | +        self.left = left  | 
 | 156 | +        self.right = right  | 
 | 157 | +    def _format_marker(self, pbar):  | 
 | 158 | +        if isinstance(self.marker, (str, unicode)):  | 
 | 159 | +            return self.marker  | 
 | 160 | +        else:  | 
 | 161 | +            return self.marker.update(pbar)  | 
 | 162 | +    def update(self, pbar, width):  | 
 | 163 | +        percent = pbar.percentage()  | 
 | 164 | +        cwidth = width - len(self.left) - len(self.right)  | 
 | 165 | +        marked_width = int(percent * cwidth / 100)  | 
 | 166 | +        m = self._format_marker(pbar)  | 
 | 167 | +        bar = (self.left + (m*marked_width).ljust(cwidth) + self.right)  | 
 | 168 | +        return bar  | 
 | 169 | + | 
 | 170 | +class ReverseBar(Bar):  | 
 | 171 | +    "The reverse bar of progress, or bar of regress. :)"  | 
 | 172 | +    def update(self, pbar, width):  | 
 | 173 | +        percent = pbar.percentage()  | 
 | 174 | +        cwidth = width - len(self.left) - len(self.right)  | 
 | 175 | +        marked_width = int(percent * cwidth / 100)  | 
 | 176 | +        m = self._format_marker(pbar)  | 
 | 177 | +        bar = (self.left + (m*marked_width).rjust(cwidth) + self.right)  | 
 | 178 | +        return bar  | 
 | 179 | + | 
 | 180 | +default_widgets = [Percentage(), ' ', Bar()]  | 
 | 181 | +class ProgressBar(object):  | 
 | 182 | +    """This is the ProgressBar class, it updates and prints the bar.  | 
 | 183 | +
  | 
 | 184 | +    The term_width parameter may be an integer. Or None, in which case  | 
 | 185 | +    it will try to guess it, if it fails it will default to 80 columns.  | 
 | 186 | +
  | 
 | 187 | +    The simple use is like this:  | 
 | 188 | +    >>> pbar = ProgressBar().start()  | 
 | 189 | +    >>> for i in xrange(100):  | 
 | 190 | +    ...    # do something  | 
 | 191 | +    ...    pbar.update(i+1)  | 
 | 192 | +    ...  | 
 | 193 | +    >>> pbar.finish()  | 
 | 194 | +
  | 
 | 195 | +    But anything you want to do is possible (well, almost anything).  | 
 | 196 | +    You can supply different widgets of any type in any order. And you  | 
 | 197 | +    can even write your own widgets! There are many widgets already  | 
 | 198 | +    shipped and you should experiment with them.  | 
 | 199 | +
  | 
 | 200 | +    When implementing a widget update method you may access any  | 
 | 201 | +    attribute or function of the ProgressBar object calling the  | 
 | 202 | +    widget's update method. The most important attributes you would  | 
 | 203 | +    like to access are:  | 
 | 204 | +    - currval: current value of the progress, 0 <= currval <= maxval  | 
 | 205 | +    - maxval: maximum (and final) value of the progress  | 
 | 206 | +    - finished: True if the bar is have finished (reached 100%), False o/w  | 
 | 207 | +    - start_time: first time update() method of ProgressBar was called  | 
 | 208 | +    - seconds_elapsed: seconds elapsed since start_time  | 
 | 209 | +    - percentage(): percentage of the progress (this is a method)  | 
 | 210 | +    """  | 
 | 211 | +    def __init__(self, maxval=100, widgets=default_widgets, term_width=None,  | 
 | 212 | +                 fd=sys.stderr):  | 
 | 213 | +        assert maxval > 0  | 
 | 214 | +        self.maxval = maxval  | 
 | 215 | +        self.widgets = widgets  | 
 | 216 | +        self.fd = fd  | 
 | 217 | +        self.signal_set = False  | 
 | 218 | +        if term_width is None:  | 
 | 219 | +            try:  | 
 | 220 | +                self.handle_resize(None,None)  | 
 | 221 | +                signal.signal(signal.SIGWINCH, self.handle_resize)  | 
 | 222 | +                self.signal_set = True  | 
 | 223 | +            except (SystemExit, KeyboardInterrupt):  | 
 | 224 | +                raise  | 
 | 225 | +            except:  | 
 | 226 | +                self.term_width = int(os.environ.get('COLUMNS', 80)) - 1  | 
 | 227 | +        else:  | 
 | 228 | +            self.term_width = term_width  | 
 | 229 | + | 
 | 230 | +        self.currval = 0  | 
 | 231 | +        self.finished = False  | 
 | 232 | +        self.prev_percentage = -1  | 
 | 233 | +        self.start_time = None  | 
 | 234 | +        self.seconds_elapsed = 0  | 
 | 235 | + | 
 | 236 | +    def handle_resize(self, signum, frame):  | 
 | 237 | +        h,w=array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0'*8))[:2]  | 
 | 238 | +        self.term_width = w  | 
 | 239 | + | 
 | 240 | +    def percentage(self):  | 
 | 241 | +        "Returns the percentage of the progress."  | 
 | 242 | +        return self.currval*100.0 / self.maxval  | 
 | 243 | + | 
 | 244 | +    def _format_widgets(self):  | 
 | 245 | +        r = []  | 
 | 246 | +        hfill_inds = []  | 
 | 247 | +        num_hfill = 0  | 
 | 248 | +        currwidth = 0  | 
 | 249 | +        for i, w in enumerate(self.widgets):  | 
 | 250 | +            if isinstance(w, ProgressBarWidgetHFill):  | 
 | 251 | +                r.append(w)  | 
 | 252 | +                hfill_inds.append(i)  | 
 | 253 | +                num_hfill += 1  | 
 | 254 | +            elif isinstance(w, (str, unicode)):  | 
 | 255 | +                r.append(w)  | 
 | 256 | +                currwidth += len(w)  | 
 | 257 | +            else:  | 
 | 258 | +                weval = w.update(self)  | 
 | 259 | +                currwidth += len(weval)  | 
 | 260 | +                r.append(weval)  | 
 | 261 | +        for iw in hfill_inds:  | 
 | 262 | +            r[iw] = r[iw].update(self, (self.term_width-currwidth)/num_hfill)  | 
 | 263 | +        return r  | 
 | 264 | + | 
 | 265 | +    def _format_line(self):  | 
 | 266 | +        return ''.join(self._format_widgets()).ljust(self.term_width)  | 
 | 267 | + | 
 | 268 | +    def _need_update(self):  | 
 | 269 | +        return int(self.percentage()) != int(self.prev_percentage)  | 
 | 270 | + | 
 | 271 | +    def update(self, value):  | 
 | 272 | +        "Updates the progress bar to a new value."  | 
 | 273 | +        assert 0 <= value <= self.maxval  | 
 | 274 | +        self.currval = value  | 
 | 275 | +        if not self._need_update() or self.finished:  | 
 | 276 | +            return  | 
 | 277 | +        if not self.start_time:  | 
 | 278 | +            self.start_time = time.time()  | 
 | 279 | +        self.seconds_elapsed = time.time() - self.start_time  | 
 | 280 | +        self.prev_percentage = self.percentage()  | 
 | 281 | +        if value != self.maxval:  | 
 | 282 | +            self.fd.write(self._format_line() + '\r')  | 
 | 283 | +        else:  | 
 | 284 | +            self.finished = True  | 
 | 285 | +            self.fd.write(self._format_line() + '\n')  | 
 | 286 | + | 
 | 287 | +    def start(self):  | 
 | 288 | +        """Start measuring time, and prints the bar at 0%.  | 
 | 289 | +
  | 
 | 290 | +        It returns self so you can use it like this:  | 
 | 291 | +        >>> pbar = ProgressBar().start()  | 
 | 292 | +        >>> for i in xrange(100):  | 
 | 293 | +        ...    # do something  | 
 | 294 | +        ...    pbar.update(i+1)  | 
 | 295 | +        ...  | 
 | 296 | +        >>> pbar.finish()  | 
 | 297 | +        """  | 
 | 298 | +        self.update(0)  | 
 | 299 | +        return self  | 
 | 300 | + | 
 | 301 | +    def finish(self):  | 
 | 302 | +        """Used to tell the progress is finished."""  | 
 | 303 | +        self.update(self.maxval)  | 
 | 304 | +        if self.signal_set:  | 
 | 305 | +            signal.signal(signal.SIGWINCH, signal.SIG_DFL)  | 
 | 306 | +          | 
 | 307 | + | 
 | 308 | + | 
 | 309 | + | 
 | 310 | + | 
 | 311 | + | 
 | 312 | +if __name__=='__main__':  | 
 | 313 | +    import os  | 
 | 314 | + | 
 | 315 | +    def example1():  | 
 | 316 | +        widgets = ['Test: ', Percentage(), ' ', Bar(marker=RotatingMarker()),  | 
 | 317 | +                   ' ', ETA(), ' ', FileTransferSpeed()]  | 
 | 318 | +        pbar = ProgressBar(widgets=widgets, maxval=10000000).start()  | 
 | 319 | +        for i in range(1000000):  | 
 | 320 | +            # do something  | 
 | 321 | +            pbar.update(10*i+1)  | 
 | 322 | +        pbar.finish()  | 
 | 323 | +        print  | 
 | 324 | + | 
 | 325 | +    def example2():  | 
 | 326 | +        class CrazyFileTransferSpeed(FileTransferSpeed):  | 
 | 327 | +            "It's bigger between 45 and 80 percent"  | 
 | 328 | +            def update(self, pbar):  | 
 | 329 | +                if 45 < pbar.percentage() < 80:  | 
 | 330 | +                    return 'Bigger Now ' + FileTransferSpeed.update(self,pbar)  | 
 | 331 | +                else:  | 
 | 332 | +                    return FileTransferSpeed.update(self,pbar)  | 
 | 333 | + | 
 | 334 | +        widgets = [CrazyFileTransferSpeed(),' <<<', Bar(), '>>> ', Percentage(),' ', ETA()]  | 
 | 335 | +        pbar = ProgressBar(widgets=widgets, maxval=10000000)  | 
 | 336 | +        # maybe do something  | 
 | 337 | +        pbar.start()  | 
 | 338 | +        for i in range(2000000):  | 
 | 339 | +            # do something  | 
 | 340 | +            pbar.update(5*i+1)  | 
 | 341 | +        pbar.finish()  | 
 | 342 | +        print  | 
 | 343 | + | 
 | 344 | +    def example3():  | 
 | 345 | +        widgets = [Bar('>'), ' ', ETA(), ' ', ReverseBar('<')]  | 
 | 346 | +        pbar = ProgressBar(widgets=widgets, maxval=10000000).start()  | 
 | 347 | +        for i in range(1000000):  | 
 | 348 | +            # do something  | 
 | 349 | +            pbar.update(10*i+1)  | 
 | 350 | +        pbar.finish()  | 
 | 351 | +        print  | 
 | 352 | + | 
 | 353 | +    def example4():  | 
 | 354 | +        widgets = ['Test: ', Percentage(), ' ',  | 
 | 355 | +                   Bar(marker='0',left='[',right=']'),  | 
 | 356 | +                   ' ', ETA(), ' ', FileTransferSpeed()]  | 
 | 357 | +        pbar = ProgressBar(widgets=widgets, maxval=500)  | 
 | 358 | +        pbar.start()  | 
 | 359 | +        for i in range(100,500+1,50):  | 
 | 360 | +            time.sleep(0.2)  | 
 | 361 | +            pbar.update(i)  | 
 | 362 | +        pbar.finish()  | 
 | 363 | +        print  | 
 | 364 | + | 
 | 365 | + | 
 | 366 | +    example1()  | 
 | 367 | +    example2()  | 
 | 368 | +    example3()  | 
 | 369 | +    example4()  | 
 | 370 | + | 
0 commit comments