[PATCH 3 of 3] Extension to replace the simple progress indicator with an enhanced version

Thomas Arendsen Hein thomas at intevation.de
Mon Jul 23 02:49:46 CDT 2007


# HG changeset patch
# User Thomas Arendsen Hein <thomas at intevation.de>
# Date 1185174976 -7200
# Node ID eb5165cd4432537205edea38e63b6b65e2d24596
# Parent  6c4ee431568afee001ec7d5296972f12d9d09463
Extension to replace the simple progress indicator with an enhanced version.

hgext/progressbar/progressbar.py was downloaded from
http://cheeseshop.python.org/pypi/progressbar/2.2
(written by Nilton Volpato) and is completely unmodified.

diff -r 6c4ee431568a -r eb5165cd4432 hgext/progressbar/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/progressbar/__init__.py	Mon Jul 23 09:16:16 2007 +0200
@@ -0,0 +1,40 @@
+# Enhanced progress indicator for Mercurial
+#
+# Copyright 2007 by Intevation GmbH <intevation at intevation.de>
+# Author(s):
+# Thomas Arendsen Hein <thomas at intevation.de>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from mercurial import progress
+import progressbar as pb
+
+class progressbar(progress.noprogress):
+
+    """Enhanced progress indicator"""
+
+    def __init__(self, msg, maxval, fd):
+        widgets = ['%s ' % msg,
+                   pb.Percentage(), pb.Bar(), ' ', pb.ETA()]
+        self._progressbar = pb.ProgressBar(maxval, widgets, fd=fd)
+        self._clean = True
+        self.update(0)
+
+    def update(self, value=None):
+        self._clean = False
+        if value is None:
+            value = self._progressbar.currval + 1
+        self._progressbar.update(value)
+
+    def finish(self):
+        self._clean = True
+        self._progressbar.finish()
+
+    def interrupt(self):
+        if not self._clean:
+            self._clean = True
+            self._progressbar.fd.write("%s\r" %
+                                       (' '*self._progressbar.term_width))
+
+progress.progress = progressbar
diff -r 6c4ee431568a -r eb5165cd4432 hgext/progressbar/progressbar.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/progressbar/progressbar.py	Mon Jul 23 09:16:16 2007 +0200
@@ -0,0 +1,368 @@
+#!/usr/bin/python
+# -*- coding: iso-8859-1 -*-
+#
+# progressbar  - Text progressbar library for python.
+# Copyright (c) 2005 Nilton Volpato
+# 
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+# 
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+# 
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+"""Text progressbar library for python.
+
+This library provides a text mode progressbar. This is tipically used
+to display the progress of a long running operation, providing a
+visual clue that processing is underway.
+
+The ProgressBar class manages the progress, and the format of the line
+is given by a number of widgets. A widget is an object that may
+display diferently depending on the state of the progress. There are
+three types of widget:
+- a string, which always shows itself;
+- a ProgressBarWidget, which may return a diferent value every time
+it's update method is called; and
+- a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it
+expands to fill the remaining width of the line.
+
+The progressbar module is very easy to use, yet very powerful. And
+automatically supports features like auto-resizing when available.
+"""
+
+__author__ = "Nilton Volpato"
+__author_email__ = "first-name dot last-name @ gmail.com"
+__date__ = "2006-05-07"
+__version__ = "2.2"
+
+# Changelog
+#
+# 2006-05-07: v2.2 fixed bug in windows
+# 2005-12-04: v2.1 autodetect terminal width, added start method
+# 2005-12-04: v2.0 everything is now a widget (wow!)
+# 2005-12-03: v1.0 rewrite using widgets
+# 2005-06-02: v0.5 rewrite
+# 2004-??-??: v0.1 first version
+
+
+import sys, time
+from array import array
+try:
+    from fcntl import ioctl
+    import termios
+except ImportError:
+    pass
+import signal
+
+class ProgressBarWidget(object):
+    """This is an element of ProgressBar formatting.
+
+    The ProgressBar object will call it's update value when an update
+    is needed. It's size may change between call, but the results will
+    not be good if the size changes drastically and repeatedly.
+    """
+    def update(self, pbar):
+        """Returns the string representing the widget.
+
+        The parameter pbar is a reference to the calling ProgressBar,
+        where one can access attributes of the class for knowing how
+        the update must be made.
+
+        At least this function must be overriden."""
+        pass
+
+class ProgressBarWidgetHFill(object):
+    """This is a variable width element of ProgressBar formatting.
+
+    The ProgressBar object will call it's update value, informing the
+    width this object must the made. This is like TeX \\hfill, it will
+    expand to fill the line. You can use more than one in the same
+    line, and they will all have the same width, and together will
+    fill the line.
+    """
+    def update(self, pbar, width):
+        """Returns the string representing the widget.
+
+        The parameter pbar is a reference to the calling ProgressBar,
+        where one can access attributes of the class for knowing how
+        the update must be made. The parameter width is the total
+        horizontal width the widget must have.
+
+        At least this function must be overriden."""
+        pass
+
+
+class ETA(ProgressBarWidget):
+    "Widget for the Estimated Time of Arrival"
+    def format_time(self, seconds):
+        return time.strftime('%H:%M:%S', time.gmtime(seconds))
+    def update(self, pbar):
+        if pbar.currval == 0:
+            return 'ETA:  --:--:--'
+        elif pbar.finished:
+            return 'Time: %s' % self.format_time(pbar.seconds_elapsed)
+        else:
+            elapsed = pbar.seconds_elapsed
+            eta = elapsed * pbar.maxval / pbar.currval - elapsed
+            return 'ETA:  %s' % self.format_time(eta)
+
+class FileTransferSpeed(ProgressBarWidget):
+    "Widget for showing the transfer speed (useful for file transfers)."
+    def __init__(self):
+        self.fmt = '%6.2f %s'
+        self.units = ['B','K','M','G','T','P']
+    def update(self, pbar):
+        if pbar.seconds_elapsed < 2e-6:#== 0:
+            bps = 0.0
+        else:
+            bps = float(pbar.currval) / pbar.seconds_elapsed
+        spd = bps
+        for u in self.units:
+            if spd < 1000:
+                break
+            spd /= 1000
+        return self.fmt % (spd, u+'/s')
+
+class RotatingMarker(ProgressBarWidget):
+    "A rotating marker for filling the bar of progress."
+    def __init__(self, markers='|/-\\'):
+        self.markers = markers
+        self.curmark = -1
+    def update(self, pbar):
+        if pbar.finished:
+            return self.markers[0]
+        self.curmark = (self.curmark + 1)%len(self.markers)
+        return self.markers[self.curmark]
+
+class Percentage(ProgressBarWidget):
+    "Just the percentage done."
+    def update(self, pbar):
+        return '%3d%%' % pbar.percentage()
+
+class Bar(ProgressBarWidgetHFill):
+    "The bar of progress. It will strech to fill the line."
+    def __init__(self, marker='#', left='|', right='|'):
+        self.marker = marker
+        self.left = left
+        self.right = right
+    def _format_marker(self, pbar):
+        if isinstance(self.marker, (str, unicode)):
+            return self.marker
+        else:
+            return self.marker.update(pbar)
+    def update(self, pbar, width):
+        percent = pbar.percentage()
+        cwidth = width - len(self.left) - len(self.right)
+        marked_width = int(percent * cwidth / 100)
+        m = self._format_marker(pbar)
+        bar = (self.left + (m*marked_width).ljust(cwidth) + self.right)
+        return bar
+
+class ReverseBar(Bar):
+    "The reverse bar of progress, or bar of regress. :)"
+    def update(self, pbar, width):
+        percent = pbar.percentage()
+        cwidth = width - len(self.left) - len(self.right)
+        marked_width = int(percent * cwidth / 100)
+        m = self._format_marker(pbar)
+        bar = (self.left + (m*marked_width).rjust(cwidth) + self.right)
+        return bar
+
+default_widgets = [Percentage(), ' ', Bar()]
+class ProgressBar(object):
+    """This is the ProgressBar class, it updates and prints the bar.
+
+    The term_width parameter may be an integer. Or None, in which case
+    it will try to guess it, if it fails it will default to 80 columns.
+
+    The simple use is like this:
+    >>> pbar = ProgressBar().start()
+    >>> for i in xrange(100):
+    ...    # do something
+    ...    pbar.update(i+1)
+    ...
+    >>> pbar.finish()
+
+    But anything you want to do is possible (well, almost anything).
+    You can supply different widgets of any type in any order. And you
+    can even write your own widgets! There are many widgets already
+    shipped and you should experiment with them.
+
+    When implementing a widget update method you may access any
+    attribute or function of the ProgressBar object calling the
+    widget's update method. The most important attributes you would
+    like to access are:
+    - currval: current value of the progress, 0 <= currval <= maxval
+    - maxval: maximum (and final) value of the progress
+    - finished: True if the bar is have finished (reached 100%), False o/w
+    - start_time: first time update() method of ProgressBar was called
+    - seconds_elapsed: seconds elapsed since start_time
+    - percentage(): percentage of the progress (this is a method)
+    """
+    def __init__(self, maxval=100, widgets=default_widgets, term_width=None,
+                 fd=sys.stderr):
+        assert maxval > 0
+        self.maxval = maxval
+        self.widgets = widgets
+        self.fd = fd
+        self.signal_set = False
+        if term_width is None:
+            try:
+                self.handle_resize(None,None)
+                signal.signal(signal.SIGWINCH, self.handle_resize)
+                self.signal_set = True
+            except:
+                self.term_width = 79
+        else:
+            self.term_width = term_width
+
+        self.currval = 0
+        self.finished = False
+        self.prev_percentage = -1
+        self.start_time = None
+        self.seconds_elapsed = 0
+
+    def handle_resize(self, signum, frame):
+        h,w=array('h', ioctl(self.fd,termios.TIOCGWINSZ,'\0'*8))[:2]
+        self.term_width = w
+
+    def percentage(self):
+        "Returns the percentage of the progress."
+        return self.currval*100.0 / self.maxval
+
+    def _format_widgets(self):
+        r = []
+        hfill_inds = []
+        num_hfill = 0
+        currwidth = 0
+        for i, w in enumerate(self.widgets):
+            if isinstance(w, ProgressBarWidgetHFill):
+                r.append(w)
+                hfill_inds.append(i)
+                num_hfill += 1
+            elif isinstance(w, (str, unicode)):
+                r.append(w)
+                currwidth += len(w)
+            else:
+                weval = w.update(self)
+                currwidth += len(weval)
+                r.append(weval)
+        for iw in hfill_inds:
+            r[iw] = r[iw].update(self, (self.term_width-currwidth)/num_hfill)
+        return r
+
+    def _format_line(self):
+        return ''.join(self._format_widgets()).ljust(self.term_width)
+
+    def _need_update(self):
+        return int(self.percentage()) != int(self.prev_percentage)
+
+    def update(self, value):
+        "Updates the progress bar to a new value."
+        assert 0 <= value <= self.maxval
+        self.currval = value
+        if not self._need_update() or self.finished:
+            return
+        if not self.start_time:
+            self.start_time = time.time()
+        self.seconds_elapsed = time.time() - self.start_time
+        self.prev_percentage = self.percentage()
+        if value != self.maxval:
+            self.fd.write(self._format_line() + '\r')
+        else:
+            self.finished = True
+            self.fd.write(self._format_line() + '\n')
+
+    def start(self):
+        """Start measuring time, and prints the bar at 0%.
+
+        It returns self so you can use it like this:
+        >>> pbar = ProgressBar().start()
+        >>> for i in xrange(100):
+        ...    # do something
+        ...    pbar.update(i+1)
+        ...
+        >>> pbar.finish()
+        """
+        self.update(0)
+        return self
+
+    def finish(self):
+        """Used to tell the progress is finished."""
+        self.update(self.maxval)
+        if self.signal_set:
+            signal.signal(signal.SIGWINCH, signal.SIG_DFL)
+        
+
+
+
+
+
+if __name__=='__main__':
+    import os
+
+    def example1():
+        widgets = ['Test: ', Percentage(), ' ', Bar(marker=RotatingMarker()),
+                   ' ', ETA(), ' ', FileTransferSpeed()]
+        pbar = ProgressBar(widgets=widgets, maxval=10000000).start()
+        for i in range(1000000):
+            # do something
+            pbar.update(10*i+1)
+        pbar.finish()
+        print
+
+    def example2():
+        class CrazyFileTransferSpeed(FileTransferSpeed):
+            "It's bigger between 45 and 80 percent"
+            def update(self, pbar):
+                if 45 < pbar.percentage() < 80:
+                    return 'Bigger Now ' + FileTransferSpeed.update(self,pbar)
+                else:
+                    return FileTransferSpeed.update(self,pbar)
+
+        widgets = [CrazyFileTransferSpeed(),' <<<', Bar(), '>>> ', Percentage(),' ', ETA()]
+        pbar = ProgressBar(widgets=widgets, maxval=10000000)
+        # maybe do something
+        pbar.start()
+        for i in range(2000000):
+            # do something
+            pbar.update(5*i+1)
+        pbar.finish()
+        print
+
+    def example3():
+        widgets = [Bar('>'), ' ', ETA(), ' ', ReverseBar('<')]
+        pbar = ProgressBar(widgets=widgets, maxval=10000000).start()
+        for i in range(1000000):
+            # do something
+            pbar.update(10*i+1)
+        pbar.finish()
+        print
+
+    def example4():
+        widgets = ['Test: ', Percentage(), ' ',
+                   Bar(marker='0',left='[',right=']'),
+                   ' ', ETA(), ' ', FileTransferSpeed()]
+        pbar = ProgressBar(widgets=widgets, maxval=500)
+        pbar.start()
+        for i in range(100,500+1,50):
+            time.sleep(0.2)
+            pbar.update(i)
+        pbar.finish()
+        print
+
+
+    example1()
+    example2()
+    example3()
+    example4()
+
diff -r 6c4ee431568a -r eb5165cd4432 setup.py
--- a/setup.py	Mon Jul 23 09:12:57 2007 +0200
+++ b/setup.py	Mon Jul 23 09:16:16 2007 +0200
@@ -61,7 +61,8 @@ setup(name='mercurial',
       url='http://selenic.com/mercurial',
       description='Scalable distributed SCM',
       license='GNU GPL',
-      packages=['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert'],
+      packages=['mercurial', 'mercurial.hgweb',
+                'hgext', 'hgext.convert', 'hgext.progressbar'],
       ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
                    Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
                    Extension('mercurial.base85', ['mercurial/base85.c']),



More information about the Mercurial-devel mailing list