Permalink
Browse files

Added counters, timers and action bars

 - added the ability to have counters, timers and action bars
    - unknown max value does not break the ProgressBar anymore
    - added widgets which make sense for an unknown max value:
       - Timer - displays the elapsed time
       - Counter - displays the curval
       - FormatLabel
          - using a format string allows the user to create a fairly
            complex label
       - BouncingBar

 - added examples to show counters, timers and action bars
 - example decorator
    - queues up the examples for running
    - wraps the example in a KeyboardInterrupt handler which will
      skip the current example
  • Loading branch information...
1 parent 12984d2 commit f691ebd9e2550f440ae9aebafedea6b9f9ebb17c @terencehonles terencehonles committed May 15, 2011
Showing with 200 additions and 34 deletions.
  1. +92 −15 examples.py
  2. +108 −19 progressbar.py
View
@@ -1,20 +1,28 @@
#!/usr/bin/python
+# -*- coding: utf-8 -*-
import sys
import time
-from progressbar import ProgressBar, Percentage, Bar, ETA, FileTransferSpeed, \
- RotatingMarker, ReverseBar, SimpleProgress
+from progressbar import AnimatedMarker, Bar, BouncingBar, Counter, ETA, \
+ FileTransferSpeed, FormatLabel, Percentage, \
+ ProgressBar, ReverseBar, RotatingMarker, \
+ SimpleProgress, Timer
+examples = []
def example(fn):
try: name = 'Example %d' % int(fn.__name__[7:])
except: name = fn.__name__
def wrapped():
- sys.stdout.write('Running: %s\n' % name)
- fn()
- sys.stdout.write('Finished: %s\n\n' % name)
-
+ try:
+ sys.stdout.write('Running: %s\n' % name)
+ fn()
+ sys.stdout.write('\n')
+ except KeyboardInterrupt:
+ sys.stdout.write('\nSkipping example.\n\n')
+
+ examples.append(wrapped)
return wrapped
@@ -105,13 +113,82 @@ def example8():
for i in pbar((i for i in range(80))):
time.sleep(0.01)
+@example
+def example9():
+ pbar = ProgressBar(widgets=['Working: ', AnimatedMarker()])
+ for i in pbar((i for i in range(50))):
+ time.sleep(.08)
+
+@example
+def example10():
+ widgets = ['Processed: ', Counter(), ' lines (', Timer(), ')']
+ pbar = ProgressBar(widgets=widgets)
+ for i in pbar((i for i in range(150))):
+ time.sleep(0.1)
+
+@example
+def example11():
+ widgets = [FormatLabel('Processed: %(value)d lines (in: %(elapsed)s)')]
+ pbar = ProgressBar(widgets=widgets)
+ for i in pbar((i for i in range(150))):
+ time.sleep(0.1)
+
+@example
+def example12():
+ widgets = ['Balloon: ', AnimatedMarker(markers='.oO@* ')]
+ pbar = ProgressBar(widgets=widgets)
+ for i in pbar((i for i in range(24))):
+ time.sleep(0.3)
+
+@example
+def example13():
+ # You may need python 3.x to see this correctly
+ try:
+ widgets = ['Arrows: ', AnimatedMarker(markers='←↖↑↗→↘↓↙')]
+ pbar = ProgressBar(widgets=widgets)
+ for i in pbar((i for i in range(24))):
+ time.sleep(0.3)
+ except UnicodeError: sys.stdout.write('Unicode error: skipping example')
+
+@example
+def example14():
+ # You may need python 3.x to see this correctly
+ try:
+ widgets = ['Arrows: ', AnimatedMarker(markers='◢◣◤◥')]
+ pbar = ProgressBar(widgets=widgets)
+ for i in pbar((i for i in range(24))):
+ time.sleep(0.3)
+ except UnicodeError: sys.stdout.write('Unicode error: skipping example')
+
+@example
+def example15():
+ # You may need python 3.x to see this correctly
+ try:
+ widgets = ['Wheels: ', AnimatedMarker(markers='◐◓◑◒')]
+ pbar = ProgressBar(widgets=widgets)
+ for i in pbar((i for i in range(24))):
+ time.sleep(0.3)
+ except UnicodeError: sys.stdout.write('Unicode error: skipping example')
+
+@example
+def example16():
+ widgets = [FormatLabel('Bouncer: value %(value)d - '), BouncingBar()]
+ pbar = ProgressBar(widgets=widgets)
+ for i in pbar((i for i in range(180))):
+ time.sleep(0.05)
+
+@example
+def example17():
+ widgets = [FormatLabel('Animated Bouncer: value %(value)d - '),
+ BouncingBar(marker=RotatingMarker())]
+
+ pbar = ProgressBar(widgets=widgets)
+ for i in pbar((i for i in range(180))):
+ time.sleep(0.05)
+
+
if __name__ == '__main__':
- example0()
- example1()
- example2()
- example3()
- example4()
- example5()
- example6()
- example7()
- example8()
+ try:
+ for example in examples: example()
+ except KeyboardInterrupt:
+ sys.stdout('\nQuitting examples.\n')
View
@@ -1,5 +1,5 @@
#!/usr/bin/python
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
#
# progressbar - Text progress bar library for Python.
# Copyright (c) 2005 Nilton Volpato
@@ -87,6 +87,8 @@ def format_updatable(updatable, pbar):
if hasattr(updatable, 'update'): return updatable.update(pbar)
else: return updatable
+class UnknownLength: pass
+
class ProgressBarWidget(object):
'''The base class for all widgets
@@ -128,16 +130,33 @@ def update(self, pbar, width):
'''
-class ETA(ProgressBarWidget):
- 'Widget which attempts to estimate the time of arrival.'
+class Timer(ProgressBarWidget):
+ 'Widget which displays the elapsed seconds.'
+ __slots__ = ('format',)
TIME_SENSITIVE = True
- def format_time(self, seconds):
+ def __init__(self, format='Elapsed Time: %s'):
+ self.format = format
+
+ @staticmethod
+ def format_time(seconds):
'Formats time as the string "HH:MM:SS".'
return str(datetime.timedelta(seconds=int(seconds)))
+
+ def update(self, pbar):
+ 'Updates the widget to show the elapsed time.'
+
+ return self.format % self.format_time(pbar.seconds_elapsed)
+
+
+class ETA(Timer):
+ 'Widget which attempts to estimate the time of arrival.'
+
+ TIME_SENSITIVE = True
+
def update(self, pbar):
'Updates the widget to show the ETA or total time when finished.'
@@ -198,13 +217,52 @@ def update(self, pbar):
RotatingMarker = AnimatedMarker
+class Counter(ProgressBarWidget):
+ 'Displays the current count'
+
+ __slots__ = ('format',)
+
+ def __init__(self, format='%d'):
+ self.format = format
+
+ def update(self, pbar):
+ return self.format % pbar.currval
+
+
class Percentage(ProgressBarWidget):
'Displays the current percentage as a number with a percent sign.'
def update(self, pbar):
return '%3d%%' % pbar.percentage()
+class FormatLabel(Timer):
+ 'Displays a formatted label'
+
+ mapping = {
+ 'elapsed': ('seconds_elapsed', Timer.format_time),
+ 'finished': ('finished', None),
+ 'last_update': ('last_update_time', None),
+ 'max': ('maxval', None),
+ 'seconds': ('seconds_elapsed', None),
+ 'start': ('start_time', None),
+ 'value': ('currval', None)
+ }
+
+ __slots__ = ('format',)
+ def __init__(self, format): self.format = format
+
+ def update(self, pbar):
+ context = {}
+ for name, (key, transform) in self.mapping.items():
+ try:
+ value = getattr(pbar, key)
+ context[name] = value if transform is None else transform(value)
+ except: pass
+
+ return self.format % context
+
+
class SimpleProgress(ProgressBarWidget):
'Returns progress as a count of the total (e.g.: "5 of 47")'
@@ -272,6 +330,28 @@ def __init__(self, marker='#', left='|', right='|', fill=' ',
self.fill_left = fill_left
+class BouncingBar(Bar):
+ def update(self, pbar, width):
+ 'Updates the progress bar and its subcomponents'
+
+ left, marker, right = (format_updatable(i, pbar) for i in
+ (self.left, self.marker, self.right))
+
+ width -= len(left) + len(right)
+
+ if pbar.finished: return '%s%s%s' % (left, width * marker, right)
+
+ position = int(pbar.currval % (width * 2 - 1))
+ if position > width: position = width * 2 - position
+ lpad = self.fill * (position - 1)
+ rpad = self.fill * (width - len(marker) - len(lpad))
+
+ # Swap if we want to bounce the other way
+ if not self.fill_left: rpad, lpad = lpad, rpad
+
+ return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)
+
+
class ProgressBar(object):
'''The ProgressBar class which updates and prints the bar.
@@ -350,27 +430,26 @@ def __init__(self, maxval=None, widgets=None, term_width=None, poll=1,
except:
self.term_width = self._env_size()
+ self.__iterable = None
self._update_widgets()
self.currval = 0
self.finished = False
- self.start_time = None
self.last_update_time = None
- self.seconds_elapsed = 0
self.poll = poll
- self.__iterable = None
+ self.seconds_elapsed = 0
+ self.start_time = None
+ self.update_interval = 1
def __call__(self, iterable):
'Use a ProgressBar to iterate through an iterable'
try:
self.maxval = len(iterable)
- except TypeError:
- # If the iterable has no length, then rely on the value provided
- # by the user, otherwise fail.
- if not (isinstance(self.maxval, (int, long)) and self.maxval > 0):
- raise RuntimeError('Could not determine maxval from iterable. '
- 'You must explicitly provide a maxval.')
+ except:
+ if self.maxval is None:
+ self.maxval = UnknownLength
+
self.__iterable = iter(iterable)
return self
@@ -469,9 +548,15 @@ def _update_widgets(self):
def update(self, value):
'Updates the ProgressBar to a new value.'
- assert 0 <= value <= self.maxval, '0 <= %d <= %d' % (value, self.maxval)
+ if value is not None:
+ if (self.maxval is not UnknownLength
+ and not 0 <= value <= self.maxval):
+
+ raise ValueError('Value out of range')
+
+ self.currval = value
+
- self.currval = value
if not self._need_update(): return
if self.start_time is None:
raise RuntimeError('You must call "start" before calling "update"')
@@ -495,13 +580,17 @@ def start(self):
>>> pbar.finish()
'''
- if self.maxval is None: self.maxval = self._DEFAULT_MAXVAL
- assert self.maxval > 0
+ if self.maxval is None:
+ self.maxval = self._DEFAULT_MAXVAL
self.num_intervals = max(100, self.term_width)
- self.update_interval = self.maxval / self.num_intervals
self.next_update = 0
+ if self.maxval is not UnknownLength:
+ if self.maxval < 0: raise ValueError('Value out of range')
+ self.update_interval = self.maxval / self.num_intervals
+
+
self.start_time = self.last_update_time = time.time()
self.update(0)
@@ -512,7 +601,7 @@ def finish(self):
'Puts the ProgressBar bar in the finished state.'
self.finished = True
- self.update(self.maxval)
+ self.update(self.maxval if self.maxval is not UnknownLength else None)
self.fd.write('\n')
if self.signal_set:
signal.signal(signal.SIGWINCH, signal.SIG_DFL)

0 comments on commit f691ebd

Please sign in to comment.