#!/usr/bin/env python2 # # Python progressbar functions. # # Copyright (C) 2014 Sebastian Lackner # # 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 # import fcntl import os import signal import struct import sys import termios def _sig_winch(signum=None, frame=None): """Signal handler for SIGWINCH.""" global _term_width try: h, w, hp, wp = struct.unpack('HHHH', fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))) _term_width = w except IOError: # ignore 'IOError: [Errno 25] Inappropriate ioctl for device', # which can occur when resizing the window while the output is redirected pass try: _sig_winch() signal.signal(signal.SIGWINCH, _sig_winch) except IOError: _term_width = int(os.environ.get('COLUMNS', 80)) - 1 class ProgressBar(object): def __init__(self, desc="", msg=None, current=0, total=100): """Initialize a new progress bar with given properties.""" self.desc = desc self.msg = msg self.current = current self.total = total def __enter__(self): self.update() return self def __exit__(self, type, value, traceback): if type is not None: sys.stdout.write("\r") msg = "" else: msg = None self.finish(msg) if self.msg is not None: sys.stdout.write("\n") def update(self, value = None): """Redraw the progressbar and optionally update the value.""" if value is not None: self.current = value if self.current == 0 or (self.current >= self.total and self.msg is None): sys.stdout.write("%s\r" % (' ' * _term_width)) sys.stdout.flush() return width = _term_width / 2 s1 = self.desc.ljust(width - 1, ' ')[:width - 1] width = _term_width - width if self.current >= self.total: s2 = self.msg.ljust(width, ' ')[:width] elif width > 2: numbars = min(self.current * (width - 2) / self.total, width - 2) s2 = "[%s%s]" % ('#' * numbars, '-' * (width - 2 - numbars)) percent = " %d%% " % min(self.current * 100 / self.total, 100) i = (len(s2)-len(percent))/2 s2 = "%s%s%s" % (s2[:i], percent, s2[i+len(percent):]) sys.stdout.write("%s %s\r" % (s1, s2)) sys.stdout.flush() def finish(self, msg = None): """Finalize the progressbar.""" if msg is not None: self.msg = msg self.current = self.total self.update() sys.stdout.flush() if __name__ == '__main__': import time print "" with ProgressBar(desc="description") as x: for i in xrange(100): x.update(i) time.sleep(1)