17 Commits
1.5 ... 1.6

Author SHA1 Message Date
Georgios Verigakis
e3dbaf52e1 Bump version 2021-07-28 09:51:10 +03:00
Georgios Verigakis
ca6310204e Use the formal form everywhere 2020-07-20 15:42:07 +03:00
Georgios Verigakis
b8fdfc782d Add color support
Fixes #27
2020-07-20 15:34:23 +03:00
Georgios Verigakis
0faea87060 Style change 2020-07-20 13:41:50 +03:00
Georgios Verigakis
64671e1ceb atexit.unregister does not exist in Python 2.7
Implement this with __del__ instead
2020-07-20 13:32:47 +03:00
Georgios Verigakis
12e46ed702 Do not use ANSI to clear line 2020-07-20 13:22:48 +03:00
Georgios Verigakis
d325dab247 Support formatted messages to remaining progress types
This is a followup to the changes for #76. We can now remove the write function.
2020-07-20 12:46:43 +03:00
Georgios Verigakis
e5c2368c6b Merge pull request #79 from mathstuf/avoid-division-by-zero
progress: avoid division by zero
2020-07-20 12:39:10 +03:00
Georgios Verigakis
03d4adbf73 Merge pull request #78 from mathstuf/iter-value-as-property
iter: expose the iteration value to the object
2020-07-20 12:38:34 +03:00
Georgios Verigakis
42fd17f67c Merge pull request #77 from mathstuf/spinner-format-message
spinner: support formatted messages
2020-07-20 12:35:01 +03:00
Ben Boeckel
0d93258f52 progress: avoid division by zero
This occurs otherwise with `bar.iter([])`.
2020-03-31 15:14:55 -04:00
Ben Boeckel
4af220e573 iter: expose the iteration value to the object
Mentioned in #76.
2020-03-30 20:23:15 -04:00
Ben Boeckel
25cefd12db spinner: support formatted messages
Fixes #76
2020-03-30 20:14:43 -04:00
Georgios Verigakis
1ed414290f Merge pull request #69 from frasern/atexit
Fixed #64 -- ensure hidden cursor is reshown at exit.
2019-07-02 15:12:13 +03:00
Fraser Nevett
f6390b76f2 Fixed #64 -- ensure hidden cursor is reshown at exit. 2019-06-04 13:26:06 +01:00
Georgios Verigakis
80a91b9c78 Merge pull request #68 from edwardbarak/patch-1
Add additional help when AttributeError is raised with regards check_tty
2019-05-06 11:47:07 +03:00
Edward Barak
57a36d49f2 Add help message to check_tty AttributeError 2019-04-28 22:44:01 -04:00
8 changed files with 140 additions and 34 deletions

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com> # Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
# #
# Permission to use, copy, modify, and distribute this software for any # Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above # purpose with or without fee is hereby granted, provided that the above

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com> # Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
# #
# Permission to use, copy, modify, and distribute this software for any # Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above # purpose with or without fee is hereby granted, provided that the above
@@ -24,7 +24,7 @@ except ImportError:
from time import time as monotonic from time import time as monotonic
__version__ = '1.5' __version__ = '1.6'
HIDE_CURSOR = '\x1b[?25l' HIDE_CURSOR = '\x1b[?25l'
SHOW_CURSOR = '\x1b[?25h' SHOW_CURSOR = '\x1b[?25h'
@@ -46,14 +46,19 @@ class Infinite(object):
for key, val in kwargs.items(): for key, val in kwargs.items():
setattr(self, key, val) setattr(self, key, val)
self._width = 0 self._max_width = 0
self._hidden_cursor = False
self.message = message self.message = message
if self.file and self.is_tty(): if self.file and self.is_tty():
if self.hide_cursor: if self.hide_cursor:
print(HIDE_CURSOR, end='', file=self.file) print(HIDE_CURSOR, end='', file=self.file)
print(self.message, end='', file=self.file) self._hidden_cursor = True
self.file.flush() self.writeln('')
def __del__(self):
if self._hidden_cursor:
print(SHOW_CURSOR, end='', file=self.file)
def __getitem__(self, key): def __getitem__(self, key):
if key.startswith('_'): if key.startswith('_'):
@@ -85,31 +90,30 @@ class Infinite(object):
def start(self): def start(self):
pass pass
def clearln(self):
if self.file and self.is_tty():
print('\r\x1b[K', end='', file=self.file)
def write(self, s):
if self.file and self.is_tty():
line = self.message + s.ljust(self._width)
print('\r' + line, end='', file=self.file)
self._width = max(self._width, len(s))
self.file.flush()
def writeln(self, line): def writeln(self, line):
if self.file and self.is_tty(): if self.file and self.is_tty():
self.clearln() width = len(line)
print(line, end='', file=self.file) if width < self._max_width:
# Add padding to cover previous contents
line += ' ' * (self._max_width - width)
else:
self._max_width = width
print('\r' + line, end='', file=self.file)
self.file.flush() self.file.flush()
def finish(self): def finish(self):
if self.file and self.is_tty(): if self.file and self.is_tty():
print(file=self.file) print(file=self.file)
if self.hide_cursor: if self._hidden_cursor:
print(SHOW_CURSOR, end='', file=self.file) print(SHOW_CURSOR, end='', file=self.file)
self._hidden_cursor = False
def is_tty(self): def is_tty(self):
return self.file.isatty() if self.check_tty else True try:
return self.file.isatty() if self.check_tty else True
except AttributeError:
msg = "%s has no attribute 'isatty'. Try setting check_tty=False." % self
raise AttributeError(msg)
def next(self, n=1): def next(self, n=1):
now = monotonic() now = monotonic()
@@ -120,10 +124,13 @@ class Infinite(object):
self.update() self.update()
def iter(self, it): def iter(self, it):
self.iter_value = None
with self: with self:
for x in it: for x in it:
self.iter_value = x
yield x yield x
self.next() self.next()
del self.iter_value
def __enter__(self): def __enter__(self):
self.start() self.start()
@@ -152,6 +159,8 @@ class Progress(Infinite):
@property @property
def progress(self): def progress(self):
if self.max == 0:
return 0
return min(1, self.index / self.max) return min(1, self.index / self.max)
@property @property
@@ -171,7 +180,10 @@ class Progress(Infinite):
except TypeError: except TypeError:
pass pass
self.iter_value = None
with self: with self:
for x in it: for x in it:
self.iter_value = x
yield x yield x
self.next() self.next()
del self.iter_value

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com> # Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
# #
# Permission to use, copy, modify, and distribute this software for any # Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above # purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@ from __future__ import unicode_literals
import sys import sys
from . import Progress from . import Progress
from .colors import color
class Bar(Progress): class Bar(Progress):
@@ -28,13 +29,14 @@ class Bar(Progress):
bar_suffix = '| ' bar_suffix = '| '
empty_fill = ' ' empty_fill = ' '
fill = '#' fill = '#'
color = None
def update(self): def update(self):
filled_length = int(self.width * self.progress) filled_length = int(self.width * self.progress)
empty_length = self.width - filled_length empty_length = self.width - filled_length
message = self.message % self message = self.message % self
bar = self.fill * filled_length bar = color(self.fill * filled_length, fg=self.color)
empty = self.empty_fill * empty_length empty = self.empty_fill * empty_length
suffix = self.suffix % self suffix = self.suffix % self
line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix, line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix,
@@ -74,7 +76,7 @@ class IncrementalBar(Bar):
nempty = self.width - nfull # Number of empty chars nempty = self.width - nfull # Number of empty chars
message = self.message % self message = self.message % self
bar = self.phases[-1] * nfull bar = color(self.phases[-1] * nfull, fg=self.color)
current = self.phases[phase] if phase > 0 else '' current = self.phases[phase] if phase > 0 else ''
empty = self.empty_fill * max(0, nempty - len(current)) empty = self.empty_fill * max(0, nempty - len(current))
suffix = self.suffix % self suffix = self.suffix % self

79
progress/colors.py Normal file
View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Georgios Verigakis <verigak@gmail.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from functools import partial
COLORS = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan',
'white')
STYLES = ('bold', 'faint', 'italic', 'underline', 'blink', 'blink2',
'negative', 'concealed', 'crossed')
def color(s, fg=None, bg=None, style=None):
sgr = []
if fg:
if fg in COLORS:
sgr.append(str(30 + COLORS.index(fg)))
elif isinstance(fg, int) and 0 <= fg <= 255:
sgr.append('38;5;%d' % int(fg))
else:
raise Exception('Invalid color "%s"' % fg)
if bg:
if bg in COLORS:
sgr.append(str(40 + COLORS.index(bg)))
elif isinstance(bg, int) and 0 <= bg <= 255:
sgr.append('48;5;%d' % bg)
else:
raise Exception('Invalid color "%s"' % bg)
if style:
for st in style.split('+'):
if st in STYLES:
sgr.append(str(1 + STYLES.index(st)))
else:
raise Exception('Invalid style "%s"' % st)
if sgr:
prefix = '\x1b[' + ';'.join(sgr) + 'm'
suffix = '\x1b[0m'
return prefix + s + suffix
else:
return s
# Foreground shortcuts
black = partial(color, fg='black')
red = partial(color, fg='red')
green = partial(color, fg='green')
yellow = partial(color, fg='yellow')
blue = partial(color, fg='blue')
magenta = partial(color, fg='magenta')
cyan = partial(color, fg='cyan')
white = partial(color, fg='white')
# Style shortcuts
bold = partial(color, style='bold')
faint = partial(color, style='faint')
italic = partial(color, style='italic')
underline = partial(color, style='underline')
blink = partial(color, style='blink')
blink2 = partial(color, style='blink2')
negative = partial(color, style='negative')
concealed = partial(color, style='concealed')
crossed = partial(color, style='crossed')

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com> # Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
# #
# Permission to use, copy, modify, and distribute this software for any # Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above # purpose with or without fee is hereby granted, provided that the above
@@ -20,12 +20,16 @@ from . import Infinite, Progress
class Counter(Infinite): class Counter(Infinite):
def update(self): def update(self):
self.write(str(self.index)) message = self.message % self
line = ''.join([message, str(self.index)])
self.writeln(line)
class Countdown(Progress): class Countdown(Progress):
def update(self): def update(self):
self.write(str(self.remaining)) message = self.message % self
line = ''.join([message, str(self.remaining)])
self.writeln(line)
class Stack(Progress): class Stack(Progress):
@@ -34,7 +38,9 @@ class Stack(Progress):
def update(self): def update(self):
nphases = len(self.phases) nphases = len(self.phases)
i = min(nphases - 1, int(self.progress * nphases)) i = min(nphases - 1, int(self.progress * nphases))
self.write(self.phases[i]) message = self.message % self
line = ''.join([message, self.phases[i]])
self.writeln(line)
class Pie(Stack): class Pie(Stack):

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com> # Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
# #
# Permission to use, copy, modify, and distribute this software for any # Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above # purpose with or without fee is hereby granted, provided that the above
@@ -24,7 +24,9 @@ class Spinner(Infinite):
def update(self): def update(self):
i = self.index % len(self.phases) i = self.index % len(self.phases)
self.write(self.phases[i]) message = self.message % self
line = ''.join([message, self.phases[i]])
self.writeln(line)
class PieSpinner(Spinner): class PieSpinner(Spinner):

View File

@@ -10,7 +10,7 @@ setup(
version=progress.__version__, version=progress.__version__,
description='Easy to use progress bars', description='Easy to use progress bars',
long_description=open('README.rst').read(), long_description=open('README.rst').read(),
author='Giorgos Verigakis', author='Georgios Verigakis',
author_email='verigak@gmail.com', author_email='verigak@gmail.com',
url='http://github.com/verigak/progress/', url='http://github.com/verigak/progress/',
license='ISC', license='ISC',

View File

@@ -11,6 +11,7 @@ from progress.bar import (Bar, ChargingBar, FillingSquaresBar,
from progress.spinner import (Spinner, PieSpinner, MoonSpinner, LineSpinner, from progress.spinner import (Spinner, PieSpinner, MoonSpinner, LineSpinner,
PixelSpinner) PixelSpinner)
from progress.counter import Counter, Countdown, Stack, Pie from progress.counter import Counter, Countdown, Stack, Pie
from progress.colors import bold
def sleep(): def sleep():
@@ -20,9 +21,9 @@ def sleep():
for bar_cls in (Bar, ChargingBar, FillingSquaresBar, FillingCirclesBar): for bar_cls in (Bar, ChargingBar, FillingSquaresBar, FillingCirclesBar):
suffix = '%(index)d/%(max)d [%(elapsed)d / %(eta)d / %(eta_td)s]' suffix = '%(index)d/%(max)d [%(elapsed)d / %(eta)d / %(eta_td)s] (%(iter_value)s)'
bar = bar_cls(bar_cls.__name__, suffix=suffix) bar = bar_cls(bar_cls.__name__, suffix=suffix)
for i in bar.iter(range(200)): for i in bar.iter(range(200, 400)):
sleep() sleep()
for bar_cls in (IncrementalBar, PixelBar, ShadyBar): for bar_cls in (IncrementalBar, PixelBar, ShadyBar):
@@ -32,8 +33,12 @@ for bar_cls in (IncrementalBar, PixelBar, ShadyBar):
bar.next() bar.next()
sleep() sleep()
bar = IncrementalBar(bold('Corolored'), color='green')
for i in bar.iter(range(200)):
sleep()
for spin in (Spinner, PieSpinner, MoonSpinner, LineSpinner, PixelSpinner): for spin in (Spinner, PieSpinner, MoonSpinner, LineSpinner, PixelSpinner):
for i in spin(spin.__name__ + ' ').iter(range(100)): for i in spin(spin.__name__ + ' %(index)d ').iter(range(100)):
sleep() sleep()
for singleton in (Counter, Countdown, Stack, Pie): for singleton in (Counter, Countdown, Stack, Pie):