I implemented a class to animate waiting for processes:
from sys import stdout, stderr
from threading import Thread
def reset_string(string):
"""Returns the reset string required to delete the provided string."""
strlen = len(string)
return ''.join(('\b' * strlen, ' ' * strlen, '\b' * strlen))
class TTYAnimation:
"""Text animation for interactive TTY."""
def __init__(self, message='', animation='–\\|/', end='',
template='{}: {}', interval=0.1, file=stdout):
self.message = message
self.animation = animation
self.template = template
self.interval = interval
self.file = file
self.result = end
self._thread = None
self._run = False
def __enter__(self):
"""Starts animation iff in interactive Shell."""
self.start()
return self
def __exit__(self, *_):
"""Stops the animation."""
self.stop()
def print_and_reset(self, string):
"""Prints an animation string and resets
it after the specified interval.
"""
print(string, flush=True, end='', file=self.file)
sleep(self.interval)
print(reset_string(string), flush=True, end='', file=self.file)
def finalize(self, result):
"""Prints the terminal animation string."""
if result is True:
animation = str(LogLevel.SUCCESS)
elif result is False:
animation = str(LogLevel.ERROR)
else:
animation = result
text = self.template.format(self.message, animation)
print(text, file=self.file, flush=True)
def run(self):
"""Runs the animation.
Do not call this method directly,
use start() instead.
"""
while self._run:
for item in self.animation:
if not self._run:
break
self.print_and_reset(self.template.format(self.message, item))
def start(self):
"""Starts the animation."""
if self._thread is None:
self._run = True
self._thread = Thread(target=self.run)
self._thread.start()
return True
return False
def stop(self, result=None):
"""Stops the animation."""
if self._thread is None:
return False
self._run = False
self._thread.join()
self._thread = None
self.finalize(self.result if result is None else result)
return True
The full module can be found here.
I'd like to reduce the class' complexity while maintaining the current behaviour and get rid of threading, if possible.
Invocation example
from time import sleep
with TTYAnimation('Hang in there') as anim:
sleep(5)